Guten Morgen

This commit is contained in:
firestar5683
2026-09-05 11:21:21 -05:00
parent 097d63caef
commit cec1a0fb62
26 changed files with 429 additions and 48 deletions
+1
View File
@@ -708,6 +708,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SubaruSNG", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"SubaruSNGManualParkingBrake", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruStopStartOff", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"SubaruRedneckCruise", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"TacoTune", {PERSISTENT, BOOL, "0", "0", 2}},
{"TeslaCoopSteering", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"TestAlert", {CLEAR_ON_MANAGER_START, STRING, "", ""}},
+4 -1
View File
@@ -245,7 +245,10 @@ class CarState(CarStateBase):
return button_events
def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]:
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA:
if self.CP.carFingerprint == CAR.KIA_RAY_EV:
self.lda_button = int(cp.vl["BCM_PO_11"]["RAY_LKAS_BTN"] != 0) \
if cp.ts_nanos["BCM_PO_11"]["RAY_LKAS_BTN"] > 0 else 0
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA:
self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0
elif self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp)
@@ -1018,6 +1018,46 @@ class TestHyundaiFingerprint:
assert ret.cruiseState.enabled
assert ret.cruiseState.speed == pytest.approx(10 * 0.2777778)
def test_kia_ray_ev_decodes_bcm_lkas_button_pulse(self):
toggles = get_test_toggles()
CP = CarInterface.get_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], True, False, False, toggles)
FPCP = CarInterface.get_starpilot_params(CAR.KIA_RAY_EV, gen_empty_fingerprint(), [], CP, toggles)
car_state = CarState(CP, FPCP)
can_parsers = car_state.get_can_parsers(CP)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
def update(ray_lkas_button: int, frame: int):
msg = packer.make_can_msg("BCM_PO_11", 0, {"RAY_LKAS_BTN": ray_lkas_button})
can_parsers[Bus.pt].update([(frame, [msg])])
return car_state.update(can_parsers, toggles)[0]
update(0, 1)
ret = update(1, 2)
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
ret = update(0, 3)
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
ret = update(2, 4)
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
def test_non_ray_does_not_use_ray_lkas_signal(self):
CP = CarInterface.get_params(CAR.KIA_FORTE_2021_NON_SCC, gen_empty_fingerprint(), [], False, False, False, None)
car_state = CarState(CP, CarInterface.get_starpilot_params(CAR.KIA_FORTE_2021_NON_SCC,
gen_empty_fingerprint(), [], CP, get_test_toggles()))
parser_cycle = SimpleNamespace(
vl={
"CLU13": {"CF_Clu_LdwsLkasSW": 0},
"BCM_PO_11": {"LDA_BTN": 0, "RAY_LKAS_BTN": 1},
},
ts_nanos={
"CLU13": {"CF_Clu_LdwsLkasSW": 1},
"BCM_PO_11": {"LDA_BTN": 1, "RAY_LKAS_BTN": 1},
},
)
assert not car_state.create_lkas_button_events(parser_cycle, 0)
def test_hyundai_redneck_cruise_availability(self, monkeypatch):
class FakeParams:
def __init__(self, *args, **kwargs):
+9 -1
View File
@@ -22,7 +22,7 @@ from opendbc.car.honda.values import CAR as HONDA, HONDA_BOSCH, HondaFlags, Hond
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags, ALT_BUS_LDA_BUTTON_CARS
from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.subaru.values import CAR as SUBARU, SubaruSafetyFlags
from opendbc.car.subaru.values import CAR as SUBARU, SUBARU_REDNECK_CRUISE_CARS, SubaruSafetyFlags
from opendbc.car.toyota.values import CAR as TOYOTA, NO_DSU_CAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaStarPilotFlags, ToyotaSafetyFlags
from opendbc.car.values import PLATFORMS
from opendbc.can import CANParser
@@ -300,6 +300,14 @@ class CarInterfaceBase(ABC):
if getattr(starpilot_toggles, "subaru_sng", False):
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.STOP_AND_GO.value
fp_ret.redneckCruiseAvailable = candidate in SUBARU_REDNECK_CRUISE_CARS
if fp_ret.redneckCruiseAvailable and params.get_bool("SubaruRedneckCruise") and \
not CP.openpilotLongitudinalControl:
fp_ret.pcmCruiseSpeed = False
CP.openpilotLongitudinalControl = True
CP.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
fp_ret.safetyConfigs[-1].safetyParam |= SubaruSafetyFlags.REDNECK_CRUISE.value
return fp_ret
@staticmethod
@@ -37,6 +37,8 @@ _STOP_START_STARTUP_DELAY_FRAMES = 100
_STOP_START_STARTUP_DEADLINE_FRAMES = 1000
_STOP_START_PULSE_FRAMES = 30
_STOP_START_PULSE_PERIOD_FRAMES = 5
_REDNECK_BUTTON_INTERVAL_FRAMES = 10
_REDNECK_BUTTON_COPIES = 2
def get_safety_CP():
@@ -87,6 +89,7 @@ class CarController(CarControllerBase):
self.stop_start_initial_state = None
self.stop_start_counter = 0
self.stop_start_acknowledged = False
self.last_redneck_button_frame = 0
def _stop_start_off_request(self, CC, CS, starpilot_toggles):
"""Send one bounded Subaru Stop/Start OFF request after ignition.
@@ -409,6 +412,10 @@ class CarController(CarControllerBase):
actuators = CC.actuators
hud_control = CC.hudControl
pcm_cancel_cmd = CC.cruiseControl.cancel
subaru_redneck_cruise = bool(
self.CP.carFingerprint == CAR.SUBARU_IMPREZA_2020 and
getattr(starpilot_toggles, "subaru_redneck_cruise", False)
)
can_sends = []
@@ -473,7 +480,8 @@ class CarController(CarControllerBase):
else:
if self.frame % 10 == 0:
can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled,
self.CP.openpilotLongitudinalControl, CC.longActive, hud_control.leadVisible,
self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise,
CC.longActive, hud_control.leadVisible,
self.status_bus))
can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive, hud_control.visualAlert,
@@ -491,7 +499,7 @@ class CarController(CarControllerBase):
can_sends.append(subarucan.create_brake_pedal(self.packer, self.frame // 2, CS.brake_pedal_msg,
speed_cmd, pcm_cancel_cmd))
if self.CP.openpilotLongitudinalControl:
if self.CP.openpilotLongitudinalControl and not subaru_redneck_cruise:
if self.frame % 5 == 0:
can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg,
self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm))
@@ -507,6 +515,20 @@ class CarController(CarControllerBase):
bus = CanBus.alt_for_cp(self.CP) if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else self.main_bus
can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd))
if subaru_redneck_cruise:
redneck_button = {
1: subarucan.CRUISE_BUTTON_RESUME,
2: subarucan.CRUISE_BUTTON_SET,
}.get(getattr(CS, "redneck_send_button", 0))
cruise_buttons_msg = getattr(CS, "cruise_buttons_msg", None)
if redneck_button and cruise_buttons_msg and self.frame - self.last_redneck_button_frame >= _REDNECK_BUTTON_INTERVAL_FRAMES:
counter = (int(cruise_buttons_msg["COUNTER"]) + 1) % 0x10
for copy_idx in range(_REDNECK_BUTTON_COPIES):
can_sends.append(subarucan.create_cruise_buttons(
self.packer, counter + copy_idx, cruise_buttons_msg, redneck_button, self.main_bus,
))
self.last_redneck_button_frame = self.frame
if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT:
# Tester present (keeps eyesight disabled)
if self.frame % 100 == 0:
+23 -2
View File
@@ -1,12 +1,20 @@
import copy
from cereal import custom
from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, structs
from opendbc.car import Bus, create_button_events, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import CarStateBase
from opendbc.car.subaru.values import DBC, CanBus, SUBARU_STOP_START_CARS, SubaruFlags
from opendbc.car.subaru.values import DBC, CanBus, SUBARU_REDNECK_CRUISE_CARS, SUBARU_STOP_START_CARS, SubaruFlags
from opendbc.car import CanSignalRateCalculator
ButtonType = structs.CarState.ButtonEvent.Type
SUBARU_CRUISE_BUTTONS = {
"Main": ButtonType.mainCruise,
"Set": ButtonType.decelCruise,
"Resume": ButtonType.accelCruise,
}
class CarState(CarStateBase):
def __init__(self, CP, FPCP):
@@ -18,6 +26,8 @@ class CarState(CarStateBase):
self.dashlights_msg = {}
self.dashlights_dat = b""
self.stop_start_state = 0
self.cruise_buttons_msg = {}
self.cruise_buttons = {button: 0 for button in SUBARU_CRUISE_BUTTONS}
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt]
@@ -136,6 +146,17 @@ class CarState(CarStateBase):
self.es_status_msg = copy.copy(cp_es_brake.vl["ES_Status"])
self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"])
if self.CP.carFingerprint in SUBARU_REDNECK_CRUISE_CARS:
cruise_buttons = cp.vl["Cruise_Buttons"]
if getattr(starpilot_toggles, "subaru_redneck_cruise", False):
ret.buttonEvents = []
for button, button_type in SUBARU_CRUISE_BUTTONS.items():
ret.buttonEvents.extend(create_button_events(
int(bool(cruise_buttons[button])), self.cruise_buttons[button], {1: button_type},
))
self.cruise_buttons = {button: int(bool(cruise_buttons[button])) for button in SUBARU_CRUISE_BUTTONS}
self.cruise_buttons_msg = copy.copy(cruise_buttons)
if not (self.CP.flags & SubaruFlags.HYBRID):
self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"])
@@ -3,6 +3,10 @@ from opendbc.car.subaru.values import CanBus
VisualAlert = structs.CarControl.HUDControl.VisualAlert
CRUISE_BUTTON_MAIN = 1
CRUISE_BUTTON_SET = 2
CRUISE_BUTTON_RESUME = 3
def create_steering_control(packer, apply_torque, steer_req):
values = {
@@ -67,6 +71,19 @@ def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long
return packer.make_can_msg("ES_Distance", bus, values)
def create_cruise_buttons(packer, frame, cruise_buttons_msg, button, bus=CanBus.main):
values = {s: cruise_buttons_msg[s] for s in [
"CHECKSUM",
"Signal1",
"Signal2",
]}
values["COUNTER"] = frame % 0x10
values["Main"] = button == CRUISE_BUTTON_MAIN
values["Set"] = button == CRUISE_BUTTON_SET
values["Resume"] = button == CRUISE_BUTTON_RESUME
return packer.make_can_msg("Cruise_Buttons", bus, values)
def create_es_lkas_state(packer, frame, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart,
bus=CanBus.main):
values = {s: es_lkas_state_msg[s] for s in [
@@ -5,7 +5,7 @@ from types import SimpleNamespace
import pytest
from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, fw_versions, structs
from opendbc.car import Bus, fw_versions, gen_empty_fingerprint, structs
from opendbc.car.fw_query_definitions import StdQueries
from opendbc.car.subaru import subarucan
from opendbc.car.subaru.carcontroller import CarController
@@ -67,6 +67,56 @@ def test_preglobal_sng_does_not_send_standstill_keepalive_without_manual_toggle(
assert speed_cmd is False
def test_redneck_cruise_buttons_use_resume_for_increase_and_set_for_decrease():
dbc = DBC[CAR.SUBARU_IMPREZA_2020][Bus.pt]
packer = CANPacker(dbc)
parser = CANParser(dbc, [("Cruise_Buttons", 0)], CanBus.main)
stock_buttons = defaultdict(int)
resume_msg = subarucan.create_cruise_buttons(
packer, 1, stock_buttons, subarucan.CRUISE_BUTTON_RESUME, CanBus.main,
)
parser.update([(1, [resume_msg])])
assert parser.vl["Cruise_Buttons"]["Resume"] == 1
assert parser.vl["Cruise_Buttons"]["Set"] == 0
set_msg = subarucan.create_cruise_buttons(
packer, 2, stock_buttons, subarucan.CRUISE_BUTTON_SET, CanBus.main,
)
parser.update([(2, [set_msg])])
assert parser.vl["Cruise_Buttons"]["Resume"] == 0
assert parser.vl["Cruise_Buttons"]["Set"] == 1
def test_redneck_cruise_is_only_available_on_the_experimental_impreza(monkeypatch):
class FakeParams:
def __init__(self, **_kwargs):
pass
def get_bool(self, key):
return key == "SubaruRedneckCruise"
monkeypatch.setattr("opendbc.car.interfaces.Params", FakeParams)
toggles = SimpleNamespace(subaru_sng=False)
impreza_cp = CarInterface.get_non_essential_params(CAR.SUBARU_IMPREZA_2020)
impreza_fpcp = CarInterface.get_starpilot_params(
CAR.SUBARU_IMPREZA_2020, gen_empty_fingerprint(), [], impreza_cp, toggles,
)
assert impreza_fpcp.redneckCruiseAvailable
assert not impreza_fpcp.pcmCruiseSpeed
assert impreza_cp.openpilotLongitudinalControl
assert impreza_cp.safetyConfigs[0].safetyParam & SubaruSafetyFlags.REDNECK_CRUISE
old_impreza_cp = CarInterface.get_non_essential_params(CAR.SUBARU_IMPREZA)
old_impreza_fpcp = CarInterface.get_starpilot_params(
CAR.SUBARU_IMPREZA, gen_empty_fingerprint(), [], old_impreza_cp, toggles,
)
assert not old_impreza_fpcp.redneckCruiseAvailable
assert old_impreza_fpcp.pcmCruiseSpeed
assert not old_impreza_cp.openpilotLongitudinalControl
class TestSubaruFingerprint:
def test_eyesight_queries_do_not_change_diagnostic_state(self, monkeypatch):
camera_requests = [request for request in FW_QUERY_CONFIG.requests if CarParams.Ecu.fwdCamera in request.whitelist_ecus]
@@ -89,6 +89,7 @@ class SubaruSafetyFlags(IntFlag):
D_PLATFORM_CAMERA = 64
FIXED_ANGLE_LIMITS = 128
STOP_START_BUTTON = 256
REDNECK_CRUISE = 512
LEGACY_2025_ANGLE_LIMITS = FIXED_ANGLE_LIMITS
@@ -275,6 +276,10 @@ SUBARU_STOP_START_CARS = (
CAR.SUBARU_LEGACY_2025,
)
SUBARU_REDNECK_CRUISE_CARS = (
CAR.SUBARU_IMPREZA_2020,
)
SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
@@ -11,7 +11,7 @@ from opendbc.car.interfaces import CarControllerBase
from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.values import CAR, MIN_ACC_SPEED, NO_STOP_TIMER_CAR, PEDAL_TRANSITION, TSS2_CAR, \
CarControllerParams, ToyotaFlags, \
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR
UNSUPPORTED_DSU_CAR, LEGACY_PRIUS_CAR, RADAR_ACC_CAR, SECOC_CAR
from opendbc.can import CANPacker
Ecu = structs.CarParams.Ecu
@@ -49,6 +49,8 @@ MAX_USER_TORQUE = 500
PARK = structs.CarState.GearShifter.park
REVERSE = structs.CarState.GearShifter.reverse
TOYOTA_AUTO_HOLD_CARS = TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR
# Lock / unlock door commands - Credit goes to AlexandreSato!
LOCK_CMD = b"\x40\x05\x30\x11\x00\x80\x00\x00"
UNLOCK_CMD = b"\x40\x05\x30\x11\x00\x40\x00\x00"
@@ -72,6 +74,14 @@ def should_bypass_toyota_long_pid(CP, starpilot_toggles=None) -> bool:
) or highlander_sdsu)
def supports_toyota_auto_hold(CP, auto_hold_enabled: bool) -> bool:
return (
auto_hold_enabled and
CP.carFingerprint in TOYOTA_AUTO_HOLD_CARS and
bool(CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
)
def get_long_tune(CP, params):
kiBP = [2., 5.]
kiV = [0.5, 0.25]
@@ -243,11 +253,8 @@ class CarController(CarControllerBase):
self.secoc_prev_reset_counter = 0
self.doors_locked = False
self.auto_brake_hold = bool(self.CP.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value)
self.brake_hold_active = False
self._brake_hold_counter = 0
self._brake_hold_reset = False
self._prev_brake_pressed = False
def _compute_interceptor_gas_cmd(self, CC, CS):
if not (self.CP.enableGasInterceptorDEPRECATED and self.CP.openpilotLongitudinalControl and CC.longActive):
@@ -299,15 +306,12 @@ class CarController(CarControllerBase):
not CS.out.gasPressed and not CS.out.cruiseState.enabled and
CS.out.gearShifter not in (PARK, REVERSE))
if brake_hold_allowed:
if brake_hold_allowed and not self.brake_hold_active and CS.out.brakePressed:
self._brake_hold_counter += 1
self.brake_hold_active = self._brake_hold_counter > brake_hold_allowed_timer and not self._brake_hold_reset
self._brake_hold_reset = not self._prev_brake_pressed and CS.out.brakePressed and not self._brake_hold_reset
else:
self.brake_hold_active = self._brake_hold_counter > brake_hold_allowed_timer
elif not brake_hold_allowed:
self._brake_hold_counter = 0
self.brake_hold_active = False
self._brake_hold_reset = False
self._prev_brake_pressed = CS.out.brakePressed
if self.frame % 2 == 0:
can_sends.append(toyotacan.create_brake_hold_command(self.packer, self.frame, CS.pre_collision_2, self.brake_hold_active))
@@ -409,8 +413,11 @@ class CarController(CarControllerBase):
# *** gas and brake ***
self._update_standstill_request(CC, CS, actuators, starpilot_toggles)
if self.auto_brake_hold:
if supports_toyota_auto_hold(self.CP, getattr(starpilot_toggles, "toyota_auto_hold", False)):
can_sends.extend(self.create_auto_brake_hold_messages(CS))
elif self.brake_hold_active:
self._brake_hold_counter = 0
self.brake_hold_active = False
interceptor_gas_cmd = self._compute_interceptor_gas_cmd(CC, CS)
@@ -12,7 +12,8 @@ from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_fee
get_prius_positive_feedforward_scale, \
limit_interceptor_pcm_accel, \
limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \
limit_prius_stopping_accel, should_bypass_toyota_long_pid, update_permit_braking
limit_prius_stopping_accel, should_bypass_toyota_long_pid, supports_toyota_auto_hold, \
update_permit_braking
from opendbc.car.toyota.carstate import CarState, LKAS_BUTTON_CAR, calculate_interceptor_gas_pressed, create_lkas_button_events
from opendbc.car.toyota.fingerprints import FW_VERSIONS
from opendbc.car.toyota.interface import CarInterface
@@ -205,6 +206,22 @@ class TestToyotaInterfaces:
assert car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
assert car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
def test_auto_hold_is_disabled_by_default(self):
params = Params()
params.remove("ToyotaAutoHold")
car_params = CarInterface.get_params(
CAR.TOYOTA_CAMRY_TSS2,
{bus: {} for bus in range(8)},
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=SimpleNamespace(),
)
assert not car_params.flags & ToyotaFlags.AUTO_BRAKE_HOLD.value
assert not car_params.alternativeExperience & ALTERNATIVE_EXPERIENCE.ALLOW_AEB
def test_prius_openpilot_long_uses_hybrid_long_defaults(self):
car_params = CarInterface.get_params(
CAR.TOYOTA_PRIUS,
@@ -750,6 +767,74 @@ class TestToyotaCarController:
assert controller.standstill_req is True
def test_toyota_auto_hold_requires_toggle_supported_car_and_capability(self):
CP = SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY_TSS2,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
)
assert supports_toyota_auto_hold(CP, True)
assert not supports_toyota_auto_hold(CP, False)
assert not supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY_TSS2,
flags=0,
), True)
assert not supports_toyota_auto_hold(SimpleNamespace(
carFingerprint=CAR.TOYOTA_CAMRY,
flags=ToyotaFlags.AUTO_BRAKE_HOLD.value,
), True)
def test_toyota_auto_hold_latches_after_brake_press_until_gas(self):
controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
controller.frame = 0
controller.brake_hold_active = False
controller._brake_hold_counter = 0
cs = SimpleNamespace(
out=SimpleNamespace(
standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False,
brakePressed=True,
gearShifter=structs.CarState.GearShifter.drive,
),
pre_collision_2={},
)
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
assert controller.brake_hold_active
cs.out.brakePressed = False
controller.frame = 2
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
assert controller.brake_hold_active
cs.out.gasPressed = True
controller.frame = 4
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
assert not controller.brake_hold_active
def test_toyota_auto_hold_does_not_trigger_without_brake_press(self):
controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
controller.frame = 0
controller.brake_hold_active = False
controller._brake_hold_counter = 0
cs = SimpleNamespace(
out=SimpleNamespace(
standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False,
brakePressed=False,
gearShifter=structs.CarState.GearShifter.drive,
),
pre_collision_2={},
)
controller.create_auto_brake_hold_messages(cs, brake_hold_allowed_timer=0)
assert not controller.brake_hold_active
def test_prius_resume_request_releases_standstill_latch(self):
controller = self._make_controller(standstill_req=True, last_standstill=True)
@@ -893,14 +978,12 @@ class TestToyotaCarController:
controller.frame = 0
controller.brake_hold_active = False
controller._brake_hold_counter = 0
controller._brake_hold_reset = False
controller._prev_brake_pressed = False
cs = SimpleNamespace(
out=SimpleNamespace(
standstill=True,
cruiseState=SimpleNamespace(available=True, enabled=False),
gasPressed=False,
brakePressed=False,
brakePressed=True,
gearShifter=structs.CarState.GearShifter.drive,
),
pre_collision_2={},
@@ -1497,6 +1497,7 @@ BO_ 913 BCM_PO_11: 8 Vector__XXX
SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS
SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS
SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX
SG_ RAY_LKAS_BTN : 0|2@1+ (1,0) [0|3] "" XXX
BO_ 1426 LABEL11: 8 XXX
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
+30 -1
View File
@@ -35,6 +35,7 @@
#define MSG_SUBARU_ES_DashStatus 0x321U
#define MSG_SUBARU_ES_LKAS_State 0x322U
#define MSG_SUBARU_ES_Infotainment 0x323U
#define MSG_SUBARU_Cruise_Buttons 0x146U
#define MSG_SUBARU_ES_UDS_Request 0x787U
@@ -56,6 +57,9 @@
#define SUBARU_COMMON_TX_MSGS(alt_bus) \
{MSG_SUBARU_ES_Distance, alt_bus, 8, .check_relay = false}, \
#define SUBARU_REDNECK_TX_MSGS() \
{MSG_SUBARU_Cruise_Buttons, SUBARU_MAIN_BUS, 8, .check_relay = false}, \
#define SUBARU_D_PLATFORM_ANGLE_TX_MSGS(bus) \
{MSG_SUBARU_ES_LKAS_ANGLE, bus, 8, .check_relay = true}, \
{MSG_SUBARU_ES_DashStatus, bus, 8, .check_relay = true}, \
@@ -113,6 +117,7 @@ static bool subaru_lkas_angle = false;
static bool subaru_d_platform = false;
static bool subaru_fixed_angle_limits = false;
static bool subaru_stop_start_button = false;
static bool subaru_redneck_cruise = false;
static uint32_t subaru_get_checksum(const CANPacket_t *msg) {
return (uint8_t)msg->data[0];
@@ -297,6 +302,12 @@ static bool subaru_tx_hook(const CANPacket_t *msg) {
violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg);
}
if (msg->addr == MSG_SUBARU_Cruise_Buttons) {
violation |= !subaru_redneck_cruise;
violation |= msg->bus != SUBARU_MAIN_BUS;
violation |= subaru_get_checksum(msg) != subaru_compute_checksum(msg);
}
if (violation){
tx = false;
}
@@ -309,6 +320,19 @@ static safety_config subaru_init(uint16_t param) {
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
};
static const CanMsg SUBARU_REDNECK_TX_MSGS_CONFIG[] = {
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
SUBARU_REDNECK_TX_MSGS()
};
static const CanMsg SUBARU_REDNECK_STOP_AND_GO_TX_MSGS_CONFIG[] = {
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS)
SUBARU_REDNECK_TX_MSGS()
SUBARU_STOP_AND_GO_ADDITIONAL_TX_MSGS()
};
static const CanMsg SUBARU_LONG_TX_MSGS[] = {
SUBARU_BASE_TX_MSGS(SUBARU_MAIN_BUS, MSG_SUBARU_ES_LKAS)
SUBARU_COMMON_LONG_TX_MSGS(SUBARU_MAIN_BUS)
@@ -405,6 +429,9 @@ static safety_config subaru_init(uint16_t param) {
const uint16_t SUBARU_PARAM_STOP_START_BUTTON = 256;
subaru_stop_start_button = GET_FLAG(param, SUBARU_PARAM_STOP_START_BUTTON);
const uint16_t SUBARU_PARAM_REDNECK_CRUISE = 512;
subaru_redneck_cruise = GET_FLAG(param, SUBARU_PARAM_REDNECK_CRUISE);
#ifdef ALLOW_DEBUG
const uint16_t SUBARU_PARAM_LONGITUDINAL = 2;
subaru_longitudinal = GET_FLAG(param, SUBARU_PARAM_LONGITUDINAL);
@@ -422,7 +449,9 @@ static safety_config subaru_init(uint16_t param) {
ret = subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_LONG_TX_MSGS) : \
BUILD_SAFETY_CFG(subaru_gen2_rx_checks, SUBARU_GEN2_TX_MSGS);
} else {
ret = subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_LONG_TX_MSGS) : \
ret = subaru_redneck_cruise ? (subaru_stop_and_go ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_REDNECK_STOP_AND_GO_TX_MSGS_CONFIG) : \
BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_REDNECK_TX_MSGS_CONFIG)) : \
subaru_longitudinal ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_LONG_TX_MSGS) : \
subaru_stop_and_go ? BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_STOP_AND_GO_TX_MSGS) : \
BUILD_SAFETY_CFG(subaru_rx_checks, SUBARU_TX_MSGS);
}
+1 -1
View File
@@ -202,7 +202,7 @@ class CarSpecificEvents:
ray_ev = self.CP.carFingerprint == HYUNDAI_CAR.KIA_RAY_EV
events = self.create_common_events(
CS, CS_prev, extra_gears=extra_gears,
pcm_enable=self.CP.pcmCruise and not ray_ev,
pcm_enable=self.CP.pcmCruise,
allow_button_cancel=False,
ignore_cruise_state=ray_ev,
)
+2 -1
View File
@@ -203,7 +203,8 @@ class Car:
self.mock_carstate = MockCarState()
self.v_cruise_helper = VCruiseHelper(self.CP, self.FPCP)
self.redneck_cruise = RedneckCruise(self.CP, self.FPCP) if self.CP.brand == "hyundai" and self.FPCP.redneckCruiseAvailable and not self.FPCP.pcmCruiseSpeed else None
self.redneck_cruise = RedneckCruise(self.CP, self.FPCP) if self.CP.brand in ("hyundai", "subaru") and \
self.FPCP.redneckCruiseAvailable and not self.FPCP.pcmCruiseSpeed else None
self.is_metric = self.params.get_bool("IsMetric")
self.safe_mode = self.params.get_bool("SafeMode")
+18 -1
View File
@@ -1,5 +1,9 @@
from cereal import car
from types import SimpleNamespace
from cereal import car
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR
from openpilot.selfdrive.car.car_specific import CarSpecificEvents
from openpilot.selfdrive.car.cruise_state import should_cancel_stock_cruise, should_flag_cruise_mismatch
@@ -39,3 +43,16 @@ def test_pcm_cruise_behavior_is_unchanged():
assert should_cancel_stock_cruise(cp, cruise_enabled=True, controls_enabled=False)
assert not should_flag_cruise_mismatch(cp, cruise_enabled=True, controls_enabled=True, effective_pcm_cruise=True)
assert should_flag_cruise_mismatch(cp, cruise_enabled=True, controls_enabled=False, effective_pcm_cruise=True)
def test_kia_ray_ev_allows_stock_cruise_to_enable_controls():
for candidate, ignore_cruise_state in ((HYUNDAI_CAR.KIA_RAY_EV, True), (HYUNDAI_CAR.HYUNDAI_SONATA, False)):
cp = SimpleNamespace(brand="hyundai", carFingerprint=candidate, flags=0)
handler = CarSpecificEvents(cp)
captured = {}
handler.create_common_events = lambda *args, **kwargs: captured.update(kwargs)
handler.update(SimpleNamespace(), SimpleNamespace(), SimpleNamespace())
assert captured["pcm_enable"] is True
assert captured["ignore_cruise_state"] is ignore_cruise_state
@@ -3713,6 +3713,7 @@
"picker_description": "Holds Toyota/Lexus brakes at stops when cruise is available.",
"data_type": "bool",
"ui_type": "toggle",
"galaxy_only": true,
"settings_tier": "simple"
},
{
@@ -4947,6 +4948,18 @@
"parent_key": "GalaxyDeveloperMode",
"settings_tier": "advanced"
},
{
"key": "SubaruRedneckCruise",
"label": "Subaru Redneck Cruise",
"description": "On supported Subaru Impreza-family cars, use stock Resume/Set button presses to match the cluster set speed to StarPilot's target.",
"picker_description": "Matches StarPilot's target speed with Subaru Resume/Set.",
"data_type": "bool",
"ui_type": "toggle",
"galaxy_only": true,
"vehicle_makes": ["Subaru"],
"parent_key": "GalaxyDeveloperMode",
"settings_tier": "advanced"
},
{
"key": "TurnSteeringLimitMuteSpeed",
"label": "Mute Turn Limit Alert Below",
+2
View File
@@ -199,6 +199,7 @@ SAFE_MODE_MANAGED_KEYS = (
"SubaruSNG",
"SubaruSNGManualParkingBrake",
"SubaruStopStartOff",
"SubaruRedneckCruise",
"VoltSNG",
"JeepBrakeHold",
"GMAutoHold",
@@ -217,6 +218,7 @@ SAFE_MODE_FIXED_VALUES = {
"LongitudinalPersonality": int(log.LongitudinalPersonality.relaxed),
"UseAutoSteerDelay": True,
"SubaruStopStartOff": False,
"SubaruRedneckCruise": False,
}
SAFE_MODE_STOCK_PARAM_MAP = {
+2
View File
@@ -381,6 +381,8 @@ def update_maps(now, params, params_memory, manual_update=False):
if maps_downloaded and params.get("LastMapsUpdate") == todays_date and not manual_update:
return
params_memory.put_bool("DownloadMaps", True)
pm = messaging.PubMaster(["mapdIn"])
sm = messaging.SubMaster(["mapdExtendedOut"])
+14 -4
View File
@@ -20,7 +20,7 @@ from opendbc.car.gm.values import CAR as GM_CAR, EV_CAR as GM_EV_CAR, GMFlags
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR, EV_CAR as HYUNDAI_EV_CAR, HyundaiFlags, HyundaiStarPilotSafetyFlags
from opendbc.car.interfaces import TORQUE_SUBSTITUTE_PATH, CarInterfaceBase, GearShifter
from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.subaru.values import SUBARU_STOP_START_CARS, SubaruFlags
from opendbc.car.subaru.values import SUBARU_REDNECK_CRUISE_CARS, SUBARU_STOP_START_CARS, SubaruFlags
from opendbc.car.tesla.values import CAR as TESLA_CAR
from opendbc.car.toyota.values import CAR as TOYOTA_CAR, ToyotaStarPilotFlags
from openpilot.common.basedir import BASEDIR
@@ -672,14 +672,24 @@ class StarPilotVariables:
toggle.experimental_mode_available = (
toggle.openpilot_longitudinal or lateral_only_experimental_available(CP)
)
if not toggle.redneck_cruise_available or (toggle.openpilot_longitudinal and FPCP.pcmCruiseSpeed):
hyundai_redneck_available = toggle.car_make == "hyundai" and toggle.redneck_cruise_available
if toggle.car_make == "hyundai" and (not toggle.redneck_cruise_available or
(toggle.openpilot_longitudinal and FPCP.pcmCruiseSpeed)):
self.params.put_bool("RedneckCruise", False)
toggle.redneck_cruise = self.get_value(
"RedneckCruise",
condition=toggle.redneck_cruise_available and not toggle.openpilot_longitudinal,
condition=hyundai_redneck_available and not toggle.openpilot_longitudinal,
)
if toggle.redneck_cruise_available and not FPCP.pcmCruiseSpeed:
if hyundai_redneck_available and not FPCP.pcmCruiseSpeed:
toggle.redneck_cruise = True
toggle.subaru_redneck_cruise = self.get_value(
"SubaruRedneckCruise", condition=toggle.car_model in SUBARU_REDNECK_CRUISE_CARS,
)
if toggle.car_model in SUBARU_REDNECK_CRUISE_CARS and not FPCP.pcmCruiseSpeed:
toggle.subaru_redneck_cruise = True
if toggle.car_make == "subaru":
toggle.redneck_cruise = bool(toggle.subaru_redneck_cruise and not FPCP.pcmCruiseSpeed)
pcm_cruise = CP.pcmCruise
prohibited_main_aol = not toggle.openpilot_longitudinal and hyundai_can_use_lkas_for_aol
startAccel = CP.startAccel
+23 -17
View File
@@ -1,4 +1,6 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import signal
import subprocess
@@ -7,12 +9,7 @@ import time
from collections import defaultdict, deque
from pathlib import Path
from cereal import messaging
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
MAPD_DIR = Path(BASEDIR) / "starpilot/navigation"
MAPD_DIR = Path(__file__).resolve().parent
MAPD_BIN = MAPD_DIR / "mapd"
OFFLINE_ROOT = Path("/data/media/0/osm/offline")
RESTART_DELAY_S = 0.25
@@ -24,6 +21,11 @@ WAIT_FOR_GPS_EXIT_CODE = 4
ROAD_STATE_POLL_S = 1.0
def _cloudlog():
from openpilot.common.swaglog import cloudlog
return cloudlog
def extract_bounds_filename(line: str) -> str | None:
try:
payload = json.loads(line)
@@ -89,7 +91,7 @@ def quarantine_offline_tile(filename: str) -> Path | None:
try:
tile_path.relative_to(OFFLINE_ROOT)
except ValueError:
cloudlog.warning(f"mapd_wrapper refusing to quarantine unexpected path: {filename}")
_cloudlog().warning(f"mapd_wrapper refusing to quarantine unexpected path: {filename}")
return None
if not tile_path.is_file():
@@ -99,7 +101,7 @@ def quarantine_offline_tile(filename: str) -> Path | None:
try:
tile_path.rename(quarantined)
except OSError:
cloudlog.exception(f"mapd_wrapper failed to quarantine offline data: {tile_path}")
_cloudlog().exception(f"mapd_wrapper failed to quarantine offline data: {tile_path}")
return None
return quarantined
@@ -116,17 +118,17 @@ def terminate_child(proc: subprocess.Popen[str]) -> None:
try:
proc.wait(timeout=2)
except subprocess.TimeoutExpired:
cloudlog.error(f"mapd_wrapper child did not exit after kill: pid={proc.pid}")
_cloudlog().error(f"mapd_wrapper child did not exit after kill: pid={proc.pid}")
def run_mapd_once() -> int:
try:
OFFLINE_ROOT.mkdir(parents=True, exist_ok=True)
except PermissionError:
cloudlog.exception(f"mapd_wrapper cannot create offline directory: {OFFLINE_ROOT}")
_cloudlog().exception(f"mapd_wrapper cannot create offline directory: {OFFLINE_ROOT}")
return 2
except OSError:
cloudlog.exception(f"mapd_wrapper failed to prepare offline directory: {OFFLINE_ROOT}")
_cloudlog().exception(f"mapd_wrapper failed to prepare offline directory: {OFFLINE_ROOT}")
return 2
proc = subprocess.Popen(
@@ -157,11 +159,11 @@ def run_mapd_once() -> int:
missing_tile = monitor.current_filename
if is_offline_read_error(line) and missing_tile is not None and not Path(missing_tile).is_file():
if is_null_island_tile(missing_tile):
cloudlog.info(f"mapd_wrapper received a location before GPS fix; waiting to restart mapd: {missing_tile}")
_cloudlog().info(f"mapd_wrapper received a location before GPS fix; waiting to restart mapd: {missing_tile}")
terminate_child(proc)
return WAIT_FOR_GPS_EXIT_CODE
cloudlog.info(f"mapd_wrapper has no offline tile for {missing_tile}; stopping mapd until the next drive")
_cloudlog().info(f"mapd_wrapper has no offline tile for {missing_tile}; stopping mapd until the next drive")
terminate_child(proc)
return MISSING_COVERAGE_EXIT_CODE
@@ -171,15 +173,15 @@ def run_mapd_once() -> int:
quarantined = quarantine_offline_tile(bad_tile)
if quarantined is None:
if not OFFLINE_ROOT.exists():
cloudlog.warning(f"mapd_wrapper detected repeated offline read failures for {bad_tile}, but {OFFLINE_ROOT} does not exist; backing off mapd restarts")
_cloudlog().warning(f"mapd_wrapper detected repeated offline read failures for {bad_tile}, but {OFFLINE_ROOT} does not exist; backing off mapd restarts")
terminate_child(proc)
return 2
cloudlog.warning(f"mapd_wrapper detected repeated offline read failures for {bad_tile}, but could not quarantine it")
_cloudlog().warning(f"mapd_wrapper detected repeated offline read failures for {bad_tile}, but could not quarantine it")
else:
message = f"mapd_wrapper quarantined corrupt offline tile: {bad_tile} -> {quarantined}"
print(message, flush=True)
cloudlog.warning(message)
_cloudlog().warning(message)
terminate_child(proc)
return 1 if quarantined is not None else 2
@@ -195,7 +197,9 @@ def wait_for_road_state_change(params: Params) -> None:
def wait_for_gps_fix_or_road_state_change(params: Params, sm=None) -> None:
initial_onroad = params.get_bool("IsOnroad")
sm = sm or messaging.SubMaster(["gpsLocationExternal"])
if sm is None:
from cereal import messaging
sm = messaging.SubMaster(["gpsLocationExternal"])
while params.get_bool("IsOnroad") == initial_onroad:
sm.update(1000)
@@ -204,6 +208,8 @@ def wait_for_gps_fix_or_road_state_change(params: Params, sm=None) -> None:
def main() -> None:
from openpilot.common.params import Params
params = Params()
while True:
exit_code = run_mapd_once()
@@ -40,6 +40,7 @@ const VEHICLE_SETTING_MAKES = {
SubaruSNG: ["Subaru"],
SubaruSNGManualParkingBrake: ["Subaru"],
SubaruStopStartOff: ["Subaru"],
SubaruRedneckCruise: ["Subaru"],
ClusterOffset: ["Lexus", "Toyota"],
SNGHack: ["Lexus", "Toyota"],
ToyotaAutoHold: ["Lexus", "Toyota"],
@@ -28,6 +28,7 @@ const VEHICLE_SETTING_MAKES = {
SubaruSNG: ["Subaru"],
SubaruSNGManualParkingBrake: ["Subaru"],
SubaruStopStartOff: ["Subaru"],
SubaruRedneckCruise: ["Subaru"],
ClusterOffset: ["Lexus", "Toyota"],
SNGHack: ["Lexus", "Toyota"],
ToyotaAutoHold: ["Lexus", "Toyota"],
@@ -321,6 +321,13 @@ def test_hidden_feature_defaults_remain_enabled():
assert _declared_default(key) == "1"
def test_toyota_auto_hold_is_galaxy_only():
setting = _params_by_section(_layout())["Vehicle"]["ToyotaAutoHold"]
assert setting["galaxy_only"] is True
assert setting["ui_type"] == "toggle"
assert setting["data_type"] == "bool"
def test_human_acceleration_param_is_removed():
params_source = PARAM_KEYS_PATH.read_text(encoding="utf-8")
assert '{"HumanAcceleration",' not in params_source
+9 -1
View File
@@ -117,6 +117,14 @@ def run_navigationd(started: bool, params: Params, CP: car.CarParams, starpilot_
return started and params.get("NavDestination") is not None
def run_mapd(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool:
if started:
return True
memory_params = Params(memory=True)
return memory_params.get_bool("DownloadMaps") or memory_params.get_bool("CancelDownloadMaps")
def bluetooth_enabled(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool:
return params.get_bool("BluetoothEnabled")
@@ -216,7 +224,7 @@ else:
procs += [
PythonProcess("device_syncd", "starpilot.system.device_syncd", always_run),
PythonProcess("starpilot_process", "starpilot.starpilot_process", always_run),
PythonProcess("mapd", "starpilot.navigation.mapd_wrapper", always_run, nice=19),
PythonProcess("mapd", "starpilot.navigation.mapd_wrapper", run_mapd, nice=19),
PythonProcess("navigationd", "starpilot.navigation.navigationd", run_navigationd, nice=19),
PythonProcess("speed_limit_filler", "starpilot.system.speed_limit_filler", run_speed_limit_filler, nice=19),
PythonProcess("speed_limit_vision", "starpilot.system.speed_limit_vision", run_speed_limit_vision, nice=19),
@@ -4,6 +4,7 @@ import pytest
from cereal import car
from opendbc.car.ford.values import CAR as FORD_CAR
import openpilot.system.manager.process_config as process_config
from openpilot.system.manager.process_config import (
allow_uploads,
bluetooth_enabled,
@@ -48,6 +49,31 @@ def test_uploader_runs_at_background_priority():
assert managed_processes["uploader"].nice == 19
def test_mapd_runs_onroad_or_during_offroad_map_transfer(monkeypatch):
class MemoryParams:
values = {}
def __init__(self, *, memory=False):
assert memory
def get_bool(self, key):
return self.values.get(key, False)
monkeypatch.setattr(process_config, "Params", MemoryParams)
params = object()
CP = car.CarParams.new_message()
toggles = SimpleNamespace()
assert process_config.run_mapd(True, params, CP, toggles)
assert not process_config.run_mapd(False, params, CP, toggles)
MemoryParams.values["DownloadMaps"] = True
assert process_config.run_mapd(False, params, CP, toggles)
MemoryParams.values = {"DownloadMaps": False, "CancelDownloadMaps": True}
assert process_config.run_mapd(False, params, CP, toggles)
@pytest.mark.parametrize("enabled", [False, True])
def test_bluetooth_process_is_param_gated(enabled):
params = SimpleNamespace(get_bool=lambda key: enabled if key == "BluetoothEnabled" else False)