From 244aa673710dff175df6357f432ac06f2c1f7ba8 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Thu, 10 Sep 2026 11:02:23 -0500 Subject: [PATCH] Blueberry Pancakes --- opendbc_repo/opendbc/car/gm/carstate.py | 18 +++ opendbc_repo/opendbc/car/gm/tests/test_gm.py | 14 ++ .../opendbc/car/hyundai/carcontroller.py | 11 +- .../opendbc/car/hyundai/tests/test_hyundai.py | 17 +-- .../opendbc/car/subaru/carcontroller.py | 15 +- .../opendbc/car/subaru/tests/test_subaru.py | 39 +++++- opendbc_repo/opendbc/safety/modes/gm.h | 6 +- opendbc_repo/opendbc/safety/tests/test_gm.py | 25 ++++ panda/board/drivers/can_common.h | 13 ++ panda/tests/libpanda/libpanda_py.py | 6 + panda/tests/libpanda/panda.c | 16 +++ panda/tests/usbprotocol/test_ignition.py | 45 ++++++ .../lib/longitudinal_vehicle_tunes.py | 8 ++ .../controls/tests/test_starpilot_planner.py | 13 +- selfdrive/selfdrived/events.py | 15 +- starpilot/car/ford/lateral.py | 4 +- starpilot/car/ford/tests/test_lateral.py | 49 +++++++ .../common/assets/device_settings_layout.json | 6 +- .../assets/mobile/js/components/AppShell.js | 47 ++++--- .../mobile/js/components/GalaxyToggleCard.js | 28 ++-- .../mobile/js/components/LanguageSelector.js | 65 +++++++++ .../the_galaxy/assets/mobile/js/i18n.js | 132 ++++++++++++++++++ .../assets/mobile/js/views/Settings.js | 24 ++-- .../the_galaxy/tests/test_ui_vue_frontend.py | 15 ++ 24 files changed, 558 insertions(+), 73 deletions(-) create mode 100644 panda/tests/usbprotocol/test_ignition.py create mode 100644 starpilot/system/the_galaxy/assets/mobile/js/components/LanguageSelector.js create mode 100644 starpilot/system/the_galaxy/assets/mobile/js/i18n.js diff --git a/opendbc_repo/opendbc/car/gm/carstate.py b/opendbc_repo/opendbc/car/gm/carstate.py index 81145c7e72..3fe7306054 100644 --- a/opendbc_repo/opendbc/car/gm/carstate.py +++ b/opendbc_repo/opendbc/car/gm/carstate.py @@ -18,6 +18,7 @@ from opendbc.car.gm.values import ( AccState, CanBus, CruiseButtons, + GM_AUTO_HOLD_CARS, GMFlags, SDGM_CAR, STEER_THRESHOLD, @@ -68,6 +69,18 @@ def update_auto_hold_drive_timers(in_drive_for_hold: bool, moving_for_hold: bool return auto_hold_drive_time, one_pedal_drive_time +def is_gm_auto_hold_active(car_fingerprint: str, auto_hold_engaged: bool, in_drive_for_hold: bool, + cruise_available: bool, standstill: bool, gas_pressed: bool) -> bool: + return ( + auto_hold_engaged and + car_fingerprint in GM_AUTO_HOLD_CARS and + in_drive_for_hold and + cruise_available and + standstill and + not gas_pressed + ) + + def update_startup_acc_fault_suppression(car_fingerprint: str, system_power_mode: int, previous_system_power_mode: int, timer: float, acc_state: int, friction_brake_unavailable: bool) -> tuple[float, bool]: @@ -431,6 +444,11 @@ class CarState(CarStateBase): self.auto_hold_fault_suppression_timer = max(self.auto_hold_fault_suppression_timer - DT_CTRL, 0.0) ret.accFaulted = False + ret.brakeHoldActive = is_gm_auto_hold_active( + self.CP.carFingerprint, self.auto_hold_engaged, in_drive_for_hold, + ret.cruiseState.available, ret.standstill, ret.gasPressed, + ) + if self.CP.enableBsm and not sdgm_non_volt: ret.leftBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["LeftBSM"] == 1 ret.rightBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["RightBSM"] == 1 diff --git a/opendbc_repo/opendbc/car/gm/tests/test_gm.py b/opendbc_repo/opendbc/car/gm/tests/test_gm.py index db4c52c134..e29af56be5 100644 --- a/opendbc_repo/opendbc/car/gm/tests/test_gm.py +++ b/opendbc_repo/opendbc/car/gm/tests/test_gm.py @@ -11,6 +11,7 @@ from opendbc.car.gm import gmcan from opendbc.car.gm.carstate import ( CarState as GMCarState, get_hard_cruise_buttons, + is_gm_auto_hold_active, update_auto_hold_drive_timers, update_startup_acc_fault_suppression, ) @@ -210,6 +211,19 @@ class TestBoltGps: class TestGMCarState: + @parameterized.expand([ + (CAR.BUICK_LACROSSE, True, True, True, True, False, True), + (CAR.CHEVROLET_VOLT, True, True, True, True, False, True), + (CAR.CHEVROLET_BOLT_CC_2017, True, True, True, True, False, False), + (CAR.BUICK_LACROSSE, True, True, True, False, False, False), + (CAR.BUICK_LACROSSE, True, True, True, True, True, False), + ]) + def test_auto_hold_alert_state_requires_supported_complete_stop(self, car_fingerprint, engaged, in_drive, + cruise_available, standstill, gas_pressed, expected): + assert is_gm_auto_hold_active( + car_fingerprint, engaged, in_drive, cruise_available, standstill, gas_pressed, + ) is expected + def test_lacrosse_startup_acc_fault_is_suppressed(self): timer, suppressed = update_startup_acc_fault_suppression( CAR.BUICK_LACROSSE, 2, 0, 0.0, 3, False, diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 44cdbd5590..27a8a809c9 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -16,6 +16,7 @@ from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, Hyundai from opendbc.car.interfaces import CarControllerBase from opendbc.car.vehicle_model import VehicleModel from openpilot.common.params import Params +from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import get_hyundai_canfd_scc_jerk_limits from openpilot.starpilot.common.testing_grounds import testing_ground VisualAlert = structs.CarControl.HUDControl.VisualAlert @@ -862,7 +863,6 @@ class CarController(CarControllerBase): longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False)) lfa_status_cars = ( CAR.HYUNDAI_IONIQ_6, - CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.KIA_EV6, ) lfa_longitudinal_active = self.CP.openpilotLongitudinalControl \ @@ -896,7 +896,8 @@ class CarController(CarControllerBase): if angle_lkas_alt: steering_msg_active = bool(steering_msg_active and drive_gear) angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive) - forward_stock_lkas = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and angle_lkas_alt and ( + forward_stock_lkas = (self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR or + self.CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026) and angle_lkas_alt and ( angle_lkas_alt_standstill_handoff or not (drive_gear and (CC.latActive or CC.enabled)) ) preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint) @@ -1023,7 +1024,11 @@ class CarController(CarControllerBase): CC.rightBlinker)) if self.frame % 2 == 0: if self.CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN: - acc_kwargs = {} + scc_jerk_limits = get_hyundai_canfd_scc_jerk_limits(self.CP) + acc_kwargs = { + "jerk_upper": scc_jerk_limits[0], + "jerk_lower": scc_jerk_limits[1], + } else: lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos) acc_kwargs = { diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index ad3d320ff4..a45d6c1802 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -2555,6 +2555,7 @@ class TestHyundaiFingerprint: "DAMP_FACTOR": 100, } cc = SimpleNamespace(enabled=True, latActive=True, + longActive=False, actuators=SimpleNamespace(longControlState=LongCtrlState.off), leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace()) @@ -2588,7 +2589,7 @@ class TestHyundaiFingerprint: cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2) steering_names = [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in inactive_msgs if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")] - assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)] + assert steering_names == [("LKAS", can_bus.ACAN)] controller.frame = 1 cc.longActive = True @@ -2755,7 +2756,7 @@ class TestHyundaiFingerprint: assert len([msg for msg in msgs if msg[0] == 0x110]) == expected_lkas_msgs @pytest.mark.parametrize("standstill", [False, True]) - def test_sportage_angle_lkas_alt_publishes_inactive_status(self, standstill): + def test_sportage_angle_lkas_alt_forwards_stock_status_when_inactive(self, standstill): CP = CarParams.new_message() CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026 CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING | @@ -2763,7 +2764,6 @@ class TestHyundaiFingerprint: CP.openpilotLongitudinalControl = False controller = CarController(DBC[CP.carFingerprint], CP) - can_bus = CanBus(CP) cc = SimpleNamespace(enabled=False, latActive=False, actuators=SimpleNamespace(longControlState=LongCtrlState.off), leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace()) @@ -2773,16 +2773,7 @@ class TestHyundaiFingerprint: msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1) - lkas_msgs = [msg for msg in msgs if msg[0] == 0x110] - assert len(lkas_msgs) == 1 - - parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN) - parser.update([(1, lkas_msgs)]) - assert parser.can_valid - assert parser.vl["LKAS_ALT"]["LKA_ICON"] == 1 - assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 1 - assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 0 - assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 1 + assert not [msg for msg in msgs if msg[0] in (0x110, 0x12A)] def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self): CP = CarParams.new_message() diff --git a/opendbc_repo/opendbc/car/subaru/carcontroller.py b/opendbc_repo/opendbc/car/subaru/carcontroller.py index 6e3edbc5b5..e2d4fdf779 100644 --- a/opendbc_repo/opendbc/car/subaru/carcontroller.py +++ b/opendbc_repo/opendbc/car/subaru/carcontroller.py @@ -222,12 +222,14 @@ class CarController(CarControllerBase): self.angle_reclaim_frames = 0 self.angle_reclaim_start_angle = 0.0 - def _angle_manual_handoff(self, CS, lat_active): + def _angle_manual_handoff(self, CS, lat_active, use_steering_pressed=False): if not lat_active: self._reset_angle_handoff() return False driver_override = self._update_angle_driver_override(CS) + if use_steering_pressed: + driver_override = driver_override or getattr(CS.out, "steeringPressed", False) if driver_override: self.angle_handoff_active = True self.angle_override_hold_frames = _ANGLE_OVERRIDE_HOLD_FRAMES @@ -325,7 +327,9 @@ class CarController(CarControllerBase): lkas_available = CC.latActive and (not mads_only or mads_only_ok) and \ CS.out.gearShifter == structs.CarState.GearShifter.drive and not CS.out.standstill - manual_handoff = self._angle_manual_handoff(CS, CC.latActive) + manual_handoff = self._angle_manual_handoff( + CS, CC.latActive, use_steering_pressed=self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023, + ) lkas_active = lkas_available and not manual_handoff if lkas_active and not self.angle_lkas_active: @@ -408,6 +412,11 @@ class CarController(CarControllerBase): return subarucan.create_steering_control(self.packer, apply_torque, apply_steer_req) + def _lkas_status_active(self, CC): + if self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023: + return self.angle_lkas_active + return CC.latActive + def update(self, CC, CS, now_nanos, starpilot_toggles): actuators = CC.actuators hud_control = CC.hudControl @@ -484,7 +493,7 @@ class CarController(CarControllerBase): 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, + can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus)) diff --git a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py index 962e194f66..c5901320b2 100644 --- a/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py +++ b/opendbc_repo/opendbc/car/subaru/tests/test_subaru.py @@ -744,10 +744,10 @@ def test_ascent_angle_controller_blocks_parking_lot_aol_engagement(): assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg) -def test_lkas_hud_state_uses_lateral_active(): +def test_lkas_hud_state_uses_outback_angle_request_state(): update_source = inspect.getsource(CarController.update) - assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.latActive" in update_source + assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC)" in update_source assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.enabled" not in update_source @@ -765,3 +765,38 @@ def test_lkas_hud_active_bit_follows_lateral_state(enabled, expected): assert parser.can_valid assert parser.vl["ES_LKAS_State"]["LKAS_ACTIVE"] == expected + + +def test_outback_manual_steering_releases_angle_request_before_lkas_fault(): + CP = CarInterface.get_non_essential_params(CAR.SUBARU_OUTBACK_2023) + controller = CarController({}, CP) + CC = SimpleNamespace( + enabled=False, + latActive=True, + actuators=SimpleNamespace(steeringAngleDeg=-225.0), + ) + CS = SimpleNamespace(out=SimpleNamespace( + vEgoRaw=0.9, + steeringAngleDeg=-57.0, + steeringRateDeg=-45.0, + steeringTorque=-127.0, + steeringPressed=True, + gearShifter=structs.CarState.GearShifter.drive, + standstill=False, + )) + parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main) + + msg = controller.lateral_angle(CC, CS) + parser.update([(1, [msg])]) + + assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0 + assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg) + assert not controller._lkas_status_active(CC) + + +def test_other_angle_cars_keep_lateral_status_behavior(): + CP = CarInterface.get_non_essential_params(CAR.SUBARU_CROSSTREK_2025) + controller = CarController({}, CP) + controller.angle_lkas_active = False + + assert controller._lkas_status_active(SimpleNamespace(latActive=True)) diff --git a/opendbc_repo/opendbc/safety/modes/gm.h b/opendbc_repo/opendbc/safety/modes/gm.h index 69c722c3b9..fef65fc1e1 100644 --- a/opendbc_repo/opendbc/safety/modes/gm.h +++ b/opendbc_repo/opendbc/safety/modes/gm.h @@ -60,6 +60,7 @@ static bool gm_panda_3d1_sched = false; static bool gm_panda_paddle_sched = false; static bool gm_bolt_2022_pedal = false; static bool gm_alt_brake = false; +static bool gm_volt_cc_gateway = false; static bool gm_volt_auto_hold = false; static bool gm_volt_one_pedal = false; @@ -261,7 +262,8 @@ static void gm_rx_hook(const CANPacket_t *msg) { } if ((msg->addr == 0xF1U) && gm_alt_brake) { - brake_pressed = msg->data[1] >= 6U; + const uint8_t brake_threshold = gm_volt_cc_gateway ? 21U : 6U; + brake_pressed = msg->data[1] >= brake_threshold; } if ((msg->addr == 0xC9U) && (gm_hw == GM_CAM) && !gm_force_brake_c9) { @@ -720,7 +722,7 @@ static safety_config gm_init(uint16_t param) { gm_cc_long = GET_FLAG(param, GM_PARAM_CC_LONG); gm_has_acc = !GET_FLAG(param, GM_PARAM_NO_ACC); gm_pedal_long = GET_FLAG(param, GM_PARAM_PEDAL_LONG); - const bool gm_volt_cc_gateway = GET_FLAG(param, GM_PARAM_VOLT_CC_GATEWAY) && gm_no_camera && !gm_pedal_long && !gm_has_acc; + gm_volt_cc_gateway = GET_FLAG(param, GM_PARAM_VOLT_CC_GATEWAY) && gm_no_camera && !gm_pedal_long && !gm_has_acc; enable_gas_interceptor = GET_FLAG(param, GM_PARAM_PEDAL_INTERCEPTOR); gm_force_ascm = GET_FLAG(param, GM_PARAM_HW_ASCM_LONG); gm_force_brake_c9 = GET_FLAG(param, GM_PARAM_FORCE_BRAKE_C9); diff --git a/opendbc_repo/opendbc/safety/tests/test_gm.py b/opendbc_repo/opendbc/safety/tests/test_gm.py index 286c441c94..d054077b79 100755 --- a/opendbc_repo/opendbc/safety/tests/test_gm.py +++ b/opendbc_repo/opendbc/safety/tests/test_gm.py @@ -654,6 +654,31 @@ class TestGmCcLongitudinalNoCameraSafety(TestGmCcLongitudinalSafety): self.safety.init_tests() +def test_gm_volt_cc_gateway_brake_threshold_matches_carstate(): + safety = libsafety_py.libsafety + safety.set_safety_hooks( + CarParams.SafetyModel.gm, + GMSafetyFlags.FLAG_GM_NO_CAMERA | + GMSafetyFlags.FLAG_GM_NO_ACC | + GMSafetyFlags.FLAG_GM_CC_LONG | + GMSafetyFlags.FLAG_GM_VOLT_CC_GATEWAY, + ) + safety.init_tests() + safety.set_controls_allowed(True) + + cruise = common.make_msg(0, 0x3D1, 8, bytes([0, 0, 0, 0, 0x80, 0, 0, 0])) + safety.safety_rx_hook(cruise) + assert safety.get_controls_allowed() + + noisy_brake = libsafety_py.make_CANPacket(0xF1, 0, b"\x34\x06\x05\x40\x00\x00") + safety.safety_rx_hook(noisy_brake) + assert safety.get_controls_allowed() + + pressed_brake = libsafety_py.make_CANPacket(0xF1, 0, b"\x34\x15\x05\x40\x00\x00") + safety.safety_rx_hook(pressed_brake) + assert not safety.get_controls_allowed() + + class TestGmCcLongitudinalPandaSchedSafety(TestGmCcLongitudinalSafety): FWD_BLACKLISTED_ADDRS = {2: [0x180, 0x370], 0: [0x184, 0x3D1]} INTERCEPTOR_GAS_PRESSED = 596 diff --git a/panda/board/drivers/can_common.h b/panda/board/drivers/can_common.h index 92c0090a21..0b551dc1b8 100644 --- a/panda/board/drivers/can_common.h +++ b/panda/board/drivers/can_common.h @@ -249,6 +249,19 @@ void ignition_can_hook(CANPacket_t *msg) { ignition_can_cnt = 0U; } + // Volkswagen MEB exception + if ((msg->addr == 0x3C0U) && (len == 4)) { + int counter = msg->data[1] & 0xFU; + + static int prev_counter_vw_meb = -1; + if ((counter == ((prev_counter_vw_meb + 1) % 16)) && (prev_counter_vw_meb != -1)) { + // Klemmen_Status_01->ZAS_Kl_15 + ignition_can = ((msg->data[2] >> 1) & 1U) != 0U; + ignition_can_cnt = 0U; + } + prev_counter_vw_meb = counter; + } + } } diff --git a/panda/tests/libpanda/libpanda_py.py b/panda/tests/libpanda/libpanda_py.py index eef722c06a..ed7306f4f2 100644 --- a/panda/tests/libpanda/libpanda_py.py +++ b/panda/tests/libpanda/libpanda_py.py @@ -25,6 +25,12 @@ typedef struct { ffi.cdef(""" int set_safety_hooks(uint16_t mode, uint16_t param); + +void ignition_can_hook(CANPacket_t *msg); +void set_ignition_can_for_test(bool enabled); +bool get_ignition_can_for_test(void); +void set_ignition_can_cnt_for_test(uint32_t cnt); +uint32_t get_ignition_can_cnt_for_test(void); """) ffi.cdef(""" diff --git a/panda/tests/libpanda/panda.c b/panda/tests/libpanda/panda.c index 2d17d64e34..e3ea0d1b04 100644 --- a/panda/tests/libpanda/panda.c +++ b/panda/tests/libpanda/panda.c @@ -24,5 +24,21 @@ can_ring *tx1_q = &can_tx1_q; can_ring *tx2_q = &can_tx2_q; can_ring *tx3_q = &can_tx3_q; +void set_ignition_can_for_test(bool enabled) { + ignition_can = enabled; +} + +bool get_ignition_can_for_test(void) { + return ignition_can; +} + +void set_ignition_can_cnt_for_test(uint32_t cnt) { + ignition_can_cnt = cnt; +} + +uint32_t get_ignition_can_cnt_for_test(void) { + return ignition_can_cnt; +} + #include "comms_definitions.h" #include "can_comms.h" diff --git a/panda/tests/usbprotocol/test_ignition.py b/panda/tests/usbprotocol/test_ignition.py new file mode 100644 index 0000000000..4197bc5657 --- /dev/null +++ b/panda/tests/usbprotocol/test_ignition.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import unittest + +from panda.tests.libpanda import libpanda_py + + +lpp = libpanda_py.libpanda + + +def volkswagen_meb_ignition_msg(counter, ignition, bus=0, length=4): + dat = bytearray(length) + if length >= 2: + dat[1] = counter & 0xF + if length >= 3: + dat[2] = int(ignition) << 1 + return libpanda_py.make_CANPacket(0x3C0, bus, dat) + + +class TestIgnitionCanHook(unittest.TestCase): + def test_volkswagen_meb_ignition(self): + lpp.set_ignition_can_for_test(False) + lpp.set_ignition_can_cnt_for_test(123) + + # A single frame is insufficient: the rolling counter must first be established. + lpp.ignition_can_hook(volkswagen_meb_ignition_msg(7, True)) + self.assertFalse(lpp.get_ignition_can_for_test()) + self.assertEqual(lpp.get_ignition_can_cnt_for_test(), 123) + + lpp.ignition_can_hook(volkswagen_meb_ignition_msg(8, True)) + self.assertTrue(lpp.get_ignition_can_for_test()) + self.assertEqual(lpp.get_ignition_can_cnt_for_test(), 0) + + # A duplicate counter must not change ignition state. + lpp.set_ignition_can_cnt_for_test(123) + lpp.ignition_can_hook(volkswagen_meb_ignition_msg(8, False)) + self.assertTrue(lpp.get_ignition_can_for_test()) + self.assertEqual(lpp.get_ignition_can_cnt_for_test(), 123) + + lpp.ignition_can_hook(volkswagen_meb_ignition_msg(9, False)) + self.assertFalse(lpp.get_ignition_can_for_test()) + self.assertEqual(lpp.get_ignition_can_cnt_for_test(), 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/controls/lib/longitudinal_vehicle_tunes.py b/selfdrive/controls/lib/longitudinal_vehicle_tunes.py index 0e05946964..bb66a5874c 100644 --- a/selfdrive/controls/lib/longitudinal_vehicle_tunes.py +++ b/selfdrive/controls/lib/longitudinal_vehicle_tunes.py @@ -23,6 +23,8 @@ HONDA_ACCORD_LOW_SPEED_STOP_MAX_LEAD_SPEED = 1.0 HONDA_ACCORD_STANDSTILL_GUARD_MAX_EGO_SPEED = 0.25 HYUNDAI_ELANTRA_LEAD_FOLLOW_JERK_SCALE = 1.25 GENESIS_GV70_ELECTRIFIED_LEAD_FOLLOW_JERK_SCALE = 1.75 +GENESIS_GV70_ELECTRIFIED_SCC_JERK_UPPER = 1.5 +GENESIS_GV70_ELECTRIFIED_SCC_JERK_LOWER = 2.0 FORD_LIGHTNING_LEAD_FOLLOW_JERK_SCALE = 1.35 HONDA_CRV_5G_LEAD_FOLLOW_JERK_SCALE = 1.35 GM_SILVERADO_EARLY_FOLLOW_MIN_EGO_SPEED = 18.0 @@ -524,6 +526,12 @@ def get_lead_follow_jerk_scale(CP): return 1.0 +def get_hyundai_canfd_scc_jerk_limits(CP): + if str(getattr(CP, "carFingerprint", "")) == "GENESIS_GV70_ELECTRIFIED_1ST_GEN": + return GENESIS_GV70_ELECTRIFIED_SCC_JERK_UPPER, GENESIS_GV70_ELECTRIFIED_SCC_JERK_LOWER + return None + + def get_honda_accord_lead_departure_tune(CP): if CP.brand == "honda" and str(CP.carFingerprint) == "HONDA_ACCORD": return ( diff --git a/selfdrive/controls/tests/test_starpilot_planner.py b/selfdrive/controls/tests/test_starpilot_planner.py index df00fd98da..77d6872ee5 100644 --- a/selfdrive/controls/tests/test_starpilot_planner.py +++ b/selfdrive/controls/tests/test_starpilot_planner.py @@ -5,7 +5,10 @@ from types import SimpleNamespace from openpilot.common.constants import CV from openpilot.common.realtime import DT_MDL from openpilot.starpilot.controls.starpilot_planner import StarPilotPlanner, get_force_stop_jerk_scale -from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import get_lead_follow_jerk_scale +from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import ( + get_hyundai_canfd_scc_jerk_limits, + get_lead_follow_jerk_scale, +) import openpilot.starpilot.controls.starpilot_planner as starpilot_planner_module @@ -48,6 +51,14 @@ def test_lead_follow_jerk_scale_is_platform_specific(): assert get_lead_follow_jerk_scale(SimpleNamespace(brand="other", carFingerprint="OTHER_CAR")) == 1.0 +def test_genesis_gv70_scc_jerk_limits_are_platform_specific(): + gv70 = SimpleNamespace(brand="hyundai", carFingerprint="GENESIS_GV70_ELECTRIFIED_1ST_GEN") + other = SimpleNamespace(brand="hyundai", carFingerprint="HYUNDAI_IONIQ_6") + + assert get_hyundai_canfd_scc_jerk_limits(gv70) == (1.5, 2.0) + assert get_hyundai_canfd_scc_jerk_limits(other) is None + + def make_sm(planner, *, frame: int, v_ego: float, left_blinker: bool, right_blinker: bool = False, standstill: bool = False): return FakeSM(frame, { "radarState": SimpleNamespace( diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 6d530cab47..cb5f4c64b1 100644 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -265,6 +265,15 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 1.5) +def brake_hold_alert(CP, *_args) -> Alert: + alert_text = "Car in Auto Hold mode" if CP.brand == "gm" else "Press Resume to Exit Brake Hold" + return Alert( + alert_text, + "", + AlertStatus.userPrompt, AlertSize.small, + Priority.LOW, VisualAlert.none, AudibleAlert.none, .2) + + def speed_limit_changed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality, starpilot_toggles: SimpleNamespace) -> Alert: return Alert( "Speed limit changed", @@ -802,11 +811,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.brakeHold: { - ET.WARNING: Alert( - "Press Resume to Exit Brake Hold", - "", - AlertStatus.userPrompt, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .2), + ET.WARNING: brake_hold_alert, }, EventName.parkBrake: { diff --git a/starpilot/car/ford/lateral.py b/starpilot/car/ford/lateral.py index 1da986882e..10726cf9af 100644 --- a/starpilot/car/ford/lateral.py +++ b/starpilot/car/ford/lateral.py @@ -44,6 +44,7 @@ FORD_MANUAL_TURN_LATCH_CARS = frozenset({ CAR.FORD_MUSTANG_MACH_E_MK1, }) MANUAL_TURN_ENTRY_ANGLE_DEG = 12.0 +MANUAL_TURN_RELEASE_ANGLE_DEG = 12.0 MANUAL_TURN_RECOVERY_SECONDS = 0.25 @@ -207,7 +208,8 @@ class FordLateralController: self.manual_turn_recovery_timer = 0.0 return False - if CS.out.steeringPressed or blinker_direction != 0.0: + if (CS.out.steeringPressed or blinker_direction != 0.0 or + abs(CS.out.steeringAngleDeg) > MANUAL_TURN_RELEASE_ANGLE_DEG): self.manual_turn_recovery_timer = 0.0 else: self.manual_turn_recovery_timer += STEER_DT diff --git a/starpilot/car/ford/tests/test_lateral.py b/starpilot/car/ford/tests/test_lateral.py index f6ee1304a5..8c495a431f 100644 --- a/starpilot/car/ford/tests/test_lateral.py +++ b/starpilot/car/ford/tests/test_lateral.py @@ -208,6 +208,55 @@ def test_mach_e_signaled_manual_turn_yields_until_inputs_settle(controller): assert result.curvature > 0.0 +def test_mach_e_manual_turn_waits_for_wheel_to_unwind(controller): + controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1 + CC = SimpleNamespace(latActive=True) + actuators = SimpleNamespace(curvature=0.006) + + assert not controller.update(CC, car_state( + steering_pressed=True, steering_angle=-30.0, steering_torque=-2.0, + right_blinker=True), actuators).active + + for _ in range(8): + result = controller.update(CC, car_state(steering_angle=-35.0), actuators) + assert not result.active + + for _ in range(4): + result = controller.update(CC, car_state(steering_angle=-10.0), actuators) + assert not result.active + result = controller.update(CC, car_state(steering_angle=-10.0), actuators) + assert result.active + + +def test_mach_e_left_manual_turn_waits_for_wheel_to_unwind(controller): + controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1 + CC = SimpleNamespace(latActive=True) + actuators = SimpleNamespace(curvature=-0.006) + + assert not controller.update(CC, car_state( + steering_pressed=True, steering_angle=30.0, steering_torque=2.0, + left_blinker=True), actuators).active + + for _ in range(8): + result = controller.update(CC, car_state(steering_angle=35.0), actuators) + assert not result.active + + for _ in range(4): + result = controller.update(CC, car_state(steering_angle=10.0), actuators) + assert not result.active + result = controller.update(CC, car_state(steering_angle=10.0), actuators) + assert result.active + + +def test_non_mach_e_signaled_turn_does_not_latch(controller): + CC = SimpleNamespace(latActive=True) + result = controller.update(CC, car_state( + steering_pressed=True, steering_angle=30.0, steering_torque=2.0, + left_blinker=True), SimpleNamespace(curvature=-0.006)) + + assert result.active + + def test_mach_e_opposite_blinker_correction_does_not_start_manual_turn(controller): controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1 diff --git a/starpilot/common/assets/device_settings_layout.json b/starpilot/common/assets/device_settings_layout.json index eeee2a09b1..7b7d0ff201 100644 --- a/starpilot/common/assets/device_settings_layout.json +++ b/starpilot/common/assets/device_settings_layout.json @@ -3600,9 +3600,9 @@ }, { "key": "GMAutoHold", - "label": "Volt Auto Hold", - "description": "Hold the car at a stop on supported non-CC-only Chevy Volts until the gas pedal is pressed.", - "picker_description": "Holds Chevy Volt brakes until the gas pedal is pressed.", + "label": "GM Auto Hold", + "description": "Hold supported GM vehicles at a stop until the gas pedal is pressed.", + "picker_description": "Holds supported GM vehicle brakes until the gas pedal is pressed.", "data_type": "bool", "ui_type": "toggle", "settings_tier": "simple" diff --git a/starpilot/system/the_galaxy/assets/mobile/js/components/AppShell.js b/starpilot/system/the_galaxy/assets/mobile/js/components/AppShell.js index e13ee4d1b7..11d5217a0b 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/components/AppShell.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/components/AppShell.js @@ -1,6 +1,7 @@ import { store, navigate, goBack, toolHref, toggleTheme } from "../store.js" import { api } from "../api.js" import { usePolling } from "../composables.js" +import { languageState, setLanguage, t } from "../i18n.js" const NAV = { recordings: [ @@ -37,7 +38,7 @@ export const AppShell = { }, computed: { online() { return store.online }, - statusLabel() { return store.online ? store.deviceStatus : "Offline" }, + statusLabel() { return store.online ? t(store.deviceStatus, store.deviceStatus) : t("Offline") }, isLight() { return store.theme === "light" }, drawerOpen: { get() { return store.drawerOpen }, @@ -57,6 +58,7 @@ export const AppShell = { }, }, methods: { + tr(key, fallback = key) { return t(key, fallback) }, closeDrawer() { store.drawerOpen = false }, back() { goBack() }, async refreshStatus() { @@ -69,6 +71,14 @@ export const AppShell = { store.online = false } }, + async loadLanguage() { + try { + const values = await api.getParams() + setLanguage(values?.LanguageSetting || languageState.code || "en") + } catch (e) { + setLanguage(languageState.code || "en") + } + }, clearSearch() { store.search = "" this.$nextTick(() => { const el = this.$refs.searchInput; if (el) el.focus() }) @@ -89,6 +99,7 @@ export const AppShell = { }, }, created() { + this.loadLanguage() this.statusPoll = usePolling(() => this.refreshStatus(), { interval: 5000 }) this.statusPoll.start() }, @@ -98,21 +109,21 @@ export const AppShell = { template: `
{{ error }}
+