diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 9d9a88d1bb..45041f593d 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -24,6 +24,12 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * comma Prime support * Personal Mapbox/Amap/Google Maps token support * Instructions on how to set up your iOS Siri Shortcuts: https://routinehub.co/shortcut/17677/ +* NEW❗: Forced Offroad mode + * Force sunnypilot in the offroad state even when the car is on + * When Forced Offroad mode is on, allows changing offroad-only settings even when the car is turned on +* UPDATED: Hyundai CAN-FD Camera-based SCC + * NEW❗: Parse lead info for camera-based SCC platforms with longitudinal support + * Improve lead tracking when using openpilot longitudinal * RE-ENABLED: Map-based Turn Speed Control (M-TSC) for supported platforms * openpilot Longitudianl Control available cars * Custom Stock Longitudinal Control available cars @@ -41,7 +47,7 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * UPDATED: Neural Network Lateral Control (NNLC) * FIXED: Hotfix for "lazy" steering performance in tighter curves thanks to twilsonco! * Due to upstream changes with platform simplifications, most platforms will match and fallback to combined platform model - * This will be updated when the new mapping of platforms are restructured (thanks @twilsconso 😉) + * This will be updated when the new mapping of platforms are restructured (thanks @twilsonco 😉) * UI Updates * Display Metrics Below Chevron * NEW❗: Metrics is now being displayed below the chevron instead of above diff --git a/common/params.cc b/common/params.cc index 63eee698aa..1b5d31291e 100644 --- a/common/params.cc +++ b/common/params.cc @@ -255,6 +255,7 @@ std::unordered_map keys = { {"EnhancedScc", PERSISTENT | BACKUP}, {"FeatureStatus", PERSISTENT | BACKUP}, {"FleetManagerPin", PERSISTENT}, + {"ForceOffroad", CLEAR_ON_MANAGER_START}, {"GmapKey", PERSISTENT | BACKUP}, {"HandsOnWheelMonitoring", PERSISTENT | BACKUP}, {"HideVEgoUi", PERSISTENT | BACKUP}, @@ -324,6 +325,7 @@ std::unordered_map keys = { {"VisionCurveLaneless", PERSISTENT | BACKUP}, {"VwAccType", PERSISTENT | BACKUP}, {"VwCCOnly", PERSISTENT | BACKUP}, + {"Offroad_ForceStatus", CLEAR_ON_MANAGER_START}, {"Offroad_SupersededUpdate", PERSISTENT}, {"SunnylinkCache_Users", PERSISTENT}, diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index fcbf58999e..eb301b1f00 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -229,7 +229,7 @@ void can_recv_thread(std::vector pandas) { } } -std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool spoofing_started) { +std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool spoofing_started, Params ¶ms) { bool ignition_local = false; const uint32_t pandas_cnt = pandas.size(); @@ -277,7 +277,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector health.ignition_line_pkt = 0; } - ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)); + ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)) && !params.getBool("ForceOffroad"); pandaStates.push_back(health); } @@ -432,7 +432,7 @@ void panda_state_thread(std::vector pandas, bool spoofing_started) { send_peripheral_state(&pm, peripheral_panda); } - auto ignition_opt = send_panda_states(&pm, pandas, spoofing_started); + auto ignition_opt = send_panda_states(&pm, pandas, spoofing_started, params); if (!ignition_opt) { LOGE("Failed to get ignition_opt"); diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 8ca6668b30..7b221595c9 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -141,7 +141,7 @@ class CarInterface(CarInterfaceBase): self.CS.accEnabled = False self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled - ret, self.CS = self.get_sp_common_state(ret, self.CS) + ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) # MADS BUTTON if self.CS.out.madsEnabled != self.CS.madsEnabled: diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 5d6fa7dde4..ed35d1ba43 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -104,7 +104,7 @@ class CarInterface(CarInterfaceBase): self.CS.accEnabled = False self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled - ret, self.CS = self.get_sp_common_state(ret, self.CS) + ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) if self.CS.out.madsEnabled != self.CS.madsEnabled: if self.mads_event_lock: diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 7787fabb02..465109eb4c 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -251,7 +251,7 @@ class CarInterface(CarInterfaceBase): self.CS.accEnabled = False self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled - ret, self.CS = self.get_sp_common_state(ret, self.CS) + ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(distance_button)) # MADS BUTTON if self.CS.out.madsEnabled != self.CS.madsEnabled: diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 14975f045f..d412f81142 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -28,7 +28,7 @@ def get_can_messages(CP, gearbox_msg): ("STEER_MOTOR_TORQUE", 0), # TODO: not on every car ] - if CP.carFingerprint in (SERIAL_STEERING | (CAR.HONDA_ODYSSEY_CHN, )): + if CP.carFingerprint in (SERIAL_STEERING | {CAR.HONDA_ODYSSEY_CHN, }): messages += [ ("SCM_FEEDBACK", 25), ("SCM_BUTTONS", 50), @@ -58,7 +58,7 @@ def get_can_messages(CP, gearbox_msg): ("ACC_CONTROL", 50), ] else: # Nidec signals - if CP.carFingerprint in (SERIAL_STEERING | (CAR.HONDA_ODYSSEY_CHN, )): + if CP.carFingerprint in (SERIAL_STEERING | {CAR.HONDA_ODYSSEY_CHN, }): messages.append(("CRUISE_PARAMS", 10)) else: messages.append(("CRUISE_PARAMS", 50)) @@ -78,7 +78,7 @@ def get_can_messages(CP, gearbox_msg): if CP.carFingerprint in HONDA_BOSCH_RADARLESS: messages.append(("CRUISE_FAULT_STATUS", 50)) - elif CP.carFingerprint == CAR.CLARITY: + elif CP.carFingerprint == CAR.HONDA_CLARITY: messages.append(("BRAKE_ERROR", 100)), elif CP.openpilotLongitudinalControl: messages.append(("STANDSTILL", 50)) @@ -150,7 +150,7 @@ class CarState(CarStateBase): if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS: ret.accFaulted = bool(cp.vl["CRUISE_FAULT_STATUS"]["CRUISE_FAULT"]) - elif self.CP.carFingerprint == CAR.CLARITY: + elif self.CP.carFingerprint == CAR.HONDA_CLARITY: ret.accFaulted = bool(cp.vl["BRAKE_ERROR"]["BRAKE_ERROR_1"] or cp.vl["BRAKE_ERROR"]["BRAKE_ERROR_2"]) else: # On some cars, these two signals are always 1, this flag is masking a bug in release @@ -250,12 +250,12 @@ class CarState(CarStateBase): if ret.brake > 0.1: ret.brakePressed = True - if self.CP.carFingerprint in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, - CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): + if self.CP.carFingerprint in (CAR.HONDA_CIVIC, CAR.HONDA_ODYSSEY, CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_CRV_5G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, + CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.ACURA_RDX_3G, CAR.HONDA_E): ret.brakeLightsDEPRECATED = bool(cp.vl["ACC_CONTROL"]['BRAKE_LIGHTS'] != 0 or ret.brake > 0.4) if not self.CP.openpilotLongitudinalControl else \ bool(ret.brake > 0.4) - elif self.CP.carFingerprint in HONDA_BOSCH and self.CP.carFingerprint not in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, - CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E) and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: + elif self.CP.carFingerprint in HONDA_BOSCH and self.CP.carFingerprint not in (CAR.HONDA_CIVIC, CAR.HONDA_ODYSSEY, CAR.HONDA_ODYSSEY_CHN, CAR.HONDA_CRV_5G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, + CAR.HONDA_CIVIC_BOSCH_DIESEL, CAR.HONDA_CRV_HYBRID, CAR.ACURA_RDX_3G, CAR.HONDA_E) and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: ret.brakeLightsDEPRECATED = bool(cp.vl["ACC_CONTROL"]['BRAKE_LIGHTS'] != 0 or ret.brake > 0.4) if not self.CP.openpilotLongitudinalControl else \ bool(ret.brake > 0.4) @@ -264,7 +264,7 @@ class CarState(CarStateBase): if self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: ret.stockAeb = (not self.CP.openpilotLongitudinalControl) and bool(cp.vl["ACC_CONTROL"]["AEB_STATUS"] and cp.vl["ACC_CONTROL"]["ACCEL_COMMAND"] < -1e-5) else: - aeb_sig = "COMPUTER_BRAKE_ALT" if self.CP.carFingerprint == CAR.CLARITY else "COMPUTER_BRAKE" + aeb_sig = "COMPUTER_BRAKE_ALT" if self.CP.carFingerprint == CAR.HONDA_CLARITY else "COMPUTER_BRAKE" ret.stockAeb = bool(cp_cam.vl["BRAKE_COMMAND"]["AEB_REQ_1"] and cp_cam.vl["BRAKE_COMMAND"][aeb_sig] > 1e-5) self.acc_hud = False diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 070cc3e1ca..3eb05aa239 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -348,5 +348,3 @@ SERIAL_STEERING = {CAR.HONDA_ACCORD_4CYL_9TH_GEN, } DBC = CAR.create_dbc_map() - -SERIAL_STEERING = {CAR.HONDA_ACCORD_4CYL_9TH_GEN, } diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 4c891fbe81..173694d116 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -269,11 +269,11 @@ class CarController(CarControllerBase): # send resume at a max freq of 10Hz if (self.frame - self.last_button_frame) * DT_CTRL > 0.1 * send_freq: # send 25 messages at a time to increases the likelihood of cruise buttons being accepted - can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, self.cruise_button, self.CP.carFingerprint)] * 25) + can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, self.cruise_button, self.CP)] * 25) if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15 * send_freq: self.last_button_frame = self.frame elif self.frame % 2 == 0: - can_sends.extend([hyundaican.create_clu11(self.packer, (self.frame // 2) + 1, CS.clu11, self.cruise_button, self.CP.carFingerprint)] * 25) + can_sends.extend([hyundaican.create_clu11(self.packer, (self.frame // 2) + 1, CS.clu11, self.cruise_button, self.CP)] * 25) # Parse lead distance from radarState and display the corresponding distance in the car's cluster if self.CP.openpilotLongitudinalControl and self.sm.updated['radarState'] and self.frame % 5 == 0: diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index d06cdbd209..d2a719ccd9 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -97,6 +97,9 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] ret.experimentalLongitudinalAvailable = candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR) + if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC and not hda2: + ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value + ret.radarUnavailable = False else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] @@ -239,7 +242,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled - ret, self.CS = self.get_sp_common_state(ret, self.CS) + ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=(self.CS.cruise_buttons[-1] == 3)) # MADS BUTTON if self.CS.out.madsEnabled != self.CS.madsEnabled: diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 76f2c370d6..f00b993563 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -3,7 +3,8 @@ import math from cereal import car from opendbc.can.parser import CANParser from openpilot.selfdrive.car.interfaces import RadarInterfaceBase -from openpilot.selfdrive.car.hyundai.values import DBC, HyundaiFlagsSP +from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus +from openpilot.selfdrive.car.hyundai.values import DBC, HyundaiFlagsSP, CANFD_CAR RADAR_START_ADDR = 0x500 RADAR_MSG_COUNT = 32 @@ -12,12 +13,18 @@ RADAR_MSG_COUNT = 32 def get_radar_can_parser(CP): if DBC[CP.carFingerprint]['radar'] is None: - if CP.spFlags & HyundaiFlagsSP.SP_ENHANCED_SCC: - lead_src, bus = "ESCC", 0 - elif CP.spFlags & HyundaiFlagsSP.SP_CAMERA_SCC_LEAD: - lead_src, bus = "SCC11", 2 + if CP.carFingerprint in CANFD_CAR: + if CP.spFlags & HyundaiFlagsSP.SP_CAMERA_SCC_LEAD: + lead_src, bus = "SCC_CONTROL", CanBus(CP).CAM + else: + return None else: - return None + if CP.spFlags & HyundaiFlagsSP.SP_ENHANCED_SCC: + lead_src, bus = "ESCC", 0 + elif CP.spFlags & HyundaiFlagsSP.SP_CAMERA_SCC_LEAD: + lead_src, bus = "SCC11", 2 + else: + return None messages = [(lead_src, 50)] return CANParser(DBC[CP.carFingerprint]['pt'], messages, bus) @@ -28,10 +35,12 @@ def get_radar_can_parser(CP): class RadarInterface(RadarInterfaceBase): def __init__(self, CP): super().__init__(CP) + self.CP = CP self.enhanced_scc = (CP.spFlags & HyundaiFlagsSP.SP_ENHANCED_SCC) and DBC[CP.carFingerprint]['radar'] is None self.camera_scc = CP.spFlags & HyundaiFlagsSP.SP_CAMERA_SCC_LEAD self.updated_messages = set() self.trigger_msg = 0x2AB if self.enhanced_scc else \ + 0x1A0 if self.camera_scc and CP.carFingerprint in CANFD_CAR else \ 0x420 if self.camera_scc else \ (RADAR_START_ADDR + RADAR_MSG_COUNT - 1) self.track_id = 0 @@ -68,9 +77,12 @@ class RadarInterface(RadarInterfaceBase): ret.errors = errors if self.enhanced_scc or self.camera_scc: - msg_src = "ESCC" if self.enhanced_scc else "SCC11" + msg_src = "ESCC" if self.enhanced_scc else \ + "SCC_CONTROL" if self.CP.carFingerprint in CANFD_CAR else \ + "SCC11" msg = self.rcp.vl[msg_src] - valid = msg['ACC_ObjStatus'] + valid = msg['ACC_ObjDist'] < 204.6 if self.CP.carFingerprint in CANFD_CAR else \ + msg['ACC_ObjStatus'] for ii in range(1): if valid: if ii not in self.pts: diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 036e7ba2d5..fc9715cb82 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -593,7 +593,7 @@ class CarInterfaceBase(ABC): else: return CS.madsEnabled - def get_sp_common_state(self, cs_out, CS, min_enable_speed_pcm=False, gear_allowed=True): + def get_sp_common_state(self, cs_out, CS, min_enable_speed_pcm=False, gear_allowed=True, gap_button=False): cs_out.cruiseState.enabled = CS.accEnabled if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed or min_enable_speed_pcm else \ cs_out.cruiseState.enabled @@ -603,6 +603,9 @@ class CarInterfaceBase(ABC): elif not cs_out.cruiseState.enabled and CS.out.cruiseState.enabled: CS.madsEnabled = False + if self.CP.openpilotLongitudinalControl: + self.toggle_exp_mode(gap_button) + cs_out.belowLaneChangeSpeed = cs_out.vEgo < LANE_CHANGE_SPEED_MIN and self.below_speed_pause if cs_out.gearShifter in [GearShifter.park, GearShifter.reverse] or cs_out.doorOpen or \ @@ -628,6 +631,19 @@ class CarInterfaceBase(ABC): return cs_out, CS + # TODO: SP: use upstream's buttonEvents counter checks from controlsd + def toggle_exp_mode(self, gap_pressed): + if gap_pressed: + if not self.experimental_mode_hold: + self.gap_button_counter += 1 + if self.gap_button_counter > 50: + self.gap_button_counter = 0 + self.experimental_mode_hold = True + self.param_s.put_bool_nonblocking("ExperimentalMode", not self.experimental_mode) + else: + self.gap_button_counter = 0 + self.experimental_mode_hold = False + def create_sp_events(self, CS, cs_out, events, main_enabled=False, allow_enable=True, enable_pressed=False, enable_from_brake=False, enable_pressed_long=False, enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise)): diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 2d6b68f9e1..8d49218b02 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -77,7 +77,7 @@ class CarInterface(CarInterfaceBase): self.CS.accEnabled = False self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled - ret, self.CS = self.get_sp_common_state(ret, self.CS) + ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) # MADS BUTTON if self.CS.out.madsEnabled != self.CS.madsEnabled: diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 4416299230..878d8f14c0 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -51,7 +51,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled - ret, self.CS = self.get_sp_common_state(ret, self.CS) + ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) # CANCEL if self.CS.out.cruiseState.enabled and not ret.cruiseState.enabled: diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 314c5cdd86..188964d667 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -104,7 +104,7 @@ class CarState(CarStateBase): ret.standstill = abs(ret.vEgoRaw) < 1e-3 - if self.CP.carFingerprint != CAR.PRIUS_V: + if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V: self.lta_status = cp_cam.vl["LKAS_HUD"]["SET_ME_X02"] if ((self.prev_lta_status == 16 and self.lta_status == 0) or (self.prev_lta_status == 0 and self.lta_status == 16)) and not self.lta_status_active: @@ -113,7 +113,7 @@ class CarState(CarStateBase): self.prev_lta_status = self.lta_status if self.lta_status_active: self.lkas_enabled = self.lta_status - elif self.CP.carFingerprint != CAR.PRIUS_V: + elif self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V: self.lkas_enabled = cp_cam.vl["LKAS_HUD"]["LKAS_STATUS"] if self.prev_lkas_enabled is None: self.prev_lkas_enabled = self.lkas_enabled diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 51d76dece0..38f4753c47 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -224,7 +224,7 @@ class CarInterface(CarInterfaceBase): if not self.CP.pcmCruise: ret.cruiseState.enabled = self.CS.accEnabled - ret, self.CS = self.get_sp_common_state(ret, self.CS) + ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(distance_button)) # CANCEL if self.CS.out.cruiseState.enabled and not ret.cruiseState.enabled: diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 5fef307f8a..096cfdf060 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -130,7 +130,7 @@ class CarState(CarStateBase): ret.cruiseState.available = pt_cp.vl["TSK_06"]["TSK_Status"] in (2, 3, 4, 5) ret.cruiseState.enabled = pt_cp.vl["TSK_06"]["TSK_Status"] in (3, 4, 5) - if self.CP.pcmCruise: + if self.CP.pcmCruise and not self.CP.spFlags & VolkswagenFlagsSP.SP_CC_ONLY_NO_RADAR: # Cruise Control mode; check for distance UI setting from the radar. # ECM does not manage this, so do not need to check for openpilot longitudinal ret.cruiseState.nonAdaptive = ext_cp.vl["ACC_02"]["ACC_Gesetzte_Zeitluecke"] == 0 diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index ff927f563c..48eddc7b84 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -209,34 +209,6 @@ class Controls: if any(ps.controlsAllowed for ps in self.sm['pandaStates']): self.state = State.enabled - def get_road_edge(self, carstate, model_v2): - # Lane detection by FrogAi - one_blinker = carstate.leftBlinker != carstate.rightBlinker - if not self.edge_toggle: - self.road_edge = False - elif one_blinker: - # Set the minimum lane threshold to 3.0 meters - min_lane_threshold = 3.0 - # Set the blinker index based on which signal is on - blinker_index = 0 if carstate.leftBlinker else 1 - desired_edge = model_v2.roadEdges[blinker_index] - current_lane = model_v2.laneLines[blinker_index + 1] - # Check if both the desired lane and the current lane have valid x and y values - if all([desired_edge.x, desired_edge.y, current_lane.x, current_lane.y]) and len(desired_edge.x) == len(current_lane.x): - # Interpolate the x and y values to the same length - x = np.linspace(desired_edge.x[0], desired_edge.x[-1], num=len(desired_edge.x)) - lane_y = np.interp(x, current_lane.x, current_lane.y) - desired_y = np.interp(x, desired_edge.x, desired_edge.y) - # Calculate the width of the lane we're wanting to change into - lane_width = np.abs(desired_y - lane_y) - # Set road_edge to False if the lane width is not larger than the threshold - self.road_edge = not (np.amax(lane_width) > min_lane_threshold) - else: - self.road_edge = True - else: - # Default to setting "road_edge" to False - self.road_edge = False - def update_events(self, CS): """Compute onroadEvents from carState""" @@ -760,12 +732,9 @@ class Controls: # decrement personality on distance button press if self.CP.openpilotLongitudinalControl: - if self.v_cruise_helper.update_personality: + if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents): self.personality = (self.personality - 1) % 3 self.params.put_nonblocking('LongitudinalPersonality', str(self.personality)) - if self.v_cruise_helper.update_experimental_mode: - self.experimental_mode = not self.experimental_mode - self.params.put_bool_nonblocking('ExperimentalMode', self.experimental_mode) return CC, lac_log @@ -798,7 +767,7 @@ class Controls: hudControl.speedVisible = self.enabled_long hudControl.lanesVisible = self.enabled hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead - hudControl.leadDistanceBars = self.personality + 1 + hudControl.leadDistanceBars = PERSONALITY_MAPPING.get(self.personality, log.LongitudinalPersonality.standard) + 1 hudControl.rightLaneVisible = True hudControl.leftLaneVisible = True diff --git a/selfdrive/controls/lib/alerts_offroad.json b/selfdrive/controls/lib/alerts_offroad.json index 0293a655fa..4bb7fd1736 100644 --- a/selfdrive/controls/lib/alerts_offroad.json +++ b/selfdrive/controls/lib/alerts_offroad.json @@ -52,5 +52,9 @@ "Offroad_OSMUpdateRequired": { "text": "OpenStreetMap database is out of date. New maps must be downloaded if you wish to continue using OpenStreetMap data for Enhanced Speed Control and road name display.\n\n%1", "severity": 0 + }, + "Offroad_ForceStatus": { + "text": "sunnypilot is now in Forced Offroad mode. sunnypilot won't start until Forced Offroad mode is disabled. Go to \"Settings\" -> \"Device\" -> \"Unforce Offroad\" to exit Force Offroad mode.", + "severity": 1 } } diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 0c80d186e5..61edf7aade 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -75,7 +75,7 @@ class VCruiseHelper: self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET self.v_cruise_kph_last = 0 - self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0, ButtonType.gapAdjustCruise: 0} + self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers} self.is_metric_prev = None @@ -84,9 +84,6 @@ class VCruiseHelper: self.slc_state_prev = SpeedLimitControlState.inactive self.slc_speed_limit_offsetted = 0 - self.update_personality = False - self.update_experimental_mode = False - @property def v_cruise_initialized(self): return self.v_cruise_kph != V_CRUISE_UNSET @@ -123,8 +120,6 @@ class VCruiseHelper: long_press = False button_type = None - update_personality = False - update_experimental_mode = False v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT v_cruise_delta_mltplr = 10 if is_metric else 5 @@ -145,15 +140,6 @@ class VCruiseHelper: if button_type is None: return - if button_type == ButtonType.gapAdjustCruise and self.button_timers[ButtonType.gapAdjustCruise]: - if self.button_timers[ButtonType.gapAdjustCruise] < 50: - update_personality = True - elif self.button_timers[ButtonType.gapAdjustCruise] == 50: - update_experimental_mode = True - - self.update_personality = update_personality - self.update_experimental_mode = update_experimental_mode - resume_button = ButtonType.accelCruise if not self.CP.pcmCruiseSpeed: if self.CP.carName == "chrysler": @@ -168,9 +154,6 @@ class VCruiseHelper: if not self.button_change_states[button_type]["enabled"]: return - if button_type == ButtonType.gapAdjustCruise: - return - pressed_value = (1 if long_press else v_cruise_delta_mltplr) if reverse_acc else (v_cruise_delta_mltplr if long_press else 1) long_press_state = not long_press if reverse_acc else long_press v_cruise_delta = v_cruise_delta * pressed_value diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index c4dade1319..b203f56b7d 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -103,6 +103,7 @@ def manager_init() -> None: ("OsmDbUpdatesCheck", "0"), ("OsmDownloadedDate", "0"), ("OSMDownloadProgress", "{}"), + ("SidebarTemperatureOptions", "0"), ("SunnylinkEnabled", "1"), ] if not PC: diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index d602594e8a..13c1f05c30 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -45,7 +45,7 @@ def only_offroad(started, params, CP: car.CarParams) -> bool: def model_use_nav(started, params, CP: car.CarParams) -> bool: custom_model, model_gen = get_model_generation(params) - return started and custom_model and model_gen != 4 + return started and custom_model and model_gen not in (0, 4) procs = [ DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 10d0c13883..57b9914239 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -311,6 +311,12 @@ def thermald_thread(end_event, hw_queue) -> None: # ensure device is fully booted startup_conditions["device_booted"] = startup_conditions.get("device_booted", False) or HARDWARE.booted() + # user-forced status + force_offroad = params.get_bool("ForceOffroad") + startup_conditions["not_force_offroad"] = not force_offroad + onroad_conditions["not_force_offroad"] = not force_offroad + set_offroad_alert("Offroad_ForceStatus", force_offroad) + # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger extra_text = f"{offroad_comp_temp:.1f}C" diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 886653bd4b..4c9a2e9085 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -420,13 +420,24 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible); } + offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad")); + offroad_btn->setObjectName("offroad_btn"); + QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanel::forceoffroad); + + QVBoxLayout *buttons_layout = new QVBoxLayout(); + buttons_layout->setSpacing(24); + buttons_layout->addLayout(power_layout); + buttons_layout->addWidget(offroad_btn); + setStyleSheet(R"( #reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; } #reboot_btn:pressed { background-color: #4a4a4a; } #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } #poweroff_btn:pressed { background-color: #FF2424; } )"); - addItem(power_layout); + addItem(buttons_layout); + + updateLabels(); } void DevicePanel::onPinFileChanged(const QString &file_path) { @@ -501,9 +512,51 @@ void DevicePanel::poweroff() { } } +void DevicePanel::forceoffroad() { + if (!uiState()->engaged()) { + if (params.getBool("ForceOffroad")) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) { + // Check engaged again in case it changed while the dialog was open + if (!uiState()->engaged()) { + params.remove("ForceOffroad"); + } + } + } else { + if (ConfirmationDialog::confirm(tr("Are you sure you want to force offroad?"), tr("Force"), this)) { + // Check engaged again in case it changed while the dialog was open + if (!uiState()->engaged()) { + params.putBool("ForceOffroad", true); + } + } + } + } else { + ConfirmationDialog::alert(tr("Disengage to Force Offroad"), this); + } + + updateLabels(); +} + void DevicePanel::showEvent(QShowEvent *event) { pair_device->setVisible(uiState()->primeType() == PrimeType::UNPAIRED); ListWidget::showEvent(event); + updateLabels(); +} + +void DevicePanel::updateLabels() { + if (!isVisible()) { + return; + } + + bool force_offroad_param = params.getBool("ForceOffroad"); + QString offroad_btn_style = force_offroad_param ? "#393939" : "#E22C2C"; + QString offroad_btn_pressed_style = force_offroad_param ? "#4a4a4a" : "#FF2424"; + QString btn_common_style = QString("QPushButton { height: 120px; border-radius: 15px; background-color: %1; }" + "QPushButton:pressed { background-color: %2; }") + .arg(offroad_btn_style, + offroad_btn_pressed_style); + + offroad_btn->setText(force_offroad_param ? tr("Unforce Offroad") : tr("Force Offroad")); + offroad_btn->setStyleSheet(btn_common_style + offroad_btn_style + offroad_btn_pressed_style); } void SettingsWindow::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index b1ee32f39d..0e550850e8 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -63,6 +63,9 @@ private slots: void updateCalibDescription(); void onPinFileChanged(const QString &file_path); void refreshPin(); + void forceoffroad(); + + void updateLabels(); private: Params params; @@ -72,6 +75,8 @@ private: QString pin_title = tr("Fleet Manager PIN:") + " "; QString pin = "OFF"; QFileSystemWatcher *fs_watch; + + QPushButton *offroad_btn; }; class TogglesPanel : public ListWidget { diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc index e765792b4d..6c55bd1655 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc @@ -107,16 +107,9 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { connect(restoreSettings, &QPushButton::clicked, [=]() { is_restore = true; backup_settings->started(); - if (uiState()->isSunnylinkSponsor()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to restore the last backed up sunnypilot settings?"), tr("Restore"), this)) { - backup_settings->getParams(); - } else { - backup_settings->finished(); - } + if (ConfirmationDialog::confirm(tr("Are you sure you want to restore the last backed up sunnypilot settings?"), tr("Restore"), this)) { + backup_settings->getParams(); } else { - if (ConfirmationDialog::confirm(tr("Early alpha access only. Become a sponsor to get early access to sunnylink features."), tr("Become a Sponsor"), this)) { - status_popup->exec(); - } backup_settings->finished(); } is_restore = false; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 11ac3ee650..b7b37ec9de 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -165,6 +165,7 @@ static void update_sockets(UIState *s) { static void update_state(UIState *s) { SubMaster &sm = *(s->sm); UIScene &scene = s->scene; + auto params = Params(); if (sm.updated("liveCalibration")) { auto live_calib = sm["liveCalibration"].getLiveCalibration(); @@ -214,7 +215,7 @@ static void update_state(UIState *s) { float scale = (cam_state.getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f; scene.light_sensor = std::max(100.0f - scale * cam_state.getExposureValPercent(), 0.0f); } - scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; + scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); scene.world_objects_visible = scene.world_objects_visible || (scene.started &&