diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aceeff95bd..a2c27da614 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,6 +10,7 @@ variables: GIT_CONFIG_USER_NAME: "Gitlab Pipeline" PUBLIC_REPO_URL: "https://github.com/sunnyhaibin/sunnypilot" + stages: - build - sanity @@ -29,13 +30,13 @@ default: - 'git config --global user.name "${GIT_CONFIG_USER_NAME}"' -workflow: # If running on any branch other than main, use the `aws-datacontracts-dev` account; otherwise use `aws-datacontracts` +workflow: # If running on any branch other than main. rules: # We are an MR, but it's a draft, we won't proceed with anything. - if: '$CI_MERGE_REQUEST_TITLE =~ /^wip:/i || $CI_MERGE_REQUEST_TITLE =~ /^draft:/i' when: never # We are a merge request - - if: $CI_MERGE_REQUEST_IID #|| $CI_COMMIT_REF_NAME == "gitlab-pipelines" # TBD once merged + - if: $CI_MERGE_REQUEST_IID variables: EXTRA_VERSION_IDENTIFIER: "-${CI_PIPELINE_IID}" NEW_BRANCH: ${CI_COMMIT_REF_NAME}-prebuilt @@ -98,7 +99,7 @@ build: - sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py # comment panda jungle when prebuilt - scons -j$(nproc) cache_dir=${CI_DIR}/scons_cache --minimal - touch ${BUILD_DIR}/prebuilt - - sudo rm -rf ${OUTPUT_DIR} + - sudo rm -rf ${OUTPUT_DIR} - mkdir -p ${OUTPUT_DIR} # We first include the paths we want to keep, even if we later will be excluding the other things on those paths - rsync -avm @@ -141,7 +142,7 @@ build: --chown=comma:comma ${BUILD_DIR}/ ${OUTPUT_DIR}/ after_script: - # cleanup build dir after doing work + # cleanup build dir after doing work - find $BUILD_DIR/ -mindepth 1 -delete artifacts: paths: diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 06a2961de7..2fc99ab8d0 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -40,6 +40,11 @@ sunnypilot - 0.9.8.0 (2024-xx-xx) * Auto Unlock by Shift to P: All doors are automatically unlocked when shifting the shift lever to P * FIXED: Driving Personality: * Maniac mode now correctly enforced when selected +* UI Updates + * Display Metrics Below Chevron + * NEW❗: Time to Lead Car + * Displays the time to reach the position previously occupied by the lead car + * NEW❗: Display Distance, Speed, and Time to Lead Car simultaneously * Kia Ceed Plug-in Hybrid Non-SCC 2022 support thanks to TerminatorNL! sunnypilot - 0.9.7.1 (2024-06-13) diff --git a/common/params.cc b/common/params.cc index 054c5d2355..86f18a0a5e 100644 --- a/common/params.cc +++ b/common/params.cc @@ -336,6 +336,8 @@ std::unordered_map keys = { {"SunnylinkCache_Users", PERSISTENT}, {"SunnylinkCache_Roles", PERSISTENT}, + {"EnableGitlabRunner", PERSISTENT | BACKUP}, + {"EnableSunnylinkUploader", PERSISTENT | BACKUP}, // PFEIFER - MAPD {{ {"MapdVersion", PERSISTENT}, diff --git a/release/release_files.py b/release/release_files.py index 31d1518d74..4ae0aad1ed 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -88,6 +88,8 @@ sunnypilot_blacklist = [ "codecov.yml", "conftest.py", "poetry.lock", + ".git-crypt/", + ".venv", ] # Merge the blacklists diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 6aebd2bd90..207c90678d 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -43,10 +43,28 @@ def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, ca return events -def create_mads_event(mads_event_lock: bool) -> capnp.lib.capnp._DynamicStructBuilder: - be = car.CarState.ButtonEvent(pressed=mads_event_lock) - be.type = ButtonType.altButton1 - return be +class ButtonEvents: + def __init__(self) -> None: + self.is_mads: bool = False + + @staticmethod + def create_cancel_event(long_enabled: bool, prev_long_enabled: bool) -> list[capnp.lib.capnp._DynamicStructBuilder]: + events: list[capnp.lib.capnp._DynamicStructBuilder] = [] + + if not long_enabled and prev_long_enabled: + events.append(car.CarState.ButtonEvent(pressed=True, + type=ButtonType.cancel)) + return events + + def create_mads_event(self, mads_enabled: bool, prev_mads_enabled: bool) -> list[capnp.lib.capnp._DynamicStructBuilder]: + events: list[capnp.lib.capnp._DynamicStructBuilder] = [] + + mads_changed = prev_mads_enabled != mads_enabled + if (mads_changed and not self.is_mads) or (not mads_changed and self.is_mads): + events.append(car.CarState.ButtonEvent(pressed=mads_changed, type=ButtonType.altButton1)) + self.is_mads = not self.is_mads + + return events def gen_empty_fingerprint(): diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index f9277af62e..54bed40b51 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -1,3 +1,5 @@ +from cereal import car + import cereal.messaging as messaging from common.conversions import Conversions as CV from opendbc.can.packer import CANPacker @@ -9,7 +11,7 @@ from openpilot.selfdrive.car.chrysler.values import RAM_CARS, RAM_DT, CarControl from openpilot.selfdrive.car.interfaces import CarControllerBase, FORWARD_GEARS from openpilot.selfdrive.controls.lib.drive_helpers import FCA_V_CRUISE_MIN -BUTTONS_STATES = ["accelCruise", "decelCruise", "cancel", "resumeCruise"] +ButtonType = car.CarState.ButtonEvent.Type class CarController(CarControllerBase): @@ -104,7 +106,7 @@ class CarController(CarControllerBase): self.last_button_frame = CS.button_counter if ram_cars: - if CS.buttonStates["cancel"]: + if any(b.type == ButtonType.cancel for b in CS.out.buttonEvents): can_sends.append(chryslercan.create_cruise_buttons(self.packer, CS.button_counter, das_bus, self.CP, cancel=True)) else: can_sends.append(chryslercan.create_cruise_buttons(self.packer, CS.button_counter, das_bus, self.CP, @@ -189,8 +191,10 @@ class CarController(CarControllerBase): # multikyd methods, sunnyhaibin logic def get_cruise_buttons_status(self, CS): - if not CS.out.cruiseState.enabled or any(CS.buttonStates[button_state] for button_state in BUTTONS_STATES): - self.timer = 40 + if not CS.out.cruiseState.enabled: + for be in CS.out.buttonEvents: + if be.type in (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.resumeCruise) and be.pressed: + self.timer = 40 elif self.timer: self.timer -= 1 else: diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index d29be9226e..a58681175e 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -3,7 +3,7 @@ from openpilot.common.conversions import Conversions as CV from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine from openpilot.selfdrive.car.interfaces import CarStateBase -from openpilot.selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS, BUTTON_STATES +from openpilot.selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS, BUTTONS class CarState(CarStateBase): @@ -29,8 +29,7 @@ class CarState(CarStateBase): self.lkas_heartbit = None self.lkas_disabled = False - self.buttonStates = BUTTON_STATES.copy() - self.buttonStatesPrev = BUTTON_STATES.copy() + self.button_states = {button.event_type: False for button in BUTTONS} def update(self, cp, cp_cam): @@ -41,7 +40,6 @@ class CarState(CarStateBase): self.prev_mads_enabled = self.mads_enabled self.prev_lkas_enabled = self.lkas_enabled - self.buttonStatesPrev = self.buttonStates.copy() # lock info ret.doorOpen = any([cp.vl["BCM_1"]["DOOR_OPEN_FL"], @@ -76,6 +74,16 @@ class CarState(CarStateBase): unit=1, ) + # Buttons + for button in BUTTONS: + state = (cp.vl[button.can_addr][button.can_msg] in button.values) + if self.button_states[button.event_type] != state: + event = car.CarState.ButtonEvent.new_message() + event.type = button.event_type + event.pressed = state + self.button_events.append(event) + self.button_states[button.event_type] = state + # button presses ret.leftBlinker, ret.rightBlinker = ret.leftBlinkerOn, ret.rightBlinkerOn = self.update_blinker_from_stalk(200, cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1, cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 2) @@ -116,11 +124,6 @@ class CarState(CarStateBase): ret.leftBlindspot = cp.vl["BSM_1"]["LEFT_STATUS"] == 1 ret.rightBlindspot = cp.vl["BSM_1"]["RIGHT_STATUS"] == 1 - self.buttonStates["accelCruise"] = bool(cp.vl["CRUISE_BUTTONS"]["ACC_Accel"]) - self.buttonStates["decelCruise"] = bool(cp.vl["CRUISE_BUTTONS"]["ACC_Decel"]) - self.buttonStates["cancel"] = bool(cp.vl["CRUISE_BUTTONS"]["ACC_Cancel"]) - self.buttonStates["resumeCruise"] = bool(cp.vl["CRUISE_BUTTONS"]["ACC_Resume"]) - self.lkas_car_model = cp_cam.vl["DAS_6"]["CAR_MODEL"] self.button_counter = cp.vl["CRUISE_BUTTONS"]["COUNTER"] self.cruise_buttons = cp.vl["CRUISE_BUTTONS"] diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index d09fa7ac56..d4911375e7 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event -from openpilot.selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerFlagsSP, BUTTON_STATES +from openpilot.selfdrive.car import create_button_events, get_safety_config +from openpilot.selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags, ChryslerFlagsSP from openpilot.selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -13,7 +13,6 @@ GearShifter = car.CarState.GearShifter class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): super().__init__(CP, CarController, CarState) - self.buttonStatesPrev = BUTTON_STATES.copy() @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): @@ -94,19 +93,12 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) - - for button in self.CS.buttonStates: - if self.CS.buttonStates[button] != self.buttonStatesPrev[button]: - be = car.CarState.ButtonEvent.new_message() - be.type = button - be.pressed = self.CS.buttonStates[button] - buttonEvents.append(be) + self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, - buttonEvents, c.vCruise, + self.CS.button_events, c.vCruise, enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.resumeCruise) if not self.CP.pcmCruiseSpeed else (ButtonType.accelCruise, ButtonType.decelCruise), resume_button=(ButtonType.resumeCruise,) if not self.CP.pcmCruiseSpeed else @@ -125,7 +117,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = self.get_sp_started_mads(ret, self.CS) if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed: - if any(b.type == ButtonType.cancel for b in buttonEvents): + if any(b.type == ButtonType.cancel for b in self.CS.button_events): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) if self.get_sp_pedal_disengage(ret): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) @@ -143,17 +135,10 @@ class CarInterface(CarInterfaceBase): 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: - if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] # events events = self.create_common_events(ret, c, extra_gears=[car.CarState.GearShifter.low], pcm_enable=False) @@ -180,6 +165,4 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() - self.buttonStatesPrev = self.CS.buttonStates.copy() - return ret diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 85835d453a..c681040023 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -1,3 +1,4 @@ +from collections import namedtuple from enum import IntFlag from dataclasses import dataclass, field @@ -8,6 +9,7 @@ from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarPar from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu +Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) class ChryslerFlags(IntFlag): @@ -113,12 +115,12 @@ class CarControllerParams: self.STEER_MAX = 261 # higher than this faults the EPS -BUTTON_STATES = { - "accelCruise": False, - "decelCruise": False, - "cancel": False, - "resumeCruise": False, -} +BUTTONS = [ + Button(car.CarState.ButtonEvent.Type.accelCruise, "CRUISE_BUTTONS", "ACC_Accel", [1]), + Button(car.CarState.ButtonEvent.Type.decelCruise, "CRUISE_BUTTONS", "ACC_Decel", [1]), + Button(car.CarState.ButtonEvent.Type.cancel, "CRUISE_BUTTONS", "ACC_Cancel", [1]), + Button(car.CarState.ButtonEvent.Type.resumeCruise, "CRUISE_BUTTONS", "ACC_Resume", [1]), +] STEER_THRESHOLD = 120 diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 21e2f12def..e52454f81d 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -3,7 +3,7 @@ from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.ford.fordcan import CanBus -from openpilot.selfdrive.car.ford.values import DBC, CarControllerParams, FordFlags, BUTTON_STATES +from openpilot.selfdrive.car.ford.values import DBC, CarControllerParams, FordFlags, BUTTONS from openpilot.selfdrive.car.interfaces import CarStateBase GearShifter = car.CarState.GearShifter @@ -24,15 +24,14 @@ class CarState(CarStateBase): self.lkas_enabled = None self.prev_lkas_enabled = None - self.buttonStates = BUTTON_STATES.copy() - self.buttonStatesPrev = BUTTON_STATES.copy() + + self.button_states = {button.event_type: False for button in BUTTONS} def update(self, cp, cp_cam): ret = car.CarState.new_message() self.prev_mads_enabled = self.mads_enabled self.prev_lkas_enabled = self.lkas_enabled - self.buttonStatesPrev = self.buttonStates.copy() # Occasionally on startup, the ABS module recalibrates the steering pinion offset, so we need to block engagement # The vehicle usually recovers out of this state within a minute of normal driving @@ -87,6 +86,16 @@ class CarState(CarStateBase): else: ret.gearShifter = GearShifter.drive + # Buttons + for button in BUTTONS: + state = (cp.vl[button.can_addr][button.can_msg] in button.values) + if self.button_states[button.event_type] != state: + event = car.CarState.ButtonEvent.new_message() + event.type = button.event_type + event.pressed = state + self.button_events.append(event) + self.button_states[button.event_type] = state + # safety ret.stockFcw = bool(cp_cam.vl["ACCDATA_3"]["FcwVisblWarn_B_Rq"]) ret.stockAeb = bool(cp_cam.vl["ACCDATA_2"]["CmbbBrkDecel_B_Rq"]) @@ -112,13 +121,6 @@ class CarState(CarStateBase): self.lkas_enabled = bool(cp.vl["Steering_Data_FD1"]["TjaButtnOnOffPress"]) - self.buttonStates["accelCruise"] = bool(cp.vl["Steering_Data_FD1"]["CcAslButtnSetIncPress"]) - self.buttonStates["decelCruise"] = bool(cp.vl["Steering_Data_FD1"]["CcAslButtnSetDecPress"]) - self.buttonStates["cancel"] = bool(cp.vl["Steering_Data_FD1"]["CcAslButtnCnclPress"]) - self.buttonStates["setCruise"] = bool(cp.vl["Steering_Data_FD1"]["CcAslButtnSetPress"]) - self.buttonStates["resumeCruise"] = bool(cp.vl["Steering_Data_FD1"]["CcAsllButtnResPress"]) - self.buttonStates["gapAdjustCruise"] = bool(cp.vl["Steering_Data_FD1"]["AccButtnGapTogglePress"]) - # Stock steering buttons so that we can passthru blinkers etc. self.buttons_stock_values = cp.vl["Steering_Data_FD1"] # Stock values from IPMA so that we can retain some stock functionality diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 0c3449dc14..18dd7b4fa3 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,9 +1,9 @@ from cereal import car from panda import Panda from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.ford.fordcan import CanBus -from openpilot.selfdrive.car.ford.values import Ecu, FordFlags, BUTTON_STATES +from openpilot.selfdrive.car.ford.values import Ecu, FordFlags from openpilot.selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -15,8 +15,6 @@ class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): super().__init__(CP, CarController, CarState) - self.buttonStatesPrev = BUTTON_STATES.copy() - @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "ford" @@ -76,19 +74,12 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) - - for button in self.CS.buttonStates: - if self.CS.buttonStates[button] != self.buttonStatesPrev[button]: - be = car.CarState.ButtonEvent.new_message() - be.type = button - be.pressed = self.CS.buttonStates[button] - buttonEvents.append(be) + self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, - buttonEvents, c.vCruise) + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -101,7 +92,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = False if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0): - if any(b.type == ButtonType.cancel for b in buttonEvents): + if any(b.type == ButtonType.cancel for b in self.CS.button_events): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) if self.get_sp_pedal_disengage(ret): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) @@ -114,16 +105,10 @@ class CarInterface(CarInterfaceBase): 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: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.manumatic], pcm_enable=False) @@ -134,7 +119,4 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() - # update previous car states - self.buttonStatesPrev = self.CS.buttonStates.copy() - return ret diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index edfee3ea1b..04bd592e22 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,5 +1,6 @@ import copy import re +from collections import namedtuple from dataclasses import dataclass, field, replace from enum import Enum, IntFlag @@ -11,6 +12,7 @@ from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, Ca from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, LiveFwVersions, OfflineFwVersions, Request, StdQueries, p16 Ecu = car.CarParams.Ecu +Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) class CarControllerParams: @@ -46,16 +48,6 @@ class FordFlags(IntFlag): CANFD = 1 -BUTTON_STATES = { - "accelCruise": False, - "decelCruise": False, - "cancel": False, - "setCruise": False, - "resumeCruise": False, - "gapAdjustCruise": False -} - - class RADAR: DELPHI_ESR = 'ford_fusion_2018_adas' DELPHI_MRR = 'FORD_CADS' @@ -154,6 +146,15 @@ class CAR(Platforms): ) +BUTTONS = [ + Button(car.CarState.ButtonEvent.Type.accelCruise, "Steering_Data_FD1", "CcAslButtnSetIncPress", [1]), + Button(car.CarState.ButtonEvent.Type.decelCruise, "Steering_Data_FD1", "CcAslButtnSetDecPress", [1]), + Button(car.CarState.ButtonEvent.Type.cancel, "Steering_Data_FD1", "CcAslButtnCnclPress", [1]), + Button(car.CarState.ButtonEvent.Type.setCruise, "Steering_Data_FD1", "CcAslButtnSetPress", [1]), + Button(car.CarState.ButtonEvent.Type.resumeCruise, "Steering_Data_FD1", "CcAsllButtnResPress", [1]), +] + + # FW response contains a combined software and part number # A-Z except no I, O or W # e.g. NZ6A-14C204-AAA diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 22a8d619a6..0e428bc13b 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -6,7 +6,7 @@ from panda import Panda from openpilot.common.basedir import BASEDIR from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel @@ -206,12 +206,11 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback) self.sp_update_params() - buttonEvents = [] distance_button = 0 # Don't add event if transitioning from INIT, unless it's to an actual button if self.CS.cruise_buttons != CruiseButtons.UNPRESS or self.CS.prev_cruise_buttons != CruiseButtons.INIT: - buttonEvents = [ + self.CS.button_events = [ *create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, unpressed_btn=CruiseButtons.UNPRESS), *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, @@ -222,11 +221,11 @@ class CarInterface(CarInterfaceBase): self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) if not self.CP.pcmCruise: - if any(b.type == ButtonType.accelCruise and b.pressed for b in buttonEvents): + if any(b.type == ButtonType.accelCruise and b.pressed for b in self.CS.button_events): self.CS.accEnabled = True self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, - buttonEvents, c.vCruise) + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -239,7 +238,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = False if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0): - if any(b.type == ButtonType.cancel for b in buttonEvents): + if any(b.type == ButtonType.cancel for b in self.CS.button_events): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) if self.get_sp_pedal_disengage(ret): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) @@ -252,17 +251,10 @@ class CarInterface(CarInterfaceBase): 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: - if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] # The ECM allows enabling on falling edge of set, but only rising edge of resume events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 457b095e6a..72ab68647f 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -268,7 +268,7 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) self.sp_update_params() - buttonEvents = [ + self.CS.button_events = [ *create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT), *create_button_events(self.CS.cruise_setting, self.CS.prev_cruise_setting, SETTINGS_BUTTONS_DICT), ] @@ -276,7 +276,7 @@ class CarInterface(CarInterfaceBase): self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, - buttonEvents, c.vCruise) + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -289,7 +289,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = False if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed: - if any(b.type == ButtonType.cancel for b in buttonEvents): + if any(b.type == ButtonType.cancel for b in self.CS.button_events): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) if self.get_sp_pedal_disengage(ret): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) @@ -304,7 +304,10 @@ class CarInterface(CarInterfaceBase): min_enable_speed_pcm=(self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed), gap_button=(self.CS.cruise_setting == 3)) - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] # events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low], pcm_enable=False) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 5b284abdcb..9e6b08f7e1 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -7,7 +7,7 @@ from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, HyundaiFlagsSP, CANFD_UNSUPPORTED_LONGITUDINAL_CAR, NON_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, \ UNSUPPORTED_LONGITUDINAL_CAR, Buttons from openpilot.selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR -from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.disable_ecu import disable_ecu @@ -207,10 +207,10 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - buttonEvents = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT) + self.CS.button_events = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT) self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, - buttonEvents, c.vCruise) + self.CS.button_events, c.vCruise) self.CS.mads_enabled = False if not self.mads_main_toggle else self.CS.mads_enabled @@ -233,7 +233,7 @@ class CarInterface(CarInterfaceBase): if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed: if not self.CP.pcmCruise: - if any(b.type == ButtonType.cancel for b in buttonEvents): + if any(b.type == ButtonType.cancel for b in self.CS.button_events): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) if not self.CP.pcmCruiseSpeed: if not ret.cruiseState.enabled: @@ -244,17 +244,10 @@ class CarInterface(CarInterfaceBase): 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: - if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state # To avoid re-engaging when openpilot cancels, check user engagement intention via buttons diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 5fe64832c8..b8ca13ec86 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -1,3 +1,4 @@ +import capnp import json import os import numpy as np @@ -17,7 +18,7 @@ from openpilot.common.simple_kalman import KF1D, get_kalman_gain from openpilot.common.numpy_fast import clip from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL -from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG +from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG, ButtonEvents from openpilot.selfdrive.car.values import PLATFORMS from openpilot.selfdrive.controls.lib.desire_helper import get_min_lateral_speed from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, get_friction @@ -253,6 +254,7 @@ class CarInterfaceBase(ABC): self.last_mads_init = 0. self.madsEnabledInit = False self.madsEnabledInitPrev = False + self.button_events = ButtonEvents() self.lat_torque_nn_model = None eps_firmware = str(next((fw.fwVersion for fw in CP.carFw if fw.ecu == "eps"), "")) @@ -425,6 +427,8 @@ class CarInterfaceBase(ABC): if cp is not None: cp.update_strings(can_strings) + self.CS.button_events = [] + # get CarState ret = self._update(c) @@ -780,6 +784,8 @@ class CarStateBase(ABC): self.prev_mads_enabled = False self.control_initialized = False + self.button_events: list[capnp.lib.capnp._DynamicStructBuilder] = [] + Q = [[0.0, 0.0], [0.0, 100.0]] R = 0.3 A = [[1.0, DT_CTRL], [0.0, 1.0]] diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index 9832cebe46..368198e1c8 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -11,8 +11,7 @@ from openpilot.selfdrive.car.mazda.values import CarControllerParams, Buttons from openpilot.selfdrive.controls.lib.drive_helpers import MAZDA_V_CRUISE_MIN VisualAlert = car.CarControl.HUDControl.VisualAlert - -BUTTONS_STATES = ["accelCruise", "decelCruise", "cancel", "resumeCruise"] +ButtonType = car.CarState.ButtonEvent.Type class CarController(CarControllerBase): @@ -141,8 +140,10 @@ class CarController(CarControllerBase): # multikyd methods, sunnyhaibin logic def get_cruise_buttons_status(self, CS): if not CS.out.cruiseState.enabled: - if any(CS.buttonStates[button_state] for button_state in BUTTONS_STATES): - self.timer = 40 + for be in CS.out.buttonEvents: + if be.type in (ButtonType.accelCruise, ButtonType.resumeCruise, + ButtonType.decelCruise, ButtonType.setCruise) and be.pressed: + self.timer = 40 elif self.timer: self.timer -= 1 else: diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index dea768c03a..e5808238f8 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -3,7 +3,7 @@ from openpilot.common.conversions import Conversions as CV from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from openpilot.selfdrive.car.interfaces import CarStateBase -from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, MazdaFlags, BUTTON_STATES +from openpilot.selfdrive.car.mazda.values import DBC, LKAS_LIMITS, MazdaFlags, BUTTONS class CarState(CarStateBase): def __init__(self, CP): @@ -24,8 +24,7 @@ class CarState(CarStateBase): self.lkas_enabled = False self.prev_lkas_enabled = False - self.buttonStates = BUTTON_STATES.copy() - self.buttonStatesPrev = BUTTON_STATES.copy() + self.button_states = {button.event_type: False for button in BUTTONS} def update(self, cp, cp_cam): @@ -36,7 +35,6 @@ class CarState(CarStateBase): self.prev_mads_enabled = self.mads_enabled self.prev_lkas_enabled = self.lkas_enabled - self.buttonStatesPrev = self.buttonStates.copy() ret.wheelSpeeds = self.get_wheel_speeds( cp.vl["WHEEL_SPEEDS"]["FL"], @@ -56,6 +54,16 @@ class CarState(CarStateBase): can_gear = int(cp.vl["GEAR"]["GEAR"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) + # Buttons + for button in BUTTONS: + state = (cp.vl[button.can_addr][button.can_msg] in button.values) + if self.button_states[button.event_type] != state: + event = car.CarState.ButtonEvent.new_message() + event.type = button.event_type + event.pressed = state + self.button_events.append(event) + self.button_states[button.event_type] = state + ret.genericToggle = bool(cp.vl["BLINK_INFO"]["HIGH_BEAMS"]) ret.leftBlindspot = cp.vl["BSM"]["LEFT_BS_STATUS"] != 0 ret.rightBlindspot = cp.vl["BSM"]["RIGHT_BS_STATUS"] != 0 @@ -114,11 +122,6 @@ class CarState(CarStateBase): self.acc_active_last = ret.cruiseState.enabled - self.buttonStates["accelCruise"] = bool(cp.vl["CRZ_BTNS"]["SET_P"]) - self.buttonStates["decelCruise"] = bool(cp.vl["CRZ_BTNS"]["SET_M"]) - self.buttonStates["cancel"] = bool(cp.vl["CRZ_BTNS"]["CAN_OFF"]) - self.buttonStates["resumeCruise"] = bool(cp.vl["CRZ_BTNS"]["RES"]) - self.crz_btns_counter = cp.vl["CRZ_BTNS"]["CTR"] # camera signals diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index bb9a3f93a0..9447803ae6 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 from cereal import car from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS, BUTTON_STATES -from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event +from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -12,7 +12,6 @@ GearShifter = car.CarState.GearShifter class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): super().__init__(CP, CarController, CarState) - self.buttonStatesPrev = BUTTON_STATES.copy() @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): @@ -41,19 +40,15 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() # TODO: add button types for inc and dec - buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) - - for button in self.CS.buttonStates: - if self.CS.buttonStates[button] != self.buttonStatesPrev[button]: - be = car.CarState.ButtonEvent.new_message() - be.type = button - be.pressed = self.CS.buttonStates[button] - buttonEvents.append(be) + self.CS.button_events = [ + *self.CS.button_events, + *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) + ] self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, - buttonEvents, c.vCruise) + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -66,7 +61,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = False if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed: - if any(b.type == ButtonType.cancel for b in buttonEvents): + if any(b.type == ButtonType.cancel for b in self.CS.button_events): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) if self.get_sp_pedal_disengage(ret): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) @@ -79,17 +74,10 @@ class CarInterface(CarInterfaceBase): 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: - if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] # events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], @@ -108,6 +96,4 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() - self.buttonStatesPrev = self.CS.buttonStates.copy() - return ret diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 2aac951dbb..2889cbb7b2 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -1,3 +1,4 @@ +from collections import namedtuple from dataclasses import dataclass, field from enum import IntFlag @@ -8,6 +9,7 @@ from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarPar from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu +Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) # Steer torque limits @@ -26,14 +28,6 @@ class CarControllerParams: pass -BUTTON_STATES = { - "accelCruise": False, - "decelCruise": False, - "cancel": False, - "resumeCruise": False, -} - - @dataclass class MazdaCarDocs(CarDocs): package: str = "All" @@ -98,6 +92,14 @@ class Buttons: CANCEL = 4 +BUTTONS = [ + Button(car.CarState.ButtonEvent.Type.accelCruise, "CRZ_BTNS", "SET_P", [1]), + Button(car.CarState.ButtonEvent.Type.decelCruise, "CRZ_BTNS", "SET_M", [1]), + Button(car.CarState.ButtonEvent.Type.cancel, "CRZ_BTNS", "CAN_OFF", [1]), + Button(car.CarState.ButtonEvent.Type.resumeCruise, "CRZ_BTNS", "RES", [1]), +] + + FW_QUERY_CONFIG = FwQueryConfig( requests=[ # TODO: check data to ensure ABS does not skip ISO-TP frames on bus 0 diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 3d744a41a0..4a9e5a9125 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,6 +1,6 @@ from cereal import car from panda import Panda -from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.nissan.values import CAR @@ -34,7 +34,7 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_adas, self.cp_cam) self.sp_update_params() - buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) + self.CS.button_events = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) @@ -53,24 +53,11 @@ class CarInterface(CarInterfaceBase): 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: - be = car.CarState.ButtonEvent.new_message() - be.pressed = True - be.type = ButtonType.cancel - buttonEvents.append(be) - - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled), + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], pcm_enable=False) diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 744c017a96..7eefeb66eb 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,6 +1,6 @@ from cereal import car from panda import Panda -from openpilot.selfdrive.car import get_safety_config, create_mads_event +from openpilot.selfdrive.car import get_safety_config from openpilot.selfdrive.car.disable_ecu import disable_ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, SubaruFlags, SubaruFlagsSP @@ -118,8 +118,6 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) self.sp_update_params() - buttonEvents = [] - self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) if ret.cruiseState.available: @@ -145,24 +143,11 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS) - # CANCEL - if self.CS.out.cruiseState.enabled and not ret.cruiseState.enabled: - be = car.CarState.ButtonEvent.new_message() - be.pressed = True - be.type = ButtonType.cancel - buttonEvents.append(be) - - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled), + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low], pcm_enable=False) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 451265f9cd..d8b2516aba 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -5,7 +5,7 @@ from panda import Panda from panda.python import uds from openpilot.selfdrive.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, ToyotaFlagsSP, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \ MIN_ACC_SPEED, EPS_SCALE, UNSUPPORTED_DSU_CAR, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR -from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event +from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.disable_ecu import disable_ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase @@ -212,11 +212,10 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - buttonEvents = [] distance_button = 0 if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) or (self.CP.flags & ToyotaFlags.SMART_DSU and not self.CP.flags & ToyotaFlags.RADAR_CAN_FILTER): - buttonEvents = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) + self.CS.button_events = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) distance_button = self.CS.distance_button self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) @@ -245,24 +244,11 @@ class CarInterface(CarInterfaceBase): 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: - be = car.CarState.ButtonEvent.new_message() - be.pressed = True - be.type = ButtonType.cancel - buttonEvents.append(be) - - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled), + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] # events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 6ec27b8be0..1dc4c0e430 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -4,7 +4,7 @@ from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser from openpilot.selfdrive.car.volkswagen.values import DBC, CANBUS, NetworkLocation, TransmissionType, GearShifter, \ - CarControllerParams, VolkswagenFlags, BUTTON_STATES, VolkswagenFlagsSP + CarControllerParams, VolkswagenFlags, VolkswagenFlagsSP class CarState(CarStateBase): @@ -17,8 +17,6 @@ class CarState(CarStateBase): self.esp_hold_confirmation = False self.upscale_lead_car_signal = False self.eps_stock_values = False - self.buttonStates = BUTTON_STATES.copy() - self.buttonStatesPrev = BUTTON_STATES.copy() def create_button_events(self, pt_cp, buttons): button_events = [] @@ -41,7 +39,6 @@ class CarState(CarStateBase): ret = car.CarState.new_message() self.prev_mads_enabled = self.mads_enabled - self.buttonStatesPrev = self.buttonStates.copy() # Update vehicle speed and acceleration from ABS wheel speeds. ret.wheelSpeeds = self.get_wheel_speeds( @@ -150,18 +147,10 @@ class CarState(CarStateBase): if ret.cruiseState.speed > 90: ret.cruiseState.speed = 0 - # Update control button states for turn signals and ACC controls. - self.buttonStates["accelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Hoch"]) - self.buttonStates["decelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Runter"]) - self.buttonStates["cancel"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Abbrechen"]) - self.buttonStates["setCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Setzen"]) - self.buttonStates["resumeCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Wiederaufnahme"]) - self.buttonStates["gapAdjustCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Verstellung_Zeitluecke"]) - # Update button states for turn signals and ACC controls, capture all ACC button state/config for passthrough ret.leftBlinker = ret.leftBlinkerOn = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Left"]) ret.rightBlinker = ret.rightBlinkerOn = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Right"]) - ret.buttonEvents = self.create_button_events(pt_cp, self.CCP.BUTTONS) + self.button_events = self.create_button_events(pt_cp, self.CCP.BUTTONS) self.gra_stock_values = pt_cp.vl["GRA_ACC_01"] # Additional safety checks performed in CarInterface. @@ -177,7 +166,6 @@ class CarState(CarStateBase): ret = car.CarState.new_message() self.prev_mads_enabled = self.mads_enabled - self.buttonStatesPrev = self.buttonStates.copy() # Update vehicle speed and acceleration from ABS wheel speeds. ret.wheelSpeeds = self.get_wheel_speeds( @@ -265,18 +253,10 @@ class CarState(CarStateBase): if ret.cruiseState.speed > 70: # 255 kph in m/s == no current setpoint ret.cruiseState.speed = 0 - # Update control button states for turn signals and ACC controls. - self.buttonStates["accelCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Up_kurz"]) - self.buttonStates["decelCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Down_kurz"]) - self.buttonStates["cancel"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Abbrechen"]) - self.buttonStates["setCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Neu_Setzen"]) - self.buttonStates["resumeCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Recall"]) - self.buttonStates["gapAdjustCruise"] = bool(pt_cp.vl["GRA_Neu"]["GRA_Zeitluecke"]) - # Update button states for turn signals and ACC controls, capture all ACC button state/config for passthrough ret.leftBlinker, ret.rightBlinker = ret.leftBlinkerOn, ret.rightBlinkerOn = self.update_blinker_from_stalk(300, pt_cp.vl["Gate_Komf_1"]["GK1_Blinker_li"], pt_cp.vl["Gate_Komf_1"]["GK1_Blinker_re"]) - ret.buttonEvents = self.create_button_events(pt_cp, self.CCP.BUTTONS) + self.button_events = self.create_button_events(pt_cp, self.CCP.BUTTONS) self.gra_stock_values = pt_cp.vl["GRA_Neu"] # Additional safety checks performed in CarInterface. diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 31dea25cbb..d965cf2e8b 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,10 +1,10 @@ from cereal import car from panda import Panda from openpilot.common.params import Params -from openpilot.selfdrive.car import get_safety_config, create_mads_event +from openpilot.selfdrive.car import get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.volkswagen.values import CAR, CANBUS, CarControllerParams, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags, \ - BUTTON_STATES, VolkswagenFlagsSP + VolkswagenFlagsSP ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -21,8 +21,6 @@ class CarInterface(CarInterfaceBase): self.ext_bus = CANBUS.cam self.cp_ext = self.cp_cam - self.buttonStatesPrev = BUTTON_STATES.copy() - @staticmethod def _get_params(ret, candidate: CAR, fingerprint, car_fw, experimental_long, docs): ret.carName = "volkswagen" @@ -117,21 +115,10 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_ext, self.CP.transmissionType) self.sp_update_params() - buttonEvents = [] - - # Check for and process state-change events (button press or release) from - # the turn stalk switch or ACC steering wheel/control stalk buttons. - for button in self.CS.buttonStates: - if self.CS.buttonStates[button] != self.buttonStatesPrev[button]: - be = car.CarState.ButtonEvent.new_message() - be.type = button - be.pressed = self.CS.buttonStates[button] - buttonEvents.append(be) - self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, - buttonEvents, c.vCruise, + self.CS.button_events, c.vCruise, enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise)) if ret.cruiseState.available: @@ -144,7 +131,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = self.get_sp_started_mads(ret, self.CS) if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed: - if any(b.type == ButtonType.cancel for b in buttonEvents): + if any(b.type == ButtonType.cancel for b in self.CS.button_events): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) if self.get_sp_pedal_disengage(ret): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) @@ -155,19 +142,13 @@ 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, gap_button=bool(self.CS.buttonStates["gapAdjustCruise"])) + ret, self.CS = self.get_sp_common_state(ret, self.CS, + gap_button=any(b.type == ButtonType.gapAdjustCruise and b.pressed for b in self.CS.button_events)) - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = buttonEvents + ret.buttonEvents = [ + *self.CS.button_events, + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], pcm_enable=False, @@ -199,8 +180,4 @@ class CarInterface(CarInterfaceBase): ret.events = events.to_msg() - # update previous car states - self.buttonStatesPrev = self.CS.buttonStates.copy() - return ret - diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 2996db602f..80b7ca0b0b 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -147,16 +147,6 @@ class VolkswagenFlagsSP(IntFlag): SP_CC_ONLY_NO_RADAR = 2 -BUTTON_STATES = { - "accelCruise": False, - "decelCruise": False, - "cancel": False, - "setCruise": False, - "resumeCruise": False, - "gapAdjustCruise": False -} - - @dataclass class VolkswagenMQBPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_mqb_2010', None)) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc index 46bd84b6d9..742725e8ab 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc @@ -82,12 +82,12 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { dev_ui_settings->showDescription(); // Visuals: Display Metrics above Chevron - std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Distance\nSpeed")}; + std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")}; chevron_info_settings = new ButtonParamControl( "ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."), "../assets/offroad/icon_blank.png", chevron_info_settings_texts, - 340 + 300 ); chevron_info_settings->showDescription(); diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 009ff76138..bb19798e92 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1525,20 +1525,28 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState painter.drawPolygon(chevron, std::size(chevron)); if (num == 0) { // Display metrics to the 0th lead car - int chevron_types = 2; + const int chevron_types = 3; + const int chevron_all = chevron_types + 1; // All metrics QStringList chevron_text[chevron_types]; int position; float val; - if (chevron_data == 1 || chevron_data == 3) { + if (chevron_data == 1 || chevron_data == chevron_all) { position = 0; val = std::max(0.0f, d_rel); chevron_text[position].append(QString::number(val,'f', 0) + " " + "m"); } - if (chevron_data == 2 || chevron_data == 3) { + if (chevron_data == 2 || chevron_data == chevron_all) { position = (chevron_data == 2) ? 0 : 1; val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast(MS_TO_KPH) : static_cast(MS_TO_MPH))); chevron_text[position].append(QString::number(val,'f', 0) + " " + (is_metric ? "km/h" : "mph")); } + if (chevron_data == 3 || chevron_data == chevron_all) { + position = (chevron_data == 3) ? 0 : 2; + val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f; + + QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---"; + chevron_text[position].append(ttc_str); + } float str_w = 200; // Width of the text box, might need adjustment float str_h = 50; // Height of the text box, adjust as necessary diff --git a/system/manager/manager.py b/system/manager/manager.py index 388198736c..032fc0a667 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -117,6 +117,8 @@ def manager_init() -> None: ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), + ("EnableGitlabRunner", "0"), + ("EnableSunnylinkUploader", "0"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat().encode('utf8'))) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 8818d3b851..a718ea021b 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -6,7 +6,7 @@ from openpilot.system.hardware import PC, TICI from openpilot.selfdrive.modeld.custom_model_metadata import CustomModelMetadata, ModelCapabilities from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess from openpilot.system.mapd_manager import MAPD_PATH, COMMON_DIR -from openpilot.system.manager.sunnylink import sunnylink_need_register, sunnylink_ready +from openpilot.system.manager.sunnylink import sunnylink_need_register, sunnylink_ready, use_sunnylink_uploader WEBCAM = os.getenv("USE_WEBCAM") is not None @@ -44,6 +44,10 @@ def only_onroad(started: bool, params, CP: car.CarParams) -> bool: def only_offroad(started, params, CP: car.CarParams) -> bool: return not started +def use_gitlab_runner(started, params, CP: car.CarParams) -> bool: + return (not PC and params.get_bool("EnableGitlabRunner") and only_offroad(started, params, CP) + and os.path.exists("./gitlab_runner.sh")) + def model_use_nav(started, params, CP: car.CarParams) -> bool: custom_model_metadata = CustomModelMetadata(params=params, init_only=True) return started and custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO @@ -56,6 +60,10 @@ def sunnylink_need_register_shim(started, params, CP: car.CarParams) -> bool: """Shim for sunnylink_need_register to match the process manager signature.""" return sunnylink_need_register(params) +def use_sunnylink_uploader_shim(started, params, CP: car.CarParams) -> bool: + """Shim for use_sunnylink_uploader to match the process manager signature.""" + return use_sunnylink_uploader(params) and os.path.exists("../loggerd/sunnylink_uploader.py") + procs = [ DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), @@ -112,21 +120,12 @@ procs = [ PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar), PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), + # Sunnypilot devs + NativeProcess("gitlab_runner_start", "system/manager", ["./gitlab_runner.sh", "start"], use_gitlab_runner, sigkill=False), # Sunnylink <3 DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), PythonProcess("sunnylink_registration", "system.manager.sunnylink", sunnylink_need_register_shim), + PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", use_sunnylink_uploader_shim), ] -if os.path.exists("../loggerd/sunnylink_uploader.py"): - procs += [ - PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", sunnylink_ready_shim), - ] - -if os.path.exists("./gitlab_runner.sh") and not PC: - # Only devs! - procs += [ - NativeProcess("gitlab_runner_start", "system/manager", ["./gitlab_runner.sh", "start"], only_offroad, sigkill=False), - NativeProcess("gitlab_runner_stop", "system/manager", ["./gitlab_runner.sh", "stop"], only_onroad, sigkill=False) - ] - managed_processes = {p.name: p for p in procs} diff --git a/system/manager/sunnylink.py b/system/manager/sunnylink.py index 84161fb817..10a5f95a3d 100755 --- a/system/manager/sunnylink.py +++ b/system/manager/sunnylink.py @@ -27,6 +27,11 @@ def sunnylink_ready(params=Params()) -> bool: return is_sunnylink_enabled and is_registered +def use_sunnylink_uploader(params) -> bool: + """Check if the device is ready to use Sunnylink and the uploader is enabled.""" + return sunnylink_ready(params) and params.get_bool("EnableSunnylinkUploader") + + def sunnylink_need_register(params=Params()) -> bool: """Check if the device needs to be registered with Sunnylink.""" is_sunnylink_enabled, is_registered = get_sunnylink_status(params)