diff --git a/common/params_keys.h b/common/params_keys.h index c03e5839c..a453774ea 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -708,6 +708,7 @@ inline static std::unordered_map 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, "", ""}}, diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py index 525fdd2bd..e161fc37e 100644 --- a/opendbc_repo/opendbc/car/hyundai/carstate.py +++ b/opendbc_repo/opendbc/car/hyundai/carstate.py @@ -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) diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index 22bf3ca56..5e76e116e 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -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): diff --git a/opendbc_repo/opendbc/car/interfaces.py b/opendbc_repo/opendbc/car/interfaces.py index 1f4c3ad52..f64b7891a 100644 --- a/opendbc_repo/opendbc/car/interfaces.py +++ b/opendbc_repo/opendbc/car/interfaces.py @@ -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 diff --git a/opendbc_repo/opendbc/car/subaru/carcontroller.py b/opendbc_repo/opendbc/car/subaru/carcontroller.py index bde3669b9..7724e1ce5 100644 --- a/opendbc_repo/opendbc/car/subaru/carcontroller.py +++ b/opendbc_repo/opendbc/car/subaru/carcontroller.py @@ -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: diff --git a/opendbc_repo/opendbc/car/subaru/carstate.py b/opendbc_repo/opendbc/car/subaru/carstate.py index cee89d2df..b5fe92105 100644 --- a/opendbc_repo/opendbc/car/subaru/carstate.py +++ b/opendbc_repo/opendbc/car/subaru/carstate.py @@ -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"]) diff --git a/opendbc_repo/opendbc/car/subaru/subarucan.py b/opendbc_repo/opendbc/car/subaru/subarucan.py index 1d2d04d2e..93b6a49b5 100644 --- a/opendbc_repo/opendbc/car/subaru/subarucan.py +++ b/opendbc_repo/opendbc/car/subaru/subarucan.py @@ -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 [ diff --git a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py index d6ce08e54..454a8a93a 100644 --- a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py +++ b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py @@ -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] diff --git a/opendbc_repo/opendbc/car/subaru/values.py b/opendbc_repo/opendbc/car/subaru/values.py index 4bf94c610..6bf3a03e8 100644 --- a/opendbc_repo/opendbc/car/subaru/values.py +++ b/opendbc_repo/opendbc/car/subaru/values.py @@ -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]) + \ diff --git a/opendbc_repo/opendbc/car/toyota/carcontroller.py b/opendbc_repo/opendbc/car/toyota/carcontroller.py index fe6c84964..b783021fc 100644 --- a/opendbc_repo/opendbc/car/toyota/carcontroller.py +++ b/opendbc_repo/opendbc/car/toyota/carcontroller.py @@ -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) diff --git a/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py b/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py index b16bef762..0bbd1b161 100644 --- a/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py +++ b/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py @@ -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={}, diff --git a/opendbc_repo/opendbc/dbc/hyundai_kia_generic.dbc b/opendbc_repo/opendbc/dbc/hyundai_kia_generic.dbc index de6ee7d89..b26f0f392 100644 --- a/opendbc_repo/opendbc/dbc/hyundai_kia_generic.dbc +++ b/opendbc_repo/opendbc/dbc/hyundai_kia_generic.dbc @@ -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 diff --git a/opendbc_repo/opendbc/safety/modes/subaru.h b/opendbc_repo/opendbc/safety/modes/subaru.h index ba5f05845..f76bb5b1a 100644 --- a/opendbc_repo/opendbc/safety/modes/subaru.h +++ b/opendbc_repo/opendbc/safety/modes/subaru.h @@ -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); } diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 2b7beb98e..efa953056 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -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, ) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index ae4d77b87..8e761c203 100644 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -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") diff --git a/selfdrive/car/tests/test_car_specific.py b/selfdrive/car/tests/test_car_specific.py index 7b3eb01e1..a205f364f 100644 --- a/selfdrive/car/tests/test_car_specific.py +++ b/selfdrive/car/tests/test_car_specific.py @@ -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 diff --git a/starpilot/common/assets/device_settings_layout.json b/starpilot/common/assets/device_settings_layout.json index dde3e088d..57f6093e8 100644 --- a/starpilot/common/assets/device_settings_layout.json +++ b/starpilot/common/assets/device_settings_layout.json @@ -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", diff --git a/starpilot/common/safe_mode.py b/starpilot/common/safe_mode.py index f96e72ceb..a6fbb1f7c 100644 --- a/starpilot/common/safe_mode.py +++ b/starpilot/common/safe_mode.py @@ -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 = { diff --git a/starpilot/common/starpilot_functions.py b/starpilot/common/starpilot_functions.py index 1d5d08bec..571362435 100644 --- a/starpilot/common/starpilot_functions.py +++ b/starpilot/common/starpilot_functions.py @@ -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"]) diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index b72dbd11e..ac7927901 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -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 diff --git a/starpilot/navigation/mapd_wrapper.py b/starpilot/navigation/mapd_wrapper.py index 55a93c902..dbc7736ba 100644 --- a/starpilot/navigation/mapd_wrapper.py +++ b/starpilot/navigation/mapd_wrapper.py @@ -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() diff --git a/starpilot/system/the_galaxy/assets/components/tools/device_settings.js b/starpilot/system/the_galaxy/assets/components/tools/device_settings.js index 08840a7d5..f7ca579d1 100644 --- a/starpilot/system/the_galaxy/assets/components/tools/device_settings.js +++ b/starpilot/system/the_galaxy/assets/components/tools/device_settings.js @@ -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"], diff --git a/starpilot/system/the_galaxy/assets/mobile/js/params.js b/starpilot/system/the_galaxy/assets/mobile/js/params.js index ab0140d11..53b71be84 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/params.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/params.js @@ -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"], diff --git a/starpilot/system/the_galaxy/tests/test_device_settings_layout.py b/starpilot/system/the_galaxy/tests/test_device_settings_layout.py index 100359d7c..5ec2fd1ad 100644 --- a/starpilot/system/the_galaxy/tests/test_device_settings_layout.py +++ b/starpilot/system/the_galaxy/tests/test_device_settings_layout.py @@ -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 diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 9d94384ba..7f8a16213 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -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), diff --git a/system/manager/test/test_process_config.py b/system/manager/test/test_process_config.py index 268135c4b..2088385bd 100644 --- a/system/manager/test/test_process_config.py +++ b/system/manager/test/test_process_config.py @@ -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)