diff --git a/opendbc_repo/opendbc/car/nissan/carcontroller.py b/opendbc_repo/opendbc/car/nissan/carcontroller.py index 1480eaf84..66a183654 100644 --- a/opendbc_repo/opendbc/car/nissan/carcontroller.py +++ b/opendbc_repo/opendbc/car/nissan/carcontroller.py @@ -29,7 +29,7 @@ class CarController(CarControllerBase): can_sends = [] - if self.CP.openpilotLongitudinalControl and self.car_fingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC): + if self.CP.openpilotLongitudinalControl and self.car_fingerprint == CAR.NISSAN_LEAF: accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)) stopping = actuators.longControlState == LongCtrlState.stopping brake_mode = CC.longActive and (accel < CarControllerParams.PROPILOT_ACCEL_MIN or stopping) diff --git a/opendbc_repo/opendbc/car/nissan/carstate.py b/opendbc_repo/opendbc/car/nissan/carstate.py index 4f297986c..2a9cade83 100644 --- a/opendbc_repo/opendbc/car/nissan/carstate.py +++ b/opendbc_repo/opendbc/car/nissan/carstate.py @@ -134,7 +134,7 @@ class CarState(CarStateBase): buttonEvents = create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) - if self.CP.carFingerprint in (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC): + if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint == CAR.NISSAN_LEAF: prev_set_button = self.set_button prev_res_button = self.res_button prev_cancel_button = self.cancel_button diff --git a/opendbc_repo/opendbc/car/nissan/interface.py b/opendbc_repo/opendbc/car/nissan/interface.py index 7b0505fd6..734303f16 100644 --- a/opendbc_repo/opendbc/car/nissan/interface.py +++ b/opendbc_repo/opendbc/car/nissan/interface.py @@ -7,9 +7,16 @@ from opendbc.car.nissan.values import CAR, CarControllerParams, NissanSafetyFlag NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_RX_OFFSET -LEAF_LONGITUDINAL_CARS = (CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC) LEAF_ADAS_ECU_ADDR = 0x707 LEAF_ADAS_ECU_BUS = 0 +LEAF_2025_SV_PLUS_CAMERA_FW = b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80' + + +def is_leaf_2025_sv_plus_longitudinal(candidate, car_fw): + return candidate == CAR.NISSAN_LEAF and any( + fw.address == LEAF_ADAS_ECU_ADDR and bytes(fw.fwVersion) == LEAF_2025_SV_PLUS_CAMERA_FW + for fw in car_fw + ) class CarInterface(CarInterfaceBase): @@ -18,7 +25,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed): - if CP.carFingerprint in LEAF_LONGITUDINAL_CARS and CP.openpilotLongitudinalControl: + if CP.carFingerprint == CAR.NISSAN_LEAF and CP.openpilotLongitudinalControl: return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX return CarInterfaceBase.get_pid_accel_limits(CP, current_speed, cruise_speed) @@ -35,7 +42,7 @@ class CarInterface(CarInterfaceBase): ret.steerControlType = structs.CarParams.SteerControlType.angle ret.radarUnavailable = True - ret.alphaLongitudinalAvailable = candidate in LEAF_LONGITUDINAL_CARS + ret.alphaLongitudinalAvailable = is_leaf_2025_sv_plus_longitudinal(candidate, car_fw) ret.openpilotLongitudinalControl = alpha_long and ret.alphaLongitudinalAvailable ret.pcmCruise = not ret.openpilotLongitudinalControl @@ -55,7 +62,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, can_recv, can_send): - if not (CP.openpilotLongitudinalControl and CP.carFingerprint in LEAF_LONGITUDINAL_CARS): + if not (CP.openpilotLongitudinalControl and CP.carFingerprint == CAR.NISSAN_LEAF): return from openpilot.common.params import Params @@ -83,7 +90,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def deinit(CP, can_recv, can_send): - if not (CP.openpilotLongitudinalControl and CP.carFingerprint in LEAF_LONGITUDINAL_CARS): + if not (CP.openpilotLongitudinalControl and CP.carFingerprint == CAR.NISSAN_LEAF): return communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, diff --git a/opendbc_repo/opendbc/car/nissan/tests/test_nissan.py b/opendbc_repo/opendbc/car/nissan/tests/test_nissan.py index bd7ace556..0d954b4f9 100644 --- a/opendbc_repo/opendbc/car/nissan/tests/test_nissan.py +++ b/opendbc_repo/opendbc/car/nissan/tests/test_nissan.py @@ -4,16 +4,21 @@ import pytest from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs, uds from opendbc.car.nissan.carstate import CarState -from opendbc.car.nissan.interface import CarInterface +from opendbc.car.nissan.interface import CarInterface, LEAF_2025_SV_PLUS_CAMERA_FW from opendbc.car.nissan.values import CAR, CarControllerParams, NissanSafetyFlags TEST_TOGGLES = SimpleNamespace(force_torque_controller=False, nnff=False, nnff_lite=False, trailer_load_kg=0) +SUPPORTED_LEAF_FW = [structs.CarParams.CarFw( + ecu=structs.CarParams.Ecu.fwdCamera, + fwVersion=LEAF_2025_SV_PLUS_CAMERA_FW, + address=0x707, +)] def run_controller(alpha_long, accel=0.0, long_active=True, long_state=structs.CarControl.Actuators.LongControlState.pid): - CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), [], alpha_long, False, False, TEST_TOGGLES) - FPCP = CarInterface.get_starpilot_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), [], CP, TEST_TOGGLES) + CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, alpha_long, False, False, TEST_TOGGLES) + FPCP = CarInterface.get_starpilot_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, CP, TEST_TOGGLES) CI = CarInterface(CP, FPCP) CI.update([], TEST_TOGGLES) @@ -26,10 +31,9 @@ def run_controller(alpha_long, accel=0.0, long_active=True, long_state=structs.C return {msg[0]: msg for msg in can_sends} -@pytest.mark.parametrize("candidate", [CAR.NISSAN_LEAF, CAR.NISSAN_LEAF_IC]) -def test_leaf_alpha_long_params(candidate): - stock = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, None) - alpha_long = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, None) +def test_leaf_2025_sv_plus_alpha_long_params(): + stock = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, False, False, False, None) + alpha_long = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None) assert stock.alphaLongitudinalAvailable assert not stock.openpilotLongitudinalControl @@ -44,6 +48,20 @@ def test_leaf_alpha_long_params(candidate): assert CarInterface.get_pid_accel_limits(alpha_long, 0, 0) == (CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) +@pytest.mark.parametrize(("candidate", "car_fw"), [ + (CAR.NISSAN_LEAF, []), + (CAR.NISSAN_LEAF, [structs.CarParams.CarFw(address=0x707, fwVersion=b"different firmware")]), + (CAR.NISSAN_LEAF_IC, SUPPORTED_LEAF_FW), +]) +def test_other_leaf_variants_do_not_offer_alpha_long(candidate, car_fw): + CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), car_fw, True, False, False, None) + + assert not CP.alphaLongitudinalAvailable + assert not CP.openpilotLongitudinalControl + assert CP.pcmCruise + assert not (CP.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL) + + def test_non_leaf_does_not_offer_alpha_long(): CP = CarInterface.get_params(CAR.NISSAN_ROGUE, gen_empty_fingerprint(), [], True, False, False, None) @@ -94,8 +112,8 @@ def test_alpha_long_controller_sends_inactive_commands_when_disengaged(): @pytest.mark.parametrize(("signal", "button_type"), [("SET_BUTTON", ButtonType.decelCruise), ("RES_BUTTON", ButtonType.accelCruise)]) def test_leaf_set_resume_release_enables_alpha_long(signal, button_type): - CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), [], True, False, False, TEST_TOGGLES) - FPCP = CarInterface.get_starpilot_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), [], CP, TEST_TOGGLES) + CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, TEST_TOGGLES) + FPCP = CarInterface.get_starpilot_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, CP, TEST_TOGGLES) CS = CarState(CP, FPCP) parsers = CS.get_can_parsers(CP) @@ -111,7 +129,7 @@ def test_leaf_set_resume_release_enables_alpha_long(signal, button_type): @pytest.mark.parametrize("ecu_disabled", [False, True]) def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled): - CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), [], True, False, False, None) + CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None) calls = [] def fake_disable_ecu(*args, **kwargs): @@ -139,7 +157,7 @@ def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled): def test_leaf_kwp_session_can_confirm_ecu_disable(monkeypatch): - CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), [], True, False, False, None) + CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None) results = iter((False, True)) monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: next(results)) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 1480d361e..2fa7fc388 100644 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -367,10 +367,8 @@ class Car: was_openpilot_long = self.CP.openpilotLongitudinalControl self.CI.init(self.CP, *self.can_callbacks) # If ECU disable was skipped/failed, strip LONG safety flag from BOTH CarParams - # and StarPilotCarParams (pandad ORs both safetyParams together) - # Use the pre-init longitudinal state here, since Hyundai init() may already - # flip CP.openpilotLongitudinalControl to False as part of the fallback. - if was_openpilot_long and self.CP.brand in ("hyundai", "nissan") and self.params.get_bool("EcuDisableFailed"): + nissan_leaf_alpha_long = self.CP.brand == "nissan" and self.CP.carFingerprint == "NISSAN_LEAF" + if was_openpilot_long and (self.CP.brand == "hyundai" or nissan_leaf_alpha_long) and self.params.get_bool("EcuDisableFailed"): # ECU disable failed/rejected - switch to lateral-only mode with stock ACC # Keep this local to avoid importing every brand's values into card.py. LONG_FLAG = 4 if self.CP.brand == "hyundai" else 2 if self.CP.brand == "nissan" else 0 @@ -378,9 +376,6 @@ class Car: cfg.safetyParam &= ~LONG_FLAG for cfg in self.FPCP.safetyConfigs: cfg.safetyParam &= ~LONG_FLAG - # Let stock ACC manage cruise (prevents "controls mismatch" error) - # Clear openpilotLongitudinalControl so controlsd doesn't set - # cruiseControl.override=True (which fights stock ACC and causes engage flicker) self.CP.pcmCruise = True self.CP.openpilotLongitudinalControl = False self.params.put("CarParams", self.CP.to_bytes()) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index ec5b7a0e6..dcc1b4b2a 100644 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -12,6 +12,7 @@ from openpilot.common.swaglog import cloudlog from opendbc.car.car_helpers import interfaces from opendbc.car.chrysler.values import pacifica_hybrid_aol_stock_acc_mode from opendbc.car.gm.values import CAR as GM_CAR +from opendbc.car.nissan.values import CAR as NISSAN_CAR from opendbc.car.vehicle_model import VehicleModel from openpilot.selfdrive.controls.lib.drive_helpers import MAX_LATERAL_JERK, clip_curvature, get_lateral_active from openpilot.selfdrive.controls.lib.lane_centering import LaneCenteringController @@ -365,11 +366,12 @@ class Controls: if self.ecu_disable_failed_checked: return - # ControlsReady is set after CarInterface.init(), where Hyundai ECU disable - # writes EcuDisableFailed. Once init has completed, the value is stable. if self.params.get_bool("ControlsReady"): self.ecu_disable_failed = self.params.get_bool("EcuDisableFailed") self.ecu_disable_failed_checked = True + if self.ecu_disable_failed and self.CP.carFingerprint == NISSAN_CAR.NISSAN_LEAF: + self.CP.openpilotLongitudinalControl = False + self.CP.pcmCruise = True def state_control(self): CS = self.sm['carState'] diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index dc8438bf7..3e57cd448 100644 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -13,6 +13,7 @@ from msgq.visionipc import VisionIpcClient, VisionStreamType from opendbc.car.chrysler.values import pacifica_hybrid_aol_stock_acc_mode from opendbc.car.gm.values import GMFlags from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR +from opendbc.car.nissan.values import CAR as NISSAN_CAR from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL @@ -223,6 +224,10 @@ class SelfdriveD: self.logged_comm_issue = None self.not_running_prev = None self.experimental_mode = False + self.ecu_disable_failed = False + self.ecu_disable_failed_checked = not ( + self.CP.openpilotLongitudinalControl and self.CP.carFingerprint == NISSAN_CAR.NISSAN_LEAF + ) self.safe_mode = self.params.get_bool("SafeMode") self.personality = log.LongitudinalPersonality.relaxed if self.safe_mode else self.params.get("LongitudinalPersonality", return_default=True) self.recalibrating_seen = False @@ -276,6 +281,23 @@ class SelfdriveD: self.FPCP = messaging.log_from_bytes(self.params.get("StarPilotCarParams", block=True), custom.StarPilotCarParams) + def update_ecu_disable_failed(self): + if self.ecu_disable_failed_checked: + return + if self.CP.carFingerprint != NISSAN_CAR.NISSAN_LEAF: + self.ecu_disable_failed_checked = True + return + + if self.params.get_bool("ControlsReady"): + self.ecu_disable_failed = self.params.get_bool("EcuDisableFailed") + self.ecu_disable_failed_checked = True + if self.ecu_disable_failed: + fallback_cp = messaging.log_from_bytes(self.params.get("CarParams"), car.CarParams) + fallback_fpcp = messaging.log_from_bytes(self.params.get("StarPilotCarParams"), custom.StarPilotCarParams) + self.CP.openpilotLongitudinalControl = fallback_cp.openpilotLongitudinalControl + self.CP.pcmCruise = fallback_cp.pcmCruise + self.FPCP = fallback_fpcp + def clear_longitudinal_excessive_actuation_alert(self): alert = self.params.get("Offroad_ExcessiveActuation") if not alert: @@ -305,6 +327,7 @@ class SelfdriveD: def update_events(self, CS): """Compute onroadEvents from carState""" + self.update_ecu_disable_failed() self.events.clear() self.starpilot_events.clear() diff --git a/selfdrive/selfdrived/tests/test_selfdrived.py b/selfdrive/selfdrived/tests/test_selfdrived.py index f0ab22c99..965bd5553 100644 --- a/selfdrive/selfdrived/tests/test_selfdrived.py +++ b/selfdrive/selfdrived/tests/test_selfdrived.py @@ -1,7 +1,24 @@ -from cereal import car +from cereal import car, custom 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 commanded_torque_at_max_for_saturation +from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD, commanded_torque_at_max_for_saturation + + +class FakeFallbackParams: + def __init__(self, controls_ready, ecu_disable_failed, fallback_cp, fallback_fpcp): + self.controls_ready = controls_ready + self.ecu_disable_failed = ecu_disable_failed + self.values = { + "CarParams": fallback_cp.to_bytes(), + "StarPilotCarParams": fallback_fpcp.to_bytes(), + } + + def get_bool(self, key): + return self.controls_ready if key == "ControlsReady" else self.ecu_disable_failed + + def get(self, key): + return self.values[key] def test_immediate_max_output_saturation_is_torque_controller_only(): @@ -27,3 +44,64 @@ def test_gv70_uses_normal_saturation_timer_at_max_output(): CP.lateralTuning.init("torque") assert not commanded_torque_at_max_for_saturation(CP, 1.0) + + +def test_ecu_disable_fallback_synchronizes_behavior_and_safety_params(): + initial_cp = car.CarParams.new_message() + initial_cp.carFingerprint = NISSAN_CAR.NISSAN_LEAF + initial_cp.openpilotLongitudinalControl = True + initial_cp.pcmCruise = False + initial_cp.safetyConfigs = [car.CarParams.SafetyConfig.new_message(safetyParam=2)] + initial_fpcp = custom.StarPilotCarParams.new_message() + initial_fpcp.safetyConfigs = [custom.StarPilotCarParams.SafetyConfig.new_message(safetyParam=2)] + + fallback_cp = car.CarParams.new_message() + fallback_cp.openpilotLongitudinalControl = False + fallback_cp.pcmCruise = True + fallback_cp.safetyConfigs = [car.CarParams.SafetyConfig.new_message(safetyParam=0)] + fallback_fpcp = custom.StarPilotCarParams.new_message() + fallback_fpcp.safetyConfigs = [custom.StarPilotCarParams.SafetyConfig.new_message(safetyParam=0)] + + selfdrived = SelfdriveD.__new__(SelfdriveD) + selfdrived.CP = initial_cp + selfdrived.FPCP = initial_fpcp + selfdrived.params = FakeFallbackParams(True, True, fallback_cp, fallback_fpcp) + selfdrived.ecu_disable_failed = False + selfdrived.ecu_disable_failed_checked = False + + selfdrived.update_ecu_disable_failed() + + assert selfdrived.ecu_disable_failed + assert selfdrived.ecu_disable_failed_checked + assert not selfdrived.CP.openpilotLongitudinalControl + assert selfdrived.CP.pcmCruise + assert selfdrived.FPCP.safetyConfigs[0].safetyParam == 0 + + +def test_ecu_disable_fallback_does_not_change_other_cars(): + initial_cp = car.CarParams.new_message() + initial_cp.carFingerprint = HYUNDAI_CAR.HYUNDAI_SONATA + initial_cp.openpilotLongitudinalControl = True + initial_cp.pcmCruise = False + initial_fpcp = custom.StarPilotCarParams.new_message() + initial_fpcp.safetyConfigs = [custom.StarPilotCarParams.SafetyConfig.new_message(safetyParam=4)] + + fallback_cp = car.CarParams.new_message() + fallback_cp.openpilotLongitudinalControl = False + fallback_cp.pcmCruise = True + fallback_fpcp = custom.StarPilotCarParams.new_message() + fallback_fpcp.safetyConfigs = [custom.StarPilotCarParams.SafetyConfig.new_message(safetyParam=0)] + + selfdrived = SelfdriveD.__new__(SelfdriveD) + selfdrived.CP = initial_cp + selfdrived.FPCP = initial_fpcp + selfdrived.params = FakeFallbackParams(True, True, fallback_cp, fallback_fpcp) + selfdrived.ecu_disable_failed = False + selfdrived.ecu_disable_failed_checked = False + + selfdrived.update_ecu_disable_failed() + + assert selfdrived.ecu_disable_failed_checked + assert selfdrived.CP.openpilotLongitudinalControl + assert not selfdrived.CP.pcmCruise + assert selfdrived.FPCP.safetyConfigs[0].safetyParam == 4 diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 42e5d8e60..115fe9277 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -88,7 +88,8 @@ def allow_logging(started: bool, params: Params, CP: car.CarParams, starpilot_to return not starpilot_toggles.no_logging def allow_uploads(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool: - return params.get_bool("AlwaysAllowUploads") or not starpilot_toggles.no_uploads or starpilot_toggles.no_onroad_uploads + return (params.get_bool("AlwaysAllowUploads") or not starpilot_toggles.no_uploads or + (starpilot_toggles.no_onroad_uploads and not started)) def run_speed_limit_filler(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool: return starpilot_toggles.speed_limit_filler @@ -227,7 +228,7 @@ procs = [ PythonProcess("hardwared", "system.hardware.hardwared", always_run), PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC), PythonProcess("updated", "system.updated.updated", always_run, enabled=not PC), - PythonProcess("uploader", "system.loggerd.uploader", allow_uploads), + PythonProcess("uploader", "system.loggerd.uploader", allow_uploads, nice=19), PythonProcess("statsd", "system.statsd", always_run), PythonProcess("feedbackd", "selfdrive.ui.feedback.feedbackd", only_onroad), diff --git a/system/manager/test/test_process_config.py b/system/manager/test/test_process_config.py new file mode 100644 index 000000000..872ebd7a9 --- /dev/null +++ b/system/manager/test/test_process_config.py @@ -0,0 +1,38 @@ +from types import SimpleNamespace + +import pytest + +from cereal import car +from openpilot.system.manager.process_config import allow_uploads, managed_processes + + +class FakeParams: + def __init__(self, always_allow_uploads: bool = False): + self.always_allow_uploads = always_allow_uploads + + def get_bool(self, key: str) -> bool: + assert key == "AlwaysAllowUploads" + return self.always_allow_uploads + + +@pytest.mark.parametrize( + "started,no_uploads,no_onroad_uploads,always_allow_uploads,expected", + [ + (True, False, False, False, True), + (False, False, False, False, True), + (True, True, False, False, False), + (False, True, False, False, False), + (True, True, True, False, False), + (False, True, True, False, True), + (True, True, False, True, True), + ], +) +def test_allow_uploads(started, no_uploads, no_onroad_uploads, always_allow_uploads, expected): + params = FakeParams(always_allow_uploads) + toggles = SimpleNamespace(no_uploads=no_uploads, no_onroad_uploads=no_onroad_uploads) + + assert allow_uploads(started, params, car.CarParams.new_message(), toggles) is expected + + +def test_uploader_runs_at_background_priority(): + assert managed_processes["uploader"].nice == 19