From 079cd7833e720ff35103dc4e07b06b840f7379b4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 16 Aug 2023 23:03:01 -0400 Subject: [PATCH 001/575] Subaru: gen2 torque increase --- panda | 2 +- selfdrive/car/subaru/values.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/panda b/panda index 59f157dea5..b2b9ad9e92 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 59f157dea5cab5e77cb59156cafea40a98ad174a +Subproject commit b2b9ad9e92c35f9b39bc0b5457212220e3b087a1 diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index b45916c281..b50772ad32 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -22,9 +22,9 @@ class CarControllerParams: self.STEER_DRIVER_FACTOR = 1 # from dbc if CP.carFingerprint in GLOBAL_GEN2: - self.STEER_MAX = 1000 - self.STEER_DELTA_UP = 40 - self.STEER_DELTA_DOWN = 40 + self.STEER_MAX = 1400 + self.STEER_DELTA_UP = 25 + self.STEER_DELTA_DOWN = 25 elif CP.carFingerprint == CAR.IMPREZA_2020: self.STEER_MAX = 1439 elif CP.safetyConfigs[0].safetyParam == Panda.FLAG_SUBARU_MAX_STEER_IMPREZA_2018: From 8a7f1b588b91fff9ab2c035b92aa75e3ac777431 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 17 Aug 2023 17:53:02 -0400 Subject: [PATCH 002/575] match --- selfdrive/car/subaru/values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index b50772ad32..918498f9a3 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -24,7 +24,7 @@ class CarControllerParams: if CP.carFingerprint in GLOBAL_GEN2: self.STEER_MAX = 1400 self.STEER_DELTA_UP = 25 - self.STEER_DELTA_DOWN = 25 + self.STEER_DELTA_DOWN = 35 elif CP.carFingerprint == CAR.IMPREZA_2020: self.STEER_MAX = 1439 elif CP.safetyConfigs[0].safetyParam == Panda.FLAG_SUBARU_MAX_STEER_IMPREZA_2018: From f5ce706df7a489314fc3282c646f6d5f814b372c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 17 Aug 2023 18:05:08 -0400 Subject: [PATCH 003/575] revert for now --- selfdrive/car/subaru/values.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 918498f9a3..4824ac72bf 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -23,8 +23,8 @@ class CarControllerParams: if CP.carFingerprint in GLOBAL_GEN2: self.STEER_MAX = 1400 - self.STEER_DELTA_UP = 25 - self.STEER_DELTA_DOWN = 35 + self.STEER_DELTA_UP = 40 + self.STEER_DELTA_DOWN = 40 elif CP.carFingerprint == CAR.IMPREZA_2020: self.STEER_MAX = 1439 elif CP.safetyConfigs[0].safetyParam == Panda.FLAG_SUBARU_MAX_STEER_IMPREZA_2018: From 99507779198f433116454900bff605e934a77147 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 18 Aug 2023 08:54:30 -0400 Subject: [PATCH 004/575] Hyundai CAN-FD: upstream taco tune --- panda | 2 +- selfdrive/car/hyundai/carcontroller.py | 4 ++++ selfdrive/car/hyundai/interface.py | 4 ++++ selfdrive/car/hyundai/values.py | 14 ++++++++++++-- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/panda b/panda index 59f157dea5..f618bcf519 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 59f157dea5cab5e77cb59156cafea40a98ad174a +Subproject commit f618bcf519ec631718016c9c9cbc71a0a257f8f1 diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 98b50fae4d..5b90013294 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -117,8 +117,12 @@ class CarController: hud_control = CC.hudControl # steering torque + if self.CP.spFlags & HyundaiFlagsSP.SP_UPSTREAM_TACO.value: + self.params = CarControllerParams(self.CP, CS.out.vEgoRaw) new_steer = int(round(actuators.steer * self.params.STEER_MAX)) apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) + if self.CP.spFlags & HyundaiFlagsSP.SP_UPSTREAM_TACO.value: + apply_steer = clip(apply_steer, -self.params.STEER_MAX, self.params.STEER_MAX) if not CC.latActive: apply_steer = 0 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 6a7bbd404f..ad988ab1ff 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -270,6 +270,8 @@ class CarInterface(CarInterfaceBase): if 0x1fa in fingerprint[CAN.ECAN]: ret.spFlags |= HyundaiFlagsSP.SP_NAV_MSG.value + if Params().get("DongleId", encoding='utf8') in ("012c95f06918eca4", ): + ret.spFlags |= HyundaiFlagsSP.SP_UPSTREAM_TACO.value else: ret.enableBsm = 0x58b in fingerprint[0] @@ -289,6 +291,8 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC + if ret.spFlags & HyundaiFlagsSP.SP_UPSTREAM_TACO: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_UPSTREAM_TACO else: if candidate in LEGACY_SAFETY_MODE_CAR: # these cars require a special panda safety mode due to missing counters and checksums in the messages diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 95629a38a0..d220e6d077 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -4,7 +4,7 @@ from enum import Enum, IntFlag from typing import Dict, List, Optional, Set, Tuple, Union from cereal import car -from panda.python import uds +from panda.python import uds, Panda from common.conversions import Conversions as CV from selfdrive.car import dbc_dict from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column @@ -17,7 +17,7 @@ class CarControllerParams: ACCEL_MIN = -3.5 # m/s ACCEL_MAX = 2.0 # m/s - def __init__(self, CP): + def __init__(self, CP, vEgoRaw=100.): self.STEER_DELTA_UP = 3 self.STEER_DELTA_DOWN = 7 self.STEER_DRIVER_ALLOWANCE = 50 @@ -33,6 +33,7 @@ class CarControllerParams: self.STEER_THRESHOLD = 250 self.STEER_DELTA_UP = 2 self.STEER_DELTA_DOWN = 3 + self.upstream_taco(CP, vEgoRaw) # To determine the limit for your car, find the maximum value that the stock LKAS will request. # If the max stock LKAS request is <384, add your car to this list. @@ -51,6 +52,14 @@ class CarControllerParams: else: self.STEER_MAX = 384 + def upstream_taco(self, CP, v_ego_raw): + if CP.safetyConfigs[-1].safetyParam == Panda.FLAG_HYUNDAI_UPSTREAM_TACO: + self.STEER_MAX = 384 if v_ego_raw < 11. else 330 + self.STEER_DRIVER_ALLOWANCE = 350 + self.STEER_THRESHOLD = 350 + self.STEER_DELTA_UP = 10 if v_ego_raw < 11. else 2 + self.STEER_DELTA_DOWN = 10 if v_ego_raw < 11. else 3 + class HyundaiFlags(IntFlag): CANFD_HDA2 = 1 @@ -69,6 +78,7 @@ class HyundaiFlagsSP(IntFlag): SP_ENHANCED_SCC = 1 SP_CAN_LFA_BTN = 2 SP_NAV_MSG = 4 + SP_UPSTREAM_TACO = 8 class CAR: From b4ef0c3a5ed42f84c50e9108f6b2915d9c67e14f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 18 Aug 2023 10:31:33 -0400 Subject: [PATCH 005/575] bytes instead --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index ad988ab1ff..4ac3b9ab8b 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -270,7 +270,7 @@ class CarInterface(CarInterfaceBase): if 0x1fa in fingerprint[CAN.ECAN]: ret.spFlags |= HyundaiFlagsSP.SP_NAV_MSG.value - if Params().get("DongleId", encoding='utf8') in ("012c95f06918eca4", ): + if Params().get("DongleId", encoding='utf8') == b"012c95f06918eca4": ret.spFlags |= HyundaiFlagsSP.SP_UPSTREAM_TACO.value else: ret.enableBsm = 0x58b in fingerprint[0] From 4f9841302e0ec58f23d46cd92e05b87243fba64c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 18 Aug 2023 10:40:29 -0400 Subject: [PATCH 006/575] only behind flags --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index f618bcf519..d36b7b460f 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit f618bcf519ec631718016c9c9cbc71a0a257f8f1 +Subproject commit d36b7b460f951e7c2b00422e8734bf3e279f9082 From 42e842324afe2ebae1594c7ca71392754ee488ca Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 18 Aug 2023 10:43:53 -0400 Subject: [PATCH 007/575] Try this --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 4ac3b9ab8b..725c603c27 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -270,7 +270,7 @@ class CarInterface(CarInterfaceBase): if 0x1fa in fingerprint[CAN.ECAN]: ret.spFlags |= HyundaiFlagsSP.SP_NAV_MSG.value - if Params().get("DongleId", encoding='utf8') == b"012c95f06918eca4": + if Params().get("DongleId", encoding='utf8') == "012c95f06918eca4": ret.spFlags |= HyundaiFlagsSP.SP_UPSTREAM_TACO.value else: ret.enableBsm = 0x58b in fingerprint[0] From f6be6beb2b9cbddab4021ba9bb68443ea597cba9 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 18 Aug 2023 10:51:31 -0400 Subject: [PATCH 008/575] use this instead --- selfdrive/car/hyundai/values.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index d220e6d077..aa03a3031f 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -27,13 +27,13 @@ class CarControllerParams: self.STEER_STEP = 1 # 100 Hz if CP.carFingerprint in CANFD_CAR: - self.STEER_MAX = 270 - self.STEER_DRIVER_ALLOWANCE = 250 + upstream_taco = CP.safetyConfigs[-1].safetyParam == Panda.FLAG_HYUNDAI_UPSTREAM_TACO + self.STEER_MAX = 270 if not upstream_taco else 384 if vEgoRaw < 11. else 330 + self.STEER_DRIVER_ALLOWANCE = 250 if not upstream_taco else 350 self.STEER_DRIVER_MULTIPLIER = 2 - self.STEER_THRESHOLD = 250 - self.STEER_DELTA_UP = 2 - self.STEER_DELTA_DOWN = 3 - self.upstream_taco(CP, vEgoRaw) + self.STEER_THRESHOLD = 250 if not upstream_taco else 350 + self.STEER_DELTA_UP = 2 if not upstream_taco else 10 if vEgoRaw < 11. else 2 + self.STEER_DELTA_DOWN = 3 if not upstream_taco else 10 if vEgoRaw < 11. else 3 # To determine the limit for your car, find the maximum value that the stock LKAS will request. # If the max stock LKAS request is <384, add your car to this list. @@ -52,14 +52,6 @@ class CarControllerParams: else: self.STEER_MAX = 384 - def upstream_taco(self, CP, v_ego_raw): - if CP.safetyConfigs[-1].safetyParam == Panda.FLAG_HYUNDAI_UPSTREAM_TACO: - self.STEER_MAX = 384 if v_ego_raw < 11. else 330 - self.STEER_DRIVER_ALLOWANCE = 350 - self.STEER_THRESHOLD = 350 - self.STEER_DELTA_UP = 10 if v_ego_raw < 11. else 2 - self.STEER_DELTA_DOWN = 10 if v_ego_raw < 11. else 3 - class HyundaiFlags(IntFlag): CANFD_HDA2 = 1 From 665c4a69f455364172650d1f86ad4ff2d24e1d90 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 18 Aug 2023 11:07:21 -0400 Subject: [PATCH 009/575] use SSH to enable instead --- common/params.cc | 1 + selfdrive/car/hyundai/interface.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/params.cc b/common/params.cc index c7b8652bb0..a86f8e8fa1 100644 --- a/common/params.cc +++ b/common/params.cc @@ -299,6 +299,7 @@ std::unordered_map keys = { {"TurnSpeedControl", PERSISTENT}, {"TurnVisionControl", PERSISTENT}, {"DriverCameraHardwareMissing", PERSISTENT}, + {"UpstreamTaco", PERSISTENT}, {"VisionCurveLaneless", PERSISTENT}, {"VwAccType", PERSISTENT}, {"Offroad_SupersededUpdate", PERSISTENT}, diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 725c603c27..dc068e8163 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -270,7 +270,7 @@ class CarInterface(CarInterfaceBase): if 0x1fa in fingerprint[CAN.ECAN]: ret.spFlags |= HyundaiFlagsSP.SP_NAV_MSG.value - if Params().get("DongleId", encoding='utf8') == "012c95f06918eca4": + if Params().get_bool("UpstreamTaco"): ret.spFlags |= HyundaiFlagsSP.SP_UPSTREAM_TACO.value else: ret.enableBsm = 0x58b in fingerprint[0] From b4884c720daa222cd664204c9cc4f21c46f45fb1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 18 Aug 2023 11:33:48 -0400 Subject: [PATCH 010/575] This is why it doesn't work --- selfdrive/car/hyundai/values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index aa03a3031f..e8a416eca3 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -27,7 +27,7 @@ class CarControllerParams: self.STEER_STEP = 1 # 100 Hz if CP.carFingerprint in CANFD_CAR: - upstream_taco = CP.safetyConfigs[-1].safetyParam == Panda.FLAG_HYUNDAI_UPSTREAM_TACO + upstream_taco = CP.safetyConfigs[-1].safetyParam & Panda.FLAG_HYUNDAI_UPSTREAM_TACO self.STEER_MAX = 270 if not upstream_taco else 384 if vEgoRaw < 11. else 330 self.STEER_DRIVER_ALLOWANCE = 250 if not upstream_taco else 350 self.STEER_DRIVER_MULTIPLIER = 2 From fb3991c07a1a606a0387eff5120bbec93198dd7b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 18 Aug 2023 11:33:58 -0400 Subject: [PATCH 011/575] Revert "use SSH to enable instead" This reverts commit 665c4a69f455364172650d1f86ad4ff2d24e1d90. --- common/params.cc | 1 - selfdrive/car/hyundai/interface.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/common/params.cc b/common/params.cc index a86f8e8fa1..c7b8652bb0 100644 --- a/common/params.cc +++ b/common/params.cc @@ -299,7 +299,6 @@ std::unordered_map keys = { {"TurnSpeedControl", PERSISTENT}, {"TurnVisionControl", PERSISTENT}, {"DriverCameraHardwareMissing", PERSISTENT}, - {"UpstreamTaco", PERSISTENT}, {"VisionCurveLaneless", PERSISTENT}, {"VwAccType", PERSISTENT}, {"Offroad_SupersededUpdate", PERSISTENT}, diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index dc068e8163..725c603c27 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -270,7 +270,7 @@ class CarInterface(CarInterfaceBase): if 0x1fa in fingerprint[CAN.ECAN]: ret.spFlags |= HyundaiFlagsSP.SP_NAV_MSG.value - if Params().get_bool("UpstreamTaco"): + if Params().get("DongleId", encoding='utf8') == "012c95f06918eca4": ret.spFlags |= HyundaiFlagsSP.SP_UPSTREAM_TACO.value else: ret.enableBsm = 0x58b in fingerprint[0] From 1088d425f445aac4002b79d24942b876c6ba378d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 19 Aug 2023 14:35:38 -0400 Subject: [PATCH 012/575] lat planner retune --- selfdrive/controls/lib/lateral_planner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 5d850d180d..d65c4b66f0 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -17,13 +17,13 @@ LaneChangeState = log.LateralPlan.LaneChangeState PATH_COST = 1.0 LATERAL_MOTION_COST = 0.11 -LATERAL_ACCEL_COST = 0.0 +LATERAL_ACCEL_COST = 1.0 LATERAL_JERK_COST = 0.04 # Extreme steering rate is unpleasant, even # when it does not cause bad jerk. # TODO this cost should be lowered when low # speed lateral control is stable on all cars -STEERING_RATE_COST = 700.0 +STEERING_RATE_COST = 8.0 class LateralPlanner: From d3f58ba9d75ff16dc39df0aad03f6bc1c9c114bc Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 8 Sep 2023 11:02:16 -0400 Subject: [PATCH 013/575] variable fix --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 3fe052cdc2..005e7b295c 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 3fe052cdc2e8afce9d6fb4b5716a9c83c8276baa +Subproject commit 005e7b295c45da4167a84cd1c21d6daf59615e70 From ad07b13f23723b28ebded95788380fda93a5e144 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 23 Sep 2023 00:37:25 -0400 Subject: [PATCH 014/575] Hyundai CAN-FD: longitudinal support for all platforms --- panda | 2 +- selfdrive/car/hyundai/carcontroller.py | 5 +-- selfdrive/car/hyundai/hyundaicanfd.py | 55 +++++++++++++------------- selfdrive/car/hyundai/interface.py | 4 +- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/panda b/panda index 235217badc..48a1f96b92 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 235217badc8d88d6d21def19cd93ba8cc12ae8c0 +Subproject commit 48a1f96b9243215991494055ebfec3ce487406ac diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 07929e3602..eb652c768b 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -179,7 +179,7 @@ class CarController: if self.frame % 100 == 0 and not ((self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) or escc) and \ self.CP.carFingerprint not in CAMERA_SCC_CAR and self.CP.openpilotLongitudinalControl: # for longitudinal control, either radar or ADAS driving ECU - addr, bus = 0x7d0, 0 + addr, bus = 0x7d0, self.CAN.ECAN if self.CP.carFingerprint in CANFD_CAR else 0 if self.CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, self.CAN.ECAN can_sends.append([addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus]) @@ -212,8 +212,7 @@ class CarController: can_sends.extend(hyundaicanfd.create_spas_messages(self.packer, self.CAN, self.frame, CC.leftBlinker, CC.rightBlinker)) if self.CP.openpilotLongitudinalControl: - if hda2: - can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)) + can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame, hda2=hda2)) if self.frame % 2 == 0: can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled and CS.out.cruiseState.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, set_speed_in_units, CS.gac_tr_cluster)) diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index 2489790085..14353edb13 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -172,16 +172,12 @@ def create_spas_messages(packer, CAN, frame, left_blink, right_blink): return ret -def create_adrv_messages(packer, CAN, frame): +def create_adrv_messages(packer, CAN, frame, hda2=False): # messages needed to car happy after disabling # the ADAS Driving ECU to do longitudinal control ret = [] - values = { - } - ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values)) - if frame % 2 == 0: values = { 'AEB_SETTING': 0x1, # show AEB disabled icon @@ -192,32 +188,37 @@ def create_adrv_messages(packer, CAN, frame): } ret.append(packer.make_can_msg("ADRV_0x160", CAN.ECAN, values)) - if frame % 5 == 0: + if hda2: values = { - 'SET_ME_1C': 0x1c, - 'SET_ME_FF': 0xff, - 'SET_ME_TMP_F': 0xf, - 'SET_ME_TMP_F_2': 0xf, } - ret.append(packer.make_can_msg("ADRV_0x1ea", CAN.ECAN, values)) + ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values)) - values = { - 'SET_ME_E1': 0xe1, - 'SET_ME_3A': 0x3a, - } - ret.append(packer.make_can_msg("ADRV_0x200", CAN.ECAN, values)) + if frame % 5 == 0: + values = { + 'SET_ME_1C': 0x1c, + 'SET_ME_FF': 0xff, + 'SET_ME_TMP_F': 0xf, + 'SET_ME_TMP_F_2': 0xf, + } + ret.append(packer.make_can_msg("ADRV_0x1ea", CAN.ECAN, values)) - if frame % 20 == 0: - values = { - 'SET_ME_15': 0x15, - } - ret.append(packer.make_can_msg("ADRV_0x345", CAN.ECAN, values)) + values = { + 'SET_ME_E1': 0xe1, + 'SET_ME_3A': 0x3a, + } + ret.append(packer.make_can_msg("ADRV_0x200", CAN.ECAN, values)) - if frame % 100 == 0: - values = { - 'SET_ME_22': 0x22, - 'SET_ME_41': 0x41, - } - ret.append(packer.make_can_msg("ADRV_0x1da", CAN.ECAN, values)) + if frame % 20 == 0: + values = { + 'SET_ME_15': 0x15, + } + ret.append(packer.make_can_msg("ADRV_0x345", CAN.ECAN, values)) + + if frame % 100 == 0: + values = { + 'SET_ME_22': 0x22, + 'SET_ME_41': 0x41, + } + ret.append(packer.make_can_msg("ADRV_0x1da", CAN.ECAN, values)) return ret diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 85b9f06d7d..dc2e013073 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -262,7 +262,7 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] - ret.experimentalLongitudinalAvailable = candidate in (HYBRID_CAR | EV_CAR) and candidate not in (CANFD_RADAR_SCC_CAR | NON_SCC_CAR) + ret.experimentalLongitudinalAvailable = candidate not in NON_SCC_CAR else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] @@ -350,7 +350,7 @@ class CarInterface(CarInterfaceBase): def init(CP, logcan, sendcan): if CP.openpilotLongitudinalControl and not ((CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) or (CP.spFlags & HyundaiFlagsSP.SP_ENHANCED_SCC)) and \ CP.carFingerprint not in CAMERA_SCC_CAR: - addr, bus = 0x7d0, 0 + addr, bus = 0x7d0, CanBus(CP).ECAN if CP.carFingerprint in CANFD_CAR else 0 if CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, CanBus(CP).ECAN disable_ecu(logcan, sendcan, bus=bus, addr=addr, com_cont_req=b'\x28\x83\x01') From c0352280104bbfd2fc9f0e3ba1d1043056839098 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 25 Sep 2023 20:35:26 -0400 Subject: [PATCH 015/575] Merge commaai:openpilot/30021 --- panda | 2 +- selfdrive/car/hyundai/carcontroller.py | 5 +- selfdrive/car/hyundai/hyundaicanfd.py | 69 ++++++++++++++------------ 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/panda b/panda index 48a1f96b92..92af908c72 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 48a1f96b9243215991494055ebfec3ce487406ac +Subproject commit 92af908c7292f0e67db5cc1009936328ac26d40d diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index eb652c768b..84f40d08bd 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -212,7 +212,10 @@ class CarController: can_sends.extend(hyundaicanfd.create_spas_messages(self.packer, self.CAN, self.frame, CC.leftBlinker, CC.rightBlinker)) if self.CP.openpilotLongitudinalControl: - can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame, hda2=hda2)) + if hda2: + can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)) + else: + can_sends.extend(hyundaicanfd.create_fca_warning_light(self.packer, self.CAN, self.frame)) if self.frame % 2 == 0: can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled and CS.out.cruiseState.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, set_speed_in_units, CS.gac_tr_cluster)) diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index 14353edb13..9805036af8 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -172,12 +172,8 @@ def create_spas_messages(packer, CAN, frame, left_blink, right_blink): return ret -def create_adrv_messages(packer, CAN, frame, hda2=False): - # messages needed to car happy after disabling - # the ADAS Driving ECU to do longitudinal control - +def create_fca_warning_light(packer, CAN, frame): ret = [] - if frame % 2 == 0: values = { 'AEB_SETTING': 0x1, # show AEB disabled icon @@ -187,38 +183,47 @@ def create_adrv_messages(packer, CAN, frame, hda2=False): 'SET_ME_9': 0x9, } ret.append(packer.make_can_msg("ADRV_0x160", CAN.ECAN, values)) + return ret - if hda2: + +def create_adrv_messages(packer, CAN, frame): + # messages needed to car happy after disabling + # the ADAS Driving ECU to do longitudinal control + + ret = [] + + values = { + } + ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values)) + + ret.extend(create_fca_warning_light(packer, CAN, frame)) + + if frame % 5 == 0: values = { + 'SET_ME_1C': 0x1c, + 'SET_ME_FF': 0xff, + 'SET_ME_TMP_F': 0xf, + 'SET_ME_TMP_F_2': 0xf, } - ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values)) + ret.append(packer.make_can_msg("ADRV_0x1ea", CAN.ECAN, values)) - if frame % 5 == 0: - values = { - 'SET_ME_1C': 0x1c, - 'SET_ME_FF': 0xff, - 'SET_ME_TMP_F': 0xf, - 'SET_ME_TMP_F_2': 0xf, - } - ret.append(packer.make_can_msg("ADRV_0x1ea", CAN.ECAN, values)) + values = { + 'SET_ME_E1': 0xe1, + 'SET_ME_3A': 0x3a, + } + ret.append(packer.make_can_msg("ADRV_0x200", CAN.ECAN, values)) - values = { - 'SET_ME_E1': 0xe1, - 'SET_ME_3A': 0x3a, - } - ret.append(packer.make_can_msg("ADRV_0x200", CAN.ECAN, values)) + if frame % 20 == 0: + values = { + 'SET_ME_15': 0x15, + } + ret.append(packer.make_can_msg("ADRV_0x345", CAN.ECAN, values)) - if frame % 20 == 0: - values = { - 'SET_ME_15': 0x15, - } - ret.append(packer.make_can_msg("ADRV_0x345", CAN.ECAN, values)) - - if frame % 100 == 0: - values = { - 'SET_ME_22': 0x22, - 'SET_ME_41': 0x41, - } - ret.append(packer.make_can_msg("ADRV_0x1da", CAN.ECAN, values)) + if frame % 100 == 0: + values = { + 'SET_ME_22': 0x22, + 'SET_ME_41': 0x41, + } + ret.append(packer.make_can_msg("ADRV_0x1da", CAN.ECAN, values)) return ret From 13453c6468286f1a75f2ec7cb1efd78c560c321f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 29 Sep 2023 09:04:59 -0400 Subject: [PATCH 016/575] fixes --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 2edefc7b81..df067415a8 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 2edefc7b81f68a36f18087e7c81e77bf3fc8204a +Subproject commit df067415a8bb6d0d0d094c76ebf71072cac2a502 From 6bb5c5166f83e939590dfcaa49dd13f65bd24e02 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 3 Oct 2023 17:07:31 -0400 Subject: [PATCH 017/575] Custom Stock Longitudinal Control: Use planner speed --- common/params.cc | 1 + selfdrive/car/hyundai/carcontroller.py | 13 ++++++++++++- selfdrive/ui/qt/offroad/settings.cc | 21 +++++++++++++++++++++ selfdrive/ui/qt/onroad.cc | 3 ++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/common/params.cc b/common/params.cc index 0c19b6dc76..78055f35fd 100644 --- a/common/params.cc +++ b/common/params.cc @@ -228,6 +228,7 @@ std::unordered_map keys = { {"CustomMapboxTokenSk", PERSISTENT}, {"CustomOffsets", PERSISTENT}, {"CustomStockLong", PERSISTENT}, + {"CustomStockLongPlanner", PERSISTENT}, {"CustomTorqueLateral", PERSISTENT}, {"DevUI", PERSISTENT}, {"DevUIInfo", PERSISTENT}, diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index e2d1532189..939739d530 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -63,7 +63,7 @@ class CarController: self.lat_disengage_init = False self.lat_active_last = False - self.sm = messaging.SubMaster(['longitudinalPlanSP']) + self.sm = messaging.SubMaster(['longitudinalPlan', 'longitudinalPlanSP']) self.param_s = Params() self.is_metric = self.param_s.get_bool("IsMetric") self.speed_limit_control_enabled = False @@ -92,12 +92,19 @@ class CarController: self.v_tsc = 0 self.m_tsc = 0 self.steady_speed = 0 + self.speeds = 0 + self.v_target_plan = 0 self.hkg_can_smooth_stop = self.param_s.get_bool("HkgSmoothStop") + self.custom_planner_speed = self.param_s.get_bool("CustomStockLongPlanner") def update(self, CC, CS, now_nanos): if not self.CP.pcmCruiseSpeed: self.sm.update(0) + if self.sm.updated['longitudinalPlan']: + _speeds = self.sm['longitudinalPlan'].speeds + self.speeds = _speeds[-1] if len(_speeds) else 0 + if self.sm.updated['longitudinalPlanSP']: self.v_tsc_state = self.sm['longitudinalPlanSP'].visionTurnControllerState self.slc_state = self.sm['longitudinalPlanSP'].speedLimitControlState @@ -110,8 +117,10 @@ class CarController: if self.frame % 200 == 0: self.speed_limit_control_enabled = self.param_s.get_bool("SpeedLimitControl") self.is_metric = self.param_s.get_bool("IsMetric") + self.custom_planner_speed = self.param_s.get_bool("CustomStockLongPlanner") self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap") self.v_cruise_min = HYUNDAI_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1) + self.v_target_plan = min(CC.vCruise * CV.KPH_TO_MS, self.speeds) actuators = CC.actuators hud_control = CC.hudControl @@ -428,6 +437,8 @@ class CarController: target_speed_kph = set_speed_kph else: target_speed_kph = min(v_cruise_kph_prev, set_speed_kph) + if self.custom_planner_speed: + target_speed_kph = self.curve_speed_hysteresis(self.v_target_plan) if self.v_tsc_state != 0 or self.m_tsc_state > 1: self.final_speed_kph = self.get_curve_speed(target_speed_kph, v_cruise_kph_prev) else: diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index a3e375f355..77ec997c19 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -52,6 +52,12 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses.\nThis feature must be used along with SLC, and/or V-TSC, and/or M-TSC."), "../assets/offroad/icon_speed_limit.png", }, + { + "CustomStockLongPlanner", + tr("Use Planner Speed"), + "", + "../assets/offroad/icon_blank.png", + }, { "ExperimentalMode", tr("Experimental Mode"), @@ -129,6 +135,9 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); }); + connect(toggles["CustomStockLong"], &ToggleControl::toggleFlipped, [=]() { + updateToggles(); + }); param_watcher = new ParamWatcher(this); @@ -150,6 +159,7 @@ void TogglesPanel::updateToggles() { auto experimental_mode_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; auto custom_stock_long_toggle = toggles["CustomStockLong"]; + auto custom_stock_long_planner = toggles["CustomStockLongPlanner"]; const QString e2e_description = QString("%1
" "

%2


" "%3
" @@ -194,6 +204,12 @@ void TogglesPanel::updateToggles() { long_personality_setting->setEnabled(true); custom_stock_long_toggle->setEnabled(false); params.remove("CustomStockLong"); + } else if (custom_stock_long_toggle->isToggled()) { + experimental_mode_toggle->setEnabled(true); + experimental_mode_toggle->setDescription(e2e_description); + long_personality_setting->setEnabled(false); + op_long_toggle->setEnabled(false); + custom_stock_long_planner->setEnabled(true); } else { // no long for now experimental_mode_toggle->setEnabled(false); @@ -214,14 +230,19 @@ void TogglesPanel::updateToggles() { experimental_mode_toggle->setDescription("" + long_desc + "

" + e2e_description); custom_stock_long_toggle->setEnabled(CP.getCustomStockLongAvailable()); + custom_stock_long_planner->setEnabled(false); + op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release); + op_long_toggle->setEnabled(CP.getExperimentalLongitudinalAvailable() && !is_release); } experimental_mode_toggle->refresh(); custom_stock_long_toggle->refresh(); + op_long_toggle->refresh(); } else { experimental_mode_toggle->setDescription(e2e_description); op_long_toggle->setVisible(false); custom_stock_long_toggle->setVisible(false); + custom_stock_long_planner->setVisible(false); } } diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index d9973c663a..9a0e5e4eab 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -365,7 +365,8 @@ ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(fals void ExperimentalButton::changeMode() { const auto cp = (*uiState()->sm)["carParams"].getCarParams(); - bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed"); + bool can_change = (hasLongitudinalControl(cp) || (cp.getCustomStockLongAvailable() && params.getBool("CustomStockLongPlanner"))) + && params.getBool("ExperimentalModeConfirmed"); if (can_change) { params.putBool("ExperimentalMode", !experimental_mode); } From f5910661cc25add7c9b84b1095d45cacbe21f091 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 3 Oct 2023 20:54:29 -0400 Subject: [PATCH 018/575] convert back --- selfdrive/car/hyundai/carcontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index e6397066c0..f2bbda76d8 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -430,7 +430,7 @@ class CarController: else: target_speed_kph = min(v_cruise_kph_prev, set_speed_kph) if self.custom_planner_speed: - target_speed_kph = self.curve_speed_hysteresis(self.v_target_plan) + target_speed_kph = self.curve_speed_hysteresis(self.v_target_plan * CV.MS_TO_KPH) if self.v_tsc_state != 0 or self.m_tsc_state > 1: self.final_speed_kph = self.get_curve_speed(target_speed_kph, v_cruise_kph_prev) else: From b1c05548c451753c5b7db9641602df097b15f547 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 3 Oct 2023 20:55:04 -0400 Subject: [PATCH 019/575] allow experimental mode --- selfdrive/controls/controlsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index d2f25b1f0f..59e62e409e 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -913,7 +913,7 @@ class Controls: self.prof.checkpoint("Ratekeeper", ignore=True) self.is_metric = self.params.get_bool("IsMetric") - self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl + self.experimental_mode = self.params.get_bool("ExperimentalMode") and (self.CP.openpilotLongitudinalControl or not self.CP.pcmCruiseSpeed) self.lane_change_set_timer = int(self.params.get("AutoLaneChangeTimer", encoding="utf8")) self.reverse_acc_change = self.params.get_bool("ReverseAccChange") From 023ee34771fc623f9777892ef2a9b3f6ce365111 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 4 Oct 2023 17:51:57 -0400 Subject: [PATCH 020/575] allow dec change --- selfdrive/ui/qt/onroad_settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/onroad_settings.cc b/selfdrive/ui/qt/onroad_settings.cc index 4edbcc75a7..d05de0849c 100644 --- a/selfdrive/ui/qt/onroad_settings.cc +++ b/selfdrive/ui/qt/onroad_settings.cc @@ -134,7 +134,7 @@ void OnroadSettings::changeGapAdjustCruise() { void OnroadSettings::changeDynamicExperimentalControl() { UIScene &scene = uiState()->scene; const auto cp = (*uiState()->sm)["carParams"].getCarParams(); - bool can_change = hasLongitudinalControl(cp) && scene.dynamic_experimental_control_toggle; + bool can_change = (hasLongitudinalControl(cp) || (cp.getCustomStockLongAvailable() && params.getBool("CustomStockLongPlanner"))) && scene.dynamic_experimental_control_toggle; if (can_change) { scene.dynamic_experimental_control = !scene.dynamic_experimental_control; params.putBool("DynamicExperimentalControl", scene.dynamic_experimental_control); From 95d111bb20a05d8cbc6bbf0b52432130af10af2d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 31 Oct 2023 19:28:55 -0400 Subject: [PATCH 021/575] var changes --- selfdrive/car/hyundai/carcontroller.py | 6 +++--- selfdrive/controls/controlsd.py | 5 ++++- selfdrive/ui/qt/onroad.cc | 2 +- selfdrive/ui/qt/onroad_settings.cc | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 144bb7ef14..7a8775e68e 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -101,7 +101,7 @@ class CarController: self.speeds = 0 self.v_target_plan = 0 self.hkg_can_smooth_stop = self.param_s.get_bool("HkgSmoothStop") - self.custom_planner_speed = self.param_s.get_bool("CustomStockLongPlanner") + self.custom_stock_planner_speed = self.param_s.get_bool("CustomStockLongPlanner") self.lead_distance = 0 def calculate_lead_distance(self, hud_control: car.CarControl.HUDControl) -> float: @@ -136,7 +136,7 @@ class CarController: if self.frame % 200 == 0: self.speed_limit_control_enabled = self.param_s.get_bool("SpeedLimitControl") self.is_metric = self.param_s.get_bool("IsMetric") - self.custom_planner_speed = self.param_s.get_bool("CustomStockLongPlanner") + self.custom_stock_planner_speed = self.param_s.get_bool("CustomStockLongPlanner") self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap") self.v_cruise_min = HYUNDAI_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1) self.v_target_plan = min(CC.vCruise * CV.KPH_TO_MS, self.speeds) @@ -453,7 +453,7 @@ class CarController: target_speed_kph = set_speed_kph else: target_speed_kph = min(v_cruise_kph_prev, set_speed_kph) - if self.custom_planner_speed: + if self.custom_stock_planner_speed: target_speed_kph = self.curve_speed_hysteresis(self.v_target_plan * CV.MS_TO_KPH) if self.v_tsc_state != 0 or self.m_tsc_state > 1: self.final_speed_kph = self.get_curve_speed(target_speed_kph, v_cruise_kph_prev) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a833243fac..83cd42e1cd 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -202,6 +202,7 @@ class Controls: self.live_torque = self.params.get_bool("LiveTorque") self.torqued_override = self.params.get_bool("TorquedOverride") + self.custom_stock_planner_speed = self.params.get_bool("CustomStockLongPlanner") self.process_not_running = False @@ -908,11 +909,13 @@ class Controls: self.prof.checkpoint("Ratekeeper", ignore=True) self.is_metric = self.params.get_bool("IsMetric") - self.experimental_mode = self.params.get_bool("ExperimentalMode") and (self.CP.openpilotLongitudinalControl or not self.CP.pcmCruiseSpeed) + self.experimental_mode = self.params.get_bool("ExperimentalMode") and (self.CP.openpilotLongitudinalControl or + (not self.CP.pcmCruiseSpeed and self.custom_stock_planner_speed)) self.lane_change_set_timer = int(self.params.get("AutoLaneChangeTimer", encoding="utf8")) self.reverse_acc_change = self.params.get_bool("ReverseAccChange") self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl") + self.custom_stock_planner_speed = self.params.get_bool("CustomStockLongPlanner") if self.sm.frame % int(2.5 / DT_CTRL) == 0: self.live_torque = self.params.get_bool("LiveTorque") diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 8326bfe511..77a0a4e242 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -357,7 +357,7 @@ ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(fals void ExperimentalButton::changeMode() { const auto cp = (*uiState()->sm)["carParams"].getCarParams(); - bool can_change = (hasLongitudinalControl(cp) || (cp.getCustomStockLongAvailable() && params.getBool("CustomStockLongPlanner"))) + bool can_change = (hasLongitudinalControl(cp) || (!cp.getPcmCruiseSpeed() && params.getBool("CustomStockLongPlanner"))) && params.getBool("ExperimentalModeConfirmed"); if (can_change) { params.putBool("ExperimentalMode", !experimental_mode); diff --git a/selfdrive/ui/qt/onroad_settings.cc b/selfdrive/ui/qt/onroad_settings.cc index ef4304d94e..67efd15e7c 100644 --- a/selfdrive/ui/qt/onroad_settings.cc +++ b/selfdrive/ui/qt/onroad_settings.cc @@ -134,7 +134,7 @@ void OnroadSettings::changeGapAdjustCruise() { void OnroadSettings::changeDynamicExperimentalControl() { UIScene &scene = uiState()->scene; const auto cp = (*uiState()->sm)["carParams"].getCarParams(); - bool can_change = (hasLongitudinalControl(cp) || (cp.getCustomStockLongAvailable() && params.getBool("CustomStockLongPlanner"))); + bool can_change = hasLongitudinalControl(cp) || (!cp.getPcmCruiseSpeed() && params.getBool("CustomStockLongPlanner")); if (can_change) { scene.dynamic_experimental_control = !scene.dynamic_experimental_control; params.putBool("DynamicExperimentalControl", scene.dynamic_experimental_control); From e4b9e7d07acc0f2247a40b6993cfa9a2e2824e5f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 31 Oct 2023 20:55:16 -0400 Subject: [PATCH 022/575] don't check at 100 hz --- selfdrive/controls/controlsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 83cd42e1cd..df280e173b 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -915,10 +915,10 @@ class Controls: self.lane_change_set_timer = int(self.params.get("AutoLaneChangeTimer", encoding="utf8")) self.reverse_acc_change = self.params.get_bool("ReverseAccChange") self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl") - self.custom_stock_planner_speed = self.params.get_bool("CustomStockLongPlanner") if self.sm.frame % int(2.5 / DT_CTRL) == 0: self.live_torque = self.params.get_bool("LiveTorque") + self.custom_stock_planner_speed = self.params.get_bool("CustomStockLongPlanner") # Sample data from sockets and get a carState CS = self.data_sample() From 8f1de6b9e2665d0228813890f59db0878bbf99ad Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 12 Nov 2023 00:32:26 -0500 Subject: [PATCH 023/575] Revert "lat planner retune" This reverts commit 1088d425f445aac4002b79d24942b876c6ba378d. --- selfdrive/controls/lib/lateral_planner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index f0c8c02b08..00d528716a 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -18,13 +18,13 @@ LaneChangeState = log.LateralPlan.LaneChangeState PATH_COST = 1.0 LATERAL_MOTION_COST = 0.11 -LATERAL_ACCEL_COST = 1.0 +LATERAL_ACCEL_COST = 0.0 LATERAL_JERK_COST = 0.04 # Extreme steering rate is unpleasant, even # when it does not cause bad jerk. # TODO this cost should be lowered when low # speed lateral control is stable on all cars -STEERING_RATE_COST = 8.0 +STEERING_RATE_COST = 700.0 class LateralPlanner: From 1add9975bd5606250415c26a57217304011da08d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Nov 2023 01:32:14 -0500 Subject: [PATCH 024/575] set to false if OP long --- selfdrive/ui/qt/offroad/settings.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index c992c8d3ec..ad9685a65e 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -229,6 +229,7 @@ void TogglesPanel::updateToggles() { long_personality_setting->setEnabled(true); op_long_toggle->setEnabled(true); custom_stock_long_toggle->setEnabled(false); + custom_stock_long_planner->setEnabled(false); params.remove("CustomStockLong"); dec_toggle->setEnabled(true); } else if (custom_stock_long_toggle->isToggled()) { From 3711d5e91a440bbad99139df0075f0d27d2e3d64 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Nov 2023 13:36:13 -0500 Subject: [PATCH 025/575] Hyundai CAN-FD Longitudinal: Parse lead info for camera-based SCC platforms --- selfdrive/car/hyundai/interface.py | 4 ++++ selfdrive/car/hyundai/radar_interface.py | 26 ++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index deb8685ae8..6c65c8ab10 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -272,6 +272,10 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kiV = [0.0] ret.experimentalLongitudinalAvailable = (candidate in (HYBRID_CAR | EV_CAR) and candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)) + if (candidate in (HYBRID_CAR | EV_CAR) and candidate not in + (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)): # TODO: Gate radar disable cars until we can verify the signals match + ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value + ret.radarUnavailable = False else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 5d7782c524..dca8b197ce 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -3,7 +3,7 @@ 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.values import DBC, HyundaiFlagsSP, CANFD_CAR RADAR_START_ADDR = 0x500 RADAR_MSG_COUNT = 32 @@ -12,12 +12,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_ENHANCED_SCC: + lead_src, bus = "SCC_CONTROL", 2 + 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 +34,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 @@ -66,9 +74,11 @@ 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['ObjValid'] for ii in range(1): if valid: if ii not in self.pts: From 6c70ed0aabd7a02f54a480d1a25814e758351866 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Nov 2023 13:42:54 -0500 Subject: [PATCH 026/575] Hyundai CAN-FD: Lead Relative Speed for camara-based SCC platforms --- opendbc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc b/opendbc index ab68094723..eee75f146e 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit ab68094723d9a615ca940eece04db2f2f755c4ff +Subproject commit eee75f146eba42d385b96b84d35eb87e31797538 From f97df7ed872d6456ba520c3b6f27c8786b4619f5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 22 Nov 2023 15:00:33 -0500 Subject: [PATCH 027/575] only tucson hev 4th gen --- selfdrive/car/hyundai/interface.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 6c65c8ab10..7984cfd204 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -151,6 +151,9 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.756 ret.steerRatio = 16. ret.tireStiffnessFactor = 0.385 + if candidate == CAR.TUCSON_HYBRID_4TH_GEN: + ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value + ret.radarUnavailable = False elif candidate == CAR.SANTA_CRUZ_1ST_GEN: ret.mass = 1870. # weight from Limited trim - the only supported trim ret.wheelbase = 3.000 @@ -272,10 +275,6 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kiV = [0.0] ret.experimentalLongitudinalAvailable = (candidate in (HYBRID_CAR | EV_CAR) and candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)) - if (candidate in (HYBRID_CAR | EV_CAR) and candidate not in - (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)): # TODO: Gate radar disable cars until we can verify the signals match - ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value - ret.radarUnavailable = False else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] From 1759c5e88423733f7456962c7c9b5d5174d9e9c6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 22 Nov 2023 15:10:04 -0500 Subject: [PATCH 028/575] use ACC_ObjDist to detect lead --- selfdrive/car/hyundai/radar_interface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index dca8b197ce..2daa709c26 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -78,7 +78,8 @@ class RadarInterface(RadarInterfaceBase): "SCC_CONTROL" if self.CP.carFingerprint in CANFD_CAR else \ "SCC11" msg = self.rcp.vl[msg_src] - valid = msg['ObjValid'] + 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: From e6366ff5a2904891d7d7123e09782284d1f41e63 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 22 Nov 2023 20:20:04 -0500 Subject: [PATCH 029/575] maybe? --- selfdrive/car/hyundai/radar_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 2daa709c26..f209aca877 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -78,7 +78,7 @@ class RadarInterface(RadarInterfaceBase): "SCC_CONTROL" if self.CP.carFingerprint in CANFD_CAR else \ "SCC11" msg = self.rcp.vl[msg_src] - valid = msg["ACC_ObjDist"] < 204.6 if self.CP.carFingerprint in CANFD_CAR else \ + valid = msg['ACC_ObjDist'] < 204.6 if self.CP.carFingerprint in CANFD_CAR else \ msg['ACC_ObjStatus'] for ii in range(1): if valid: From f017780506dfc2c2871d78cccd99b7b57bf91a4b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Nov 2023 12:53:09 -0500 Subject: [PATCH 030/575] bruh wrong flag! --- selfdrive/car/hyundai/radar_interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index f209aca877..e470011737 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -3,6 +3,7 @@ import math from cereal import car from opendbc.can.parser import CANParser from openpilot.selfdrive.car.interfaces import RadarInterfaceBase +from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus from openpilot.selfdrive.car.hyundai.values import DBC, HyundaiFlagsSP, CANFD_CAR RADAR_START_ADDR = 0x500 @@ -13,8 +14,8 @@ RADAR_MSG_COUNT = 32 def get_radar_can_parser(CP): if DBC[CP.carFingerprint]['radar'] is None: if CP.carFingerprint in CANFD_CAR: - if CP.spFlags & HyundaiFlagsSP.SP_ENHANCED_SCC: - lead_src, bus = "SCC_CONTROL", 2 + if CP.spFlags & HyundaiFlagsSP.SP_CAMERA_SCC_LEAD: + lead_src, bus = "SCC_CONTROL", CanBus(CP).CAM else: return None else: From 489661f182dfdf16f25f5749ecbb955d1d12cdbd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Nov 2023 15:56:05 -0500 Subject: [PATCH 031/575] allow all camera SCC platforms --- selfdrive/car/hyundai/interface.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 7984cfd204..28e1f7b090 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -151,9 +151,6 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.756 ret.steerRatio = 16. ret.tireStiffnessFactor = 0.385 - if candidate == CAR.TUCSON_HYBRID_4TH_GEN: - ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value - ret.radarUnavailable = False elif candidate == CAR.SANTA_CRUZ_1ST_GEN: ret.mass = 1870. # weight from Limited trim - the only supported trim ret.wheelbase = 3.000 @@ -275,6 +272,10 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kiV = [0.0] ret.experimentalLongitudinalAvailable = (candidate in (HYBRID_CAR | EV_CAR) and candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)) + if (candidate in (HYBRID_CAR | EV_CAR) and candidate not in + (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)) and not ret.flags & HyundaiFlags.CANFD_HDA2: + ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value + ret.radarUnavailable = False else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] From e76b2d449f7da231d230a54e4a5dd6d2ce98506a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Nov 2023 15:57:15 -0500 Subject: [PATCH 032/575] Update CHANGELOGS.md --- CHANGELOGS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 89e38e6b88..e62d429586 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,5 +1,8 @@ sunnypilot - 0.9.5.2 (2023-xx-xx) ======================== +* NEW❗: Hyundai CAN-FD longitudinal: + * NEW❗: Parse lead info for camera-based SCC platforms + * Improve lead tracking when using openpilot longitudinal sunnypilot - 0.9.5.1 (2023-11-17) ======================== From bfb02ca7f91e0374c5374b181ddb2657d20daf63 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 2 Dec 2023 13:13:40 +0100 Subject: [PATCH 033/575] Add connection to ExperimentalMode toggle This change introduces a connection to the "ExperimentalMode" toggle in the settings.cc file. Whenever the toggle state is flipped, the `updateToggles()` function will be called, maintaining the consistency of toggle update behavior across different settings. Update dec_toggle enablement criteria The changes in the code modified the condition for enabling the dec_toggle. Instead of relying on the status of custom_stock_long_planner, dec_toggle will now depend on the toggle status of experimental_mode_toggle. This will ensure the dec_toggle is only enabled when the experimental mode is active. Enable dec_toggle based on custom_stock_long_planner status The dec_toggle in the selfdrive user interface now mirrors the enabled status of the custom_stock_long_planner. Whenever the stock long planner toggle is switched, it will automatically enable or disable the dec_toggle. update also experiemental to disable if no custom stock with planner speed Update CustomStockLongPlanner settings interface The interface for the CustomStockLongPlanner function has been updated to improve usability. A new connection to "toggleFlipped" signal was added, allowing real-time settings update. The planner is also now enabled when the long_personality_setting is toggled on. Additionally, the refresh function call for the planner has been added to ensure correct display of current settings. However, the removal of "ExperimentalMode" is now conditional, taking place only if the planner is not toggled on. --- selfdrive/ui/qt/offroad/settings.cc | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index ad9685a65e..7c821c8d28 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -157,7 +157,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { connect(toggles["CustomStockLong"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); }); - + connect(toggles["CustomStockLongPlanner"], &ToggleControl::toggleFlipped, [=]() { + updateToggles(); + }); + connect(toggles["ExperimentalMode"], &ToggleControl::toggleFlipped, [=]() { + updateToggles(); + }); + param_watcher = new ParamWatcher(this); QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { @@ -234,12 +240,15 @@ void TogglesPanel::updateToggles() { dec_toggle->setEnabled(true); } else if (custom_stock_long_toggle->isToggled()) { op_long_toggle->setEnabled(false); - experimental_mode_toggle->setEnabled(true); + experimental_mode_toggle->setEnabled(custom_stock_long_planner->isToggled()); experimental_mode_toggle->setDescription(e2e_description); - long_personality_setting->setEnabled(false); custom_stock_long_planner->setEnabled(true); + long_personality_setting->setEnabled(custom_stock_long_planner->isToggled()); + dec_toggle->setEnabled(experimental_mode_toggle->isToggled()); + if(!custom_stock_long_planner->isToggled()) { + params.remove("ExperimentalMode"); + } params.remove("ExperimentalLongitudinalEnabled"); - params.remove("ExperimentalMode"); } else { // no long for now experimental_mode_toggle->setEnabled(false); @@ -269,6 +278,7 @@ void TogglesPanel::updateToggles() { experimental_mode_toggle->refresh(); op_long_toggle->refresh(); custom_stock_long_toggle->refresh(); + custom_stock_long_planner->refresh(); dec_toggle->refresh(); } else { experimental_mode_toggle->setDescription(e2e_description); From 56f69e8435e26a7381433fcd76f23aa963610adc Mon Sep 17 00:00:00 2001 From: Isaac <48419673+coffee-cake-isaac@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:02:44 -0500 Subject: [PATCH 034/575] Adding changes back without spacing (cherry picked from commit 8112017f9290bf02d936dd3869579a662a1fcc27) --- selfdrive/car/ford/interface.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index f5c67dc378..2189301db4 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -20,7 +20,6 @@ class CarInterface(CarInterfaceBase): @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "ford" - ret.dashcamOnly = candidate in {CAR.F_150_MK14} ret.radarUnavailable = True ret.steerControlType = car.CarParams.SteerControlType.angle @@ -62,6 +61,17 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 17.0 ret.mass = 2000 + elif candidate == CAR.F_150_LIGHTNING_MK1: + # required trim only on SuperCrew + ret.wheelbase = 3.70 + ret.steerRatio = 16.9 + ret.mass = 2948 + + elif candidate == CAR.MUSTANG_MACH_E_MK1: + ret.wheelbase = 2.984 + ret.steerRatio = 17.0 # guess + ret.mass = 2200 + elif candidate == CAR.FOCUS_MK4: ret.wheelbase = 2.7 ret.steerRatio = 15.0 @@ -154,7 +164,7 @@ class CarInterface(CarInterfaceBase): if not self.CS.vehicle_sensors_valid: events.add(car.CarEvent.EventName.vehicleSensorsInvalid) - if self.CS.hybrid_platform: + if self.CS.hybrid_platform and self.CP.carFingerprint not in CANFD_CAR: events.add(car.CarEvent.EventName.startupNoControl) ret.events = events.to_msg() From 985778c3e3c51a5944c506917a2a72477918fefd Mon Sep 17 00:00:00 2001 From: Isaac <48419673+coffee-cake-isaac@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:29:37 -0500 Subject: [PATCH 035/575] Added changes to add CAN-FD support for Ford --- selfdrive/car/ford/values.py | 61 +++++++++++++++++++++++-- selfdrive/car/tests/routes.py | 4 +- selfdrive/car/torque_data/override.yaml | 2 + 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index cb16d7111f..8b76b4d7d2 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -57,9 +57,11 @@ class CAR(StrEnum): F_150_MK14 = "FORD F-150 14TH GEN" FOCUS_MK4 = "FORD FOCUS 4TH GEN" MAVERICK_MK1 = "FORD MAVERICK 1ST GEN" + F_150_LIGHTNING_MK1 = "FORD F-150 LIGHTNING 1ST GEN" + MUSTANG_MACH_E_MK1 = "FORD MUSTANG MACH-E 1ST GEN" -CANFD_CAR = {CAR.F_150_MK14} +CANFD_CAR = {CAR.F_150_MK14, CAR.F_150_LIGHTNING_MK1, CAR.MUSTANG_MACH_E_MK1} class RADAR: @@ -69,8 +71,10 @@ class RADAR: DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR)) -# F-150 radar is not yet supported +# CAN-FD radar is not yet supported DBC[CAR.F_150_MK14] = dbc_dict("ford_lincoln_base_pt", None) +DBC[CAR.F_150_LIGHTNING_MK1] = dbc_dict("ford_lincoln_base_pt", None) +DBC[CAR.MUSTANG_MACH_E_MK1] = dbc_dict("ford_lincoln_base_pt", None) class Footnote(Enum): @@ -89,6 +93,10 @@ class FordCarInfo(CarInfo): def init_make(self, CP: car.CarParams): if CP.carFingerprint in (CAR.BRONCO_SPORT_MK1, CAR.MAVERICK_MK1): self.car_parts = CarParts([Device.threex_angled_mount, CarHarness.ford_q3]) + if CP.carFingerprint in (CAR.F_150_LIGHTNING_MK1, CAR.F_150_MK14, CAR.MUSTANG_MACH_E_MK1): + self.car_parts = CarParts([CarHarness.ford_q4]) + if CP.carFingerprint in (CAR.F_150_LIGHTNING_MK1, CAR.F_150_MK14): + self.car_parts = CarParts([Device.threex_angled_mount]) CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { @@ -101,7 +109,9 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { FordCarInfo("Ford Explorer 2020-22"), FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"), ], - CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"), + CAR.F_150_MK14: FordCarInfo("Ford F-150 2022-23", "Co-Pilot360 Active 2.0"), + CAR.F_150_LIGHTNING_MK1: FordCarInfo("Ford F-150 Lightning 2021-23", "Co-Pilot360 Active 2.0"), + CAR.MUSTANG_MACH_E_MK1: FordCarInfo("Ford Mustang Mach-E 2021-23", "Co-Pilot360 Active 2.0"), CAR.FOCUS_MK4: FordCarInfo("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS]), CAR.MAVERICK_MK1: [ FordCarInfo("Ford Maverick 2022", "LARIAT Luxury"), @@ -229,6 +239,7 @@ FW_VERSIONS = { ], (Ecu.abs, 0x760, None): [ b'PL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -238,6 +249,50 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7E0, None): [ b'PL3A-14C204-BRB\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PL3A-14C204-BTB\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'ML3A-14C204-VHD\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.F_150_LIGHTNING_MK1: { + (Ecu.abs, 0x760, None): [ + b'PL38-2D053-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'ML3T-14H102-ABT\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'NL3A-14C204-BAR\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.MUSTANG_MACH_E_MK1: { + (Ecu.eps, 0x730, None): [ + b'LJ9C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LJ9C-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LJ9C-14D003-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LJ9C-14D003-EA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'LK9C-2D053-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LK9C-2D053-CD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LK9C-2D053-CK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'ML3T-14D049-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'ML3T-14H102-ABP\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PJ6T-14H102-ABK\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'MJ98-14C204-BBP\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MJ98-14C204-AXD\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NJ98-14C204-VE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PJ98-14C204-BE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.FOCUS_MK4: { diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 8cf5f3debd..2b30f03aa4 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -16,7 +16,6 @@ from openpilot.selfdrive.car.body.values import CAR as COMMA # TODO: add routes for these cars non_tested_cars = [ - FORD.F_150_MK14, GM.CADILLAC_ATS, GM.HOLDEN_ASTRA, GM.MALIBU, @@ -53,6 +52,9 @@ routes = [ CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.EXPLORER_MK6), CarTestRoute("e886087f430e7fe7|2023-06-16--23-06-36", FORD.FOCUS_MK4), CarTestRoute("bd37e43731e5964b|2023-04-30--10-42-26", FORD.MAVERICK_MK1), + CarTestRoute("112e4d6e0cad05e1|2023-11-14--08-21-43", FORD.F_150_LIGHTNING_MK1), + CarTestRoute("24574459dd7fb3e0|2023-11-06--06-23-44", FORD.F_150_MK14), + CarTestRoute("83a4e056c7072678|2023-11-13--16-51-33", FORD.MUSTANG_MACH_E_MK1), #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION), CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA), diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 61f8dc2b3c..e6f637e794 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -26,6 +26,8 @@ FORD EXPLORER 6TH GEN: [.nan, 1.5, .nan] FORD F-150 14TH GEN: [.nan, 1.5, .nan] FORD FOCUS 4TH GEN: [.nan, 1.5, .nan] FORD MAVERICK 1ST GEN: [.nan, 1.5, .nan] +FORD F-150 LIGHTNING 1ST GEN: [.nan, 1.5, .nan] +FORD MUSTANG MACH-E 1ST GEN: [.nan, 1.5, .nan] ### # No steering wheel From 2b0bec9e651c2229e35d80a760dec6e871077d30 Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Tue, 5 Dec 2023 20:10:18 +0000 Subject: [PATCH 036/575] Updated to include deadzone for stopping --- selfdrive/car/ford/interface.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 2189301db4..60c9a3a0b6 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -84,6 +84,12 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"Unsupported car: {candidate}") + + ret.longitudinalTuning.kpBP = [0.] + ret.longitudinalTuning.kpV = [0.5] + ret.longitudinalTuning.kiV = [0.] + ret.longitudinalTuning.deadzoneBP = [0., 9.] + ret.longitudinalTuning.deadzoneV = [.0, .20] # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1 found_ecus = [fw.ecu for fw in car_fw] From bb940d1a78d3219562607e5c67c99ee24cf792df Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 7 Dec 2023 11:43:19 -0500 Subject: [PATCH 037/575] Update FCR and CHANGELOGS.md --- CHANGELOGS.md | 3 +++ selfdrive/car/sunnypilot_carname.json | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 483cf496ee..51a9786f96 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -17,6 +17,9 @@ sunnypilot - 0.9.5.2 (2023-12-07) * Hybrid 2022-23 * Plug-in Hybrid 2022-23 * NEW❗: Lane Change: When manually braking with steering engaged, turning on the turn signal will default to Nudge mode +* Ford F-150 2022-23 support +* Ford F-150 Lightning 2021-23 support +* Ford Mustang Mach-E 2021-23 support * Volkswagen MQB CC only platforms (radar or no radar) support thanks to jyoung8607! sunnypilot - 0.9.5.1 (2023-11-17) diff --git a/selfdrive/car/sunnypilot_carname.json b/selfdrive/car/sunnypilot_carname.json index 444e40515a..85bb432c0b 100644 --- a/selfdrive/car/sunnypilot_carname.json +++ b/selfdrive/car/sunnypilot_carname.json @@ -26,10 +26,13 @@ "Ford Bronco Sport 2021-22": "FORD BRONCO SPORT 1ST GEN", "Ford Escape 2020-22": "FORD ESCAPE 4TH GEN", "Ford Explorer 2020-22": "FORD EXPLORER 6TH GEN", + "Ford F-150 2022-23": "FORD F-150 14TH GEN", + "Ford F-150 Lightning 2021-23": "FORD F-150 LIGHTNING 1ST GEN", "Ford Focus 2018": "FORD FOCUS 4TH GEN", "Ford Kuga 2020-22": "FORD ESCAPE 4TH GEN", "Ford Maverick 2022": "FORD MAVERICK 1ST GEN", "Ford Maverick 2023": "FORD MAVERICK 1ST GEN", + "Ford Mustang Mach-E 2021-23": "FORD MUSTANG MACH-E 1ST GEN", "Genesis G70 2018-19": "GENESIS G70 2018", "Genesis G70 2020": "GENESIS G70 2020", "Genesis G70 Non-SCC 2021": "GENESIS G70 2021 NON-SCC", @@ -202,7 +205,6 @@ "Škoda Octavia RS 2016": "SKODA OCTAVIA 3RD GEN", "Škoda Scala 2020-23": "SKODA SCALA 1ST GEN", "Škoda Superb 2015-22": "SKODA SUPERB 3RD GEN", - "Škoda Superb 2015-22 CC Only": "SKODA SUPERB 3RD GEN CC ONLY", "Toyota Alphard 2019-20": "TOYOTA ALPHARD 2020", "Toyota Alphard Hybrid 2021": "TOYOTA ALPHARD 2020", "Toyota Avalon 2016": "TOYOTA AVALON 2016", From 71de09824c27022c83db14aba6ab0f92f0849c72 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 7 Dec 2023 11:44:08 -0500 Subject: [PATCH 038/575] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 467e0e2ca0..46141c8bed 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 467e0e2ca06ee746103e6de6f26ab18b01f928e3 +Subproject commit 46141c8bed55f8dd9b604980b299bf8e30c5a1a6 From 3196ade663a08d7c8223c1207f3769f352943cb2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 7 Dec 2023 17:33:11 -0500 Subject: [PATCH 039/575] oops by issac ;) --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 46141c8bed..30c47d0066 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 46141c8bed55f8dd9b604980b299bf8e30c5a1a6 +Subproject commit 30c47d00669cf5c0d02b2aa9edcb844208ec80d1 From 39b2dcdc0f460bbff1c7486189dd4116af19acb3 Mon Sep 17 00:00:00 2001 From: Isaac <48419673+coffee-cake-isaac@users.noreply.github.com> Date: Sun, 17 Dec 2023 12:25:58 -0500 Subject: [PATCH 040/575] Added speed sign recognition on CAN-FD Ford --- selfdrive/car/ford/carstate.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index d6265fd657..5c06da9680 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -24,6 +24,7 @@ class CarState(CarStateBase): self.prev_lkas_enabled = None self.buttonStates = BUTTON_STATES.copy() self.buttonStatesPrev = BUTTON_STATES.copy() + self._vLimit = 0 def update(self, cp, cp_cam): ret = car.CarState.new_message() @@ -75,6 +76,11 @@ class CarState(CarStateBase): ret.cruiseState.nonAdaptive = cp.vl["Cluster_Info1_FD1"]["AccEnbl_B_RqDrv"] == 0 ret.cruiseState.standstill = cp.vl["EngBrakeData"]["AccStopMde_D_Rq"] == 3 ret.accFaulted = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (1, 2) + + # SpeedLimit + self.update_traffic_signals(self.CP, cp, cp_cam) + ret.cruiseState.speedLimit = self._vLimit + if not self.CP.openpilotLongitudinalControl: ret.accFaulted = ret.accFaulted or cp_cam.vl["ACCDATA"]["CmbbDeny_B_Actl"] == 1 @@ -127,6 +133,21 @@ class CarState(CarStateBase): return ret + + def update_traffic_signals(self, CP, cp, cp_cam): + if CP.carFingerprint in CANFD_CAR: + # we'll start with CANFD - not sure if and how to check if this message is in non CANFD + origVLimit = cp_cam.vl["Traffic_RecognitnData"]["TsrVLim1MsgTxt_D_Rq"] + origVLimitUnit = cp_cam.vl["Traffic_RecognitnData"]["TsrVlUnitMsgTxt_D_Rq"] + + # convert to m/s from whatever came in + speed_factor = CV.MPH_TO_MS if origVLimitUnit == 2 else CV.KPH_TO_MS if origVLimitUnit == 1 else 0 + + vLimit = origVLimit if origVLimit not in (0, 255) else 0 + + # speed in m/s + self._vLimit = vLimit * speed_factor + @staticmethod def get_can_parser(CP): messages = [ @@ -161,6 +182,11 @@ class CarState(CarStateBase): ("BCM_Lamp_Stat_FD1", 1), ] + if CP.carFingerprint in CANFD_CAR: + messages += [ + ("Traffic_RecognitnData", 1), + ] + if CP.enableBsm and CP.carFingerprint not in CANFD_CAR: messages += [ ("Side_Detect_L_Stat", 5), From b6f1a5c42995c2187ff00b6cee0583aaa96eb729 Mon Sep 17 00:00:00 2001 From: Isaac <48419673+coffee-cake-isaac@users.noreply.github.com> Date: Sun, 17 Dec 2023 12:43:57 -0500 Subject: [PATCH 041/575] Fixed naming --- selfdrive/car/ford/carstate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 5c06da9680..cbf0270f94 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -24,7 +24,7 @@ class CarState(CarStateBase): self.prev_lkas_enabled = None self.buttonStates = BUTTON_STATES.copy() self.buttonStatesPrev = BUTTON_STATES.copy() - self._vLimit = 0 + self.prev_vLimit = 0 def update(self, cp, cp_cam): ret = car.CarState.new_message() @@ -79,7 +79,7 @@ class CarState(CarStateBase): # SpeedLimit self.update_traffic_signals(self.CP, cp, cp_cam) - ret.cruiseState.speedLimit = self._vLimit + ret.cruiseState.speedLimit = self.prev_vLimit if not self.CP.openpilotLongitudinalControl: ret.accFaulted = ret.accFaulted or cp_cam.vl["ACCDATA"]["CmbbDeny_B_Actl"] == 1 @@ -146,7 +146,7 @@ class CarState(CarStateBase): vLimit = origVLimit if origVLimit not in (0, 255) else 0 # speed in m/s - self._vLimit = vLimit * speed_factor + self.prev_vLimit = vLimit * speed_factor @staticmethod def get_can_parser(CP): From c66f3492c5835eac733a964949542e0d5b180cec Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 19 Dec 2023 17:35:39 -0500 Subject: [PATCH 042/575] cleanup --- selfdrive/car/ford/carstate.py | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index cbf0270f94..9fd2a6992e 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -77,9 +77,8 @@ class CarState(CarStateBase): ret.cruiseState.standstill = cp.vl["EngBrakeData"]["AccStopMde_D_Rq"] == 3 ret.accFaulted = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (1, 2) - # SpeedLimit - self.update_traffic_signals(self.CP, cp, cp_cam) - ret.cruiseState.speedLimit = self.prev_vLimit + if self.CP.carFingerprint in CANFD_CAR: + ret.cruiseState.speedLimit = self.update_traffic_signals(cp_cam) if not self.CP.openpilotLongitudinalControl: ret.accFaulted = ret.accFaulted or cp_cam.vl["ACCDATA"]["CmbbDeny_B_Actl"] == 1 @@ -133,20 +132,15 @@ class CarState(CarStateBase): return ret + def update_traffic_signals(self, cp_cam): + # TODO: Check if CAN platforms have the same signals + if self.CP.carFingerprint in CANFD_CAR: + v_limit = cp_cam.vl["Traffic_RecognitnData"]["TsrVLim1MsgTxt_D_Rq"] + v_limit_unit = cp_cam.vl["Traffic_RecognitnData"]["TsrVlUnitMsgTxt_D_Rq"] - def update_traffic_signals(self, CP, cp, cp_cam): - if CP.carFingerprint in CANFD_CAR: - # we'll start with CANFD - not sure if and how to check if this message is in non CANFD - origVLimit = cp_cam.vl["Traffic_RecognitnData"]["TsrVLim1MsgTxt_D_Rq"] - origVLimitUnit = cp_cam.vl["Traffic_RecognitnData"]["TsrVlUnitMsgTxt_D_Rq"] + speed_factor = CV.MPH_TO_MS if v_limit_unit == 2 else CV.KPH_TO_MS if v_limit_unit == 1 else 0 - # convert to m/s from whatever came in - speed_factor = CV.MPH_TO_MS if origVLimitUnit == 2 else CV.KPH_TO_MS if origVLimitUnit == 1 else 0 - - vLimit = origVLimit if origVLimit not in (0, 255) else 0 - - # speed in m/s - self.prev_vLimit = vLimit * speed_factor + return v_limit * speed_factor if v_limit not in (0, 255) else 0 @staticmethod def get_can_parser(CP): @@ -175,6 +169,7 @@ class CarState(CarStateBase): if CP.transmissionType == TransmissionType.automatic: messages += [ ("Gear_Shift_by_Wire_FD1", 10), + ("Traffic_RecognitnData", 1), ] elif CP.transmissionType == TransmissionType.manual: messages += [ @@ -182,11 +177,6 @@ class CarState(CarStateBase): ("BCM_Lamp_Stat_FD1", 1), ] - if CP.carFingerprint in CANFD_CAR: - messages += [ - ("Traffic_RecognitnData", 1), - ] - if CP.enableBsm and CP.carFingerprint not in CANFD_CAR: messages += [ ("Side_Detect_L_Stat", 5), From 1cf45623f144e26255240b9c2c20cccb95f0b2b0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 19 Dec 2023 17:36:45 -0500 Subject: [PATCH 043/575] more cleanup --- selfdrive/car/ford/carstate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 9fd2a6992e..c85c92f6b4 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -24,7 +24,7 @@ class CarState(CarStateBase): self.prev_lkas_enabled = None self.buttonStates = BUTTON_STATES.copy() self.buttonStatesPrev = BUTTON_STATES.copy() - self.prev_vLimit = 0 + self.v_limit = 0 def update(self, cp, cp_cam): ret = car.CarState.new_message() @@ -135,12 +135,12 @@ class CarState(CarStateBase): def update_traffic_signals(self, cp_cam): # TODO: Check if CAN platforms have the same signals if self.CP.carFingerprint in CANFD_CAR: - v_limit = cp_cam.vl["Traffic_RecognitnData"]["TsrVLim1MsgTxt_D_Rq"] + self.v_limit = cp_cam.vl["Traffic_RecognitnData"]["TsrVLim1MsgTxt_D_Rq"] v_limit_unit = cp_cam.vl["Traffic_RecognitnData"]["TsrVlUnitMsgTxt_D_Rq"] speed_factor = CV.MPH_TO_MS if v_limit_unit == 2 else CV.KPH_TO_MS if v_limit_unit == 1 else 0 - return v_limit * speed_factor if v_limit not in (0, 255) else 0 + return self.v_limit * speed_factor if self.v_limit not in (0, 255) else 0 @staticmethod def get_can_parser(CP): From 3aa30a5c5993910f6b835c32c1de8e0d0c6fb8ca Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 19 Dec 2023 17:51:43 -0500 Subject: [PATCH 044/575] Update CHANGELOGS.md --- CHANGELOGS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 981a98717c..cfa9d96ccc 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,5 +1,7 @@ sunnypilot - 0.9.6.1 (2023-xx-xx) ======================== +* NEW❗: Ford CAN-FD longitudinal + * NEW❗: Parse speed limit sign recognition from camera for certain supported platforms * UPDATED: Dynamic Experimental Control (DEC) * Synced with dragonpilot-community/dragonpilot:beta3 commit dd4c663 * UPDATED: Vision-based Turn Speed Control (V-TSC) implementation From e34ec127d1846bd8652e7f8942607b9a519ba815 Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Wed, 20 Dec 2023 13:54:44 +0000 Subject: [PATCH 045/575] Update carstate.py --- selfdrive/car/ford/carstate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index c85c92f6b4..acc8639adc 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -169,7 +169,6 @@ class CarState(CarStateBase): if CP.transmissionType == TransmissionType.automatic: messages += [ ("Gear_Shift_by_Wire_FD1", 10), - ("Traffic_RecognitnData", 1), ] elif CP.transmissionType == TransmissionType.manual: messages += [ @@ -195,6 +194,11 @@ class CarState(CarStateBase): ("IPMA_Data", 1), ] + if CP.carFingerprint in CANFD_CAR: + messages += [ + ("Traffic_RecognitnData", 1), + ] + if CP.enableBsm and CP.carFingerprint in CANFD_CAR: messages += [ ("Side_Detect_L_Stat", 5), From cdc13293ae16157dd4d74004c646a2171c611f84 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 21 Dec 2023 00:26:26 -0500 Subject: [PATCH 046/575] hoomoose dongle --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index edcd23cf1b..84b39c791d 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -305,7 +305,7 @@ class CarInterface(CarInterfaceBase): if 0x1fa in fingerprint[CAN.ECAN]: ret.spFlags |= HyundaiFlagsSP.SP_NAV_MSG.value - if Params().get("DongleId", encoding='utf8') == "012c95f06918eca4": + if Params().get("DongleId", encoding='utf8') in ("012c95f06918eca4", "68d6a96e703c00c9"): ret.spFlags |= HyundaiFlagsSP.SP_UPSTREAM_TACO.value else: ret.enableBsm = 0x58b in fingerprint[0] From e1b98b5df3e6443feab0d35de4a550937ee368cf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 13 Feb 2024 14:17:17 -0500 Subject: [PATCH 047/575] Fix sync conflict --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index ba69a408be..360cce0785 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -302,7 +302,7 @@ 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 (candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)) and not ret.flags & HyundaiFlags.CANFD_HDA2: + if (candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)) and not hda2: ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value ret.radarUnavailable = False else: From 05d9e81a0c5d59405fb4afeec8a878a99f855681 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 16 Feb 2024 07:24:55 -0500 Subject: [PATCH 048/575] Update CHANGELOGS.md --- CHANGELOGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index f53493c3db..fb29848d8b 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -26,7 +26,7 @@ sunnypilot - 0.9.6.0 (2024-xx-xx) * UPDATED: Custom Offsets * Continued support for Legacy Driving Models (e.g., ND, BDv2, BDv1, FV, NS) * Deprecate support for newer Driving Models (e.g., CHv2, CH, LAv2, LAv1) -* NEW❗: Hyundai CAN-FD longitudinal: +* NEW❗: Hyundai CAN-FD longitudinal * NEW❗: Parse lead info for camera-based SCC platforms * Improve lead tracking when using openpilot longitudinal * FIXED: New comma 3X support From c0c461862c2af847c34bd9d300a753bfdb1a33e8 Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Tue, 20 Feb 2024 20:50:17 +0000 Subject: [PATCH 049/575] Update carcontroller.py --- cereal | 2 +- opendbc | 2 +- panda | 2 +- selfdrive/car/ford/carcontroller.py | 10 ++++++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cereal b/cereal index e86a48cf41..f87b5b8395 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit e86a48cf413c48e8d5a0d247f3b64fdcb800096f +Subproject commit f87b5b839582881c4c3aed07cc2c3c20bb03df8f diff --git a/opendbc b/opendbc index a49eea191e..c88be33a8d 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit a49eea191e4957fc149178c44ee418d3387194c3 +Subproject commit c88be33a8d2d81a7043e58025decce2da4e6bad0 diff --git a/panda b/panda index 53ad150cdf..24ccef2efe 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 53ad150cdf0d1f7a61f5ac6da7a5bd3b0d6fec3e +Subproject commit 24ccef2efe7feac3ddb81dd7e697c0614e6fd541 diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 261119c485..63ac9dc4e5 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -3,7 +3,7 @@ from openpilot.common.numpy_fast import clip from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_std_steer_angle_limits from openpilot.selfdrive.car.ford import fordcan -from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams +from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams, FordFlagsSP from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX LongCtrlState = car.CarControl.Actuators.LongControlState @@ -34,6 +34,9 @@ class CarController: self.main_on_last = False self.lkas_enabled_last = False self.steer_alert_last = False + self.path_angle = 0. + self.path_offset = 0. + self.curvature_rate = 0. def update(self, CC, CS, now_nanos): can_sends = [] @@ -73,7 +76,10 @@ class CarController: # TODO: extended mode mode = 1 if CC.latActive else 0 counter = (self.frame // CarControllerParams.STEER_STEP) % 0xF - can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) + if self.CP.spFlags & FordFlagsSP.SP_ENHANCED_LAT_CONTROL.value: + can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, self.path_offset, self.path_angle, -apply_curvature, self.curvature_rate, counter)) + else: + can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) else: can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -apply_curvature, 0.)) From 4653f530d28c0889a6137198a7c2e672501c569f Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 20 Feb 2024 22:30:23 -0500 Subject: [PATCH 050/575] Revert "Update carcontroller.py" This reverts commit c0c461862c2af847c34bd9d300a753bfdb1a33e8. --- cereal | 2 +- opendbc | 2 +- panda | 2 +- selfdrive/car/ford/carcontroller.py | 10 ++-------- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/cereal b/cereal index f87b5b8395..e86a48cf41 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit f87b5b839582881c4c3aed07cc2c3c20bb03df8f +Subproject commit e86a48cf413c48e8d5a0d247f3b64fdcb800096f diff --git a/opendbc b/opendbc index c88be33a8d..a49eea191e 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit c88be33a8d2d81a7043e58025decce2da4e6bad0 +Subproject commit a49eea191e4957fc149178c44ee418d3387194c3 diff --git a/panda b/panda index 24ccef2efe..53ad150cdf 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 24ccef2efe7feac3ddb81dd7e697c0614e6fd541 +Subproject commit 53ad150cdf0d1f7a61f5ac6da7a5bd3b0d6fec3e diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 63ac9dc4e5..261119c485 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -3,7 +3,7 @@ from openpilot.common.numpy_fast import clip from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_std_steer_angle_limits from openpilot.selfdrive.car.ford import fordcan -from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams, FordFlagsSP +from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX LongCtrlState = car.CarControl.Actuators.LongControlState @@ -34,9 +34,6 @@ class CarController: self.main_on_last = False self.lkas_enabled_last = False self.steer_alert_last = False - self.path_angle = 0. - self.path_offset = 0. - self.curvature_rate = 0. def update(self, CC, CS, now_nanos): can_sends = [] @@ -76,10 +73,7 @@ class CarController: # TODO: extended mode mode = 1 if CC.latActive else 0 counter = (self.frame // CarControllerParams.STEER_STEP) % 0xF - if self.CP.spFlags & FordFlagsSP.SP_ENHANCED_LAT_CONTROL.value: - can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, self.path_offset, self.path_angle, -apply_curvature, self.curvature_rate, counter)) - else: - can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) + can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) else: can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -apply_curvature, 0.)) From bda7a60f867e32594654777b4a291c5cc721056c Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Tue, 20 Feb 2024 20:50:17 +0000 Subject: [PATCH 051/575] Updating Car Controller --- selfdrive/car/ford/carcontroller.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 261119c485..63ac9dc4e5 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -3,7 +3,7 @@ from openpilot.common.numpy_fast import clip from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_std_steer_angle_limits from openpilot.selfdrive.car.ford import fordcan -from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams +from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams, FordFlagsSP from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX LongCtrlState = car.CarControl.Actuators.LongControlState @@ -34,6 +34,9 @@ class CarController: self.main_on_last = False self.lkas_enabled_last = False self.steer_alert_last = False + self.path_angle = 0. + self.path_offset = 0. + self.curvature_rate = 0. def update(self, CC, CS, now_nanos): can_sends = [] @@ -73,7 +76,10 @@ class CarController: # TODO: extended mode mode = 1 if CC.latActive else 0 counter = (self.frame // CarControllerParams.STEER_STEP) % 0xF - can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) + if self.CP.spFlags & FordFlagsSP.SP_ENHANCED_LAT_CONTROL.value: + can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, self.path_offset, self.path_angle, -apply_curvature, self.curvature_rate, counter)) + else: + can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) else: can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -apply_curvature, 0.)) From f5374f6e892f329f07250e3a66552fd7bc087c5b Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 20 Feb 2024 16:08:39 -0500 Subject: [PATCH 052/575] Cherry pick --- selfdrive/car/ford/interface.py | 9 ++++++++- selfdrive/car/ford/values.py | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index fadae1d93c..9978b1ab6b 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -3,7 +3,8 @@ from panda import Panda from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car import get_safety_config, create_mads_event from openpilot.selfdrive.car.ford.fordcan import CanBus -from openpilot.selfdrive.car.ford.values import CANFD_CAR, CAR, Ecu, BUTTON_STATES +from openpilot.common.params import Params +from openpilot.selfdrive.car.ford.values import CANFD_CAR, CAR, Ecu, BUTTON_STATES, FordFlagsSP from openpilot.selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -45,6 +46,12 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_CANFD + if ret.spFlags & FordFlagsSP.SP_ENHANCED_LAT_CONTROL: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_ENHANCED_LAT_CONTROL + + if Params().get("DongleId", encoding='utf8') in ("4fde83db16dc0802", "112e4d6e0cad05e1", "e36b272d5679115f", "24574459dd7fb3e0", "83a4e056c7072678") + ret.spFlags |= FordFlagsSP.SP_ENHANCED_LAT_CONTROL.value + if candidate == CAR.BRONCO_SPORT_MK1: ret.wheelbase = 2.67 ret.steerRatio = 17.7 diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 387d3e6a50..84cdb56097 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -39,7 +39,6 @@ class CarControllerParams: def __init__(self, CP): pass - BUTTON_STATES = { "accelCruise": False, "decelCruise": False, @@ -134,3 +133,5 @@ FW_QUERY_CONFIG = FwQueryConfig( (Ecu.shiftByWire, 0x732, None), ], ) +class FordFlagsSP(IntFlag): + SP_ENHANCED_LAT_CONTROL = 1 \ No newline at end of file From 9d917718223a71b3f065634b7310cdabaaa72be1 Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Tue, 20 Feb 2024 22:39:49 +0000 Subject: [PATCH 053/575] Update values.py --- selfdrive/car/ford/values.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 84cdb56097..24d99f9a54 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,6 +1,6 @@ from collections import defaultdict from dataclasses import dataclass -from enum import Enum, StrEnum +from enum import Enum, StrEnum, IntFlag from typing import Dict, List, Union from cereal import car @@ -134,4 +134,4 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) class FordFlagsSP(IntFlag): - SP_ENHANCED_LAT_CONTROL = 1 \ No newline at end of file + SP_ENHANCED_LAT_CONTROL = 1 From 6e2ba55a99d69f9e9f58809c0dd02cd73f1db3c7 Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Tue, 20 Feb 2024 23:01:28 +0000 Subject: [PATCH 054/575] Update interface.py --- selfdrive/car/ford/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 9978b1ab6b..6cfe188e77 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -49,7 +49,7 @@ class CarInterface(CarInterfaceBase): if ret.spFlags & FordFlagsSP.SP_ENHANCED_LAT_CONTROL: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_ENHANCED_LAT_CONTROL - if Params().get("DongleId", encoding='utf8') in ("4fde83db16dc0802", "112e4d6e0cad05e1", "e36b272d5679115f", "24574459dd7fb3e0", "83a4e056c7072678") + if Params().get("DongleId", encoding='utf8') in ("4fde83db16dc0802", "112e4d6e0cad05e1", "e36b272d5679115f", "24574459dd7fb3e0", "83a4e056c7072678"): ret.spFlags |= FordFlagsSP.SP_ENHANCED_LAT_CONTROL.value if candidate == CAR.BRONCO_SPORT_MK1: From 01ba44eb3fbb469c3d27deaec888f1f3644a1fbf Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Mon, 11 Mar 2024 14:10:55 +0000 Subject: [PATCH 055/575] Update radar_interface.py --- selfdrive/car/ford/radar_interface.py | 64 +++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/selfdrive/car/ford/radar_interface.py b/selfdrive/car/ford/radar_interface.py index 209bbebae3..7218fc54b6 100644 --- a/selfdrive/car/ford/radar_interface.py +++ b/selfdrive/car/ford/radar_interface.py @@ -11,6 +11,7 @@ DELPHI_ESR_RADAR_MSGS = list(range(0x500, 0x540)) DELPHI_MRR_RADAR_START_ADDR = 0x120 DELPHI_MRR_RADAR_MSG_COUNT = 64 +STEER_ASSIST_DATA_MSGS = 0x3d7 def _create_delphi_esr_radar_can_parser(CP) -> CANParser: msg_n = len(DELPHI_ESR_RADAR_MSGS) @@ -28,6 +29,9 @@ def _create_delphi_mrr_radar_can_parser(CP) -> CANParser: return CANParser(RADAR.DELPHI_MRR, messages, CanBus(CP).radar) +def _create_steer_assist_data(CP) -> CANParser: + messages = [("Steer_Assist_Data", 20)] + return CANParser(RADAR.STEER_ASSIST_DATA, messages, CanBus(CP).camera) class RadarInterface(RadarInterfaceBase): def __init__(self, CP): @@ -45,6 +49,10 @@ class RadarInterface(RadarInterfaceBase): elif self.radar == RADAR.DELPHI_MRR: self.rcp = _create_delphi_mrr_radar_can_parser(CP) self.trigger_msg = DELPHI_MRR_RADAR_START_ADDR + DELPHI_MRR_RADAR_MSG_COUNT - 1 + elif self.radar == RADAR.STEER_ASSIST_DATA: + self.rcp = _create_steer_assist_data(CP) + self.trigger_msg = STEER_ASSIST_DATA_MSGS + else: raise ValueError(f"Unsupported radar: {self.radar}") @@ -68,11 +76,67 @@ class RadarInterface(RadarInterfaceBase): self._update_delphi_esr() elif self.radar == RADAR.DELPHI_MRR: self._update_delphi_mrr() + elif self.radar == RADAR.STEER_ASSIST_DATA: + self._update_steer_assist_data() ret.points = list(self.pts.values()) self.updated_messages.clear() return ret + def _update_steer_assist_data(self): + msg = self.rcp.vl["Steer_Assist_Data"] + updated_msg = self.updated_messages + + dRel = msg['CmbbObjDistLong_L_Actl'] + confidence = msg['CmbbObjConfdnc_D_Stat'] + new_track = False + + # if dRel < 1022: + if confidence > 0: + if 0 not in self.pts: + self.pts[0] = car.RadarData.RadarPoint.new_message() + self.pts[0].trackId = self.track_id + self.vRelCol[0] = collections.deque(maxlen=20) + self.track_id += 1 + new_track = True + + yRel = msg['CmbbObjDistLat_L_Actl'] + vRel = msg['CmbbObjRelLong_V_Actl'] + yvRel = msg['CmbbObjRelLat_V_Actl'] + calc = 0 + if not new_track: + # if this is a newly created track - we don't have historical data so skip it + # if we are on the same track + # Let's see if we are moving: + # positive gap - lead is moving faster than us + # negative gap - lead is moving slower than us + dDiff = dRel - self.pts[0].dRel + if (abs(vRel) < 1.0e-2): + self.vRelCol[0].append(dDiff) + vRel = sum(self.vRelCol[0]) + calc = 1 + else: + if len(self.vRelCol[0]) > 0: + self.vRelCol[0].clear() + + if abs(self.pts[0].vRel - vRel) > 2 or abs(self.pts[0].dRel - dRel) > 5: + self.pts[0].trackId = self.track_id + if len(self.vRelCol[0]) > 0: + self.vRelCol[0].clear() + self.track_id += 1 + + self.pts[0].dRel = dRel # from front of car + self.pts[0].yRel = yRel # in car frame's y axis, left is positive + self.pts[0].vRel = vRel + self.pts[0].aRel = float('nan') + self.pts[0].yvRel = yvRel + self.pts[0].measured = True + else: + if 0 in self.pts: + del self.pts[0] + del self.vRelCol[0] + + def _update_delphi_esr(self): for ii in sorted(self.updated_messages): cpt = self.rcp.vl[ii] From 709bbc8857aa5039fe087c78dd3a4bff57265fd6 Mon Sep 17 00:00:00 2001 From: Isaac Barham Date: Mon, 11 Mar 2024 14:14:50 +0000 Subject: [PATCH 056/575] Update values.py --- selfdrive/car/ford/values.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index e54c72bc67..1e64851f9f 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -52,6 +52,7 @@ BUTTON_STATES = { class RADAR: DELPHI_ESR = 'ford_fusion_2018_adas' DELPHI_MRR = 'FORD_CADS' + STEER_ASSIST_DATA = 'ford_lincoln_base_pt' class Footnote(Enum): @@ -76,7 +77,7 @@ class FordCarInfo(CarInfo): @dataclass(frozen=True) class FordPlatformConfig(PlatformConfig): - dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR)) + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.STEER_ASSIST_DATA)) class CAR(Platforms): From 7153a7bb84b19238d2362a2240755a9aba2be72a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 26 Mar 2024 17:49:01 -0400 Subject: [PATCH 057/575] NNLC: "Lazy" tight curve fixes and more toggles --- common/params.cc | 2 ++ selfdrive/controls/lib/latcontrol_torque.py | 14 ++++++++++++-- selfdrive/manager/manager.py | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/common/params.cc b/common/params.cc index e06508a283..de7345764e 100644 --- a/common/params.cc +++ b/common/params.cc @@ -276,6 +276,7 @@ std::unordered_map keys = { {"NavModelUrl", PERSISTENT | BACKUP}, {"NNFF", PERSISTENT | BACKUP}, {"NNFFCarModel", PERSISTENT | BACKUP}, + {"NNFFNoLateralJerk", PERSISTENT | BACKUP}, {"OnroadScreenOff", PERSISTENT | BACKUP}, {"OnroadScreenOffBrightness", PERSISTENT | BACKUP}, {"OnroadScreenOffEvent", PERSISTENT | BACKUP}, @@ -308,6 +309,7 @@ std::unordered_map keys = { {"SunnylinkDongleId", PERSISTENT}, {"TorqueDeadzoneDeg", PERSISTENT | BACKUP}, {"TorqueFriction", PERSISTENT | BACKUP}, + {"TorqueLateralJerk", PERSISTENT | BACKUP}, {"TorqueMaxLatAccel", PERSISTENT | BACKUP}, {"TorquedOverride", PERSISTENT | BACKUP}, {"ToyotaSnG", PERSISTENT | BACKUP}, diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index c5d0e7ef8f..53c6cabc21 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -83,7 +83,8 @@ class LatControlTorque(LatControl): self.torqued_override = self.param_s.get_bool("TorquedOverride") self._frame = 0 - self.use_lateral_jerk = False # TODO: make this a parameter in the UI + self.use_lateral_jerk = self.param_s.get_bool("TorqueLateralJerk") # TODO: make this a parameter in the UI + self.nnff_no_lateral_jerk = self.param_s.get_bool("NNFFNoLateralJerk") # TODO: make this a parameter in the UI # Twilsonco's Lateral Neural Network Feedforward self.use_nn = CI.has_lateral_torque_nn @@ -140,6 +141,8 @@ class LatControlTorque(LatControl): if self._frame % 250 == 0: self._frame = 0 self.torqued_override = self.param_s.get_bool("TorquedOverride") + self.use_lateral_jerk = self.param_s.get_bool("TorqueLateralJerk") + self.nnff_no_lateral_jerk = self.param_s.get_bool("NNFFNoLateralJerk") if not self.torqued_override: return @@ -197,7 +200,7 @@ class LatControlTorque(LatControl): predicted_lateral_jerk = get_predicted_lateral_jerk(model_data.acceleration.y, self.t_diffs) desired_lateral_jerk = (interp(self.desired_lat_jerk_time, ModelConstants.T_IDXS, model_data.acceleration.y) - desired_lateral_accel) / self.desired_lat_jerk_time lookahead_lateral_jerk = get_lookahead_value(predicted_lateral_jerk[LAT_PLAN_MIN_IDX:friction_upper_idx], desired_lateral_jerk) - if self.use_steering_angle or lookahead_lateral_jerk == 0.0: + if self.nnff_no_lateral_jerk or self.use_steering_angle or lookahead_lateral_jerk == 0.0: lookahead_lateral_jerk = 0.0 actual_lateral_jerk = 0.0 self.lat_accel_friction_factor = 1.0 @@ -231,7 +234,14 @@ class LatControlTorque(LatControl): + past_rolls + future_rolls torque_from_setpoint = self.torque_from_nn(nnff_setpoint_input) torque_from_measurement = self.torque_from_nn(nnff_measurement_input) + pid_log.error = torque_from_setpoint - torque_from_measurement + error_blend_factor = interp(abs(desired_lateral_accel), [1.0, 2.0], [0.0, 1.0]) + if error_blend_factor > 0.0: # blend in stronger error response when in high lat accel + nnff_error_input = [CS.vEgo, setpoint - measurement, lateral_jerk_setpoint - lateral_jerk_measurement, 0.0] + torque_from_error = self.torque_from_nn(nnff_error_input) + if sign(pid_log.error) == sign(torque_from_error) and abs(pid_log.error) < abs(torque_from_error): + pid_log.error = pid_log.error * (1.0 - error_blend_factor) + torque_from_error * error_blend_factor # compute feedforward (same as nn setpoint output) error = setpoint - measurement diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 944fb12789..c4fd9e1309 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -72,6 +72,7 @@ def manager_init() -> None: ("MadsIconToggle", "1"), ("MaxTimeOffroad", "9"), ("NNFF", "0"), + ("NNFFNoLateralJerk", "0"), ("OnroadScreenOff", "-2"), ("OnroadScreenOffBrightness", "50"), ("OnroadScreenOffEvent", "1"), @@ -91,6 +92,7 @@ def manager_init() -> None: ("StockLongToyota", "0"), ("TorqueDeadzoneDeg", "0"), ("TorqueFriction", "1"), + ("TorqueLateralJerk", "0"), ("TorqueMaxLatAccel", "250"), ("TrueVEgoUi", "0"), ("TurnSpeedControl", "0"), From 9777d74a747161bc35704981a7550d28bc083086 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 26 Mar 2024 17:50:48 -0400 Subject: [PATCH 058/575] Update CHANGELOGS.md --- CHANGELOGS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 57a373c944..a9853f567a 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -13,6 +13,8 @@ sunnypilot - 0.9.7.0 (2024-xx-xx) * Prevents loss of settings after device resets, offering peace of mind through end-to-end encryption * Early alpha access to all current and previous GitHub Sponsors and Patreon supporters * Go to https://discord.gg/sunnypilot and reach out to one of the moderators to confirm your alpha access +* UPDATED: Neutral Network Lateral Control (NNLC) + * FIXED: Hotfix for "lazy" steering performance in tighter curves thanks to twilsonco! * RE-ENABLED: Map-based Turn Speed Control (M-TSC) for supported platforms * openpilot Longitudianl Control available cars * Custom Stock Longitudinal Control available cars From 058487c0a4759962ff81ff7ee4d142990fbe1d8a Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 4 Apr 2024 16:23:47 +0200 Subject: [PATCH 059/575] disable on screen recorder --- selfdrive/ui/SConscript | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index fe1831f505..cb21bd055b 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -11,6 +11,7 @@ if arch == 'larch64': maps = arch in ['larch64', 'aarch64', 'x86_64'] screenrecorder = arch in ['larch64'] +screenrecorder = False # Disabling due to https://discord.com/channels/880416502577266699/1223238728852574269 if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] From 7376ac960ae168aba5ad41708fce906ef176fa94 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 4 Apr 2024 16:34:00 +0200 Subject: [PATCH 060/575] nuking the option on the settings too --- .../ui/qt/offroad/sunnypilot/display_settings.cc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc index 3d83fce252..ee8182b5d5 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc @@ -14,12 +14,13 @@ DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { .arg(tr("Disabled: Wake the brightness of the screen to display critical events.")), "../assets/offroad/icon_blank.png", }, - { - "ScreenRecorder", - tr("Enable Screen Recorder"), - tr("Enable this will display a button on the onroad screen to toggle on or off real-time screen recording with UI elements."), - "../assets/offroad/icon_blank.png" - } + // Disabling due to https://discord.com/channels/880416502577266699/1223238728852574269 + //{ + // "ScreenRecorder", + // tr("Enable Screen Recorder"), + // tr("Enable this will display a button on the onroad screen to toggle on or off real-time screen recording with UI elements."), + // "../assets/offroad/icon_blank.png" + //} }; // General: Max Time Offroad (Shutdown timer) From a26bb62ed0e59a551925fa32c610cf5a115ed84c Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 8 Apr 2024 19:08:25 +0200 Subject: [PATCH 061/575] Bumping mapd to v1.9.0 --- CHANGELOGS.md | 1 + selfdrive/manager/mapd_installer.py | 2 +- third_party/pfeiferj-mapd/mapd | Bin 8192000 -> 8192000 bytes 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 57a373c944..a99dacd417 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -5,6 +5,7 @@ sunnypilot - 0.9.7.0 (2024-xx-xx) ************************ * UPDATED: Synced with commaai's openpilot * master commit 56e343b (February 27, 2024) +* UPDATED: Bumping mapd by pfeiferj to version 1.9.0. (https://github.com/pfeiferj/mapd/releases/tag/v1.9.0) * NEW❗: Config Backup (Alpha access only for GitHub Sponsors and Patreon supporters) * Remotely back up and restore sunnypilot settings easily * Device registration with sunnylink ensures a secure, integrated experience across services diff --git a/selfdrive/manager/mapd_installer.py b/selfdrive/manager/mapd_installer.py index ec11e4aeb1..c66336750a 100755 --- a/selfdrive/manager/mapd_installer.py +++ b/selfdrive/manager/mapd_installer.py @@ -14,7 +14,7 @@ from common.params import Params from openpilot.selfdrive.mapd_manager import COMMON_DIR, MAPD_PATH, MAPD_BIN_DIR from openpilot.system.version import is_prebuilt -VERSION = 'v1.8.0' +VERSION = 'v1.9.0' URL = f"https://github.com/pfeiferj/openpilot-mapd/releases/download/{VERSION}/mapd" diff --git a/third_party/pfeiferj-mapd/mapd b/third_party/pfeiferj-mapd/mapd index 4d07be8f32cb20dc30b48805968d1a501666a0d9..d24d9b757fb67bc6f297eadddb8844452f844647 100755 GIT binary patch delta 2602686 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

u?KR93}@6x4c(kx}0FmA;y#rx^Ci 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%0D4B2DAi*^@ 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%0AAW96)o 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

FAIz`4#>lhtwBj)C>3wkGTrRcy@&QouI)iWy_rc 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 z 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

?lI2i{-fGO6PX7YF|FwZ)t*kI}e6|lhcCL+KL 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

#@pl6Yl=<%c?4+wEZ@mNI$_Se} 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

6w4fZo-&QEZ| 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

C`xVT;oten!1Af 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

eE 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�k4H1;j0#G@qhX8uZ5CHB7 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%0AM@#8w341ApBY(Jq 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 v$R5 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

yGVpVQiYm%BGoL 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%0DM!L4+K6U#%k!U`x_4o_A9^eA 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%0A$?{Th0VgI5Cf}( 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�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

1TVEBKFgpIQO4FSTd&XYJWw=W_=9pDkdC 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¨Ad%>vEg98bya|MeDvr-wq511Au(i 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

j^v`Rsh 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

g04z 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 M7A5>^|DKod|4|bFng9R* From dcb786675fe4400ae721efcc1ac354c4ba38cf1c Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 8 Apr 2024 19:12:01 +0200 Subject: [PATCH 062/575] updating changelog nicely --- CHANGELOGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index a99dacd417..f675cc1c29 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -5,7 +5,7 @@ sunnypilot - 0.9.7.0 (2024-xx-xx) ************************ * UPDATED: Synced with commaai's openpilot * master commit 56e343b (February 27, 2024) -* UPDATED: Bumping mapd by pfeiferj to version 1.9.0. (https://github.com/pfeiferj/mapd/releases/tag/v1.9.0) +* UPDATED: Bumping mapd by [@pfeiferj](https://github.com/pfeiferj) to version [v1.9.0](https://github.com/pfeiferj/mapd/releases/tag/v1.9.0). * NEW❗: Config Backup (Alpha access only for GitHub Sponsors and Patreon supporters) * Remotely back up and restore sunnypilot settings easily * Device registration with sunnylink ensures a secure, integrated experience across services From 531a75134faa1310c3122db97830cea6ea92c465 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Apr 2024 00:11:30 -0400 Subject: [PATCH 063/575] Gate behind flag --- .../ui/qt/offroad/sunnypilot/display_settings.cc | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc index ee8182b5d5..a09bc22565 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc @@ -13,14 +13,15 @@ DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { .arg(tr("Enabled: Wake the brightness of the screen to display all events.")) .arg(tr("Disabled: Wake the brightness of the screen to display critical events.")), "../assets/offroad/icon_blank.png", +#ifdef ENABLE_DASHCAM }, - // Disabling due to https://discord.com/channels/880416502577266699/1223238728852574269 - //{ - // "ScreenRecorder", - // tr("Enable Screen Recorder"), - // tr("Enable this will display a button on the onroad screen to toggle on or off real-time screen recording with UI elements."), - // "../assets/offroad/icon_blank.png" - //} + { + "ScreenRecorder", + tr("Enable Screen Recorder"), + tr("Enable this will display a button on the onroad screen to toggle on or off real-time screen recording with UI elements."), + "../assets/offroad/icon_blank.png" +#endif + } }; // General: Max Time Offroad (Shutdown timer) From 87bfd47a59ab5277f5c4f5ee83380d16f4af5565 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Apr 2024 00:19:24 -0400 Subject: [PATCH 064/575] Trigger fast UI restart directly in OMX if err != 0 --- selfdrive/ui/SConscript | 3 +-- selfdrive/ui/qt/screenrecorder/omx_encoder.cc | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 1293b51120..5eb990a9b4 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -10,8 +10,7 @@ if arch == 'larch64': base_libs.append('EGL') maps = arch in ['larch64', 'aarch64', 'x86_64'] -screenrecorder = arch in ['larch64'] -screenrecorder = False # Disabling due to https://discord.com/channels/880416502577266699/1223238728852574269 +screenrecorder = False #arch in ['larch64'] if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] diff --git a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc index d051b600f8..cbcb2989e4 100644 --- a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc +++ b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc @@ -18,6 +18,7 @@ #include "msm_media_info.h" #include "common/swaglog.h" #include "common/util.h" +#include "common/watchdog.h" using namespace libyuv; @@ -328,7 +329,8 @@ OmxEncoder::OmxEncoder(const char* path, int width, int height, int fps, int bit if (err != OMX_ErrorNone) { LOGE("error getting codec: %x", err); } - assert(err == OMX_ErrorNone); + watchdog_kick(0); + // assert(err == OMX_ErrorNone); // TODO: Investigate the insufficient memory issue in prebuilts // printf("handle: %p\n", this->handle); // setup input port From caed4b34cba9480e9aa3cb440c498c8a774a4c89 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Apr 2024 00:25:57 -0400 Subject: [PATCH 065/575] Do this if error is triggered --- selfdrive/ui/qt/screenrecorder/omx_encoder.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc index cbcb2989e4..0fde1f1ae8 100644 --- a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc +++ b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc @@ -328,8 +328,9 @@ OmxEncoder::OmxEncoder(const char* path, int width, int height, int fps, int bit int err = OMX_GetHandle(&this->handle, component, this, &omx_callbacks); if (err != OMX_ErrorNone) { LOGE("error getting codec: %x", err); + qApp->exit(18); + watchdog_kick(0); } - watchdog_kick(0); // assert(err == OMX_ErrorNone); // TODO: Investigate the insufficient memory issue in prebuilts // printf("handle: %p\n", this->handle); From 23e51659594323f71296380d57c89393442ab232 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Apr 2024 00:27:42 -0400 Subject: [PATCH 066/575] Re-enable --- selfdrive/ui/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 5eb990a9b4..b5bb2bb3c5 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -10,7 +10,7 @@ if arch == 'larch64': base_libs.append('EGL') maps = arch in ['larch64', 'aarch64', 'x86_64'] -screenrecorder = False #arch in ['larch64'] +screenrecorder = arch in ['larch64'] if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] From 452808f55063ad7c752026a9afcf2343e8aa150e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Apr 2024 00:35:44 -0400 Subject: [PATCH 067/575] lib --- selfdrive/ui/qt/screenrecorder/omx_encoder.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc index 0fde1f1ae8..a020d44afe 100644 --- a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc +++ b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include From 33f0bd323b4ae93221d5dcd996650ae5fab72cbf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Apr 2024 00:39:56 -0400 Subject: [PATCH 068/575] only skip assert and force start if OMX_ErrorInsufficientResources --- selfdrive/ui/qt/screenrecorder/omx_encoder.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc index a020d44afe..3a9e702e40 100644 --- a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc +++ b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc @@ -330,10 +330,14 @@ OmxEncoder::OmxEncoder(const char* path, int width, int height, int fps, int bit int err = OMX_GetHandle(&this->handle, component, this, &omx_callbacks); if (err != OMX_ErrorNone) { LOGE("error getting codec: %x", err); + } + // TODO: Investigate the insufficient memory issue in prebuilts, we force quit Qt UI, and trigger fast restart for now + if (err == OMX_ErrorInsufficientResources) { qApp->exit(18); watchdog_kick(0); + } else { + assert(err == OMX_ErrorNone); } - // assert(err == OMX_ErrorNone); // TODO: Investigate the insufficient memory issue in prebuilts // printf("handle: %p\n", this->handle); // setup input port From a177818e587ae7cfa57055dd7b83d1e819a3591d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 12 Apr 2024 16:31:02 +0200 Subject: [PATCH 069/575] Add Sunnylink status to Sidebar UI Included a new 'ItemStatus' variable, 'sunnylinkStatus', to the Sidebar class and draw its metric in the UI. The status is determined by the value of 'LastSunnylinkPingTime', turning Sunnylink offline if it equals 0, online if the ping time is less than 80e9, and displaying an error otherwise. --- selfdrive/ui/qt/sidebar.cc | 20 +++++++++++++++++--- selfdrive/ui/qt/sidebar.h | 4 +++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 6b0ef8c7f7..50ece47df4 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -5,6 +5,7 @@ #include #include "selfdrive/ui/qt/util.h" +#include "common/params.h" void Sidebar::drawMetric(QPainter &p, const QPair &label, QColor c, int y) { const QRect rect = {30, y, 240, 126}; @@ -152,6 +153,18 @@ void Sidebar::updateState(const UIState &s) { pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color}; } setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); + + ItemStatus sunnylinkStatus; + auto last_sunnylink_ping = std::strtol(params.get("LastSunnylinkPingTime").c_str(), nullptr, 10); + if (last_ping == 0) { + sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("OFFLINE")}, warning_color}; + } else { + if (nanos_since_boot() - last_sunnylink_ping < 80e9) + sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ONLINE")}, good_color}; + else + sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ERROR")}, danger_color}; + } + setProperty("sunnylinkStatus", QVariant::fromValue(sunnylinkStatus)); } void Sidebar::paintEvent(QPaintEvent *event) { @@ -183,7 +196,8 @@ void Sidebar::paintEvent(QPaintEvent *event) { p.drawText(r, Qt::AlignCenter, net_type); // metrics - drawMetric(p, temp_status.first, temp_status.second, 338); - drawMetric(p, panda_status.first, panda_status.second, 496); - drawMetric(p, connect_status.first, connect_status.second, 654); + drawMetric(p, temp_status.first, temp_status.second, 310); + drawMetric(p, panda_status.first, panda_status.second, 440); + drawMetric(p, connect_status.first, connect_status.second, 570); + drawMetric(p, sunnylink_status.first, sunnylink_status.second, 700); } diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 4297602908..7a7f2387bd 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -13,6 +13,7 @@ Q_DECLARE_METATYPE(ItemStatus); class Sidebar : public QFrame { Q_OBJECT Q_PROPERTY(ItemStatus connectStatus MEMBER connect_status NOTIFY valueChanged); + Q_PROPERTY(ItemStatus sunnylinkStatus MEMBER sunnylink_status NOTIFY valueChanged); Q_PROPERTY(ItemStatus pandaStatus MEMBER panda_status NOTIFY valueChanged); Q_PROPERTY(ItemStatus tempStatus MEMBER temp_status NOTIFY valueChanged); Q_PROPERTY(QString netType MEMBER net_type NOTIFY valueChanged); @@ -54,11 +55,12 @@ protected: const QColor warning_color = QColor(218, 202, 37); const QColor danger_color = QColor(201, 34, 49); - ItemStatus connect_status, panda_status, temp_status; + ItemStatus connect_status, panda_status, temp_status, sunnylink_status; QString net_type; int net_strength = 0; private: + Params params; std::unique_ptr pm; QString sidebar_temp = "0"; From abdb9e64d0fb3c2b6efd704573b438e864f21da0 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 12 Apr 2024 16:43:29 +0200 Subject: [PATCH 070/575] Add disabled state to sidebar status items A new disabled color has been added to the sidebar items. This change primarily impacts the 'Sunnylink' status where a new state has been introduced. Now, when 'Sunnylink' is not enabled, it will show as 'DISABLED' in the disabled color. --- selfdrive/ui/qt/sidebar.cc | 5 ++++- selfdrive/ui/qt/sidebar.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 50ece47df4..425f8fb92e 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -156,7 +156,10 @@ void Sidebar::updateState(const UIState &s) { ItemStatus sunnylinkStatus; auto last_sunnylink_ping = std::strtol(params.get("LastSunnylinkPingTime").c_str(), nullptr, 10); - if (last_ping == 0) { + auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); + if (!sunnylink_enabled) { + sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("DISABLED")}, disabled_color}; + } else if (last_ping == 0) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("OFFLINE")}, warning_color}; } else { if (nanos_since_boot() - last_sunnylink_ping < 80e9) diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 7a7f2387bd..be2c34cae0 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -54,6 +54,7 @@ protected: const QColor good_color = QColor(255, 255, 255); const QColor warning_color = QColor(218, 202, 37); const QColor danger_color = QColor(201, 34, 49); + const QColor disabled_color = QColor(128, 128, 128); ItemStatus connect_status, panda_status, temp_status, sunnylink_status; QString net_type; From ba9fb8af6bba1c073ee58c1f87cc8685def9d324 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 13 Apr 2024 19:03:17 +0200 Subject: [PATCH 071/575] Add sunnylink connectivity status to changelog In the update information for the changelog, a record of the new feature displaying the sunnylink connectivity status on the left sidebar has been added. This lets users and developers quickly see that this change has been implemented. --- CHANGELOGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 18c0a9df45..30881e67b2 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -25,6 +25,7 @@ sunnypilot - 0.9.7.0 (2024-xx-xx) * Display Metrics Below Chevron * NEW❗: Metrics is now being displayed below the chevron instead of above * NEW❗: Display both Distance and Speed simultaneously + * NEW❗: View sunnylink connectivity status on the left sidebar! sunnypilot - 0.9.6.1 (2024-02-27) ======================== From c6bf6886cf74272d7c8d15b95b565760f684d42b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 01:01:18 -0400 Subject: [PATCH 072/575] ui: Force offroad from settings when the car is on --- common/params.cc | 1 + selfdrive/controls/lib/alerts_offroad.json | 4 ++ selfdrive/thermald/thermald.py | 3 ++ selfdrive/ui/qt/offroad/settings.cc | 56 +++++++++++++++++++++- selfdrive/ui/qt/offroad/settings.h | 10 ++++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/common/params.cc b/common/params.cc index 9f9a053041..ccfb3a7ae2 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}, diff --git a/selfdrive/controls/lib/alerts_offroad.json b/selfdrive/controls/lib/alerts_offroad.json index e08d67f43b..13780b5221 100644 --- a/selfdrive/controls/lib/alerts_offroad.json +++ b/selfdrive/controls/lib/alerts_offroad.json @@ -56,5 +56,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 get out of Force Offroad mode.", + "severity": 1 } } diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 37206c2f06..10ab1198a3 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -309,6 +309,9 @@ 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 + startup_conditions["not_force_offroad"] = not params.get_bool("ForceOffroad") + # 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 d401102c5d..9d4905c63d 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -403,13 +403,34 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible); } + addItem(power_layout); + + // TODO: Add Force Onroad/Offroad toggle under power_layout + offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad")); + offroad_btn->setObjectName("offroad_btn"); + QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanel::forceoffroad); + if (!Hardware::PC()) { + connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + offroad_btn->setVisible(params.getBool("ForceOffroad") && offroad); + }); + } + addItem(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; } + #offroad_btn { height: 120px; border-radius: 15px; background-color: #393939; } + #offroad_btn:pressed { background-color: #4a4a4a; } )"); - addItem(power_layout); + + connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + is_onroad = !offroad; + updateLabels(); + }); + + updateLabels(); } void DevicePanel::onPinFileChanged(const QString &file_path) { @@ -484,6 +505,39 @@ void DevicePanel::poweroff() { } } +void DevicePanel::forceoffroad() { + if (!uiState()->engaged()) { + if (is_onroad) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to force offroad?"), tr("Force"), this)) { + if (!uiState()->engaged()) { + params.putBool("ForceOffroad", true); + } + } + } else { + if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) { + if (!uiState()->engaged()) { + params.remove("ForceOFfroad"); + } + } + } + } else { + ConfirmationDialog::alert(tr("Disengage to Force Offroad"), this); + } +} + +void DevicePanel::showEvent(QShowEvent *event) { + updateLabels(); +} + +void DevicePanel::updateLabels() { + if (!isVisible()) { + return; + } + + offroad_btn->setText(is_onroad ? tr("Force Offroad") : tr("Unforce Offroad")); + offroad_btn->setVisible(params.getBool("ForceOffroad") || is_onroad); +} + void SettingsWindow::showEvent(QShowEvent *event) { setCurrentPanel(0); } diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 4f12609269..8ea7835b37 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -51,6 +51,8 @@ class DevicePanel : public ListWidget { Q_OBJECT public: explicit DevicePanel(SettingsWindow *parent); +protected: + void showEvent(QShowEvent *event) override; signals: void reviewTrainingGuide(); void showDriverView(); @@ -61,6 +63,9 @@ private slots: void updateCalibDescription(); void onPinFileChanged(const QString &file_path); void refreshPin(); + void forceoffroad(); + + void updateLabels(); private: Params params; @@ -69,6 +74,11 @@ private: QString pin_title = tr("Fleet Manager PIN:") + " "; QString pin = "OFF"; QFileSystemWatcher *fs_watch; + + QPushButton *offroad_btn; + +protected: + bool is_onroad = false; }; class TogglesPanel : public ListWidget { From 153b8b8bff41ac92fef82caf25af80c2862a8d4c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 12:29:02 -0400 Subject: [PATCH 073/575] Add this check too --- selfdrive/ui/ui.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 9a6f355cc7..e2a1879c39 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 && From d2c242b17c591013dd2fe10b6e225b2fee5f324c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 12:37:57 -0400 Subject: [PATCH 074/575] Impossible condition --- selfdrive/ui/qt/offroad/settings.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 9d4905c63d..37521cee52 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -515,9 +515,7 @@ void DevicePanel::forceoffroad() { } } else { if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) { - if (!uiState()->engaged()) { - params.remove("ForceOFfroad"); - } + params.remove("ForceOFfroad"); } } } else { From 819e5660e81a6fb12fa67e0b4f7935020ba4664d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 12:41:10 -0400 Subject: [PATCH 075/575] Revert "Impossible condition" This reverts commit d2c242b17c591013dd2fe10b6e225b2fee5f324c. --- selfdrive/ui/qt/offroad/settings.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 37521cee52..9d4905c63d 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -515,7 +515,9 @@ void DevicePanel::forceoffroad() { } } else { if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) { - params.remove("ForceOFfroad"); + if (!uiState()->engaged()) { + params.remove("ForceOFfroad"); + } } } } else { From fef46d3093efc3a1e4f9c56bc014e4657f62ba1c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 12:41:15 -0400 Subject: [PATCH 076/575] wtf --- selfdrive/ui/qt/offroad/settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 9d4905c63d..403880da53 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -516,7 +516,7 @@ void DevicePanel::forceoffroad() { } else { if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) { if (!uiState()->engaged()) { - params.remove("ForceOFfroad"); + params.remove("ForceOffroad"); } } } From 2f4f0a7c16f07922a1f1ceddae275e08db51d2be Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 16:50:34 -0400 Subject: [PATCH 077/575] display message when forced offroad --- common/params.cc | 1 + selfdrive/thermald/thermald.py | 1 + 2 files changed, 2 insertions(+) diff --git a/common/params.cc b/common/params.cc index ccfb3a7ae2..b683609194 100644 --- a/common/params.cc +++ b/common/params.cc @@ -323,6 +323,7 @@ std::unordered_map keys = { {"VisionCurveLaneless", PERSISTENT | BACKUP}, {"VwAccType", PERSISTENT | BACKUP}, {"VwCCOnly", PERSISTENT | BACKUP}, + {"Offroad_ForceStatus", PERSISTENT}, {"Offroad_SupersededUpdate", PERSISTENT}, {"SunnylinkCache_Users", PERSISTENT}, diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 10ab1198a3..0a79b23da8 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -311,6 +311,7 @@ def thermald_thread(end_event, hw_queue) -> None: # user-forced status startup_conditions["not_force_offroad"] = not params.get_bool("ForceOffroad") + set_offroad_alert("Offroad_ForceStatus", True) # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger From 3071744acda58f877134e71c3723b7c589fcd9e0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 16:54:46 -0400 Subject: [PATCH 078/575] only display message when forced --- selfdrive/thermald/thermald.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 0a79b23da8..961e7131cc 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -311,7 +311,8 @@ def thermald_thread(end_event, hw_queue) -> None: # user-forced status startup_conditions["not_force_offroad"] = not params.get_bool("ForceOffroad") - set_offroad_alert("Offroad_ForceStatus", True) + if not startup_conditions["not_force_offroad"]: + set_offroad_alert("Offroad_ForceStatus", True) # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger From 2030196c8c4000e987c71540841f0562ebaa3d7d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 16:55:55 -0400 Subject: [PATCH 079/575] show button at all times --- selfdrive/ui/qt/offroad/settings.cc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 403880da53..9332b49248 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -409,11 +409,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad")); offroad_btn->setObjectName("offroad_btn"); QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanel::forceoffroad); - if (!Hardware::PC()) { - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - offroad_btn->setVisible(params.getBool("ForceOffroad") && offroad); - }); - } addItem(offroad_btn); setStyleSheet(R"( @@ -535,7 +530,6 @@ void DevicePanel::updateLabels() { } offroad_btn->setText(is_onroad ? tr("Force Offroad") : tr("Unforce Offroad")); - offroad_btn->setVisible(params.getBool("ForceOffroad") || is_onroad); } void SettingsWindow::showEvent(QShowEvent *event) { From 49ab5a1ca294c433f9cb95a2e2a1ff5c8bdc112f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 16:59:19 -0400 Subject: [PATCH 080/575] change it up --- selfdrive/thermald/thermald.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 961e7131cc..0ab3e0be11 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -312,7 +312,7 @@ def thermald_thread(end_event, hw_queue) -> None: # user-forced status startup_conditions["not_force_offroad"] = not params.get_bool("ForceOffroad") if not startup_conditions["not_force_offroad"]: - set_offroad_alert("Offroad_ForceStatus", True) + set_offroad_alert_if_changed("Offroad_ForceStatus", True) # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger From 7371b7aad5fb0ecc6c6ce5f661bc03a815bd693c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 16:59:45 -0400 Subject: [PATCH 081/575] wrong int flag --- common/params.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/params.cc b/common/params.cc index b683609194..5c7a30d012 100644 --- a/common/params.cc +++ b/common/params.cc @@ -323,7 +323,7 @@ std::unordered_map keys = { {"VisionCurveLaneless", PERSISTENT | BACKUP}, {"VwAccType", PERSISTENT | BACKUP}, {"VwCCOnly", PERSISTENT | BACKUP}, - {"Offroad_ForceStatus", PERSISTENT}, + {"Offroad_ForceStatus", CLEAR_ON_MANAGER_START}, {"Offroad_SupersededUpdate", PERSISTENT}, {"SunnylinkCache_Users", PERSISTENT}, From 4abdf85dd13eeda94a43e3b125c1bd30f3e711c4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 17:01:27 -0400 Subject: [PATCH 082/575] use the param instead --- selfdrive/ui/qt/offroad/settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 9332b49248..f43c92d3a0 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -529,7 +529,7 @@ void DevicePanel::updateLabels() { return; } - offroad_btn->setText(is_onroad ? tr("Force Offroad") : tr("Unforce Offroad")); + offroad_btn->setText(params.getBool("ForceOffroad") ? tr("Unforce Offroad") : tr("Force Offroad")); } void SettingsWindow::showEvent(QShowEvent *event) { From c0f46cd44ad2ee61d8471e616597607d7d5cbb51 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 17:06:41 -0400 Subject: [PATCH 083/575] do this instead, and flip the logics --- selfdrive/ui/qt/offroad/settings.cc | 21 +++++++++------------ selfdrive/ui/qt/offroad/settings.h | 3 --- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index f43c92d3a0..7a265cb797 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -420,11 +420,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { #offroad_btn:pressed { background-color: #4a4a4a; } )"); - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - is_onroad = !offroad; - updateLabels(); - }); - updateLabels(); } @@ -502,22 +497,24 @@ void DevicePanel::poweroff() { void DevicePanel::forceoffroad() { if (!uiState()->engaged()) { - if (is_onroad) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to force offroad?"), tr("Force"), this)) { - if (!uiState()->engaged()) { - params.putBool("ForceOffroad", true); - } - } - } else { + if (params.getBool("ForceOffroad")) { if (ConfirmationDialog::confirm(tr("Are you sure you want to unforce offroad?"), tr("Unforce"), this)) { if (!uiState()->engaged()) { params.remove("ForceOffroad"); } } + } else { + if (ConfirmationDialog::confirm(tr("Are you sure you want to force offroad?"), tr("Force"), this)) { + if (!uiState()->engaged()) { + params.putBool("ForceOffroad", true); + } + } } } else { ConfirmationDialog::alert(tr("Disengage to Force Offroad"), this); } + + updateLabels(); } void DevicePanel::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 8ea7835b37..a29dda8ffc 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -76,9 +76,6 @@ private: QFileSystemWatcher *fs_watch; QPushButton *offroad_btn; - -protected: - bool is_onroad = false; }; class TogglesPanel : public ListWidget { From 92a0777aa7a38c5eb729b07eae479dedf4ba46fb Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 17:07:38 -0400 Subject: [PATCH 084/575] use this --- selfdrive/thermald/thermald.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 0ab3e0be11..961e7131cc 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -312,7 +312,7 @@ def thermald_thread(end_event, hw_queue) -> None: # user-forced status startup_conditions["not_force_offroad"] = not params.get_bool("ForceOffroad") if not startup_conditions["not_force_offroad"]: - set_offroad_alert_if_changed("Offroad_ForceStatus", True) + set_offroad_alert("Offroad_ForceStatus", True) # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger From f5cd0b91ed83b842007aab46f2795309b9c24f75 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 17:11:36 -0400 Subject: [PATCH 085/575] do this dynamically --- selfdrive/thermald/thermald.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 961e7131cc..ee6f82883b 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -311,8 +311,7 @@ def thermald_thread(end_event, hw_queue) -> None: # user-forced status startup_conditions["not_force_offroad"] = not params.get_bool("ForceOffroad") - if not startup_conditions["not_force_offroad"]: - set_offroad_alert("Offroad_ForceStatus", True) + set_offroad_alert("Offroad_ForceStatus", not startup_conditions["not_force_offroad"]) # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger From 5c5cc631bbf59d5442feb9811195ec34cbd15008 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 17:33:33 -0400 Subject: [PATCH 086/575] More professional wording --- selfdrive/controls/lib/alerts_offroad.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/alerts_offroad.json b/selfdrive/controls/lib/alerts_offroad.json index 13780b5221..a9533b86a5 100644 --- a/selfdrive/controls/lib/alerts_offroad.json +++ b/selfdrive/controls/lib/alerts_offroad.json @@ -58,7 +58,7 @@ "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 get out of Force Offroad mode.", + "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 } } From f3e450db85b97cbb644bcf203c1fb3a20318c742 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Apr 2024 18:22:26 -0400 Subject: [PATCH 087/575] Update CHANGELOGS.md --- CHANGELOGS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 18c0a9df45..2f59fff64b 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -16,6 +16,9 @@ sunnypilot - 0.9.7.0 (2024-xx-xx) * GitHub account pairing from device settings scanning QR code * Pairing your account will allow you to access features via our API (still WIP but accessible if you dig a little on our code 😉) * Allow inheritance of your sponsorship status, allowing you to get extra features and early access whenever applicable +* 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 * RE-ENABLED: Map-based Turn Speed Control (M-TSC) for supported platforms * openpilot Longitudianl Control available cars * Custom Stock Longitudinal Control available cars From 2bbb420d2844ed69768bae16a3283e2fdb688d43 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 19 Apr 2024 11:55:32 -0400 Subject: [PATCH 088/575] Transition to offroad --- selfdrive/thermald/thermald.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index ee6f82883b..8ed338b6e9 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -310,7 +310,9 @@ def thermald_thread(end_event, hw_queue) -> None: startup_conditions["device_booted"] = startup_conditions.get("device_booted", False) or HARDWARE.booted() # user-forced status - startup_conditions["not_force_offroad"] = not params.get_bool("ForceOffroad") + 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", not startup_conditions["not_force_offroad"]) # if the temperature enters the danger zone, go offroad to cool down From 1d19029f19eae448c2cc9d880b9bf6bbd85e9a8f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 19 Apr 2024 11:59:58 -0400 Subject: [PATCH 089/575] cleaner --- selfdrive/thermald/thermald.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 8ed338b6e9..9797127908 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -313,7 +313,7 @@ def thermald_thread(end_event, hw_queue) -> None: 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", not startup_conditions["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 From b4cb35fd23200baf0a416de2bbe252892e44fb89 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 19 Apr 2024 14:19:26 -0400 Subject: [PATCH 090/575] Nuke and force immediately for all errors --- selfdrive/ui/qt/screenrecorder/omx_encoder.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc index 3a9e702e40..29607ed921 100644 --- a/selfdrive/ui/qt/screenrecorder/omx_encoder.cc +++ b/selfdrive/ui/qt/screenrecorder/omx_encoder.cc @@ -330,14 +330,19 @@ OmxEncoder::OmxEncoder(const char* path, int width, int height, int fps, int bit int err = OMX_GetHandle(&this->handle, component, this, &omx_callbacks); if (err != OMX_ErrorNone) { LOGE("error getting codec: %x", err); + // TODO: We force quit Qt UI, and trigger fast restart for now + qApp->exit(18); + watchdog_kick(0); } // TODO: Investigate the insufficient memory issue in prebuilts, we force quit Qt UI, and trigger fast restart for now + /* if (err == OMX_ErrorInsufficientResources) { qApp->exit(18); watchdog_kick(0); } else { assert(err == OMX_ErrorNone); } + */ // printf("handle: %p\n", this->handle); // setup input port From 71b489a095804a62d0dc26bd74a392674de2bd06 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Apr 2024 01:35:16 -0400 Subject: [PATCH 091/575] Handle button style properly --- selfdrive/ui/qt/offroad/settings.cc | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 7a265cb797..3551d9ea00 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -403,23 +403,26 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible); } - addItem(power_layout); - - // TODO: Add Force Onroad/Offroad toggle under power_layout offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad")); offroad_btn->setObjectName("offroad_btn"); QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanel::forceoffroad); - addItem(offroad_btn); + + 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; } - #offroad_btn { height: 120px; border-radius: 15px; background-color: #393939; } - #offroad_btn:pressed { background-color: #4a4a4a; } + #offroad_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } + #offroad_btn:pressed { background-color: #FF2424; } )"); + addItem(buttons_layout); + updateLabels(); } @@ -526,7 +529,16 @@ void DevicePanel::updateLabels() { return; } - offroad_btn->setText(params.getBool("ForceOffroad") ? tr("Unforce Offroad") : tr("Force Offroad")); + 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) { From 1ba739c372a38e6960f80d3d1ccb7bc0230e13c2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Apr 2024 01:36:09 -0400 Subject: [PATCH 092/575] less --- selfdrive/ui/qt/offroad/settings.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 3551d9ea00..f1e1e39d86 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -417,8 +417,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { #reboot_btn:pressed { background-color: #4a4a4a; } #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } #poweroff_btn:pressed { background-color: #FF2424; } - #offroad_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } - #offroad_btn:pressed { background-color: #FF2424; } )"); addItem(buttons_layout); From da14ac9bff0ff36bde4ca6310c996c806605841a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Apr 2024 01:37:42 -0400 Subject: [PATCH 093/575] lint --- selfdrive/ui/qt/offroad/settings.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index f1e1e39d86..ae33019ce2 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -418,7 +418,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } #poweroff_btn:pressed { background-color: #FF2424; } )"); - addItem(buttons_layout); updateLabels(); From b8d2e90e6275321d5fac873ba6c1dbeeb3e20479 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Apr 2024 01:39:05 -0400 Subject: [PATCH 094/575] add same comments --- selfdrive/ui/qt/offroad/settings.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index ae33019ce2..a73ca40de7 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -499,12 +499,14 @@ 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); } From 4757364fa9ddf9cb6f1c1e0cb0b61a002aefc958 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Apr 2024 01:40:06 -0400 Subject: [PATCH 095/575] lint --- selfdrive/ui/qt/offroad/settings.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index a73ca40de7..13c7dc72ec 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -531,8 +531,8 @@ void DevicePanel::updateLabels() { 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; }") + 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); From 1d1852421873ae5a2f12d50486e22639a6902583 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Apr 2024 01:40:58 -0400 Subject: [PATCH 096/575] disable screen recorder again --- selfdrive/ui/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index b5bb2bb3c5..83beaac8b3 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -10,7 +10,7 @@ if arch == 'larch64': base_libs.append('EGL') maps = arch in ['larch64', 'aarch64', 'x86_64'] -screenrecorder = arch in ['larch64'] +screenrecorder = False # arch in ['larch64'] if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] From 8f3ca7a9b22ed1385e51b4d49b226a74a462c7c7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Apr 2024 12:33:59 -0400 Subject: [PATCH 097/575] do we need this to close the relay? --- selfdrive/boardd/boardd.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index b2b59d3752..ed7d7eb815 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -266,7 +266,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(); @@ -314,7 +314,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); } @@ -470,7 +470,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"); From d0761ddcd41e7d73ddaa9bfa57aba7e979ef120b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 00:37:40 -0400 Subject: [PATCH 098/575] Update CHANGELOGS.md --- CHANGELOGS.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 7c1746f90e..72a49f1008 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,10 +1,13 @@ -sunnypilot - 0.9.7.0 (2024-xx-xx) +sunnypilot - 0.9.7.0 (2024-05-xx) ======================== * New driving model -* Support for many hybrid Ford models +* Adjust driving personality with the follow distance button +* Support for hybrid variants of supported Ford models +* Added toggle to enable driver monitoring even when openpilot is not engaged +* Fingerprinting without the OBD-II port on all cars ************************ * UPDATED: Synced with commaai's openpilot - * master commit 56e343b (February 27, 2024) + * master commit 2e6b2ef (May 9, 2024) * NEW❗: sunnylink (Alpha early access) * NEW❗: Config Backup * Remotely back up and restore sunnypilot settings easily @@ -24,8 +27,20 @@ sunnypilot - 0.9.7.0 (2024-xx-xx) * RE-ENABLED: Map-based Turn Speed Control (M-TSC) for supported platforms * openpilot Longitudianl Control available cars * Custom Stock Longitudinal Control available cars +* UPDATED: Driving Model Selector v4 + * NEW❗: Driving Model additions + * North Dakota (April 29, 2024) - NDv2 + * WD40 (April 09, 2024) - WD40 + * Duck Amigo (March 18, 2024) - DA + * Recertified Herbalist (March 01, 2024) - CHLR + * Legacy Driving Models with Navigate on openpilot (NoO) support + * Includes Duck Amigo and all preceding models * UPDATED: Reset Mapbox Access Token -> Reset Access Tokens for Map Services * Reset self-service access tokens for Mapbox, Amap, and Google Maps +* UPDATED: Upstream native support for Gap Adjust Cruise +* UPDATED: Neural Network Lateral Control (NNLC) + * Due to upstream changes with platform simplifications, most platforms would match and fallback to combined platform model + * This will be updated when the new mapping of platforms are restructured (thanks @twilsconso 😉) * UI Updates * Display Metrics Below Chevron * NEW❗: Metrics is now being displayed below the chevron instead of above From 2db0a9cffaf6080782e7f412071c503cdc31d5cf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 01:24:53 -0400 Subject: [PATCH 099/575] bump to 1600 --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index ae67efc85c..492d9df53c 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit ae67efc85c8b07166c20f86cd83d8f4de4177c64 +Subproject commit 492d9df53c394ca42404adf93ba66781d0f57f94 From 8b20794fe3790e7a2962ce5cc30a1767451f9a2f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 01:37:49 -0400 Subject: [PATCH 100/575] Update CHANGELOGS.md --- CHANGELOGS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 6b0b3bf2bf..7ecad77d66 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -24,6 +24,8 @@ 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/ +* UPDATED: Hyundai CAN-FD Radar-based SCC + * Longitudinal support for CAN-FD Radar-based SCC cars * RE-ENABLED: Map-based Turn Speed Control (M-TSC) for supported platforms * openpilot Longitudianl Control available cars * Custom Stock Longitudinal Control available cars From d852e4a4f0c37eca2138b20e17efdf55f65b37e6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 02:00:38 -0400 Subject: [PATCH 101/575] FCR: sync with upstream supported car platforms --- selfdrive/car/sunnypilot_carname.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/car/sunnypilot_carname.json b/selfdrive/car/sunnypilot_carname.json index b42d6702e4..14bd5df389 100644 --- a/selfdrive/car/sunnypilot_carname.json +++ b/selfdrive/car/sunnypilot_carname.json @@ -28,6 +28,9 @@ "Ford Escape Plug-in Hybrid 2020-22": "FORD_ESCAPE_MK4", "Ford Explorer 2020-23": "FORD_EXPLORER_MK6", "Ford Explorer Hybrid 2020-23": "FORD_EXPLORER_MK6", + "Ford F-150 2022-23": "FORD_F_150_MK14", + "Ford F-150 Hybrid 2022-23": "FORD_F_150_MK14", + "Ford F-150 Lightning 2021-23": "FORD_F_150_LIGHTNING_MK1", "Ford Focus 2018": "FORD_FOCUS_MK4", "Ford Focus Hybrid 2018": "FORD_FOCUS_MK4", "Ford Kuga 2020-22": "FORD_ESCAPE_MK4", @@ -37,6 +40,8 @@ "Ford Maverick 2023-24": "FORD_MAVERICK_MK1", "Ford Maverick Hybrid 2022": "FORD_MAVERICK_MK1", "Ford Maverick Hybrid 2023-24": "FORD_MAVERICK_MK1", + "Ford Mustang Mach-E 2021-23": "FORD_MUSTANG_MACH_E_MK1", + "Ford Ranger 2024": "FORD_RANGER_MK2", "Genesis G70 2018": "GENESIS_G70", "Genesis G70 2019-21": "GENESIS_G70_2020", "Genesis G70 2022-23": "GENESIS_G70_2020", From 2833cf9d48d812cc0ad662c6565fcdf970e91b12 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 02:01:34 -0400 Subject: [PATCH 102/575] Update CHANGELOGS.md --- CHANGELOGS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 8569e02674..a25d3e0ce5 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -45,6 +45,9 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * Display Metrics Below Chevron * NEW❗: Metrics is now being displayed below the chevron instead of above * NEW❗: Display both Distance and Speed simultaneously +* Ford F-150 2022-23 support +* Ford F-150 Lightning 2021-23 support +* Ford Mustang Mach-E 2021-23 support sunnypilot - 0.9.6.1 (2024-02-27) ======================== @@ -167,9 +170,6 @@ sunnypilot - 0.9.5.2 (2023-12-07) * Hybrid 2022-23 * Plug-in Hybrid 2022-23 * NEW❗: Lane Change: When manually braking with steering engaged, turning on the turn signal will default to Nudge mode -* Ford F-150 2022-23 support -* Ford F-150 Lightning 2021-23 support -* Ford Mustang Mach-E 2021-23 support * Volkswagen MQB CC only platforms (radar or no radar) support thanks to jyoung8607! sunnypilot - 0.9.5.1 (2023-11-17) From 79252b6b5ae30ec13514ec8cb68fa5c377ec7a95 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 02:03:17 -0400 Subject: [PATCH 103/575] Update CHANGELOGS.md --- CHANGELOGS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index b8fa8dda07..3cfd274453 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -24,6 +24,8 @@ 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❗: Ford CAN-FD longitudinal + * NEW❗: Parse speed limit sign recognition from camera for certain supported platforms * RE-ENABLED: Map-based Turn Speed Control (M-TSC) for supported platforms * openpilot Longitudianl Control available cars * Custom Stock Longitudinal Control available cars @@ -110,8 +112,6 @@ sunnypilot - 0.9.6.1 (2024-02-27) sunnypilot - 0.9.5.3 (2023-12-24) ======================== -* NEW❗: Ford CAN-FD longitudinal - * NEW❗: Parse speed limit sign recognition from camera for certain supported platforms * UPDATED: Dynamic Experimental Control (DEC) * Synced with dragonpilot-community/dragonpilot:lp-dp-beta2 commit 578d38b * UPDATED: Driving Model Selector v2 From a2b0275e37db16d9b321673888a3a8e30687d1bc Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 02:07:31 -0400 Subject: [PATCH 104/575] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 221f016f07..f86b0950cc 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 221f016f073c7ba824e7f3dd427046cafe2fca62 +Subproject commit f86b0950cc257cbde1ed360ee2872d732937fd7a From b01d6d423a7b4d6c08d50ef369ea292347a4c810 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 02:09:32 -0400 Subject: [PATCH 105/575] update flag --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index f86b0950cc..afa4a376bb 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit f86b0950cc257cbde1ed360ee2872d732937fd7a +Subproject commit afa4a376bb4f145d7829037547ece3b8ceff3482 From 17b7f0bdba17ab2270675892c1b94ef4c08bcb7d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 02:10:40 -0400 Subject: [PATCH 106/575] before flag is set --- selfdrive/car/ford/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index f8cc581101..9142fc6cc6 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -32,6 +32,9 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.] + if Params().get("DongleId", encoding='utf8') in ("4fde83db16dc0802", "112e4d6e0cad05e1", "e36b272d5679115f", "24574459dd7fb3e0", "83a4e056c7072678"): + ret.spFlags |= FordFlagsSP.SP_ENHANCED_LAT_CONTROL.value + CAN = CanBus(fingerprint=fingerprint) cfgs = [get_safety_config(car.CarParams.SafetyModel.ford)] if CAN.main >= 4: @@ -49,9 +52,6 @@ class CarInterface(CarInterfaceBase): if ret.spFlags & FordFlagsSP.SP_ENHANCED_LAT_CONTROL: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_ENHANCED_LAT_CONTROL - if Params().get("DongleId", encoding='utf8') in ("4fde83db16dc0802", "112e4d6e0cad05e1", "e36b272d5679115f", "24574459dd7fb3e0", "83a4e056c7072678"): - ret.spFlags |= FordFlagsSP.SP_ENHANCED_LAT_CONTROL.value - # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1 found_ecus = [fw.ecu for fw in car_fw] if Ecu.shiftByWire in found_ecus or 0x5A in fingerprint[CAN.main] or docs: From 298da42918bd5d1bfd22388be39c689e09572c76 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 10:58:25 -0400 Subject: [PATCH 107/575] Update CHANGELOGS.md --- CHANGELOGS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 37c54b618e..e19fa899e4 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -24,8 +24,8 @@ 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❗: Hyundai CAN-FD longitudinal - * NEW❗: Parse lead info for camera-based SCC platforms +* 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 From 6c5017678794a80d28f86e97f7b5a6b4e095408c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 11:04:36 -0400 Subject: [PATCH 108/575] Flipped platform --- selfdrive/car/ford/values.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index fe3d530145..fa79d6c3e6 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -86,7 +86,7 @@ class FordCarDocs(CarDocs): @dataclass class FordPlatformConfig(PlatformConfig): - dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.STEER_ASSIST_DATA)) + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR)) def init(self): for car_docs in list(self.car_docs): @@ -100,7 +100,7 @@ class FordPlatformConfig(PlatformConfig): @dataclass class FordCANFDPlatformConfig(FordPlatformConfig): - dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', None)) + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.STEER_ASSIST_DATA)) def init(self): super().init() From 6a05e2267dbccac98d6b6aa4004034d5f16c426e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 13:03:50 -0400 Subject: [PATCH 109/575] dcam: do not log dcam-related things for unofficial devices with no dcam --- system/loggerd/encoderd.cc | 8 ++++++-- system/loggerd/loggerd.cc | 3 ++- system/loggerd/loggerd.h | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/system/loggerd/encoderd.cc b/system/loggerd/encoderd.cc index 1b45df6827..a4fb6daaab 100644 --- a/system/loggerd/encoderd.cc +++ b/system/loggerd/encoderd.cc @@ -147,15 +147,19 @@ int main(int argc, char* argv[]) { ret = util::set_core_affinity({3}); assert(ret == 0); } + + bool no_dcam = Params().getBool("DriverCameraHardwareMissing"); if (argc > 1) { std::string arg1(argv[1]); if (arg1 == "--stream") { - encoderd_thread(stream_cameras_logged); + encoderd_thread(no_dcam ? stream_cameras_logged_no_dcam : + stream_cameras_logged); } else { LOGE("Argument '%s' is not supported", arg1.c_str()); } } else { - encoderd_thread(cameras_logged); + encoderd_thread(no_dcam ? cameras_logged_no_dcam : + cameras_logged); } return 0; } diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 3c0ffc1667..b8ec45634d 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -234,8 +234,9 @@ void loggerd_thread() { logger_rotate(&s); Params().put("CurrentRoute", s.logger.routeName()); + bool no_dcam = Params().getBool("DriverCameraHardwareMissing"); std::map encoder_infos_dict; - for (const auto &cam : cameras_logged) { + for (const auto &cam : (no_dcam ? cameras_logged_no_dcam : cameras_logged)) { for (const auto &encoder_info : cam.encoder_infos) { encoder_infos_dict[encoder_info.publish_name] = encoder_info; s.max_waiting++; diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index ea288f4861..10f54c57e4 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -152,3 +152,6 @@ const LogCameraInfo stream_driver_camera_info{ const LogCameraInfo cameras_logged[] = {road_camera_info, wide_road_camera_info, driver_camera_info}; const LogCameraInfo stream_cameras_logged[] = {stream_road_camera_info, stream_wide_road_camera_info, stream_driver_camera_info}; + +const LogCameraInfo cameras_logged_no_dcam[] = {road_camera_info, wide_road_camera_info, driver_camera_info}; +const LogCameraInfo stream_cameras_logged_no_dcam[] = {stream_road_camera_info, stream_wide_road_camera_info, stream_driver_camera_info}; From 25b4ef7833b4bb8ee55364ffac90052747024905 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 May 2024 23:27:40 -0400 Subject: [PATCH 110/575] int flag fix --- selfdrive/car/ford/carstate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 59d7c54ee6..6d032ef175 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -75,7 +75,7 @@ class CarState(CarStateBase): ret.cruiseState.standstill = cp.vl["EngBrakeData"]["AccStopMde_D_Rq"] == 3 ret.accFaulted = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (1, 2) - if self.CP.carFingerprint in CANFD_CAR: + if self.CP.flags & FordFlags.CANFD: ret.cruiseState.speedLimit = self.update_traffic_signals(cp_cam) if not self.CP.openpilotLongitudinalControl: @@ -134,7 +134,7 @@ class CarState(CarStateBase): def update_traffic_signals(self, cp_cam): # TODO: Check if CAN platforms have the same signals - if self.CP.carFingerprint in CANFD_CAR: + if self.CP.flags & FordFlags.CANFD: self.v_limit = cp_cam.vl["Traffic_RecognitnData"]["TsrVLim1MsgTxt_D_Rq"] v_limit_unit = cp_cam.vl["Traffic_RecognitnData"]["TsrVlUnitMsgTxt_D_Rq"] From cab8c62e96867da9b13ba6fb4c7f5cfc456a2f35 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 00:24:28 -0400 Subject: [PATCH 111/575] Controlsd: Fix edge_toggle variable and `leadDistanceBar` parsing with SP values --- selfdrive/controls/controlsd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 32407f97a0..b2f0632598 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -209,10 +209,10 @@ class Controls: if any(ps.controlsAllowed for ps in self.sm['pandaStates']): self.state = State.enabled - def get_road_edge(self, carstate, model_v2): + def get_road_edge(self, carstate, model_v2, edge_toggle): # Lane detection by FrogAi one_blinker = carstate.leftBlinker != carstate.rightBlinker - if not self.edge_toggle: + if not edge_toggle: self.road_edge = False elif one_blinker: # Set the minimum lane threshold to 3.0 meters @@ -795,7 +795,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 From 304311e07880daf7cfb6264e18e3f0c13e895db4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 00:27:35 -0400 Subject: [PATCH 112/575] Unused --- selfdrive/controls/controlsd.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index b2f0632598..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, edge_toggle): - # Lane detection by FrogAi - one_blinker = carstate.leftBlinker != carstate.rightBlinker - if not 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""" From e234b72aa602048895ad3dec1c09daa9cdfe946e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 03:14:25 -0400 Subject: [PATCH 113/575] Hyundai CAN Longitudinal: Auto-enable radar tracks on platforms with Mando radar --- selfdrive/car/hyundai/interface.py | 9 ++++----- selfdrive/car/hyundai/radar_interface.py | 4 +--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index d2a719ccd9..f1c2d05e38 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -87,11 +87,6 @@ class CarInterface(CarInterfaceBase): ret.steerLimitTimer = 0.4 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - if candidate in (CAR.HYUNDAI_SANTA_FE_2022, CAR.HYUNDAI_SANTA_FE_HEV_2022, CAR.HYUNDAI_SANTA_FE_PHEV_2022): - if any(fw.ecu == "fwdRadar" and fw.fwVersion is not None for fw in car_fw): - ret.radarUnavailable = False - ret.spFlags |= HyundaiFlagsSP.SP_RADAR_TRACKS.value - # *** longitudinal control *** if candidate in CANFD_CAR: ret.longitudinalTuning.kpV = [0.1] @@ -129,6 +124,10 @@ class CarInterface(CarInterfaceBase): if 0x544 in fingerprint[0]: ret.spFlags |= HyundaiFlagsSP.SP_NAV_MSG.value + if ret.flags & HyundaiFlags.MANDO_RADAR and ret.radarUnavailable: + ret.spFlags |= HyundaiFlagsSP.SP_RADAR_TRACKS.value + ret.radarUnavailable = False + # *** panda safety config *** if candidate in CANFD_CAR: cfgs = [get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd), ] diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index f00b993563..e470011737 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -48,8 +48,6 @@ class RadarInterface(RadarInterfaceBase): self.radar_off_can = CP.radarUnavailable self.rcp = get_radar_can_parser(CP) - self.sp_radar_tracks = CP.spFlags & HyundaiFlagsSP.SP_RADAR_TRACKS - def update(self, can_strings): if self.radar_off_can or (self.rcp is None): return super().update(None) @@ -57,7 +55,7 @@ class RadarInterface(RadarInterfaceBase): vls = self.rcp.update_strings(can_strings) self.updated_messages.update(vls) - if self.trigger_msg not in self.updated_messages and not self.sp_radar_tracks: + if self.trigger_msg not in self.updated_messages: return None rr = self._update(self.updated_messages) From e9bd81699e526df9fa49770e7ff7713ce08cc21a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 03:17:28 -0400 Subject: [PATCH 114/575] Update CHANGELOGS.md --- CHANGELOGS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 01ae83beba..c33d0556fd 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -27,6 +27,8 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * 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 Longitudinal + * Auto-enable radar tracks on platforms with Mando radar * 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 From ae964e0162938d935bf052af546417fc472ba440 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 09:59:29 -0400 Subject: [PATCH 115/575] Honda Platform: Remove redundant code --- selfdrive/car/honda/values.py | 2 -- 1 file changed, 2 deletions(-) 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, } From afb91322e19a2110609393a37bfb86699288fed9 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 10:09:55 -0400 Subject: [PATCH 116/575] use different list --- selfdrive/car/honda/carstate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 6582ed6aea..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)) From ecaea1ef7891ff52e02b283adb3f5581a30326cf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 10:18:03 -0400 Subject: [PATCH 117/575] NoO: Only start processes if NoO-enabled models are selected --- selfdrive/manager/process_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"), From 6ca20884d224e25e41eca62aaad64606c8fd06d2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 10:45:37 -0400 Subject: [PATCH 118/575] Manager: Initialize `SidebarTemperatureOptions` --- selfdrive/manager/manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 216131df08..4a592a0022 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -101,6 +101,7 @@ def manager_init() -> None: ("OsmDbUpdatesCheck", "0"), ("OsmDownloadedDate", "0"), ("OSMDownloadProgress", "{}"), + ("SidebarTemperatureOptions", "0"), ("SunnylinkEnabled", "1"), ] if not PC: From 34e1b8f36f4fcf1111077de540e8b652137ed5e2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 11:49:19 -0400 Subject: [PATCH 119/575] Sentry: Add `sunnylink_dongle_id` tag --- selfdrive/sentry.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/selfdrive/sentry.py b/selfdrive/sentry.py index d7472e7819..0d947ac381 100644 --- a/selfdrive/sentry.py +++ b/selfdrive/sentry.py @@ -7,6 +7,7 @@ import subprocess from enum import Enum from sentry_sdk.integrations.threading import ThreadingIntegration +from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.athena.registration import UNREGISTERED_DONGLE_ID, is_registered_device @@ -70,7 +71,7 @@ def save_exception(exc_text: str) -> None: def bind_user() -> None: - dongle_id, gitname = get_properties() + dongle_id, gitname, _ = get_properties() sentry_sdk.set_user({"id": dongle_id, "ip_address": IP_ADDRESS, "name": gitname}) @@ -90,23 +91,19 @@ def set_tag(key: str, value: str) -> None: sentry_sdk.set_tag(key, value) -def get_properties() -> tuple[str, str]: +def get_properties() -> tuple[str, str, str]: params = Params() - dongle_id = params.get("DongleId", encoding='utf-8') - if dongle_id in (None, UNREGISTERED_DONGLE_ID): - hardware_serial = params.get("HardwareSerial", encoding='utf-8') - hardware_serial = "" if hardware_serial is None else hardware_serial - dongle_id = UNREGISTERED_DONGLE_ID + hardware_serial - gitname = params.get("GithubUsername", encoding='utf-8') - if gitname is None: - gitname = "" + hardware_serial: str = params.get("HardwareSerial", encoding='utf-8') or "" + gitname: str = params.get("GithubUsername", encoding='utf-8') or "" + dongle_id: str = params.get("DongleId", encoding='utf-8') or f"{UNREGISTERED_DONGLE_ID}-{hardware_serial}" + sunnylink_dongle_id: str = params.get("SunnylinkDongleId", encoding='utf-8') or UNREGISTERED_SUNNYLINK_DONGLE_ID - return dongle_id, gitname + return dongle_id, gitname, sunnylink_dongle_id def get_init() -> None: params = Params() - dongle_id, _ = get_properties() + dongle_id, _, _ = get_properties() route_name = params.get("CurrentRoute", encoding='utf-8') subprocess.call(["./bootlog", "--started"], cwd=os.path.join(BASEDIR, "system/loggerd")) with sentry_sdk.configure_scope() as scope: @@ -124,7 +121,7 @@ def init(project: SentryProject) -> bool: #env = "release" if build_metadata.tested_channel else "master" env = build_metadata.channel_type - dongle_id, gitname = get_properties() + dongle_id, gitname, sunnylink_dongle_id = get_properties() integrations = [] if project == SentryProject.SELFDRIVE: @@ -149,6 +146,7 @@ def init(project: SentryProject) -> bool: sentry_sdk.set_tag("branch", build_metadata.channel) sentry_sdk.set_tag("commit", build_metadata.openpilot.git_commit) sentry_sdk.set_tag("device", HARDWARE.get_device_type()) + sentry_sdk.set_tag("sunnylink_dongle_id", sunnylink_dongle_id) if project == SentryProject.SELFDRIVE: sentry_sdk.Hub.current.start_session() From 82a4fea539cca888c6def6f1b9dda45be4855984 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 17 May 2024 18:27:36 +0200 Subject: [PATCH 120/575] Remove sponsor check for restoring sunnypilot settings This commit removes the clause checking whether the user is a Sunnylink sponsor before proceeding with the restoration of sunnypilot settings. Now, all users, regardless of their sponsorship status, can restore their sunnypilot settings. The related prompts and UI elements have also been cleaned up accordingly. --- selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc index e765792b4d..a065d4ab43 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc @@ -107,18 +107,11 @@ 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(); } - } 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; }); connect(backup_settings, &BackupSettings::updateLabels, this, &SunnylinkPanel::updateLabels); From fadcad879340e712b9240456f68e816000417037 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 13:17:49 -0400 Subject: [PATCH 121/575] init at 0 --- selfdrive/controls/lib/latcontrol_torque.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 53c6cabc21..e59e43217b 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -209,6 +209,7 @@ class LatControlTorque(LatControl): if self.use_nn and model_good: # update past data + pitch = 0 roll = params.roll if len(llk.calibratedOrientationNED.value) > 1: pitch = self.pitch.update(llk.calibratedOrientationNED.value[1]) From 538ac6b980c7e9df5b2b1f1e394d2de2bd4a4993 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 May 2024 19:47:59 -0400 Subject: [PATCH 122/575] ui: SLC: Fix "Customize Speed Limit Control" submenu button visibility --- selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc index ce5b115b69..0413102bb5 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc @@ -423,6 +423,7 @@ void SunnypilotPanel::hideEvent(QHideEvent *event) { void SunnypilotPanel::updateToggles() { param_watcher->addParam("DynamicLaneProfile"); param_watcher->addParam("IsOffroad"); + param_watcher->addParam("EnableSlc"); if (!isVisible()) { return; @@ -456,6 +457,7 @@ void SunnypilotPanel::updateToggles() { auto nnff_toggle = toggles["NNFF"]; auto custom_stock_long_param = params.getBool("CustomStockLong"); + auto enable_slc_param = params.getBool("EnableSlc"); auto v_tsc = toggles["TurnVisionControl"]; auto m_tsc = toggles["TurnSpeedControl"]; auto reverse_acc = toggles["ReverseAccChange"]; @@ -508,6 +510,9 @@ void SunnypilotPanel::updateToggles() { m_tsc->setEnabled(true); reverse_acc->setEnabled(true); slc_toggle->setEnabled(true); + if (!slcSettings->isVisible() && enable_slc_param) { + slcSettings->setVisible(true); + } } else { v_tsc->setEnabled(false); m_tsc->setEnabled(false); From 536c73d00b101e0367dbe347038cd601dc2786ed Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 18 May 2024 10:40:12 -0400 Subject: [PATCH 123/575] ui: Visuals: Fix freezing --- selfdrive/manager/manager.py | 1 + selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 4a592a0022..8ff820cb7b 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -70,6 +70,7 @@ def manager_init() -> None: ("LastSpeedLimitSignTap", "0"), ("LkasToggle", "0"), ("MadsIconToggle", "1"), + ("MapboxFullScreen", "0"), ("MapdVersion", f"{VERSION}"), ("MaxTimeOffroad", "9"), ("NNFF", "0"), diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc index 6d2b1ebff6..46bd84b6d9 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc @@ -60,7 +60,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { { "MapboxFullScreen", tr("Navigation: Display in Full Screen"), - QString(tr("Enable this will display the built-in navigation in full screen.
To switch back to driving view, tap on the border edge.")), + tr("Enable this will display the built-in navigation in full screen.
To switch back to driving view, tap on the border edge."), "../assets/offroad/icon_blank.png", }, { From 422773bb9f9f3850cd12f70c97dfcd4311b3ddb5 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 18 May 2024 18:50:32 +0200 Subject: [PATCH 124/575] Refactor model generation condition in modeld.py Fixed the conditional statement checking the model generation and custom model status in modeld.py. Now, the navigation features and instructions are only updated if a custom model is being used and the model generation is not 4. --- selfdrive/modeld/modeld.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 40e071294a..70777de099 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -133,7 +133,7 @@ class ModelState: self.inputs['traffic_convention'][:] = inputs['traffic_convention'] if not (self.custom_model and self.model_gen == 1): self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] - if not (self.custom_model and self.model_gen == 4): + if self.custom_model and self.model_gen != 4: self.inputs['nav_features'][:] = inputs['nav_features'] self.inputs['nav_instructions'][:] = inputs['nav_instructions'] From 47fd6fca3d6acc3a6bd21ee3e5a61bfe63e3a364 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 18 May 2024 19:12:31 +0200 Subject: [PATCH 125/575] Add default values for DrivingModel parameters The latest change includes additional default settings for the "CustomDrivingModel" and "DrivingModelGeneration" parameters. These settings were added to the default_params list within the manager.py script to ensure correct functioning under default conditions. --- selfdrive/manager/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 4a592a0022..a527d6d975 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -103,6 +103,8 @@ def manager_init() -> None: ("OSMDownloadProgress", "{}"), ("SidebarTemperatureOptions", "0"), ("SunnylinkEnabled", "1"), + ("CustomDrivingModel", "0"), + ("DrivingModelGeneration", "4"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) From 80bf5a24caca32d20d0bdbde3774218e9af5ae46 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 18 May 2024 21:52:53 +0200 Subject: [PATCH 126/575] Improve progress display for model downloads The progress indication for downloading of driving, navigation, and metadata models has been improved. We now display "PENDING" if the progress is 0.01 or less instead of showing 0%. This change gives a more intuitive indication to the users during model downloads. --- .../offroad/sunnypilot/software_settings_sp.cc | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc b/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc index eee55f119a..bbe8572797 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc @@ -128,8 +128,9 @@ void SoftwarePanelSP::HandleModelDownloadProgressReport() { // Driving model status if (isDownloadingModel()) { - description += QString(tr("Downloading Driving model") + " [%1]... (%2%)") - .arg(drivingModelName, QString::number(modelDownloadProgress.value_or(0.0), 'f', 2)); + auto modelProgress = modelDownloadProgress.value_or(0.0); + auto progressText = modelProgress <= 0.01 ? tr("PENDING")+"..." : QString::number(modelProgress, 'f', 2) + "%"; + description += QString(tr("Downloading Driving model") + " [%1]... (%2)").arg(drivingModelName, progressText); } else { if (modelFromCache) drivingModelName += QString(" " + tr("(CACHED)")); description += QString(tr("Driving model") + " [%1] " + tr("downloaded")).arg(drivingModelName); @@ -137,9 +138,10 @@ void SoftwarePanelSP::HandleModelDownloadProgressReport() { // Navigation model status if (isDownloadingNavModel() && !navModelName.isEmpty()) { + auto navModelProgress = navModelDownloadProgress.value_or(0.0); + auto progressText = navModelProgress <= 0.01 ? tr("PENDING")+"..." : QString::number(navModelProgress, 'f', 2) + "%"; if (!description.isEmpty()) description += "\n"; // Add newline if driving model status is already appended - description += QString(tr("Downloading Navigation model") + " [%1]... (%2%)") - .arg(navModelName, QString::number(navModelDownloadProgress.value_or(0.0), 'f', 2)); + description += QString(tr("Downloading Navigation model") + " [%1]... (%2)").arg(navModelName, progressText); } else if (!navModelName.isEmpty()) { if (navModelFromCache) navModelName += QString(" " + tr("(CACHED)")); if (!description.isEmpty()) description += "\n"; // Ensure newline separation @@ -148,9 +150,10 @@ void SoftwarePanelSP::HandleModelDownloadProgressReport() { // Metadata status if (isDownloadingMetadata() && !metadataName.isEmpty()) { + auto metadataProgress = metadataDownloadProgress.value_or(0.0); + auto progressText = metadataProgress <= 0.01 ? tr("PENDING")+"..." : QString::number(metadataProgress, 'f', 2) + "%"; if (!description.isEmpty()) description += "\n"; - description += QString(tr("Downloading Metadata model") + " [%1]... (%2%)") - .arg(metadataName, QString::number(metadataDownloadProgress.value_or(0.0), 'f', 2)); + description += QString(tr("Downloading Metadata model") + " [%1]... (%2)").arg(metadataName, progressText); } else if (!metadataName.isEmpty()) { if (metadataFromCache) metadataName += QString(" " + tr("(CACHED)")); if (!description.isEmpty()) description += "\n"; @@ -236,6 +239,9 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { modelDownloadProgress = 0.01; metadataDownloadProgress = 0.01; + // Sunny wants it responsive... Responsive it is... + HandleModelDownloadProgressReport(); + // Start the download, we download the other models on emit of downloadComplete if (params.get("DrivingModelGeneration") != selectedModelToDownload->generation.toStdString()) showResetParamsDialog(); From 8904cf731a1fb93e87a3e8b5ad2b7e49382c94ee Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 18 May 2024 22:29:11 +0200 Subject: [PATCH 127/575] Introduce ModelCapabilities class for model feature handling This commit introduces a new class, ModelCapabilities, to manage model features. It changes initializations and conditional statements to use this new class, improving code readability and simplicity. The new class provides an easier and clearer way to check which features are enabled, reducing the complexity associated with generation-dependent feature handling. --- release/files_common | 1 + selfdrive/controls/controlsd.py | 4 ++- selfdrive/controls/lib/desire_helper.py | 4 ++- selfdrive/controls/plannerd.py | 4 ++- selfdrive/modeld/model_capabilities.py | 19 +++++++++++ selfdrive/modeld/modeld.py | 45 +++++++++++++------------ 6 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 selfdrive/modeld/model_capabilities.py diff --git a/release/files_common b/release/files_common index 131d61f2c0..0ec43716b0 100644 --- a/release/files_common +++ b/release/files_common @@ -365,6 +365,7 @@ selfdrive/modeld/navmodeld.py selfdrive/modeld/dmonitoringmodeld.py selfdrive/modeld/constants.py selfdrive/modeld/modeld +selfdrive/modeld/model_capabilities.py selfdrive/modeld/models/__init__.py selfdrive/modeld/models/*.pxd diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 48eddc7b84..a0da74f930 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -31,6 +31,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, S from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.longcontrol import LongControl from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel +from openpilot.selfdrive.modeld.model_capabilities import ModelCapabilities from openpilot.selfdrive.sunnypilot import get_model_generation from openpilot.system.hardware import HARDWARE @@ -179,7 +180,8 @@ class Controls: self.process_not_running = False self.custom_model, self.model_gen = get_model_generation(self.params) - self.model_use_lateral_planner = self.custom_model and self.model_gen == 1 + model_capabilities = ModelCapabilities.get_by_gen(self.model_gen) + self.model_use_lateral_planner = self.custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution self.can_log_mono_time = 0 diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index bd4decbb80..6cac8a70e2 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -3,6 +3,7 @@ from openpilot.common.conversions import Conversions as CV from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.controls.lib.drive_helpers import get_road_edge +from openpilot.selfdrive.modeld.model_capabilities import ModelCapabilities from openpilot.selfdrive.sunnypilot import get_model_generation LaneChangeState = log.LaneChangeState @@ -63,7 +64,8 @@ class DesireHelper: self.lane_change_bsm_delay = self.param_s.get_bool("AutoLaneChangeBsmDelay") self.custom_model, self.model_gen = get_model_generation(self.param_s) - self.model_use_lateral_planner = self.custom_model and self.model_gen == 1 + model_capabilities = ModelCapabilities.get_by_gen(self.model_gen) + self.model_use_lateral_planner = self.custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution def read_param(self): self.edge_toggle = self.param_s.get_bool("RoadEdge") diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index a9e8cabca7..588e044c3f 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -8,6 +8,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner +from openpilot.selfdrive.modeld.model_capabilities import ModelCapabilities from openpilot.selfdrive.sunnypilot import get_model_generation import cereal.messaging as messaging @@ -47,7 +48,8 @@ def plannerd_thread(): longitudinal_planner = LongitudinalPlanner(CP) custom_model, model_gen = get_model_generation(params) - model_use_lateral_planner = custom_model and model_gen == 1 + model_capabilities = ModelCapabilities.get_by_gen(model_gen) + model_use_lateral_planner = custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution lateral_planner = LateralPlanner(CP, debug=debug_mode, model_use_lateral_planner=model_use_lateral_planner) lateral_planner_svs = ['lateralPlanDEPRECATED', 'lateralPlanSPDEPRECATED'] diff --git a/selfdrive/modeld/model_capabilities.py b/selfdrive/modeld/model_capabilities.py new file mode 100644 index 0000000000..1d12f98fd5 --- /dev/null +++ b/selfdrive/modeld/model_capabilities.py @@ -0,0 +1,19 @@ +from enum import IntEnum, auto + + +class ModelCapabilities(IntEnum): + Default = auto() + NoO = auto() + LateralPlannerSolution = auto() + DesiredCurvature = auto() + + @staticmethod + def get_by_gen(gen): + if gen == 1: + return ModelCapabilities.Default | ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO + elif gen in (2, 3): + return ModelCapabilities.Default | ModelCapabilities.DesiredCurvature | ModelCapabilities.NoO + elif gen == 4: + return ModelCapabilities.Default | ModelCapabilities.DesiredCurvature + else: + return ModelCapabilities.Default diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 70777de099..863d596927 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -20,6 +20,7 @@ from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive import sentry from openpilot.selfdrive.car.car_helpers import get_demo_car_params from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper +from openpilot.selfdrive.modeld.model_capabilities import ModelCapabilities from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState @@ -61,6 +62,7 @@ class ModelState: def __init__(self, context: CLContext): self.param_s = Params() self.custom_model, self.model_gen = get_model_generation(self.param_s) + self.model_capabilities = ModelCapabilities.get_by_gen(self.model_gen) self.frame = ModelFrame(context) self.wide_frame = ModelFrame(context) self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) @@ -69,17 +71,17 @@ class ModelState: 'prev_desired_curv': np.zeros(ModelConstants.PREV_DESIRED_CURV_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), } _inputs_2 = {} - if self.custom_model and self.model_gen != 0: - if self.model_gen == 1: + if self.custom_model: + if self.model_capabilities & ModelCapabilities.LateralPlannerSolution: _inputs = { 'lat_planner_state': np.zeros(ModelConstants.LAT_PLANNER_STATE_LEN, dtype=np.float32), } - if self.model_gen == 2: + if self.model_capabilities & ModelCapabilities.DesiredCurvature: _inputs = { 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), 'prev_desired_curvs': np.zeros(ModelConstants.PREV_DESIRED_CURVS_LEN, dtype=np.float32), } - if self.model_gen != 4: + if self.model_capabilities & ModelCapabilities.NoO: _inputs_2 = { 'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32), @@ -93,7 +95,7 @@ class ModelState: 'features_buffer': np.zeros(ModelConstants.HISTORY_BUFFER_LEN * ModelConstants.FEATURE_LEN, dtype=np.float32), } - if self.custom_model and self.model_gen != 0: + if self.custom_model: _model_name = self.param_s.get("DrivingModelText", encoding="utf8") _model_paths = {ModelRunner.THNEED: f"{CUSTOM_MODEL_PATH}/supercombo-{_model_name}.thneed"} _metadata_name = self.param_s.get("DrivingModelMetadataText", encoding="utf8") @@ -131,9 +133,9 @@ class ModelState: self.prev_desire[:] = inputs['desire'] self.inputs['traffic_convention'][:] = inputs['traffic_convention'] - if not (self.custom_model and self.model_gen == 1): + if not (self.custom_model and self.model_capabilities & ModelCapabilities.LateralPlannerSolution): self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] - if self.custom_model and self.model_gen != 4: + if self.custom_model and self.model_capabilities & ModelCapabilities.NoO: self.inputs['nav_features'][:] = inputs['nav_features'] self.inputs['nav_instructions'][:] = inputs['nav_instructions'] @@ -150,11 +152,11 @@ class ModelState: self.inputs['features_buffer'][:-ModelConstants.FEATURE_LEN] = self.inputs['features_buffer'][ModelConstants.FEATURE_LEN:] self.inputs['features_buffer'][-ModelConstants.FEATURE_LEN:] = outputs['hidden_state'][0, :] - if self.custom_model and self.model_gen != 0: - if self.model_gen == 1: + if self.custom_model: + if self.model_capabilities & ModelCapabilities.LateralPlannerSolution: self.inputs['lat_planner_state'][2] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 2]) self.inputs['lat_planner_state'][3] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 3]) - elif self.model_gen == 2: + elif self.model_capabilities & ModelCapabilities.DesiredCurvature: self.inputs['prev_desired_curvs'][:-1] = self.inputs['prev_desired_curvs'][1:] self.inputs['prev_desired_curvs'][-1] = outputs['desired_curvature'][0, 0] else: @@ -202,10 +204,11 @@ def main(demo=False): params = Params() custom_model, model_gen = get_model_generation(params) + model_capabilities = ModelCapabilities.get_by_gen(model_gen) # messaging extended_svs = ["lateralPlanDEPRECATED", "lateralPlanSPDEPRECATED"] - if custom_model and model_gen != 4: + if custom_model and model_capabilities & ModelCapabilities.NoO: extended_svs += ["navModelDEPRECATED", "navInstruction"] pm = PubMaster(["modelV2", "modelV2SP", "cameraOdometry"]) sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"] + extended_svs) @@ -221,9 +224,9 @@ def main(demo=False): model_transform_main = np.zeros((3, 3), dtype=np.float32) model_transform_extra = np.zeros((3, 3), dtype=np.float32) live_calib_seen = False - if custom_model and model_gen == 1: + if custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution: driving_style = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32) - if custom_model and model_gen != 4: + if custom_model and model_capabilities & ModelCapabilities.NoO: nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) buf_main, buf_extra = None, None @@ -278,10 +281,10 @@ def main(demo=False): meta_extra = meta_main sm.update(0) - desire = sm["lateralPlanDEPRECATED"].desire.raw if custom_model and model_gen == 1 else DH.desire + desire = sm["lateralPlanDEPRECATED"].desire.raw if custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution else DH.desire is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId - if not (custom_model and model_gen == 1): + if not (custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution): lateral_control_params = np.array([sm["carState"].vEgo, steer_delay], dtype=np.float32) if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) @@ -299,7 +302,7 @@ def main(demo=False): timestamp_llk = 0 nav_enabled = False - if custom_model and model_gen != 4: + if custom_model and model_capabilities & ModelCapabilities.NoO: # Enable/disable nav features timestamp_llk = sm["navModelDEPRECATED"].locationMonoTime nav_valid = sm.valid["navModelDEPRECATED"] # and (nanos_since_boot() - timestamp_llk < 1e9) @@ -337,7 +340,7 @@ def main(demo=False): if prepare_only: cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames") - if custom_model and model_gen == 1: + if custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution: _inputs = { 'driving_style': driving_style } @@ -345,7 +348,7 @@ def main(demo=False): _inputs = { 'lateral_control_params': lateral_control_params } - if custom_model and model_gen != 4: + if custom_model and model_capabilities & ModelCapabilities.NoO: _inputs_2 = { 'nav_features': nav_features, 'nav_instructions': nav_instructions @@ -372,9 +375,9 @@ def main(demo=False): posenet_send = messaging.new_message('cameraOdometry') fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, live_calib_seen, - custom_model and model_gen == 1, custom_model and model_gen != 4) + custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution, custom_model and model_capabilities & ModelCapabilities.NoO) - if not (custom_model and model_gen == 1): + if not (custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution): desire_state = modelv2_send.modelV2.meta.desireState l_lane_change_prob = desire_state[log.Desire.laneChangeLeft] r_lane_change_prob = desire_state[log.Desire.laneChangeRight] @@ -389,7 +392,7 @@ def main(demo=False): modelv2_sp_send = messaging.new_message('modelV2SP') modelv2_sp_send.valid = True - if not (custom_model and model_gen == 1): + if not (custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution): modelv2_sp_send.modelV2SP.laneChangePrev = DH.prev_lane_change modelv2_sp_send.modelV2SP.laneChangeEdgeBlock = lat_plan_sp.laneChangeEdgeBlockDEPRECATED pm.send('modelV2SP', modelv2_sp_send) From 34130bb7cb476385ca66bee8af89540c137c4957 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 18 May 2024 23:32:07 +0200 Subject: [PATCH 128/575] Check model capabilities when using custom model The use of a custom model is now conditional to non-default model capabilities. This change ensures that a custom model will only be used if the model capabilities are different from the default. This prevents potential issues with using custom models that don't have any distinct capabilities or are not suitable for the current application. --- selfdrive/modeld/modeld.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 863d596927..2ca2e1cfb5 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -71,7 +71,7 @@ class ModelState: 'prev_desired_curv': np.zeros(ModelConstants.PREV_DESIRED_CURV_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), } _inputs_2 = {} - if self.custom_model: + if self.custom_model and self.model_capabilities != ModelCapabilities.Default: if self.model_capabilities & ModelCapabilities.LateralPlannerSolution: _inputs = { 'lat_planner_state': np.zeros(ModelConstants.LAT_PLANNER_STATE_LEN, dtype=np.float32), @@ -95,7 +95,7 @@ class ModelState: 'features_buffer': np.zeros(ModelConstants.HISTORY_BUFFER_LEN * ModelConstants.FEATURE_LEN, dtype=np.float32), } - if self.custom_model: + if self.custom_model and self.model_capabilities != ModelCapabilities.Default: _model_name = self.param_s.get("DrivingModelText", encoding="utf8") _model_paths = {ModelRunner.THNEED: f"{CUSTOM_MODEL_PATH}/supercombo-{_model_name}.thneed"} _metadata_name = self.param_s.get("DrivingModelMetadataText", encoding="utf8") @@ -152,7 +152,7 @@ class ModelState: self.inputs['features_buffer'][:-ModelConstants.FEATURE_LEN] = self.inputs['features_buffer'][ModelConstants.FEATURE_LEN:] self.inputs['features_buffer'][-ModelConstants.FEATURE_LEN:] = outputs['hidden_state'][0, :] - if self.custom_model: + if self.custom_model and self.model_capabilities != ModelCapabilities.Default: if self.model_capabilities & ModelCapabilities.LateralPlannerSolution: self.inputs['lat_planner_state'][2] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 2]) self.inputs['lat_planner_state'][3] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 3]) From 68ae65bfa996a62b4e940e83dbdce74a34500ee8 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 19 May 2024 16:53:01 +0200 Subject: [PATCH 129/575] Update model generation conditions in modeld.py and map.cc This commit updates the conditions for the model generation within the modeld.py and map.cc files. Previously, the code was specifically excluding model generation 4, but now it excludes model generations 0 and 4. This might affect features like navigation and model parameters processing. --- selfdrive/modeld/modeld.py | 14 +++++++------- selfdrive/ui/qt/maps/map.cc | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 70777de099..2208fa42ea 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -79,7 +79,7 @@ class ModelState: 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), 'prev_desired_curvs': np.zeros(ModelConstants.PREV_DESIRED_CURVS_LEN, dtype=np.float32), } - if self.model_gen != 4: + if self.model_gen not in (0, 4): _inputs_2 = { 'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32), @@ -133,7 +133,7 @@ class ModelState: self.inputs['traffic_convention'][:] = inputs['traffic_convention'] if not (self.custom_model and self.model_gen == 1): self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] - if self.custom_model and self.model_gen != 4: + if self.custom_model and self.model_gen not in (0, 4): self.inputs['nav_features'][:] = inputs['nav_features'] self.inputs['nav_instructions'][:] = inputs['nav_instructions'] @@ -205,7 +205,7 @@ def main(demo=False): # messaging extended_svs = ["lateralPlanDEPRECATED", "lateralPlanSPDEPRECATED"] - if custom_model and model_gen != 4: + if custom_model and model_gen not in (0, 4): extended_svs += ["navModelDEPRECATED", "navInstruction"] pm = PubMaster(["modelV2", "modelV2SP", "cameraOdometry"]) sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"] + extended_svs) @@ -223,7 +223,7 @@ def main(demo=False): live_calib_seen = False if custom_model and model_gen == 1: driving_style = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32) - if custom_model and model_gen != 4: + if custom_model and model_gen not in (0, 4): nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) buf_main, buf_extra = None, None @@ -299,7 +299,7 @@ def main(demo=False): timestamp_llk = 0 nav_enabled = False - if custom_model and model_gen != 4: + if custom_model and model_gen not in (0, 4): # Enable/disable nav features timestamp_llk = sm["navModelDEPRECATED"].locationMonoTime nav_valid = sm.valid["navModelDEPRECATED"] # and (nanos_since_boot() - timestamp_llk < 1e9) @@ -345,7 +345,7 @@ def main(demo=False): _inputs = { 'lateral_control_params': lateral_control_params } - if custom_model and model_gen != 4: + if custom_model and model_gen not in (0, 4): _inputs_2 = { 'nav_features': nav_features, 'nav_instructions': nav_instructions @@ -372,7 +372,7 @@ def main(demo=False): posenet_send = messaging.new_message('cameraOdometry') fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, live_calib_seen, - custom_model and model_gen == 1, custom_model and model_gen != 4) + custom_model and model_gen == 1, custom_model and model_gen not in (0, 4)) if not (custom_model and model_gen == 1): desire_state = modelv2_send.modelV2.meta.desireState diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 48ff003ab3..ab6c9c65f7 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -161,7 +161,7 @@ void MapWindow::updateState(const UIState &s) { const SubMaster &sm = *(s.sm); update(); - if (sm.updated("modelV2") && uiState()->scene.custom_driving_model && uiState()->scene.driving_model_gen != 4) { + if (sm.updated("modelV2") && uiState()->scene.custom_driving_model && uiState()->scene.driving_model_gen != 4 && uiState()->scene.driving_model_gen != 0) { // set path color on change, and show map on rising edge of navigate on openpilot auto car_control = sm["carControl"].getCarControl(); bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() && From 62af3b760c833afa93618dd413d26d91cf0373ea Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 19 May 2024 17:37:23 +0200 Subject: [PATCH 130/575] Refactor model capabilities and update model inputs Expanded the ModelCapabilities class to better represent different model generations and their unique features. The alterations split the "DesiredCurvature" capability into two versions, "DesiredCurvatureV1" and "DesiredCurvatureV2", which have different input parameters. This change also involves updates in the "modeld.py" where conditions checking for "DesiredCurvature" are updated to check for the correct versions. --- selfdrive/modeld/model_capabilities.py | 30 ++++++++++++++++++++++---- selfdrive/modeld/modeld.py | 7 +++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/selfdrive/modeld/model_capabilities.py b/selfdrive/modeld/model_capabilities.py index 1d12f98fd5..1008fceca4 100644 --- a/selfdrive/modeld/model_capabilities.py +++ b/selfdrive/modeld/model_capabilities.py @@ -2,18 +2,40 @@ from enum import IntEnum, auto class ModelCapabilities(IntEnum): + """Model capabilities for different generations of models.""" + Default = auto() + """Default capability, used for the prebuilt model.""" + NoO = auto() + """Navigation on Openpilot capability, used for models support navigation.""" + LateralPlannerSolution = auto() - DesiredCurvature = auto() + """LateralPlannerSolution capability, used for models that support the lateral planner solution.""" + + DesiredCurvatureV1 = auto() + """ + DesiredCurvatureV1 capability: This capability is used for models that support the desired curvature. + In this version, 'prev_desired_curvs' is used as the input for the 'desired_curvature' output. + """ + + DesiredCurvatureV2 = auto() + """ + DesiredCurvatureV2 capability: This capability is used for models that support the desired curvature. + In V2, 'prev_desired_curv' (no plural) is used as the input for the same 'desired_curvature' output. + """ @staticmethod def get_by_gen(gen): + """Returns the model capabilities for a given generation.""" if gen == 1: return ModelCapabilities.Default | ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO - elif gen in (2, 3): - return ModelCapabilities.Default | ModelCapabilities.DesiredCurvature | ModelCapabilities.NoO + elif gen == 2: + return ModelCapabilities.Default | ModelCapabilities.DesiredCurvatureV1 | ModelCapabilities.NoO + elif gen == 3: + return ModelCapabilities.Default | ModelCapabilities.DesiredCurvatureV2 | ModelCapabilities.NoO elif gen == 4: - return ModelCapabilities.Default | ModelCapabilities.DesiredCurvature + return ModelCapabilities.Default | ModelCapabilities.DesiredCurvatureV2 else: + # Default model is meant to represent the capabilities of the prebuilt model. return ModelCapabilities.Default diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 2ca2e1cfb5..2b8d05984e 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -66,6 +66,7 @@ class ModelState: self.frame = ModelFrame(context) self.wide_frame = ModelFrame(context) self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) + # Default model, and as of time of writing, this model uses DesiredCurvatureV2 _inputs = { 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), 'prev_desired_curv': np.zeros(ModelConstants.PREV_DESIRED_CURV_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), @@ -76,7 +77,7 @@ class ModelState: _inputs = { 'lat_planner_state': np.zeros(ModelConstants.LAT_PLANNER_STATE_LEN, dtype=np.float32), } - if self.model_capabilities & ModelCapabilities.DesiredCurvature: + if self.model_capabilities & ModelCapabilities.DesiredCurvatureV1: _inputs = { 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), 'prev_desired_curvs': np.zeros(ModelConstants.PREV_DESIRED_CURVS_LEN, dtype=np.float32), @@ -156,10 +157,10 @@ class ModelState: if self.model_capabilities & ModelCapabilities.LateralPlannerSolution: self.inputs['lat_planner_state'][2] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 2]) self.inputs['lat_planner_state'][3] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 3]) - elif self.model_capabilities & ModelCapabilities.DesiredCurvature: + elif self.model_capabilities & ModelCapabilities.DesiredCurvatureV1: self.inputs['prev_desired_curvs'][:-1] = self.inputs['prev_desired_curvs'][1:] self.inputs['prev_desired_curvs'][-1] = outputs['desired_curvature'][0, 0] - else: + else: # Default model, and as of time of writing, this model uses DesiredCurvatureV2 self.inputs['prev_desired_curv'][:-ModelConstants.PREV_DESIRED_CURV_LEN] = self.inputs['prev_desired_curv'][ModelConstants.PREV_DESIRED_CURV_LEN:] self.inputs['prev_desired_curv'][-ModelConstants.PREV_DESIRED_CURV_LEN:] = outputs['desired_curvature'][0, :] return outputs From b659a979ed65bf9a5975fe91c4c7065dc34ffd1e Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 19 May 2024 21:50:10 +0200 Subject: [PATCH 131/575] Change base class of ModelCapabilities from IntEnum to IntFlag In model_capabilities.py, the base class of ModelCapabilities has been changed from IntEnum to IntFlag to better suit requirements. This changes how the enumeration values behave and interact. --- selfdrive/modeld/model_capabilities.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/model_capabilities.py b/selfdrive/modeld/model_capabilities.py index 1008fceca4..dea0143a8b 100644 --- a/selfdrive/modeld/model_capabilities.py +++ b/selfdrive/modeld/model_capabilities.py @@ -1,7 +1,7 @@ -from enum import IntEnum, auto +from enum import IntFlag, auto -class ModelCapabilities(IntEnum): +class ModelCapabilities(IntFlag): """Model capabilities for different generations of models.""" Default = auto() From 3551e7c4bce6f7b4b72519c5b2e2f20f408449f6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 May 2024 13:29:34 -0400 Subject: [PATCH 132/575] ui: ButtonParamControl: Handle out of bound param --- .../ui/qt/offroad/sunnypilot/visuals_settings.cc | 2 +- selfdrive/ui/qt/widgets/controls.h | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc index 6d2b1ebff6..46bd84b6d9 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc @@ -60,7 +60,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { { "MapboxFullScreen", tr("Navigation: Display in Full Screen"), - QString(tr("Enable this will display the built-in navigation in full screen.
To switch back to driving view, tap on the border edge.")), + tr("Enable this will display the built-in navigation in full screen.
To switch back to driving view, tap on the border edge."), "../assets/offroad/icon_blank.png", }, { diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 860cb7ad2b..49e261f56c 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -243,7 +243,7 @@ class ButtonParamControl : public SPAbstractControl { Q_OBJECT public: ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const std::vector &button_texts, const int minimum_button_width = 300) : SPAbstractControl(title, desc, icon) { + const std::vector &button_texts, const int minimum_button_width = 300) : SPAbstractControl(title, desc, icon), button_texts(button_texts) { const QString style = R"( QPushButton { border-radius: 20px; @@ -306,6 +306,14 @@ public: void refresh() { int value = atoi(params.get(key).c_str()); + + if (value >= button_texts.size()) { + value = button_texts.size() - 1; + } + if (value < button_texts.size()) { + value = 0; + } + button_group->button(value)->setChecked(true); } @@ -357,6 +365,7 @@ private: std::string key; Params params; QButtonGroup *button_group; + std::vector button_texts; bool button_group_enabled = true; }; From f02c4007d4b2313f0cdda48984f65477cbcd202f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 May 2024 17:50:35 -0400 Subject: [PATCH 133/575] Hyundai CAN Longitudinal: Lenient parsing for auto-enabled radar configs --- selfdrive/car/hyundai/radar_interface.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index e470011737..71715ba687 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -48,6 +48,8 @@ class RadarInterface(RadarInterfaceBase): self.radar_off_can = CP.radarUnavailable self.rcp = get_radar_can_parser(CP) + self.sp_radar_tracks = CP.spFlags & HyundaiFlagsSP.SP_RADAR_TRACKS + def update(self, can_strings): if self.radar_off_can or (self.rcp is None): return super().update(None) @@ -70,7 +72,7 @@ class RadarInterface(RadarInterfaceBase): errors = [] - if not self.rcp.can_valid: + if not self.rcp.can_valid and not self.sp_radar_tracks: errors.append("canError") ret.errors = errors From a6687d8193b035b88202d779dbabfb37a1f86161 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 24 May 2024 00:57:37 -0400 Subject: [PATCH 134/575] return empty rr if can error due to missing tracks --- selfdrive/car/hyundai/radar_interface.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 71715ba687..d6f964fc09 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -72,8 +72,11 @@ class RadarInterface(RadarInterfaceBase): errors = [] - if not self.rcp.can_valid and not self.sp_radar_tracks: - errors.append("canError") + if not self.rcp.can_valid: + if self.sp_radar_tracks: + return ret + else: + errors.append("canError") ret.errors = errors if self.enhanced_scc or self.camera_scc: From 6af6642183a1af7d83c543604fc23a1b13ffb890 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 24 May 2024 01:00:31 -0400 Subject: [PATCH 135/575] fix --- selfdrive/manager/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index d5be446764..a286f7b38e 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -26,7 +26,7 @@ def manager_init() -> None: build_metadata = get_build_metadata() params = Params() - params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START)("CustomDrivingModel", "0"), + params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION) params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION) if build_metadata.release_channel: From a5e5e82d0d34cea516f0dbba22ba95bd9b477ffb Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 24 May 2024 01:56:36 -0400 Subject: [PATCH 136/575] only set flag to false if no radar dbc assigned --- selfdrive/car/hyundai/interface.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index f1c2d05e38..79eaf88384 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -78,7 +78,6 @@ class CarInterface(CarInterfaceBase): if 0x2AB in fingerprint[0]: ret.spFlags |= HyundaiFlagsSP.SP_ENHANCED_SCC.value - ret.radarUnavailable = False if 0x53E in fingerprint[2]: ret.spFlags |= HyundaiFlagsSP.SP_LKAS12.value @@ -94,14 +93,12 @@ class CarInterface(CarInterfaceBase): 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] ret.experimentalLongitudinalAvailable = candidate not in (UNSUPPORTED_LONGITUDINAL_CAR | NON_SCC_CAR) if candidate in CAMERA_SCC_CAR: ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value - ret.radarUnavailable = False ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable ret.pcmCruise = not ret.openpilotLongitudinalControl @@ -112,6 +109,10 @@ class CarInterface(CarInterfaceBase): ret.longitudinalActuatorDelayLowerBound = 0.5 ret.longitudinalActuatorDelayUpperBound = 0.5 + if DBC[ret.carFingerprint]["radar"] is None: + if ret.spFlags & (HyundaiFlagsSP.SP_ENHANCED_SCC | HyundaiFlagsSP.SP_CAMERA_SCC_LEAD): + ret.radarUnavailable = False + # *** feature detection *** if candidate in CANFD_CAR: ret.enableBsm = 0x1e5 in fingerprint[CAN.ECAN] From 4e3a3cb72d3fba1e95435fcbf137efeb7b6a26c0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 24 May 2024 02:04:27 -0400 Subject: [PATCH 137/575] return RadarInterface superclass --- selfdrive/car/hyundai/radar_interface.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index d6f964fc09..2d822befd4 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -63,6 +63,12 @@ class RadarInterface(RadarInterfaceBase): rr = self._update(self.updated_messages) self.updated_messages.clear() + radar_error = [] + if rr is not None: + radar_error = rr.errors + if list(radar_error) and self.sp_radar_tracks: + return super().update(None) + return rr def _update(self, updated_messages): @@ -73,10 +79,7 @@ class RadarInterface(RadarInterfaceBase): errors = [] if not self.rcp.can_valid: - if self.sp_radar_tracks: - return ret - else: - errors.append("canError") + errors.append("canError") ret.errors = errors if self.enhanced_scc or self.camera_scc: From 060b8f084d47e9900eaa4f262dcf4f25f073fed1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 26 May 2024 21:26:39 +0200 Subject: [PATCH 138/575] Enable log_handler in sunnylinkd.py The 'log_handler' thread in sunnylinkd.py was previously commented out and has now been enabled for execution. This update will allow the 'log_handler' to perform its task in the thread execution sequence. --- selfdrive/athena/sunnylinkd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index 9c26dd42a1..b69bc7a762 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -11,7 +11,7 @@ import threading import time from openpilot.selfdrive.athena.athenad import ws_send, jsonrpc_handler, \ - recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage + recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler from jsonrpc import dispatcher from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection) @@ -37,7 +37,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: threading.Thread(target=ws_ping, args=(ws, end_event), name='ws_ping'), threading.Thread(target=ws_queue, args=(end_event,), name='ws_queue'), # threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'), - # threading.Thread(target=log_handler, args=(end_event,), name='log_handler'), + threading.Thread(target=log_handler, args=(end_event,), name='log_handler'), # threading.Thread(target=stat_handler, args=(end_event,), name='stat_handler'), ] + [ threading.Thread(target=jsonrpc_handler, args=(end_event,), name=f'worker_{x}') From a366d5b873c709d244db20c374d853bc32173ad9 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 26 May 2024 22:04:06 +0200 Subject: [PATCH 139/575] Add custom attribute name for Sunnylink logs The commit introduces a custom attribute name for Sunnylink log entries. The custom attribute name 'sunnylink.user.upload' is used for all operations in the athenad.py and sunnylinkd.py files that previously referred to the default LOG_ATTR_NAME. This will allow more flexibility in handling logs specific to Sunnylink. --- selfdrive/athena/athenad.py | 12 ++++++------ selfdrive/athena/sunnylinkd.py | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index d5b1e37ce1..8e95e3a4aa 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -550,7 +550,7 @@ def takeSnapshot() -> str | dict[str, str] | None: raise Exception("not available while camerad is started") -def get_logs_to_send_sorted() -> list[str]: +def get_logs_to_send_sorted(log_attr_name=LOG_ATTR_NAME) -> list[str]: # TODO: scan once then use inotify to detect file creation/deletion curr_time = int(time.time()) logs = [] @@ -558,7 +558,7 @@ def get_logs_to_send_sorted() -> list[str]: log_path = os.path.join(Paths.swaglog_root(), log_entry) time_sent = 0 try: - value = getxattr(log_path, LOG_ATTR_NAME) + value = getxattr(log_path, log_attr_name) if value is not None: time_sent = int.from_bytes(value, sys.byteorder) except (ValueError, TypeError): @@ -570,7 +570,7 @@ def get_logs_to_send_sorted() -> list[str]: return sorted(logs)[:-1] -def log_handler(end_event: threading.Event) -> None: +def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None: if PC: return @@ -580,7 +580,7 @@ def log_handler(end_event: threading.Event) -> None: try: curr_scan = time.monotonic() if curr_scan - last_scan > 10: - log_files = get_logs_to_send_sorted() + log_files = get_logs_to_send_sorted(log_attr_name) last_scan = curr_scan # send one log @@ -591,7 +591,7 @@ def log_handler(end_event: threading.Event) -> None: try: curr_time = int(time.time()) log_path = os.path.join(Paths.swaglog_root(), log_entry) - setxattr(log_path, LOG_ATTR_NAME, int.to_bytes(curr_time, 4, sys.byteorder)) + setxattr(log_path, log_attr_name, int.to_bytes(curr_time, 4, sys.byteorder)) with open(log_path) as f: jsonrpc = { "method": "forwardLogs", @@ -619,7 +619,7 @@ def log_handler(end_event: threading.Event) -> None: if log_entry and log_success: log_path = os.path.join(Paths.swaglog_root(), log_entry) try: - setxattr(log_path, LOG_ATTR_NAME, LOG_ATTR_VALUE_MAX_UNIX_TIME) + setxattr(log_path, log_attr_name, LOG_ATTR_VALUE_MAX_UNIX_TIME) except OSError: pass # file could be deleted by log rotation if curr_log == log_entry: diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index b69bc7a762..6b9028bf20 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -11,7 +11,7 @@ import threading import time from openpilot.selfdrive.athena.athenad import ws_send, jsonrpc_handler, \ - recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler + recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler, LOG_ATTR_NAME from jsonrpc import dispatcher from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection) @@ -24,6 +24,7 @@ from openpilot.common.swaglog import cloudlog SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sunnypilot.ai') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) LOCAL_PORT_WHITELIST = {8022} +SUNNYLINK_LOG_ATTR_NAME = "sunnylink.user.upload" params = Params() sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8')) @@ -37,7 +38,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: threading.Thread(target=ws_ping, args=(ws, end_event), name='ws_ping'), threading.Thread(target=ws_queue, args=(end_event,), name='ws_queue'), # threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'), - threading.Thread(target=log_handler, args=(end_event,), name='log_handler'), + threading.Thread(target=log_handler, args=(end_event,SUNNYLINK_LOG_ATTR_NAME,), name='log_handler'), # threading.Thread(target=stat_handler, args=(end_event,), name='stat_handler'), ] + [ threading.Thread(target=jsonrpc_handler, args=(end_event,), name=f'worker_{x}') From f1a31495516a65c65cc8c1068df378192a1c93d3 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 26 May 2024 22:18:24 +0200 Subject: [PATCH 140/575] Update attribute name for Sunnylink log upload The attribute name used for Sunnylink's log upload function was modified. The new attribute name "user.sunny.upload" replaces the previous "sunnylink.user.upload" to reflect recent changes in naming convention. --- selfdrive/athena/sunnylinkd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index 6b9028bf20..1b461dfbbd 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -24,7 +24,7 @@ from openpilot.common.swaglog import cloudlog SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sunnypilot.ai') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) LOCAL_PORT_WHITELIST = {8022} -SUNNYLINK_LOG_ATTR_NAME = "sunnylink.user.upload" +SUNNYLINK_LOG_ATTR_NAME = "user.sunny.upload" params = Params() sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8')) From b56692bd2e8e4ed575904c177ecd32a621e3b8d2 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 26 May 2024 22:33:34 +0200 Subject: [PATCH 141/575] Add platform-specific xattr handling in loggerd This commit adapts the xattr handling in the loggerd module to individual platforms, specifically macOS and others. It imports specific xattr functions based on the running system, enhancing compatibility and reducing the risk of errors. In specific, 'ENOATTR' error is now also taken into account which is relevant in some non-Linux platforms. --- system/loggerd/xattr_cache.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/system/loggerd/xattr_cache.py b/system/loggerd/xattr_cache.py index d3220118ac..0f7e8a8a52 100644 --- a/system/loggerd/xattr_cache.py +++ b/system/loggerd/xattr_cache.py @@ -1,5 +1,13 @@ import os import errno +import platform + +if platform.system() == 'Darwin': # macOS + from xattr import getxattr as _getxattr + from xattr import setxattr as _setxattr +else: + from os import getxattr as _getxattr + from os import setxattr as _setxattr _cached_attributes: dict[tuple, bytes | None] = {} @@ -7,10 +15,10 @@ def getxattr(path: str, attr_name: str) -> bytes | None: key = (path, attr_name) if key not in _cached_attributes: try: - response = os.getxattr(path, attr_name) + response = _getxattr(path, attr_name) except OSError as e: # ENODATA means attribute hasn't been set - if e.errno == errno.ENODATA: + if e.errno == errno.ENODATA or e.errno == errno.ENOATTR: response = None else: raise @@ -19,4 +27,4 @@ def getxattr(path: str, attr_name: str) -> bytes | None: def setxattr(path: str, attr_name: str, attr_value: bytes) -> None: _cached_attributes.pop((path, attr_name), None) - return os.setxattr(path, attr_name, attr_value) + return _setxattr(path, attr_name, attr_value) From 86ce2bc79135cd54ea94263eddbf5e6e138ca949 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 26 May 2024 23:55:41 +0200 Subject: [PATCH 142/575] Remove unused import in sunnylinkd.py The unused import, LOG_ATTR_NAME, in the file selfdrive/athena/sunnylinkd.py has been removed to provide cleaner, simpler, and more readable code. This also helps follow good coding practices such as removing unnecessary or unused imports or resources. --- selfdrive/athena/sunnylinkd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index 1b461dfbbd..6eaec38181 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -11,7 +11,7 @@ import threading import time from openpilot.selfdrive.athena.athenad import ws_send, jsonrpc_handler, \ - recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler, LOG_ATTR_NAME + recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler from jsonrpc import dispatcher from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection) From e7b40f61a8022cdacccb2d276be7bc923fe79178 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 27 May 2024 19:44:41 +0200 Subject: [PATCH 143/575] Add support for network metering and PrimeType checking in sunnylinkd This commit introduces network metering and PrimeType checking in sunnylinkd. The implementation includes an additional threading event and a new log handler function, 'sunny_log_handler'. It defines PrimeType and metering conditions to set and clear the new threading event as part of the main processing loop in handle_long_poll function. --- selfdrive/athena/sunnylinkd.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index 6eaec38181..6ebf6bf653 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -20,6 +20,7 @@ from openpilot.common.api import SunnylinkApi from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.common.swaglog import cloudlog +import cereal.messaging as messaging SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sunnypilot.ai') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) @@ -29,7 +30,9 @@ SUNNYLINK_LOG_ATTR_NAME = "user.sunny.upload" params = Params() sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8')) def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: + sm = messaging.SubMaster(['deviceState']) end_event = threading.Event() + comma_prime_cellular_end_event = threading.Event() threads = [ threading.Thread(target=ws_manage, args=(ws, end_event), name='ws_manage'), @@ -38,7 +41,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: threading.Thread(target=ws_ping, args=(ws, end_event), name='ws_ping'), threading.Thread(target=ws_queue, args=(end_event,), name='ws_queue'), # threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'), - threading.Thread(target=log_handler, args=(end_event,SUNNYLINK_LOG_ATTR_NAME,), name='log_handler'), + threading.Thread(target=sunny_log_handler, args=(end_event, comma_prime_cellular_end_event), name='log_handler'), # threading.Thread(target=stat_handler, args=(end_event,), name='stat_handler'), ] + [ threading.Thread(target=jsonrpc_handler, args=(end_event,), name=f'worker_{x}') @@ -49,10 +52,24 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: thread.start() try: while not end_event.wait(0.1): + sm.update(0) if exit_event is not None and exit_event.is_set(): end_event.set() + comma_prime_cellular_end_event.set() + + prime_type = params.get("PrimeType", encoding='utf-8') + metered = sm['deviceState'].networkMetered + + if int(prime_type) > 2 and metered: + cloudlog.info(f"sunnylinkd.handle_long_poll: PrimeType({prime_type}) > 2 and networkMetered({metered})") + comma_prime_cellular_end_event.set() + elif comma_prime_cellular_end_event.is_set(): + cloudlog.info(f"sunnylinkd.handle_long_poll: comma_prime_cellular_end_event is set and not PrimeType({prime_type}) > 2 or not networkMetered({metered})") + comma_prime_cellular_end_event.clear() + except (KeyboardInterrupt, SystemExit): end_event.set() + comma_prime_cellular_end_event.set() raise finally: for thread in threads: @@ -114,6 +131,13 @@ def ws_queue(end_event: threading.Event) -> None: cloudlog.debug("Resume requested or end_event is set, exiting ws_queue thread") +def sunny_log_handler(end_event: threading.Event, comma_prime_cellular_end_event: threading.Event) -> None: + while not end_event.wait(0.1): + if not comma_prime_cellular_end_event.is_set(): + log_handler(comma_prime_cellular_end_event, SUNNYLINK_LOG_ATTR_NAME) + comma_prime_cellular_end_event.set() + + @dispatcher.add_method def getParamsAllKeys() -> list[str]: keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] From 9d79f243ff1068c46215e7b3149e0416ead56d02 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 27 May 2024 21:51:00 +0200 Subject: [PATCH 144/575] Refactor log handling and add exception handling The changes refactor the log handling in athenad.py for more efficient processing. The log is now split into maximum chunk sizes of 128KB to prevent oversized requests. Additionally, an invalid request or response exception has been introduced for better error handling. --- selfdrive/athena/athenad.py | 39 +++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 8e95e3a4aa..aaee9fca3c 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -177,7 +177,7 @@ def jsonrpc_handler(end_event: threading.Event) -> None: send_queue.put_nowait(response.json) elif "id" in data and ("result" in data or "error" in data): log_recv_queue.put_nowait(data) - else: + elif data: raise Exception("not a valid request or response") except queue.Empty: pass @@ -570,6 +570,28 @@ def get_logs_to_send_sorted(log_attr_name=LOG_ATTR_NAME) -> list[str]: return sorted(logs)[:-1] +def add_log_to_queue(log_path, id): + # Define the maximum size of a chunk in bytes + MAX_CHUNK_SIZE = 128 * 1024 # 128KB + + # Open the log file + with open(log_path, 'r') as f: + while True: + chunk = f.read(MAX_CHUNK_SIZE) + if not chunk: + break + + jsonrpc = { + "method": "forwardLogs", + "params": { + "logs": chunk + }, + "jsonrpc": "2.0", + "id": id + } + low_priority_send_queue.put_nowait(json.dumps(jsonrpc)) + + def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None: if PC: return @@ -592,17 +614,10 @@ def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None curr_time = int(time.time()) log_path = os.path.join(Paths.swaglog_root(), log_entry) setxattr(log_path, log_attr_name, int.to_bytes(curr_time, 4, sys.byteorder)) - with open(log_path) as f: - jsonrpc = { - "method": "forwardLogs", - "params": { - "logs": f.read() - }, - "jsonrpc": "2.0", - "id": log_entry - } - low_priority_send_queue.put_nowait(json.dumps(jsonrpc)) - curr_log = log_entry + # now we need to check the log size because we cant go larger than 128kb per request so we need to split by regex for example regex.compile(r'\{(?:[^{}]|(?R))*\}') + + add_log_to_queue(log_path, log_entry) + curr_log = log_entry except OSError: pass # file could be deleted by log rotation From 797e273ba7e16f42ff09225ddd17f776e53b050d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 27 May 2024 22:19:39 +0200 Subject: [PATCH 145/575] Reduce maximum chunk size in athenad.py Updating the maximum chunk size constant in athenad.py to decrease its value from 128KB to 32KB. This change has been made to optimize the handling of log files and data transmission. --- selfdrive/athena/athenad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index aaee9fca3c..5fecb253a8 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -572,7 +572,7 @@ def get_logs_to_send_sorted(log_attr_name=LOG_ATTR_NAME) -> list[str]: def add_log_to_queue(log_path, id): # Define the maximum size of a chunk in bytes - MAX_CHUNK_SIZE = 128 * 1024 # 128KB + MAX_CHUNK_SIZE = 32 * 1024 # 32KB # Open the log file with open(log_path, 'r') as f: From 091e5d1343f393cf9215a63e6c3838ee97063407 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 27 May 2024 22:51:27 +0200 Subject: [PATCH 146/575] Adjust maximum chunk size in athenad.py This commit decreases the maximum chunk size from 32KB to 28KB in the add_log_to_queue function in athenad.py. This size reduction will affect how the log files are loaded and processed. --- selfdrive/athena/athenad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 5fecb253a8..0935c8d218 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -572,7 +572,7 @@ def get_logs_to_send_sorted(log_attr_name=LOG_ATTR_NAME) -> list[str]: def add_log_to_queue(log_path, id): # Define the maximum size of a chunk in bytes - MAX_CHUNK_SIZE = 32 * 1024 # 32KB + MAX_CHUNK_SIZE = 28 * 1024 # 32KB # Open the log file with open(log_path, 'r') as f: From 753bcce9a73919b4da4326433d3914edfbfd0583 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 May 2024 12:20:19 +0200 Subject: [PATCH 147/575] Implement compression and encoding for large log files The `add_log_to_queue` function has been updated to compress and base64 encode log files that exceed a certain size, specifically for the "sunnylink" scenario. This function also provides logging for various stages of the process, including initial file size, size post-compression/encoding, and whether the final payload is small enough to be sent in one request. The change will improve handle of larger log files and prevent payload size related issues. --- selfdrive/athena/athenad.py | 74 ++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 0935c8d218..ee47e53bea 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -16,6 +16,7 @@ import sys import tempfile import threading import time +import gzip from dataclasses import asdict, dataclass, replace from datetime import datetime from functools import partial @@ -570,29 +571,66 @@ def get_logs_to_send_sorted(log_attr_name=LOG_ATTR_NAME) -> list[str]: return sorted(logs)[:-1] -def add_log_to_queue(log_path, id): - # Define the maximum size of a chunk in bytes - MAX_CHUNK_SIZE = 28 * 1024 # 32KB +def add_log_to_queue(log_path, log_id, is_sunnylink = False): + MAX_SIZE_KB = 32 + MAX_SIZE_BYTES = MAX_SIZE_KB * 1024 - # Open the log file with open(log_path, 'r') as f: - while True: - chunk = f.read(MAX_CHUNK_SIZE) - if not chunk: - break + data = f.read() - jsonrpc = { - "method": "forwardLogs", - "params": { - "logs": chunk - }, - "jsonrpc": "2.0", - "id": id - } - low_priority_send_queue.put_nowait(json.dumps(jsonrpc)) + # Check if the file is empty + if not data: + cloudlog.warning(f"Log file {log_path} is empty.") + return + + # Log the current size of the file + current_size = os.path.getsize(log_path) + cloudlog.info(f"Current size of log file {log_path}: {current_size} bytes") + + # Initialize variables for encoding + payload = data + is_compressed = False + + if is_sunnylink and current_size > MAX_SIZE_BYTES: + # Compress and encode the data if it exceeds the maximum size + compressed_data = gzip.compress(data.encode()) + payload = base64.b64encode(compressed_data).decode() + is_compressed = True + + # Log the size after compression and encoding + compressed_size = len(compressed_data) + encoded_size = len(payload) + cloudlog.info(f"Size of log file {log_path} " + f"after compression: {compressed_size} bytes, " + f"after encoding: {encoded_size} bytes") + + jsonrpc = { + "method": "forwardLogs", + "params": { + "logs": payload + }, + "jsonrpc": "2.0", + "id": log_id + } + + if is_sunnylink and is_compressed: + jsonrpc["params"]["compressed"] = is_compressed + + jsonrpc_str = json.dumps(jsonrpc) + size_in_bytes = len(jsonrpc_str.encode('utf-8')) + + if is_sunnylink and size_in_bytes <= MAX_SIZE_BYTES: + cloudlog.info(f"Target is sunnylink and log file {log_path} is small enough to send in one request ({size_in_bytes} bytes).") + low_priority_send_queue.put_nowait(jsonrpc_str) + elif is_sunnylink: + cloudlog.warning(f"Target is sunnylink and log file {log_path} is too large to send in one request.") + else: + cloudlog.info(f"Target is not sunnylink, proceeding to send log file {log_path} in one request ({size_in_bytes} bytes).") + low_priority_send_queue.put_nowait(jsonrpc_str) def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None: + is_sunnylink = log_attr_name != LOG_ATTR_NAME if PC: return @@ -616,7 +654,7 @@ def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None setxattr(log_path, log_attr_name, int.to_bytes(curr_time, 4, sys.byteorder)) # now we need to check the log size because we cant go larger than 128kb per request so we need to split by regex for example regex.compile(r'\{(?:[^{}]|(?R))*\}') - add_log_to_queue(log_path, log_entry) + add_log_to_queue(log_path, log_entry, is_sunnylink) curr_log = log_entry except OSError: pass # file could be deleted by log rotation From 52ea16a93a4ef75698565a06fe28b191250dc8eb Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 May 2024 14:14:22 +0200 Subject: [PATCH 148/575] Update log file size calculation method The code for calculating the size of the log file has been revised. Instead of using os.path.getsize, which simply captures the size of the file on disk, we're now calculating the size of the payload after it has been serialized and encoded, plus an overhead of 100 bytes. This change provides a more accurate measure of the data that will be sent. --- selfdrive/athena/athenad.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index ee47e53bea..8ec1094124 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -583,14 +583,14 @@ def add_log_to_queue(log_path, log_id, is_sunnylink = False): cloudlog.warning(f"Log file {log_path} is empty.") return - # Log the current size of the file - current_size = os.path.getsize(log_path) - cloudlog.info(f"Current size of log file {log_path}: {current_size} bytes") - # Initialize variables for encoding payload = data is_compressed = False + # Log the current size of the file + current_size = len(json.dumps(payload).encode("utf-8")) + len(log_id.encode("utf-8")) + 100 # Add 100 bytes to account for encoding overhead + cloudlog.info(f"Current size of log file {log_path}: {current_size} bytes") + if is_sunnylink and current_size > MAX_SIZE_BYTES: # Compress and encode the data if it exceeds the maximum size compressed_data = gzip.compress(data.encode()) From 2d2a35c7e4b1111f012199a799a4e5b6fcee1601 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 May 2024 12:54:11 +0000 Subject: [PATCH 149/575] Apply 2 suggestion(s) to 1 file(s) --- selfdrive/athena/athenad.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 8ec1094124..984b724aa6 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -178,7 +178,7 @@ def jsonrpc_handler(end_event: threading.Event) -> None: send_queue.put_nowait(response.json) elif "id" in data and ("result" in data or "error" in data): log_recv_queue.put_nowait(data) - elif data: + else: raise Exception("not a valid request or response") except queue.Empty: pass @@ -652,7 +652,6 @@ def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None curr_time = int(time.time()) log_path = os.path.join(Paths.swaglog_root(), log_entry) setxattr(log_path, log_attr_name, int.to_bytes(curr_time, 4, sys.byteorder)) - # now we need to check the log size because we cant go larger than 128kb per request so we need to split by regex for example regex.compile(r'\{(?:[^{}]|(?R))*\}') add_log_to_queue(log_path, log_entry, is_sunnylink) curr_log = log_entry From 0c909e98bbddd798b965bbb0c8aa366edce89bf3 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 May 2024 14:54:35 +0200 Subject: [PATCH 150/575] Spacing --- system/loggerd/xattr_cache.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/system/loggerd/xattr_cache.py b/system/loggerd/xattr_cache.py index 0f7e8a8a52..8d950f71a2 100644 --- a/system/loggerd/xattr_cache.py +++ b/system/loggerd/xattr_cache.py @@ -3,8 +3,8 @@ import errno import platform if platform.system() == 'Darwin': # macOS - from xattr import getxattr as _getxattr - from xattr import setxattr as _setxattr + from xattr import getxattr as _getxattr + from xattr import setxattr as _setxattr else: from os import getxattr as _getxattr from os import setxattr as _setxattr @@ -28,3 +28,24 @@ def getxattr(path: str, attr_name: str) -> bytes | None: def setxattr(path: str, attr_name: str, attr_value: bytes) -> None: _cached_attributes.pop((path, attr_name), None) return _setxattr(path, attr_name, attr_value) + + + + +import os + +# Get the list of all files in current directory +files = os.listdir('.') + +# Iterate over the list of files +for file_name in files: + # Construct full file path + file_path = os.path.join('.', file_name) + # Check if it's a regular file + if os.path.isfile(file_path): + # Try to remove the xattr, if it exists + try: + os.removexattr(file_path, "user.sunny.upload") + print(f"Removed xattr from {file_name}") + except OSError: + print(f"No such xattr on {file_name}") \ No newline at end of file From f4b54b1963e8860ce326277d45e855970e9dd997 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 May 2024 15:08:40 +0200 Subject: [PATCH 151/575] Change logging level from info to debug in athenad.py and sunnylinkd.py Adjusted the logging level for certain log entries from info to debug in athenad.py and sunnylinkd.py. This change improves control over the log verbosity allowing for cleaner log files, and to debug specific sections more effectively. --- selfdrive/athena/athenad.py | 8 ++++---- selfdrive/athena/sunnylinkd.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 984b724aa6..0c5e03c79b 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -589,7 +589,7 @@ def add_log_to_queue(log_path, log_id, is_sunnylink = False): # Log the current size of the file current_size = len(json.dumps(payload).encode("utf-8")) + len(log_id.encode("utf-8")) + 100 # Add 100 bytes to account for encoding overhead - cloudlog.info(f"Current size of log file {log_path}: {current_size} bytes") + cloudlog.debug(f"Current size of log file {log_path}: {current_size} bytes") if is_sunnylink and current_size > MAX_SIZE_BYTES: # Compress and encode the data if it exceeds the maximum size @@ -600,7 +600,7 @@ def add_log_to_queue(log_path, log_id, is_sunnylink = False): # Log the size after compression and encoding compressed_size = len(compressed_data) encoded_size = len(payload) - cloudlog.info(f"Size of log file {log_path} " + cloudlog.debug(f"Size of log file {log_path} " f"after compression: {compressed_size} bytes, " f"after encoding: {encoded_size} bytes") @@ -620,12 +620,12 @@ def add_log_to_queue(log_path, log_id, is_sunnylink = False): size_in_bytes = len(jsonrpc_str.encode('utf-8')) if is_sunnylink and size_in_bytes <= MAX_SIZE_BYTES: - cloudlog.info(f"Target is sunnylink and log file {log_path} is small enough to send in one request ({size_in_bytes} bytes).") + cloudlog.debug(f"Target is sunnylink and log file {log_path} is small enough to send in one request ({size_in_bytes} bytes).") low_priority_send_queue.put_nowait(jsonrpc_str) elif is_sunnylink: cloudlog.warning(f"Target is sunnylink and log file {log_path} is too large to send in one request.") else: - cloudlog.info(f"Target is not sunnylink, proceeding to send log file {log_path} in one request ({size_in_bytes} bytes).") + cloudlog.debug(f"Target is not sunnylink, proceeding to send log file {log_path} in one request ({size_in_bytes} bytes).") low_priority_send_queue.put_nowait(jsonrpc_str) diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index 6ebf6bf653..204a712a0d 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -61,10 +61,10 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: metered = sm['deviceState'].networkMetered if int(prime_type) > 2 and metered: - cloudlog.info(f"sunnylinkd.handle_long_poll: PrimeType({prime_type}) > 2 and networkMetered({metered})") + cloudlog.debug(f"sunnylinkd.handle_long_poll: PrimeType({prime_type}) > 2 and networkMetered({metered})") comma_prime_cellular_end_event.set() elif comma_prime_cellular_end_event.is_set(): - cloudlog.info(f"sunnylinkd.handle_long_poll: comma_prime_cellular_end_event is set and not PrimeType({prime_type}) > 2 or not networkMetered({metered})") + cloudlog.debug(f"sunnylinkd.handle_long_poll: comma_prime_cellular_end_event is set and not PrimeType({prime_type}) > 2 or not networkMetered({metered})") comma_prime_cellular_end_event.clear() except (KeyboardInterrupt, SystemExit): From a79a044a259940ac0c19fca5a5831ba362836f2a Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 May 2024 19:59:36 +0200 Subject: [PATCH 152/575] Remove unnecessary os module import and file xattr removal code The import of the 'os' module is eliminated to clean up the code, as it is no longer needed. Furthermore, the code for removing the extended attribute from all files in the current directory has been removed as well. This streamlines the setxattr function and focuses only on setting the attributes without additional file operations. --- system/loggerd/xattr_cache.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/system/loggerd/xattr_cache.py b/system/loggerd/xattr_cache.py index 8d950f71a2..88aa4f7916 100644 --- a/system/loggerd/xattr_cache.py +++ b/system/loggerd/xattr_cache.py @@ -1,4 +1,3 @@ -import os import errno import platform @@ -27,25 +26,4 @@ def getxattr(path: str, attr_name: str) -> bytes | None: def setxattr(path: str, attr_name: str, attr_value: bytes) -> None: _cached_attributes.pop((path, attr_name), None) - return _setxattr(path, attr_name, attr_value) - - - - -import os - -# Get the list of all files in current directory -files = os.listdir('.') - -# Iterate over the list of files -for file_name in files: - # Construct full file path - file_path = os.path.join('.', file_name) - # Check if it's a regular file - if os.path.isfile(file_path): - # Try to remove the xattr, if it exists - try: - os.removexattr(file_path, "user.sunny.upload") - print(f"Removed xattr from {file_name}") - except OSError: - print(f"No such xattr on {file_name}") \ No newline at end of file + return _setxattr(path, attr_name, attr_value) \ No newline at end of file From a7b52c7ac958c67cb2aa3863d806cc1f9258612c Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 May 2024 21:51:18 +0200 Subject: [PATCH 153/575] Add sunnylink_uploader to process_config and implement uploader The commit adds sunnylink_uploader to the process_config and implements an uploader in a new file, sunnylink_uploader.py. The uploader handles listing, prioritizing, and uploading files to Sunnylink. Modified process_config to conditionally include the uploader process based on SunnylinkEnabled param and file existence. New getxattr and setxattr functions are also used to keep track of the uploaded files. --- selfdrive/manager/process_config.py | 13 +- system/loggerd/sunnylink_uploader.py | 298 +++++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 4 deletions(-) create mode 100755 system/loggerd/sunnylink_uploader.py diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 13c1f05c30..79c16e70ff 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -104,10 +104,15 @@ procs = [ PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), ] -if os.path.exists("../athena/manage_sunnylinkd.py") and Params().get_bool("SunnylinkEnabled"): - procs += [ - DaemonProcess("manage_sunnylinkd", "selfdrive.athena.manage_sunnylinkd", "SunnylinkdPid"), - ] +if Params().get_bool("SunnylinkEnabled"): + if os.path.exists("../athena/manage_sunnylinkd.py"): + procs += [ + DaemonProcess("manage_sunnylinkd", "selfdrive.athena.manage_sunnylinkd", "SunnylinkdPid"), + ] + if os.path.exists("../../system/loggerd/sunnylink_uploader.py"): + procs += [ + PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", always_run), + ] if os.path.exists("./gitlab_runner.sh") and True: # Of course and True is always true. Placeholder for a param :D # Only devs! diff --git a/system/loggerd/sunnylink_uploader.py b/system/loggerd/sunnylink_uploader.py new file mode 100755 index 0000000000..38661d59ba --- /dev/null +++ b/system/loggerd/sunnylink_uploader.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +import bz2 +import io +import json +import os +import random +import requests +import threading +import time +import traceback +import datetime +from typing import BinaryIO +from collections.abc import Iterator + +from cereal import log +import cereal.messaging as messaging +from openpilot.common.api import SunnylinkApi +from openpilot.common.params import Params +from openpilot.common.realtime import set_core_affinity +from openpilot.system.hardware.hw import Paths +from openpilot.system.loggerd.xattr_cache import getxattr, setxattr +from openpilot.common.swaglog import cloudlog + +NetworkType = log.DeviceState.NetworkType +UPLOAD_ATTR_NAME = 'user.sunny.upload' + +UPLOAD_ATTR_VALUE = b'1' + +UPLOAD_QLOG_QCAM_MAX_SIZE = 5 * 1e6 # MB + +allow_sleep = bool(os.getenv("UPLOADER_SLEEP", "1")) +force_wifi = os.getenv("FORCEWIFI") is not None +fake_upload = os.getenv("FAKEUPLOAD") is not None + +OFFROAD_TRANSITION_TIMEOUT = 900. # wait until offroad for 15 minutes before allowing uploads + + +class FakeRequest: + def __init__(self): + self.headers = {"Content-Length": "0"} + + +class FakeResponse: + def __init__(self): + self.status_code = 200 + self.request = FakeRequest() + + +def get_directory_sort(d: str) -> list[str]: + # ensure old format is sorted sooner + o = ["0", ] if d.startswith("2024-") else ["1", ] + return o + [s.rjust(10, '0') for s in d.rsplit('--', 1)] + +def listdir_by_creation(d: str) -> list[str]: + if not os.path.isdir(d): + return [] + + try: + paths = [f for f in os.listdir(d) if os.path.isdir(os.path.join(d, f))] + paths = sorted(paths, key=get_directory_sort) + return paths + except OSError: + cloudlog.exception("listdir_by_creation failed") + return [] + +def clear_locks(root: str) -> None: + for logdir in os.listdir(root): + path = os.path.join(root, logdir) + try: + for fname in os.listdir(path): + if fname.endswith(".lock"): + os.unlink(os.path.join(path, fname)) + except OSError: + cloudlog.exception("clear_locks failed") + + +class Uploader: + def __init__(self, dongle_id: str, root: str): + self.dongle_id = dongle_id + self.api = SunnylinkApi(dongle_id) + self.root = root + + self.params = Params() + + # stats for last successfully uploaded file + self.last_filename = "" + + self.immediate_folders = ["crash/", "boot/"] + self.immediate_priority = {"qlog": 0, "qlog.bz2": 0, "qcamera.ts": 1} + + def list_upload_files(self, metered: bool) -> Iterator[tuple[str, str, str]]: + r = self.params.get("AthenadRecentlyViewedRoutes", encoding="utf8") + requested_routes = [] if r is None else r.split(",") + + for logdir in listdir_by_creation(self.root): + path = os.path.join(self.root, logdir) + try: + names = os.listdir(path) + except OSError: + continue + + if any(name.endswith(".lock") for name in names): + continue + + for name in sorted(names, key=lambda n: self.immediate_priority.get(n, 1000)): + key = os.path.join(logdir, name) + fn = os.path.join(path, name) + # skip files already uploaded + try: + ctime = os.path.getctime(fn) + is_uploaded = getxattr(fn, UPLOAD_ATTR_NAME) == UPLOAD_ATTR_VALUE + except OSError: + cloudlog.event("uploader_getxattr_failed", key=key, fn=fn) + # deleter could have deleted, so skip + continue + if is_uploaded: + continue + + # limit uploading on metered connections + if metered: + dt = datetime.timedelta(hours=12) + if logdir in self.immediate_folders and (datetime.datetime.now() - datetime.datetime.fromtimestamp(ctime)) < dt: + continue + + if name == "qcamera.ts" and not any(logdir.startswith(r.split('|')[-1]) for r in requested_routes): + continue + + yield name, key, fn + + def next_file_to_upload(self, metered: bool) -> tuple[str, str, str] | None: + upload_files = list(self.list_upload_files(metered)) + + for name, key, fn in upload_files: + if any(f in fn for f in self.immediate_folders): + return name, key, fn + + for name, key, fn in upload_files: + if name in self.immediate_priority: + return name, key, fn + + return None + + def do_upload(self, key: str, fn: str): + url_resp = self.api.get("device/" + self.dongle_id + "/upload_url/", timeout=10, path=key, access_token=self.api.get_token()) + if url_resp.status_code == 412: + return url_resp + + url_resp_json = json.loads(url_resp.text) + url = url_resp_json['url'] + cloudlog.debug("sunnylink upload_url %s", url) + + if fake_upload: + return FakeResponse() + + with open(fn, "rb") as f: + data: BinaryIO + if key.endswith('.bz2') and not fn.endswith('.bz2'): + compressed = bz2.compress(f.read()) + data = io.BytesIO(compressed) + else: + data = f + + return requests.put(url, data=data, timeout=10) + + def upload(self, name: str, key: str, fn: str, network_type: int, metered: bool) -> bool: + try: + sz = os.path.getsize(fn) + except OSError: + cloudlog.exception("upload: getsize failed") + return False + + cloudlog.event("upload_start", key=key, fn=fn, sz=sz, network_type=network_type, metered=metered) + + if sz == 0: + # tag files of 0 size as uploaded + success = True + elif name in self.immediate_priority and sz > UPLOAD_QLOG_QCAM_MAX_SIZE: + cloudlog.event("uploader_too_large", key=key, fn=fn, sz=sz) + success = True + else: + start_time = time.monotonic() + + stat = None + last_exc = None + try: + stat = self.do_upload(key, fn) + except Exception as e: + last_exc = (e, traceback.format_exc()) + + if stat is not None and stat.status_code in (200, 201, 401, 403, 412): + self.last_filename = fn + dt = time.monotonic() - start_time + if stat.status_code == 412: + cloudlog.event("upload_ignored", key=key, fn=fn, sz=sz, network_type=network_type, metered=metered) + else: + content_length = int(stat.request.headers.get("Content-Length", 0)) + speed = (content_length / 1e6) / dt + cloudlog.event("upload_success", key=key, fn=fn, sz=sz, content_length=content_length, + network_type=network_type, metered=metered, speed=speed) + success = True + else: + success = False + cloudlog.event("upload_failed", stat=stat, exc=last_exc, key=key, fn=fn, sz=sz, network_type=network_type, metered=metered) + + if success: + # tag file as uploaded + try: + setxattr(fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE) + except OSError: + cloudlog.event("uploader_setxattr_failed", exc=last_exc, key=key, fn=fn, sz=sz) + + return success + + + def step(self, network_type: int, metered: bool) -> bool | None: + d = self.next_file_to_upload(metered) + if d is None: + return None + + name, key, fn = d + + # qlogs and bootlogs need to be compressed before uploading + if key.endswith(('qlog', 'rlog')) or (key.startswith('boot/') and not key.endswith('.bz2')): + key += ".bz2" + + return self.upload(name, key, fn, network_type, metered) + + +def main(exit_event: threading.Event = None) -> None: + if exit_event is None: + exit_event = threading.Event() + + try: + set_core_affinity([0, 1, 2, 3]) + except Exception: + cloudlog.exception("failed to set core affinity") + + clear_locks(Paths.log_root()) + + params = Params() + dongle_id = params.get("SunnylinkDongleId", encoding='utf8') + + offroad_transition_prev = 0. + offroad_last = False + + if dongle_id is None: + cloudlog.info("uploader missing dongle_id") + raise Exception("uploader can't start without dongle id") + + sm = messaging.SubMaster(['deviceState']) + uploader = Uploader(dongle_id, Paths.log_root()) + + backoff = 0.1 + while not exit_event.is_set(): + sm.update(0) + + offroad = params.get_bool("IsOffroad") + t = time.monotonic() + if offroad and not offroad_last and t > 300.: + offroad_transition_prev = time.monotonic() + offroad_last = offroad + + network_type = sm['deviceState'].networkType if not force_wifi else NetworkType.wifi + # if network_type == NetworkType.none: + # if allow_sleep: + # time.sleep(60 if offroad else 5) + # continue + + if params.get_bool("DisableOnroadUploads"): + if not offroad or (offroad_transition_prev > 0. and t - offroad_transition_prev < OFFROAD_TRANSITION_TIMEOUT): + if not offroad: + cloudlog.info("not uploading: onroad uploads disabled") + else: + wait_minutes = int(OFFROAD_TRANSITION_TIMEOUT / 60) + time_left = OFFROAD_TRANSITION_TIMEOUT - (t - offroad_transition_prev) + if time_left / 60. > 2.: + time_left_str = f"{int(time_left / 60)} minute(s)" + else: + time_left_str = f"{int(time_left)} seconds(s)" + cloudlog.info(f"not uploading: waiting until offroad for {wait_minutes} minutes; {time_left_str} left") + if allow_sleep: + time.sleep(60) + continue + + success = uploader.step(sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) + if success is None: + backoff = 60 if offroad else 5 + elif success: + backoff = 0.1 + else: + cloudlog.info("upload backoff %r", backoff) + backoff = min(backoff*2, 120) + if allow_sleep: + time.sleep(backoff + random.uniform(0, backoff)) + + +if __name__ == "__main__": + main() From daaf05e5a6acf4d0f22ad54030dbc8b0f501bf42 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 00:07:24 -0400 Subject: [PATCH 154/575] Controls: Customizable Pause Lateral Speed --- CHANGELOGS.md | 3 ++ common/params.cc | 1 + selfdrive/car/interfaces.py | 13 +++++- selfdrive/manager/manager.py | 1 + .../sunnypilot/lane_change_settings.cc | 40 ++++++++++++++++++- .../offroad/sunnypilot/lane_change_settings.h | 19 ++++++++- 6 files changed, 73 insertions(+), 4 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 2fc0388fa0..e77fdfca40 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -27,6 +27,9 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * 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: Pause Lateral Below Speed w/ Blinker + * NEW❗: Customizable Pause Lateral Speed + * Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. * UPDATED: Hyundai CAN Longitudinal * Auto-enable radar tracks on platforms with applicable Mando radar * UPDATED: Hyundai CAN-FD Camera-based SCC diff --git a/common/params.cc b/common/params.cc index 920dd564da..7762c5cfec 100644 --- a/common/params.cc +++ b/common/params.cc @@ -289,6 +289,7 @@ std::unordered_map keys = { {"OsmWayTest", PERSISTENT}, {"OsmDownloadedDate", PERSISTENT}, {"PathOffset", PERSISTENT | BACKUP}, + {"PauseLateralSpeed", PERSISTENT | BACKUP}, {"QuietDrive", PERSISTENT | BACKUP}, {"RoadEdge", PERSISTENT | BACKUP}, {"ReverseAccChange", PERSISTENT | BACKUP}, diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index fc9715cb82..3c43f12765 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -13,7 +13,7 @@ from cereal import car from openpilot.common.basedir import BASEDIR from openpilot.common.conversions import Conversions as CV from openpilot.common.simple_kalman import KF1D, get_kalman_gain -from openpilot.common.numpy_fast import clip +from openpilot.common.numpy_fast import clip, interp 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 @@ -220,7 +220,9 @@ class CarInterfaceBase(ABC): self.gear_warning = 0 self.cruise_cancelled_btn = True self.acc_mads_combo = self.param_s.get_bool("AccMadsCombo") + self.is_metric = self.param_s.get_bool("IsMetric") self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") + self.pause_lateral_speed = self.param_s.get("PauseLateralSpeed", encoding="utf8") self.prev_acc_mads_combo = False self.mads_event_lock = True self.gap_button_counter = 0 @@ -606,7 +608,10 @@ class CarInterfaceBase(ABC): 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 + below_lateral_speed = LANE_CHANGE_SPEED_MIN if not self.pause_lateral_speed else \ + self.pause_lateral_speed * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) + + cs_out.belowLaneChangeSpeed = cs_out.vEgo < below_lateral_speed and self.below_speed_pause if cs_out.gearShifter in [GearShifter.park, GearShifter.reverse] or cs_out.doorOpen or \ (cs_out.seatbeltUnlatched and cs_out.gearShifter != GearShifter.park): @@ -708,6 +713,10 @@ class CarInterfaceBase(ABC): def sp_update_params(self): self.experimental_mode = self.param_s.get_bool("ExperimentalMode") self._frame += 1 + if self._frame % 100 == 0: + self.is_metric = self.param_s.get_bool("IsMetric") + self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") + self.pause_lateral_speed = self.param_s.get("PauseLateralSpeed", encoding="utf8") if self._frame % 300 == 0: self._frame = 0 self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam") diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index a527d6d975..88681da870 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -78,6 +78,7 @@ def manager_init() -> None: ("OnroadScreenOffEvent", "1"), ("OnroadSettings", "1"), ("PathOffset", "0"), + ("PauseLateralSpeed", "0"), ("ReverseAccChange", "0"), ("ScreenRecorder", "1"), ("ShowDebugUI", "1"), diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc index e24425a6fc..cc5bab87de 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc @@ -16,7 +16,7 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { { "BelowSpeedPause", tr("Pause Lateral Below Speed w/ Blinker"), - tr("Enable this toggle to pause lateral actuation with blinker when traveling below 20 MPH or 32 km/h."), + tr("Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below."), "../assets/offroad/icon_blank.png", }, { @@ -41,16 +41,32 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { connect(auto_lane_change_timer, &AutoLaneChangeTimer::updateOtherToggles, this, &LaneChangeSettings::updateToggles); list->addItem(auto_lane_change_timer); + // Pause Lateral Below Speed w/ Blinker + pause_lateral_speed = new PauseLateralSpeed(); + pause_lateral_speed->showDescription(); + connect(pause_lateral_speed, &SPOptionControl::updateLabels, pause_lateral_speed, &PauseLateralSpeed::refresh); + for (auto &[param, title, desc, icon] : toggle_defs) { auto toggle = new ParamControl(param, title, desc, icon, this); list->addItem(toggle); toggles[param.toStdString()] = toggle; + if (param == "BelowSpeedPause") { + list->addItem(pause_lateral_speed); + } + // trigger offroadTransition when going onroad/offroad connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled); } + connect(toggles["BelowSpeedPause"], &ToggleControl::toggleFlipped, [=](bool state) { + pause_lateral_speed->setEnabled(state); + pause_lateral_speed->setVisible(state); + }); + pause_lateral_speed->setEnabled(toggles["BelowSpeedPause"]->isToggled()); + pause_lateral_speed->setVisible(toggles["BelowSpeedPause"]->isToggled()); + main_layout->addWidget(new ScrollView(list, this)); } @@ -111,3 +127,25 @@ void AutoLaneChangeTimer::refresh() { setLabel("2 " + second); } } + +PauseLateralSpeed::PauseLateralSpeed() : SPOptionControl ( + "PauseLateralSpeed", + "", + tr("Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h."), + "../assets/offroad/icon_blank.png", + {0, 255}, + 5) { + + refresh(); +} + +void PauseLateralSpeed::refresh() { + QString option = QString:: fromStdString(params.get("PauseLateralSpeed")); + bool is_metric = params.getBool("IsMetric"); + + if (option == "0") { + setLabel(tr("Default")); + } else { + setLabel(option + " " + (is_metric ? tr("km/h") : tr("mph"))); + } +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h index 86bdd3ede3..066f4821e2 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h @@ -19,6 +19,22 @@ private: Params params; }; +class PauseLateralSpeed : public SPOptionControl { + Q_OBJECT + +public: + PauseLateralSpeed(); + + void refresh(); + + signals: + void ToggleUpdated(); + +private: + Params params; +}; + + class LaneChangeSettings : public QWidget { Q_OBJECT @@ -37,4 +53,5 @@ private: std::map toggles; AutoLaneChangeTimer *auto_lane_change_timer; -}; + PauseLateralSpeed *pause_lateral_speed; +}; \ No newline at end of file From 01d58f5d592228eed4886386fc61267175f74414 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 00:10:50 -0400 Subject: [PATCH 155/575] Remove unused --- selfdrive/car/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 3c43f12765..df6efebe69 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -13,7 +13,7 @@ from cereal import car from openpilot.common.basedir import BASEDIR from openpilot.common.conversions import Conversions as CV from openpilot.common.simple_kalman import KF1D, get_kalman_gain -from openpilot.common.numpy_fast import clip, interp +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 68cd82ad28db23d85f15de39c94c5867093249bf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 00:47:09 -0400 Subject: [PATCH 156/575] Controls: Dim button if the option cannot be adjusted --- selfdrive/ui/qt/widgets/controls.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 6b34c8cde1..382f6490ef 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -483,14 +483,17 @@ public: } button_group->addButton(button, i); - int change_value = (i == 0) ? -per_value_change : per_value_change; - QObject::connect(button, &QPushButton::clicked, [=]() { + int change_value = (i == 0) ? -per_value_change : per_value_change; key = param.toStdString(); value = atoi(params.get(key).c_str()); value += change_value; value = std::clamp(value, range.min_value, range.max_value); params.put(key, QString::number(value).toStdString()); + + button_group->button(0)->setEnabled(!(value == range.min_value)); + button_group->button(1)->setEnabled(!(value == range.max_value)); + updateLabels(); if (request_update) { From abfc90cbb61d0f683b837adc43c957adc1f6d003 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 00:54:23 -0400 Subject: [PATCH 157/575] desire helepr too --- selfdrive/controls/lib/desire_helper.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index 6cac8a70e2..948e27958f 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -59,18 +59,22 @@ class DesireHelper: self.road_edge = False self.param_read_counter = 0 self.read_param() + self.is_metric = self.param_s.get_bool("IsMetric") self.edge_toggle = self.param_s.get_bool("RoadEdge") self.lane_change_set_timer = int(self.param_s.get("AutoLaneChangeTimer", encoding="utf8")) self.lane_change_bsm_delay = self.param_s.get_bool("AutoLaneChangeBsmDelay") + self.pause_lateral_speed = self.param_s.get("PauseLateralSpeed", encoding="utf8") self.custom_model, self.model_gen = get_model_generation(self.param_s) model_capabilities = ModelCapabilities.get_by_gen(self.model_gen) self.model_use_lateral_planner = self.custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution def read_param(self): + self.is_metric = self.param_s.get_bool("IsMetric") self.edge_toggle = self.param_s.get_bool("RoadEdge") self.lane_change_set_timer = int(self.param_s.get("AutoLaneChangeTimer", encoding="utf8")) self.lane_change_bsm_delay = self.param_s.get_bool("AutoLaneChangeBsmDelay") + self.pause_lateral_speed = self.param_s.get("PauseLateralSpeed", encoding="utf8") def update(self, carstate, lateral_active, lane_change_prob, model_data=None, lat_plan_sp=None): if self.param_read_counter % 50 == 0: @@ -79,7 +83,9 @@ class DesireHelper: lane_change_auto_timer = AUTO_LANE_CHANGE_TIMER.get(self.lane_change_set_timer, 2.0) v_ego = carstate.vEgo one_blinker = carstate.leftBlinker != carstate.rightBlinker - below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN + below_lateral_speed = LANE_CHANGE_SPEED_MIN if not self.pause_lateral_speed else \ + self.pause_lateral_speed * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) + below_lane_change_speed = v_ego < below_lateral_speed if self.model_use_lateral_planner: self.road_edge = get_road_edge(carstate, model_data, self.edge_toggle) From 4857b35943d2e8ba5f7c302fb00f9a601f98aaf8 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 29 May 2024 08:42:28 +0200 Subject: [PATCH 158/575] Refactor status_code checks in sunnylink_uploader.py Removed 401 and 403 status codes from the success conditions in the sunnylink_uploader.py script. The handling for these status codes has been moved to the else clause, as they were unexpectedly marked as successful previously. --- system/loggerd/sunnylink_uploader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/loggerd/sunnylink_uploader.py b/system/loggerd/sunnylink_uploader.py index 38661d59ba..45fb323f84 100755 --- a/system/loggerd/sunnylink_uploader.py +++ b/system/loggerd/sunnylink_uploader.py @@ -187,7 +187,7 @@ class Uploader: except Exception as e: last_exc = (e, traceback.format_exc()) - if stat is not None and stat.status_code in (200, 201, 401, 403, 412): + if stat is not None and stat.status_code in (200, 201, 412): self.last_filename = fn dt = time.monotonic() - start_time if stat.status_code == 412: @@ -198,7 +198,7 @@ class Uploader: cloudlog.event("upload_success", key=key, fn=fn, sz=sz, content_length=content_length, network_type=network_type, metered=metered, speed=speed) success = True - else: + else: # 401, 403... Not sure why they were up to begin with success = False cloudlog.event("upload_failed", stat=stat, exc=last_exc, key=key, fn=fn, sz=sz, network_type=network_type, metered=metered) From ab0a10cd9c1bef44f7ea48cd971d8b204b8da186 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 29 May 2024 08:42:42 +0200 Subject: [PATCH 159/575] Update Sunnylink base URL and upload attribute name Updated the base URL for SUNNYLINK to point to a new staging test environment. Changed the UPLOAD_ATTR_NAME in the sunnylink uploader from 'user.sunny.upload' to 'user.sunnydv.upload'. This provides distinction and prevents potential conflicts. --- selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h | 2 +- system/loggerd/sunnylink_uploader.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h index fdca62d1c5..575af819b5 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h @@ -10,7 +10,7 @@ #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://stg.api.sunnypilot.ai").c_str(); +const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://tst-mr-46.dev.api.sunnypilot.ai").c_str(); // sponsor QR code class SunnylinkSponsorQRWidget : public QWidget { Q_OBJECT diff --git a/system/loggerd/sunnylink_uploader.py b/system/loggerd/sunnylink_uploader.py index 45fb323f84..f90a74ce18 100755 --- a/system/loggerd/sunnylink_uploader.py +++ b/system/loggerd/sunnylink_uploader.py @@ -22,7 +22,7 @@ from openpilot.system.loggerd.xattr_cache import getxattr, setxattr from openpilot.common.swaglog import cloudlog NetworkType = log.DeviceState.NetworkType -UPLOAD_ATTR_NAME = 'user.sunny.upload' +UPLOAD_ATTR_NAME = 'user.sunnydv.upload' UPLOAD_ATTR_VALUE = b'1' From 4f5251f951ac2fec6ef7a68e4e87317b688520d9 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 29 May 2024 09:54:49 +0200 Subject: [PATCH 160/575] Add detailed log for upload failures The commit introduces more detailed log events when an attempted upload fails for any reason. Now, when there is a non-null status code like 401 or 403, it will log more information, including the decoded content of the HTTP response. This aids in quickly identifying and resolving upload issues. --- system/loggerd/sunnylink_uploader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system/loggerd/sunnylink_uploader.py b/system/loggerd/sunnylink_uploader.py index f90a74ce18..2f192b20d8 100755 --- a/system/loggerd/sunnylink_uploader.py +++ b/system/loggerd/sunnylink_uploader.py @@ -198,7 +198,10 @@ class Uploader: cloudlog.event("upload_success", key=key, fn=fn, sz=sz, content_length=content_length, network_type=network_type, metered=metered, speed=speed) success = True - else: # 401, 403... Not sure why they were up to begin with + elif stat is not None: # 401, 403... Not sure why they were up to begin with + success = False + cloudlog.event("upload_failed with content", stat=stat, exc=last_exc, key=key, fn=fn, sz=sz, network_type=network_type, metered=metered, error=stat.content.decode("utf-8")) + else: success = False cloudlog.event("upload_failed", stat=stat, exc=last_exc, key=key, fn=fn, sz=sz, network_type=network_type, metered=metered) From e053368ebfa18def8a37143d3c2f683ba9d24400 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 29 May 2024 11:28:12 +0200 Subject: [PATCH 161/575] Added headers to sunnylink upload requests The sunnylink_uploader.py file was modified to include headers in the upload request. The headers are gathered from the 'headers' field in the URL response JSON and passed into the 'put' request method. The debug log message was also updated to display these headers along with the upload URL. --- system/loggerd/sunnylink_uploader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/system/loggerd/sunnylink_uploader.py b/system/loggerd/sunnylink_uploader.py index 2f192b20d8..2207dc313f 100755 --- a/system/loggerd/sunnylink_uploader.py +++ b/system/loggerd/sunnylink_uploader.py @@ -147,7 +147,8 @@ class Uploader: url_resp_json = json.loads(url_resp.text) url = url_resp_json['url'] - cloudlog.debug("sunnylink upload_url %s", url) + headers = url_resp_json['headers'] + cloudlog.debug("sunnylink upload_url %s | Headers: %s", url, headers) if fake_upload: return FakeResponse() @@ -160,7 +161,7 @@ class Uploader: else: data = f - return requests.put(url, data=data, timeout=10) + return requests.put(url, data=data, headers=headers, timeout=10) def upload(self, name: str, key: str, fn: str, network_type: int, metered: bool) -> bool: try: From 170a0d035ee1b422c9dfac934004f214aaa4605c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 08:09:58 -0400 Subject: [PATCH 162/575] fix non-int --- selfdrive/car/interfaces.py | 2 +- selfdrive/controls/lib/desire_helper.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index df6efebe69..2fcc97411a 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -609,7 +609,7 @@ class CarInterfaceBase(ABC): self.toggle_exp_mode(gap_button) below_lateral_speed = LANE_CHANGE_SPEED_MIN if not self.pause_lateral_speed else \ - self.pause_lateral_speed * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) + float(self.pause_lateral_speed) * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) cs_out.belowLaneChangeSpeed = cs_out.vEgo < below_lateral_speed and self.below_speed_pause diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index 948e27958f..e14dcf17b7 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -84,7 +84,7 @@ class DesireHelper: v_ego = carstate.vEgo one_blinker = carstate.leftBlinker != carstate.rightBlinker below_lateral_speed = LANE_CHANGE_SPEED_MIN if not self.pause_lateral_speed else \ - self.pause_lateral_speed * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) + float(self.pause_lateral_speed) * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) below_lane_change_speed = v_ego < below_lateral_speed if self.model_use_lateral_planner: From bf41202cab54537194cca8b20c110cd45ba7b35c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 08:43:12 -0400 Subject: [PATCH 163/575] Auto Lane Change: Add option to disable lane change --- selfdrive/controls/controlsd.py | 2 -- selfdrive/controls/lib/desire_helper.py | 3 ++- .../qt/offroad/sunnypilot/lane_change_settings.cc | 14 ++++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a0da74f930..171daf091b 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -167,7 +167,6 @@ class Controls: self.nn_alert_shown = False self.enable_nnff = self.params.get_bool("NNFF") - self.lane_change_set_timer = int(self.params.get("AutoLaneChangeTimer", encoding="utf8")) self.reverse_acc_change = False self.dynamic_experimental_control = False @@ -935,7 +934,6 @@ class Controls: if self.CP.notCar: self.joystick_mode = self.params.get_bool("JoystickDebugMode") - self.lane_change_set_timer = int(self.params.get("AutoLaneChangeTimer", encoding="utf8")) self.reverse_acc_change = self.params.get_bool("ReverseAccChange") self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl") diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index 6cac8a70e2..d6eec9b603 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -34,6 +34,7 @@ DESIRES = { } AUTO_LANE_CHANGE_TIMER = { + -1: 0.0, 0: 0.0, 1: 0.1, 2: 0.5, @@ -84,7 +85,7 @@ class DesireHelper: if self.model_use_lateral_planner: self.road_edge = get_road_edge(carstate, model_data, self.edge_toggle) - if not carstate.madsEnabled or self.lane_change_timer > LANE_CHANGE_TIME_MAX: + if not carstate.madsEnabled or self.lane_change_timer > LANE_CHANGE_TIME_MAX or self.lane_change_set_timer == -1: self.lane_change_state = LaneChangeState.off self.lane_change_direction = LaneChangeDirection.none self.prev_lane_change = False diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc index e24425a6fc..94d57c5e2c 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc @@ -64,7 +64,7 @@ void LaneChangeSettings::updateToggles() { } auto auto_lane_change_bsm_delay_toggle = toggles["AutoLaneChangeBsmDelay"]; - auto auto_lane_change_timer_param = QString::fromStdString(params.get("AutoLaneChangeTimer")); + auto auto_lane_change_timer_param = std::atoi(params.get("AutoLaneChangeTimer").c_str()); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { @@ -75,7 +75,7 @@ void LaneChangeSettings::updateToggles() { if (!CP.getEnableBsm()) { params.remove("AutoLaneChangeBsmDelay"); } - auto_lane_change_bsm_delay_toggle->setEnabled(CP.getEnableBsm() && auto_lane_change_timer_param != "0"); + auto_lane_change_bsm_delay_toggle->setEnabled(CP.getEnableBsm() && (auto_lane_change_timer_param > 0)); auto_lane_change_bsm_delay_toggle->refresh(); } else { @@ -86,10 +86,10 @@ void LaneChangeSettings::updateToggles() { // Auto Lane Change Timer (ALCT) AutoLaneChangeTimer::AutoLaneChangeTimer() : SPOptionControl ( "AutoLaneChangeTimer", - tr("Auto Lane Change Timer"), - tr("Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set.\nPlease use caution when using this feature. Only use the blinker when traffic and road conditions permit."), + tr("Auto Lane Change"), + tr("Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.\nPlease use caution when using this feature. Only use the blinker when traffic and road conditions permit."), "../assets/offroad/icon_blank.png", - {0, 5}) { + {-1, 5}) { refresh(); } @@ -97,7 +97,9 @@ AutoLaneChangeTimer::AutoLaneChangeTimer() : SPOptionControl ( void AutoLaneChangeTimer::refresh() { QString option = QString::fromStdString(params.get("AutoLaneChangeTimer")); QString second = tr("s"); - if (option == "0") { + if (option == "-1") { + setLabel(tr("Off")); + } else if (option == "0") { setLabel(tr("Nudge")); } else if (option == "1") { setLabel(tr("Nudgeless")); From 62726d612dd3940f24c8efb3219c7fda80b1d1a5 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 29 May 2024 17:26:01 +0200 Subject: [PATCH 164/575] Update upload attribute name and handle no network connection The upload attribute name has been changed from 'user.sunnydv.upload' to 'user.sunny.upload' to improve consistency in nomenclature. Furthermore, logic has been added to handle case when there is no network connection available; the process will now sleep and retry rather than crashing or hanging. --- system/loggerd/sunnylink_uploader.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/system/loggerd/sunnylink_uploader.py b/system/loggerd/sunnylink_uploader.py index 2207dc313f..cb0e837901 100755 --- a/system/loggerd/sunnylink_uploader.py +++ b/system/loggerd/sunnylink_uploader.py @@ -22,7 +22,7 @@ from openpilot.system.loggerd.xattr_cache import getxattr, setxattr from openpilot.common.swaglog import cloudlog NetworkType = log.DeviceState.NetworkType -UPLOAD_ATTR_NAME = 'user.sunnydv.upload' +UPLOAD_ATTR_NAME = 'user.sunny.upload' UPLOAD_ATTR_VALUE = b'1' @@ -265,10 +265,10 @@ def main(exit_event: threading.Event = None) -> None: offroad_last = offroad network_type = sm['deviceState'].networkType if not force_wifi else NetworkType.wifi - # if network_type == NetworkType.none: - # if allow_sleep: - # time.sleep(60 if offroad else 5) - # continue + if network_type == NetworkType.none: + if allow_sleep: + time.sleep(60 if offroad else 5) + continue if params.get_bool("DisableOnroadUploads"): if not offroad or (offroad_transition_prev > 0. and t - offroad_transition_prev < OFFROAD_TRANSITION_TIMEOUT): From dceab805def5122cf9a2ad1ea601010af8faffa6 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 29 May 2024 18:22:26 +0200 Subject: [PATCH 165/575] Update SUNNYLINK_BASE_URL default value The default value of SUNNYLINK_BASE_URL has been changed to the stage environment URL. This change aids in testing and further development purposes. --- selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h index 575af819b5..fdca62d1c5 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h @@ -10,7 +10,7 @@ #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://tst-mr-46.dev.api.sunnypilot.ai").c_str(); +const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://stg.api.sunnypilot.ai").c_str(); // sponsor QR code class SunnylinkSponsorQRWidget : public QWidget { Q_OBJECT From 5fbb3d39d62884737a275a6993d639a2b7a171f9 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 15:13:12 -0400 Subject: [PATCH 166/575] Update name --- selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc index 94d57c5e2c..d2cf106bd3 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc @@ -86,7 +86,7 @@ void LaneChangeSettings::updateToggles() { // Auto Lane Change Timer (ALCT) AutoLaneChangeTimer::AutoLaneChangeTimer() : SPOptionControl ( "AutoLaneChangeTimer", - tr("Auto Lane Change"), + tr("Auto Lane Change by Blinker"), tr("Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.\nPlease use caution when using this feature. Only use the blinker when traffic and road conditions permit."), "../assets/offroad/icon_blank.png", {-1, 5}) { From 4e3f5d54cdc97ad9e3698bacd984aed2572df1ed Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 15:39:09 -0400 Subject: [PATCH 167/575] More update name --- selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc index cc5bab87de..4b725a79a4 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc @@ -15,7 +15,7 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { std::vector> toggle_defs{ { "BelowSpeedPause", - tr("Pause Lateral Below Speed w/ Blinker"), + tr("Pause Lateral Below Speed with Blinker"), tr("Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below."), "../assets/offroad/icon_blank.png", }, From 999ac0b8b7afcd4fe12a78860cfd65647587ae39 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 22:30:33 -0400 Subject: [PATCH 168/575] in future PR for customizable min lane change speed --- selfdrive/controls/lib/desire_helper.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index e14dcf17b7..5d966ce859 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -59,22 +59,18 @@ class DesireHelper: self.road_edge = False self.param_read_counter = 0 self.read_param() - self.is_metric = self.param_s.get_bool("IsMetric") self.edge_toggle = self.param_s.get_bool("RoadEdge") self.lane_change_set_timer = int(self.param_s.get("AutoLaneChangeTimer", encoding="utf8")) self.lane_change_bsm_delay = self.param_s.get_bool("AutoLaneChangeBsmDelay") - self.pause_lateral_speed = self.param_s.get("PauseLateralSpeed", encoding="utf8") self.custom_model, self.model_gen = get_model_generation(self.param_s) model_capabilities = ModelCapabilities.get_by_gen(self.model_gen) self.model_use_lateral_planner = self.custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution def read_param(self): - self.is_metric = self.param_s.get_bool("IsMetric") self.edge_toggle = self.param_s.get_bool("RoadEdge") self.lane_change_set_timer = int(self.param_s.get("AutoLaneChangeTimer", encoding="utf8")) self.lane_change_bsm_delay = self.param_s.get_bool("AutoLaneChangeBsmDelay") - self.pause_lateral_speed = self.param_s.get("PauseLateralSpeed", encoding="utf8") def update(self, carstate, lateral_active, lane_change_prob, model_data=None, lat_plan_sp=None): if self.param_read_counter % 50 == 0: @@ -83,14 +79,12 @@ class DesireHelper: lane_change_auto_timer = AUTO_LANE_CHANGE_TIMER.get(self.lane_change_set_timer, 2.0) v_ego = carstate.vEgo one_blinker = carstate.leftBlinker != carstate.rightBlinker - below_lateral_speed = LANE_CHANGE_SPEED_MIN if not self.pause_lateral_speed else \ - float(self.pause_lateral_speed) * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) - below_lane_change_speed = v_ego < below_lateral_speed + below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN if self.model_use_lateral_planner: self.road_edge = get_road_edge(carstate, model_data, self.edge_toggle) - if not carstate.madsEnabled or self.lane_change_timer > LANE_CHANGE_TIME_MAX: + if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX: self.lane_change_state = LaneChangeState.off self.lane_change_direction = LaneChangeDirection.none self.prev_lane_change = False From 317a08bff3f3aafeac74e3a2460ba30250979eeb Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 22:37:11 -0400 Subject: [PATCH 169/575] should be all toggleable when onroad --- selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc index 4b725a79a4..2c29682b4e 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc @@ -55,9 +55,6 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { if (param == "BelowSpeedPause") { list->addItem(pause_lateral_speed); } - - // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled); } connect(toggles["BelowSpeedPause"], &ToggleControl::toggleFlipped, [=](bool state) { From a7468682c4449990109602d4e5c60f3852c43c9d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 29 May 2024 22:37:50 -0400 Subject: [PATCH 170/575] new line --- selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h index 066f4821e2..dd8e1748bd 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h @@ -54,4 +54,4 @@ private: AutoLaneChangeTimer *auto_lane_change_timer; PauseLateralSpeed *pause_lateral_speed; -}; \ No newline at end of file +}; From ba6940f398abd6f631cc683b2c7c4f8f510e6a73 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 30 May 2024 13:59:06 -0400 Subject: [PATCH 171/575] Add TODO --- selfdrive/controls/lib/desire_helper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index 21ba0d6a6e..a37d425d96 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -80,6 +80,7 @@ class DesireHelper: lane_change_auto_timer = AUTO_LANE_CHANGE_TIMER.get(self.lane_change_set_timer, 2.0) v_ego = carstate.vEgo one_blinker = carstate.leftBlinker != carstate.rightBlinker + # TODO: SP: !659: User-defined minimum lane change speed below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN if self.model_use_lateral_planner: From 4b56ea9e53dfb14a55eb7569a5890dc1e9fd308b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 30 May 2024 21:10:49 -0400 Subject: [PATCH 172/575] Refactor lateral speed calculation method This commit abstracts the computation of minimal lateral speed into a function named 'get_min_lateral_speed'. It simplifies the calculation logic used for lane change speed minimum in 'selfdrive/car/interfaces.py'. The 'pause_lateral_speed' value has been changed from a string to an integer and directly used in the new function. This change improves code readability and modularity. --- selfdrive/car/interfaces.py | 12 +++++------- selfdrive/controls/lib/drive_helpers.py | 6 ++++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 2fcc97411a..50d1876d53 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -18,8 +18,7 @@ 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.values import PLATFORMS -from openpilot.selfdrive.controls.lib.desire_helper import LANE_CHANGE_SPEED_MIN -from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, get_friction +from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, get_friction, get_min_lateral_speed from openpilot.selfdrive.controls.lib.events import Events from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel @@ -222,7 +221,7 @@ class CarInterfaceBase(ABC): self.acc_mads_combo = self.param_s.get_bool("AccMadsCombo") self.is_metric = self.param_s.get_bool("IsMetric") self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") - self.pause_lateral_speed = self.param_s.get("PauseLateralSpeed", encoding="utf8") + self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8")) self.prev_acc_mads_combo = False self.mads_event_lock = True self.gap_button_counter = 0 @@ -608,10 +607,9 @@ class CarInterfaceBase(ABC): if self.CP.openpilotLongitudinalControl: self.toggle_exp_mode(gap_button) - below_lateral_speed = LANE_CHANGE_SPEED_MIN if not self.pause_lateral_speed else \ - float(self.pause_lateral_speed) * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) + lane_change_speed_min = get_min_lateral_speed(self.pause_lateral_speed, self.is_metric) - cs_out.belowLaneChangeSpeed = cs_out.vEgo < below_lateral_speed and self.below_speed_pause + 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 \ (cs_out.seatbeltUnlatched and cs_out.gearShifter != GearShifter.park): @@ -716,7 +714,7 @@ class CarInterfaceBase(ABC): if self._frame % 100 == 0: self.is_metric = self.param_s.get_bool("IsMetric") self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") - self.pause_lateral_speed = self.param_s.get("PauseLateralSpeed", encoding="utf8") + self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8")) if self._frame % 300 == 0: self._frame = 0 self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam") diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 61edf7aade..db63c05cde 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -5,6 +5,7 @@ from cereal import car, log, custom from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import clip, interp from openpilot.common.realtime import DT_MDL, DT_CTRL +from openpilot.selfdrive.controls.lib.desire_helper import LANE_CHANGE_SPEED_MIN from openpilot.selfdrive.modeld.constants import ModelConstants # WARNING: this value was determined based on the model's training distribution, @@ -341,3 +342,8 @@ def get_road_edge(carstate, model_v2, toggle): road_edge = False return road_edge + + +def get_min_lateral_speed(value: int, is_metric: bool, default: float = LANE_CHANGE_SPEED_MIN): + speed: float = default if value == 0 else value * CV.KPH_TO_MS if is_metric else CV.MPH_TO_MS + return speed From ca2d189f71e4fba37aa8bb4e426caf1451e907c7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 30 May 2024 21:12:56 -0400 Subject: [PATCH 173/575] Update CHANGELOGS.md --- CHANGELOGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 4cbf97fc0f..4eeeb94121 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -29,7 +29,7 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * When Forced Offroad mode is on, allows changing offroad-only settings even when the car is turned on * UPDATED: Auto Lane Change Timer -> Auto Lane Change by Blinker * NEW❗: New "Off" option to disable lane change by blinker -* UPDATED: Pause Lateral Below Speed w/ Blinker +* UPDATED: Pause Lateral Below Speed with Blinker * NEW❗: Customizable Pause Lateral Speed * Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. * UPDATED: Hyundai CAN Longitudinal From 29b223cb40c82c0436b6cb1279e1c5e06c48012a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 30 May 2024 23:42:21 -0400 Subject: [PATCH 174/575] move to desire helper --- selfdrive/car/interfaces.py | 3 ++- selfdrive/controls/lib/desire_helper.py | 5 +++++ selfdrive/controls/lib/drive_helpers.py | 6 ------ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 50d1876d53..23eeb1c848 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -18,7 +18,8 @@ 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.values import PLATFORMS -from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, get_friction, get_min_lateral_speed +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 from openpilot.selfdrive.controls.lib.events import Events from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index a37d425d96..bf2e2cba86 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -43,6 +43,11 @@ AUTO_LANE_CHANGE_TIMER = { } +def get_min_lateral_speed(value: int, is_metric: bool, default: float = LANE_CHANGE_SPEED_MIN): + speed: float = default if value == 0 else value * CV.KPH_TO_MS if is_metric else CV.MPH_TO_MS + return speed + + class DesireHelper: def __init__(self): self.lane_change_state = LaneChangeState.off diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index db63c05cde..61edf7aade 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -5,7 +5,6 @@ from cereal import car, log, custom from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import clip, interp from openpilot.common.realtime import DT_MDL, DT_CTRL -from openpilot.selfdrive.controls.lib.desire_helper import LANE_CHANGE_SPEED_MIN from openpilot.selfdrive.modeld.constants import ModelConstants # WARNING: this value was determined based on the model's training distribution, @@ -342,8 +341,3 @@ def get_road_edge(carstate, model_v2, toggle): road_edge = False return road_edge - - -def get_min_lateral_speed(value: int, is_metric: bool, default: float = LANE_CHANGE_SPEED_MIN): - speed: float = default if value == 0 else value * CV.KPH_TO_MS if is_metric else CV.MPH_TO_MS - return speed From a57fd5585cfc188bbe2921d6377d1371c0c0a90b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 02:30:12 -0400 Subject: [PATCH 175/575] Manager: Add Update button when Traceback is displayed --- selfdrive/ui/qt/text.cc | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 21ec5eedcf..e5b54b2a09 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -4,7 +4,9 @@ #include #include #include +#include +#include "common/params.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" @@ -32,16 +34,36 @@ int main(int argc, char *argv[]) { }); QPushButton *btn = new QPushButton(); -#ifdef __aarch64__ + QPushButton *update_btn = new QPushButton(); + update_btn->setText(QObject::tr("Update")); +//#ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); + QFutureWatcher watcher; + QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { + btn->setEnabled(true); + }); QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); -#else + QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher, &btn]() { + btn->setEnabled(false); + const std::string git_branch = Params().get("GitBranch"); + const std::string cmd1 = "git remote set-url origin-update " + Params().get("GitRemote"); + const std::string cmd2 = "git fetch origin-update " + git_branch; + const std::string cmd3 = "git reset --hard origin-update/" + git_branch; + + QFuture future = QtConcurrent::run([=]() { + std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3).c_str()); + }); + watcher.setFuture(future); + }); +//#else + update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); -#endif +//#endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); + main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); window.setStyleSheet(R"( * { @@ -58,6 +80,10 @@ int main(int argc, char *argv[]) { border-radius: 20px; margin-right: 40px; } + QPushButton:disabled { + color: #33FFFFFF; + border: 2px solid #33FFFFFF; + } )"); return a.exec(); From 0842d8ace0247562e701acb67b52b3a8d27d0c05 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 02:35:10 -0400 Subject: [PATCH 176/575] oops --- selfdrive/ui/qt/text.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index e5b54b2a09..be0a8bcd58 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { QPushButton *btn = new QPushButton(); QPushButton *update_btn = new QPushButton(); update_btn->setText(QObject::tr("Update")); -//#ifdef __aarch64__ +#ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); QFutureWatcher watcher; QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { @@ -57,11 +57,11 @@ int main(int argc, char *argv[]) { }); watcher.setFuture(future); }); -//#else +#else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); -//#endif +#endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); From ee5cdaf7bb810046b2ca0dd43c7a4cb562887219 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 02:36:47 -0400 Subject: [PATCH 177/575] should be add --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index be0a8bcd58..e9038760ee 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -48,7 +48,7 @@ int main(int argc, char *argv[]) { QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher, &btn]() { btn->setEnabled(false); const std::string git_branch = Params().get("GitBranch"); - const std::string cmd1 = "git remote set-url origin-update " + Params().get("GitRemote"); + const std::string cmd1 = "git remote add origin-update " + Params().get("GitRemote"); const std::string cmd2 = "git fetch origin-update " + git_branch; const std::string cmd3 = "git reset --hard origin-update/" + git_branch; From fb267e91c21f69382b893c1e7f3a1586a5cd5ab7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 02:45:50 -0400 Subject: [PATCH 178/575] try this --- selfdrive/ui/qt/text.cc | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index e9038760ee..a9e5ba156a 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include "common/params.h" #include "system/hardware/hw.h" @@ -38,25 +39,26 @@ int main(int argc, char *argv[]) { update_btn->setText(QObject::tr("Update")); #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); - QFutureWatcher watcher; - QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { - btn->setEnabled(true); - }); QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher, &btn]() { - btn->setEnabled(false); + + QProcess *process = new QProcess(&window); + QObject::connect(process, QOverload::of(&QProcess::finished), [btn]() { + btn->setEnabled(true); + }); + QObject::connect(update_btn, &QPushButton::clicked, [=, &process, &btn]() { const std::string git_branch = Params().get("GitBranch"); const std::string cmd1 = "git remote add origin-update " + Params().get("GitRemote"); const std::string cmd2 = "git fetch origin-update " + git_branch; const std::string cmd3 = "git reset --hard origin-update/" + git_branch; - QFuture future = QtConcurrent::run([=]() { - std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3).c_str()); - }); - watcher.setFuture(future); - }); + btn->setEnabled(false); + + QString command = QString::fromStdString("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3); + + process->start("/bin/sh", QStringList() << "-c" << command); +}); #else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); From e77ce5684b4ab29f52617f2642589dfaf94500b6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 02:51:58 -0400 Subject: [PATCH 179/575] how about this --- selfdrive/ui/qt/text.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index a9e5ba156a..333fec5610 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -47,7 +47,7 @@ int main(int argc, char *argv[]) { QObject::connect(process, QOverload::of(&QProcess::finished), [btn]() { btn->setEnabled(true); }); - QObject::connect(update_btn, &QPushButton::clicked, [=, &process, &btn]() { + QObject::connect(update_btn, &QPushButton::clicked, [&, process, btn]() { const std::string git_branch = Params().get("GitBranch"); const std::string cmd1 = "git remote add origin-update " + Params().get("GitRemote"); const std::string cmd2 = "git fetch origin-update " + git_branch; @@ -57,8 +57,8 @@ int main(int argc, char *argv[]) { QString command = QString::fromStdString("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3); - process->start("/bin/sh", QStringList() << "-c" << command); -}); + process->start("/bin/bash", QStringList() << "-c" << command); + }); #else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); From bf886e3fe6e20cee15f684e12fa60bd3885a3709 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 02:57:33 -0400 Subject: [PATCH 180/575] Revert "how about this" This reverts commit e77ce5684b4ab29f52617f2642589dfaf94500b6. --- selfdrive/ui/qt/text.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 333fec5610..a9e5ba156a 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -47,7 +47,7 @@ int main(int argc, char *argv[]) { QObject::connect(process, QOverload::of(&QProcess::finished), [btn]() { btn->setEnabled(true); }); - QObject::connect(update_btn, &QPushButton::clicked, [&, process, btn]() { + QObject::connect(update_btn, &QPushButton::clicked, [=, &process, &btn]() { const std::string git_branch = Params().get("GitBranch"); const std::string cmd1 = "git remote add origin-update " + Params().get("GitRemote"); const std::string cmd2 = "git fetch origin-update " + git_branch; @@ -57,8 +57,8 @@ int main(int argc, char *argv[]) { QString command = QString::fromStdString("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3); - process->start("/bin/bash", QStringList() << "-c" << command); - }); + process->start("/bin/sh", QStringList() << "-c" << command); +}); #else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); From c181a4edd47755049520a8141a52659b057ab564 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 02:57:34 -0400 Subject: [PATCH 181/575] Revert "try this" This reverts commit fb267e91c21f69382b893c1e7f3a1586a5cd5ab7. --- selfdrive/ui/qt/text.cc | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index a9e5ba156a..e9038760ee 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -5,7 +5,6 @@ #include #include #include -#include #include "common/params.h" #include "system/hardware/hw.h" @@ -39,26 +38,25 @@ int main(int argc, char *argv[]) { update_btn->setText(QObject::tr("Update")); #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); + QFutureWatcher watcher; + QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { + btn->setEnabled(true); + }); QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - - QProcess *process = new QProcess(&window); - QObject::connect(process, QOverload::of(&QProcess::finished), [btn]() { - btn->setEnabled(true); - }); - QObject::connect(update_btn, &QPushButton::clicked, [=, &process, &btn]() { + QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher, &btn]() { + btn->setEnabled(false); const std::string git_branch = Params().get("GitBranch"); const std::string cmd1 = "git remote add origin-update " + Params().get("GitRemote"); const std::string cmd2 = "git fetch origin-update " + git_branch; const std::string cmd3 = "git reset --hard origin-update/" + git_branch; - btn->setEnabled(false); - - QString command = QString::fromStdString("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3); - - process->start("/bin/sh", QStringList() << "-c" << command); -}); + QFuture future = QtConcurrent::run([=]() { + std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3).c_str()); + }); + watcher.setFuture(future); + }); #else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); From 8bdb6631bea1e72085479fcf8d6a6eb01aab9578 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 02:59:23 -0400 Subject: [PATCH 182/575] move it --- selfdrive/ui/qt/text.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index e9038760ee..54f138557a 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -39,9 +39,6 @@ int main(int argc, char *argv[]) { #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); QFutureWatcher watcher; - QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { - btn->setEnabled(true); - }); QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); @@ -55,6 +52,9 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3).c_str()); }); + QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { + btn->setEnabled(true); + }); watcher.setFuture(future); }); #else From 8336e7b01c2091fe8c278d5e30bf5da455fd54b6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 03:02:12 -0400 Subject: [PATCH 183/575] fix --- selfdrive/ui/qt/text.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 54f138557a..24bfcd2540 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -45,12 +45,13 @@ int main(int argc, char *argv[]) { QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher, &btn]() { btn->setEnabled(false); const std::string git_branch = Params().get("GitBranch"); - const std::string cmd1 = "git remote add origin-update " + Params().get("GitRemote"); - const std::string cmd2 = "git fetch origin-update " + git_branch; - const std::string cmd3 = "git reset --hard origin-update/" + git_branch; + const std::string cmd1 = "git remote remove origin-update"; + const std::string cmd2 = "git remote add origin-update " + Params().get("GitRemote"); + const std::string cmd3 = "git fetch origin-update " + git_branch; + const std::string cmd4 = "git reset --hard origin-update/" + git_branch; QFuture future = QtConcurrent::run([=]() { - std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3).c_str()); + std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3 + " && " cmd4).c_str()); }); QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { btn->setEnabled(true); From 46b8d83b656e81d33f5485d95bc197219d7a50cc Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 03:03:07 -0400 Subject: [PATCH 184/575] fix --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 24bfcd2540..435c451e6b 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -51,7 +51,7 @@ int main(int argc, char *argv[]) { const std::string cmd4 = "git reset --hard origin-update/" + git_branch; QFuture future = QtConcurrent::run([=]() { - std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3 + " && " cmd4).c_str()); + std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3 + " && " + cmd4).c_str()); }); QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { btn->setEnabled(true); From cd2a265364923680181803e8338e7ff0f8456dda Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 03:15:41 -0400 Subject: [PATCH 185/575] update_btn instead --- selfdrive/ui/qt/text.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 435c451e6b..3acf14a4bf 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -42,8 +42,8 @@ int main(int argc, char *argv[]) { QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher, &btn]() { - btn->setEnabled(false); + QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() { + update_btn->setEnabled(false); const std::string git_branch = Params().get("GitBranch"); const std::string cmd1 = "git remote remove origin-update"; const std::string cmd2 = "git remote add origin-update " + Params().get("GitRemote"); @@ -53,8 +53,8 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3 + " && " + cmd4).c_str()); }); - QObject::connect(&watcher, &QFutureWatcher::finished, [btn]() { - btn->setEnabled(true); + QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { + update_btn->setEnabled(true); }); watcher.setFuture(future); }); From 00ad42da50a549c1361fa1cd000020a2e822265e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 03:25:56 -0400 Subject: [PATCH 186/575] just to test lol --- selfdrive/ui/qt/text.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 3acf14a4bf..8f179789ea 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -55,6 +55,7 @@ int main(int argc, char *argv[]) { }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { update_btn->setEnabled(true); + update_btn->setText("Ready to Reboot") }); watcher.setFuture(future); }); From 2ee7a0200fb0e8cdd989eace46239f2b46816f6f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 03:27:26 -0400 Subject: [PATCH 187/575] oops --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 8f179789ea..df9de4c433 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -55,7 +55,7 @@ int main(int argc, char *argv[]) { }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { update_btn->setEnabled(true); - update_btn->setText("Ready to Reboot") + update_btn->setText("Ready to Reboot"); }); watcher.setFuture(future); }); From 920ff7836ecc2d7444328e0f677a0b4655d57c17 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 11:04:55 -0400 Subject: [PATCH 188/575] Sentry: Add dongle ID to uploaded bootlog --- selfdrive/sentry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/sentry.py b/selfdrive/sentry.py index 0d947ac381..09bd4748ab 100644 --- a/selfdrive/sentry.py +++ b/selfdrive/sentry.py @@ -12,6 +12,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.athena.registration import UNREGISTERED_DONGLE_ID, is_registered_device from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_build_metadata, get_version @@ -108,8 +109,9 @@ def get_init() -> None: subprocess.call(["./bootlog", "--started"], cwd=os.path.join(BASEDIR, "system/loggerd")) with sentry_sdk.configure_scope() as scope: if route_name is not None: - sentry_sdk.set_tag("route_name", dongle_id + "|" + route_name) - scope.add_attachment(path=os.path.join("/data/media/0/realdata/params", route_name)) + route_id = dongle_id + "/" + route_name + sentry_sdk.set_tag("route_name", route_id) + scope.add_attachment(path=os.path.join(Paths.log_root(), "params", route_name), filename=route_id) def init(project: SentryProject) -> bool: From 06784e2a80151e387fffa5824f99b3635508fb94 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:10:18 -0400 Subject: [PATCH 189/575] interact with reboot button too --- selfdrive/ui/qt/text.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index df9de4c433..62f21e550c 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -43,7 +43,9 @@ int main(int argc, char *argv[]) { Hardware::reboot(); }); QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() { + btn->setEnabled(false); update_btn->setEnabled(false); + update_btn->setText(QObject::tr("Updating...")); const std::string git_branch = Params().get("GitBranch"); const std::string cmd1 = "git remote remove origin-update"; const std::string cmd2 = "git remote add origin-update " + Params().get("GitRemote"); @@ -54,6 +56,7 @@ int main(int argc, char *argv[]) { std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3 + " && " + cmd4).c_str()); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { + btn->setEnabled(true); update_btn->setEnabled(true); update_btn->setText("Ready to Reboot"); }); From f4c7d3ecd3c9cb225fb94a9c54556c32317f7a7a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:21:11 -0400 Subject: [PATCH 190/575] check if `origin-update` exists --- selfdrive/ui/qt/text.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 62f21e550c..0b6219d4f8 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -47,13 +47,19 @@ int main(int argc, char *argv[]) { update_btn->setEnabled(false); update_btn->setText(QObject::tr("Updating...")); const std::string git_branch = Params().get("GitBranch"); + const std::string remote_check = "git remote | grep origin-update"; + std::string cmd = "cd /data/openpilot"; const std::string cmd1 = "git remote remove origin-update"; const std::string cmd2 = "git remote add origin-update " + Params().get("GitRemote"); const std::string cmd3 = "git fetch origin-update " + git_branch; const std::string cmd4 = "git reset --hard origin-update/" + git_branch; - QFuture future = QtConcurrent::run([=]() { - std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3 + " && " + cmd4).c_str()); + QFuture future = QtConcurrent::run([&]() { + if (!std::system(remote_check.c_str())) { + cmd += " && " + cmd1; + } + cmd += " && " + cmd2 + " && " + cmd3 + " && " + cmd4; + std::system(cmd.c_str()); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { btn->setEnabled(true); From 83349a13da9d40658b8f12e9e77bccc1da79e2a1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:25:21 -0400 Subject: [PATCH 191/575] do this --- selfdrive/ui/qt/text.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 0b6219d4f8..08d3964030 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { QPushButton *btn = new QPushButton(); QPushButton *update_btn = new QPushButton(); update_btn->setText(QObject::tr("Update")); -#ifdef __aarch64__ +//#ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); QFutureWatcher watcher; QObject::connect(btn, &QPushButton::clicked, [=]() { @@ -54,7 +54,7 @@ int main(int argc, char *argv[]) { const std::string cmd3 = "git fetch origin-update " + git_branch; const std::string cmd4 = "git reset --hard origin-update/" + git_branch; - QFuture future = QtConcurrent::run([&]() { + QFuture future = QtConcurrent::run([&cmd, remote_check, cmd1, cmd2, cmd3, cmd4]() { if (!std::system(remote_check.c_str())) { cmd += " && " + cmd1; } @@ -68,11 +68,11 @@ int main(int argc, char *argv[]) { }); watcher.setFuture(future); }); -#else +//#else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); -#endif +//#endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); From 30fe43bf019e4d5367a81a168a510121ba159092 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:26:29 -0400 Subject: [PATCH 192/575] oops --- selfdrive/ui/qt/text.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 08d3964030..81171af1b1 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { QPushButton *btn = new QPushButton(); QPushButton *update_btn = new QPushButton(); update_btn->setText(QObject::tr("Update")); -//#ifdef __aarch64__ +#ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); QFutureWatcher watcher; QObject::connect(btn, &QPushButton::clicked, [=]() { @@ -68,11 +68,11 @@ int main(int argc, char *argv[]) { }); watcher.setFuture(future); }); -//#else +#else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); -//#endif +#endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); From 47897309177e7e179064d20e9b2130ee494bc0c8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:27:44 -0400 Subject: [PATCH 193/575] Revert "oops" This reverts commit 30fe43bf019e4d5367a81a168a510121ba159092. --- selfdrive/ui/qt/text.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 81171af1b1..08d3964030 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { QPushButton *btn = new QPushButton(); QPushButton *update_btn = new QPushButton(); update_btn->setText(QObject::tr("Update")); -#ifdef __aarch64__ +//#ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); QFutureWatcher watcher; QObject::connect(btn, &QPushButton::clicked, [=]() { @@ -68,11 +68,11 @@ int main(int argc, char *argv[]) { }); watcher.setFuture(future); }); -#else +//#else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); -#endif +//#endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); From c661d36c340119c924355f53e643cc1b193126a8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:27:44 -0400 Subject: [PATCH 194/575] Revert "do this" This reverts commit 83349a13da9d40658b8f12e9e77bccc1da79e2a1. --- selfdrive/ui/qt/text.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 08d3964030..0b6219d4f8 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { QPushButton *btn = new QPushButton(); QPushButton *update_btn = new QPushButton(); update_btn->setText(QObject::tr("Update")); -//#ifdef __aarch64__ +#ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); QFutureWatcher watcher; QObject::connect(btn, &QPushButton::clicked, [=]() { @@ -54,7 +54,7 @@ int main(int argc, char *argv[]) { const std::string cmd3 = "git fetch origin-update " + git_branch; const std::string cmd4 = "git reset --hard origin-update/" + git_branch; - QFuture future = QtConcurrent::run([&cmd, remote_check, cmd1, cmd2, cmd3, cmd4]() { + QFuture future = QtConcurrent::run([&]() { if (!std::system(remote_check.c_str())) { cmd += " && " + cmd1; } @@ -68,11 +68,11 @@ int main(int argc, char *argv[]) { }); watcher.setFuture(future); }); -//#else +#else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); -//#endif +#endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); From 9dae5d1659fda73d04dd6db14b1b49898f5e1de2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:27:45 -0400 Subject: [PATCH 195/575] Revert "check if `origin-update` exists" This reverts commit f4c7d3ecd3c9cb225fb94a9c54556c32317f7a7a. --- selfdrive/ui/qt/text.cc | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 0b6219d4f8..62f21e550c 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -47,19 +47,13 @@ int main(int argc, char *argv[]) { update_btn->setEnabled(false); update_btn->setText(QObject::tr("Updating...")); const std::string git_branch = Params().get("GitBranch"); - const std::string remote_check = "git remote | grep origin-update"; - std::string cmd = "cd /data/openpilot"; const std::string cmd1 = "git remote remove origin-update"; const std::string cmd2 = "git remote add origin-update " + Params().get("GitRemote"); const std::string cmd3 = "git fetch origin-update " + git_branch; const std::string cmd4 = "git reset --hard origin-update/" + git_branch; - QFuture future = QtConcurrent::run([&]() { - if (!std::system(remote_check.c_str())) { - cmd += " && " + cmd1; - } - cmd += " && " + cmd2 + " && " + cmd3 + " && " + cmd4; - std::system(cmd.c_str()); + QFuture future = QtConcurrent::run([=]() { + std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3 + " && " + cmd4).c_str()); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { btn->setEnabled(true); From ce16dd2a4f016b9341355afbe5b377696fb7df13 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:41:57 -0400 Subject: [PATCH 196/575] handle things better --- selfdrive/ui/qt/text.cc | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 62f21e550c..945b5d1d81 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -47,13 +47,22 @@ int main(int argc, char *argv[]) { update_btn->setEnabled(false); update_btn->setText(QObject::tr("Updating...")); const std::string git_branch = Params().get("GitBranch"); - const std::string cmd1 = "git remote remove origin-update"; - const std::string cmd2 = "git remote add origin-update " + Params().get("GitRemote"); - const std::string cmd3 = "git fetch origin-update " + git_branch; - const std::string cmd4 = "git reset --hard origin-update/" + git_branch; + const std::string git_remote = Params().get("GitRemote"); + const std::string to_home_dir = "cd /data/openpilot"; + const std::string check_remote = "git remote | grep origin-update"; + const std::string reset_remote = "git remote remove origin-update && git remote add origin-update " + git_remote; + const std::string update_remote = "git remote set-url origin-update " + git_remote; + const std::string fetch_remote = "git fetch origin-update " + git_branch; + const std::string reset_branch = "git reset --hard origin-update/" + git_branch; + + std::string remote_cmd = update_remote; + if (!std::system((to_home_dir + " && " + check_remote).c_str())) { + remote_cmd = reset_remote; + } + const std::string cmd = " && " + to_home_dir + " && " + remote_cmd + " && " + fetch_remote + " && " + reset_branch; QFuture future = QtConcurrent::run([=]() { - std::system(("cd /data/openpilot && " + cmd1 + " && " + cmd2 + " && " + cmd3 + " && " + cmd4).c_str()); + std::system(cmd.c_str()); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { btn->setEnabled(true); From b07473b9c965e8d008c6fc8da1e7df89f80c595c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:43:11 -0400 Subject: [PATCH 197/575] oops --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 945b5d1d81..15b5d3d726 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -59,7 +59,7 @@ int main(int argc, char *argv[]) { if (!std::system((to_home_dir + " && " + check_remote).c_str())) { remote_cmd = reset_remote; } - const std::string cmd = " && " + to_home_dir + " && " + remote_cmd + " && " + fetch_remote + " && " + reset_branch; + const std::string cmd = to_home_dir + " && " + remote_cmd + " && " + fetch_remote + " && " + reset_branch; QFuture future = QtConcurrent::run([=]() { std::system(cmd.c_str()); From c06ddb81afd330f1bc5250be9ffc1991759b9f7b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 22:47:30 -0400 Subject: [PATCH 198/575] should add if does not exist --- selfdrive/ui/qt/text.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 15b5d3d726..e4446fcb80 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -51,11 +51,11 @@ int main(int argc, char *argv[]) { const std::string to_home_dir = "cd /data/openpilot"; const std::string check_remote = "git remote | grep origin-update"; const std::string reset_remote = "git remote remove origin-update && git remote add origin-update " + git_remote; - const std::string update_remote = "git remote set-url origin-update " + git_remote; + const std::string add_remote = "git remote add origin-update " + git_remote; const std::string fetch_remote = "git fetch origin-update " + git_branch; const std::string reset_branch = "git reset --hard origin-update/" + git_branch; - std::string remote_cmd = update_remote; + std::string remote_cmd = add_remote; if (!std::system((to_home_dir + " && " + check_remote).c_str())) { remote_cmd = reset_remote; } From a353db86373653d23535d6c5067f6f44dcb4a96a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:07:53 -0400 Subject: [PATCH 199/575] try printing the output in view --- selfdrive/ui/qt/text.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index e4446fcb80..dfb79385c3 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include "common/params.h" #include "system/hardware/hw.h" @@ -12,6 +13,18 @@ #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/widgets/scrollview.h" +std::string executeCommand(const char* cmd) { + std::array buffer; + std::string result; + FILE* pipe = popen(cmd, "r"); + if (!pipe) throw std::runtime_error("popen() failed!"); + while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) { + result += buffer.data(); + } + pclose(pipe); + return result; +} + int main(int argc, char *argv[]) { initApp(argc, argv); QApplication a(argc, argv); @@ -63,6 +76,10 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::system(cmd.c_str()); + std::string output = executeCommand(cmd.c_str()); + QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(output))); + QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { btn->setEnabled(true); From 8f4dc2481f5e73a32971cf144bba61df49bc95f6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:10:17 -0400 Subject: [PATCH 200/575] add debug --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index dfb79385c3..f28d751918 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -75,8 +75,8 @@ int main(int argc, char *argv[]) { const std::string cmd = to_home_dir + " && " + remote_cmd + " && " + fetch_remote + " && " + reset_branch; QFuture future = QtConcurrent::run([=]() { - std::system(cmd.c_str()); std::string output = executeCommand(cmd.c_str()); + std::cout << "Debug Output: " << output << std::endl; QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); From 520ecde1cdd7fe25376178270bcfecf28506d0ec Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:16:14 -0400 Subject: [PATCH 201/575] swaglog --- selfdrive/ui/qt/text.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index f28d751918..e451fd54bd 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -8,6 +8,7 @@ #include #include "common/params.h" +#include "common/swaglog.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" @@ -76,7 +77,7 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); - std::cout << "Debug Output: " << output << std::endl; + LOGW("Forcing update for %s", output); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); From fd68ce13ee9fbe36902e94aa261f8514ce0ecbb1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:17:24 -0400 Subject: [PATCH 202/575] convert str --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index e451fd54bd..537af84dbb 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); - LOGW("Forcing update for %s", output); + LOGW("Forcing update for %s", output.c_str()); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); From 23f263304fe2c04f22bcc567ec6224e938765b76 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:22:52 -0400 Subject: [PATCH 203/575] try this --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 537af84dbb..5e4be49870 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); - LOGW("Forcing update for %s", output.c_str()); + LOGW("CHECK OUTPUT PLS \n %s", output.c_str()); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); From db23c4a56fa18c776f418e47e5502871bd7672e8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:28:45 -0400 Subject: [PATCH 204/575] print them all! --- selfdrive/ui/qt/text.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 5e4be49870..b3989fc095 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -73,11 +73,11 @@ int main(int argc, char *argv[]) { if (!std::system((to_home_dir + " && " + check_remote).c_str())) { remote_cmd = reset_remote; } - const std::string cmd = to_home_dir + " && " + remote_cmd + " && " + fetch_remote + " && " + reset_branch; + const std::string cmd = to_home_dir + " && " + remote_cmd + " && " + fetch_remote + " && " + reset_branch + " 2>&1"; QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); - LOGW("CHECK OUTPUT PLS \n %s", output.c_str()); + LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); From 07f5592e4fbaee647a09fb339038cca651b23c68 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:31:21 -0400 Subject: [PATCH 205/575] use std::ostringstream --- selfdrive/ui/qt/text.cc | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index b3989fc095..610d6536ce 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include "common/params.h" #include "common/swaglog.h" @@ -15,15 +16,17 @@ #include "selfdrive/ui/qt/widgets/scrollview.h" std::string executeCommand(const char* cmd) { - std::array buffer; - std::string result; - FILE* pipe = popen(cmd, "r"); - if (!pipe) throw std::runtime_error("popen() failed!"); - while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) { - result += buffer.data(); + std::array buffer{}; + std::ostringstream result; + std::unique_ptr pipe(popen(cmd, "r"), pclose); + if (!pipe) { + LOGW("Failed to open pipe"); + throw std::runtime_error("popen() failed!"); } - pclose(pipe); - return result; + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result << buffer.data(); + } + return result.str(); } int main(int argc, char *argv[]) { From 97409b198cfc0cf39378ca79852661c966dca3da Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:33:07 -0400 Subject: [PATCH 206/575] disable log --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 610d6536ce..7e4c68c13f 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -80,7 +80,7 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); - LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); + //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); From a4f65ab740db72e197e23ad911c852ea9b0022bf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:33:36 -0400 Subject: [PATCH 207/575] have this so no need to manually do it --- selfdrive/controls/lib/drive_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 61edf7aade..b04dde92b0 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -5,6 +5,7 @@ from cereal import car, log, custom from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import clip, interp from openpilot.common.realtime import DT_MDL, DT_CTRL +from openpilot.selfdrive.controls.lib.desire_helper import LANE_CHANGE_SPEED_MIN from openpilot.selfdrive.modeld.constants import ModelConstants # WARNING: this value was determined based on the model's training distribution, From 3dc042e0667a7a5097dd0d5315911958cb45934e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:36:59 -0400 Subject: [PATCH 208/575] just execute for now --- selfdrive/ui/qt/text.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 7e4c68c13f..b3453467dc 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -81,9 +81,9 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); - QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(output))); - QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); + //QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, + // Q_ARG(QString, QString::fromStdString(output))); + //QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { btn->setEnabled(true); From a335542090f3466a0cd19912fb54527d9a49274b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:38:10 -0400 Subject: [PATCH 209/575] Revert "just execute for now" This reverts commit 3dc042e0667a7a5097dd0d5315911958cb45934e. --- selfdrive/ui/qt/text.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index b3453467dc..7e4c68c13f 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -81,9 +81,9 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); - //QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, - // Q_ARG(QString, QString::fromStdString(output))); - //QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); + QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(output))); + QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { btn->setEnabled(true); From bd2b6eb304a573e188fd8880a43e9bd8a7c1df9c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:40:48 -0400 Subject: [PATCH 210/575] Revert "disable log" This reverts commit 97409b198cfc0cf39378ca79852661c966dca3da. --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 7e4c68c13f..610d6536ce 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -80,7 +80,7 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); - //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); + LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); From a1662b86ebd82139c45474f62b70bbba49e99fe7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:41:37 -0400 Subject: [PATCH 211/575] execute all commands regardless of failures --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 610d6536ce..3f8ff946e7 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -76,7 +76,7 @@ int main(int argc, char *argv[]) { if (!std::system((to_home_dir + " && " + check_remote).c_str())) { remote_cmd = reset_remote; } - const std::string cmd = to_home_dir + " && " + remote_cmd + " && " + fetch_remote + " && " + reset_branch + " 2>&1"; + const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); From 449563c0bd3678b7411433613f564bda5df4c4cf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:46:03 -0400 Subject: [PATCH 212/575] add this --- selfdrive/ui/qt/text.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 3f8ff946e7..8b4806bbfd 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -67,10 +67,10 @@ int main(int argc, char *argv[]) { const std::string git_remote = Params().get("GitRemote"); const std::string to_home_dir = "cd /data/openpilot"; const std::string check_remote = "git remote | grep origin-update"; - const std::string reset_remote = "git remote remove origin-update && git remote add origin-update " + git_remote; - const std::string add_remote = "git remote add origin-update " + git_remote; - const std::string fetch_remote = "git fetch origin-update " + git_branch; - const std::string reset_branch = "git reset --hard origin-update/" + git_branch; + const std::string reset_remote = "git remote remove origin-update && git remote add origin-update " + git_remote + " 2>&1"; + const std::string add_remote = "git remote add origin-update " + git_remote + " 2>&1"; + const std::string fetch_remote = "git fetch origin-update " + git_branch + " 2>&1"; + const std::string reset_branch = "git reset --hard origin-update/" + git_branch + " 2>&1"; std::string remote_cmd = add_remote; if (!std::system((to_home_dir + " && " + check_remote).c_str())) { From d615f6dd8bfb2968b0b2b6bfad3ddf1a622aab54 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:51:33 -0400 Subject: [PATCH 213/575] try not updating scroll --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 8b4806bbfd..45080d462e 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) { LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); - QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); + //QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { btn->setEnabled(true); From 96506a90972ad0279c32c0aaed8c2b461c78598f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:53:25 -0400 Subject: [PATCH 214/575] not needed --- selfdrive/ui/qt/text.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 45080d462e..6cdd4b830e 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -83,7 +83,6 @@ int main(int argc, char *argv[]) { LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); - //QMetaObject::invokeMethod(scroll, "update", Qt::QueuedConnection); }); QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { btn->setEnabled(true); From 7703ea8c514aaf8e49f5fdfdc88942cdd9e27bf0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:58:23 -0400 Subject: [PATCH 215/575] use qprocess so text is printed live --- selfdrive/ui/qt/text.cc | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 6cdd4b830e..011ed8189c 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -55,7 +56,26 @@ int main(int argc, char *argv[]) { update_btn->setText(QObject::tr("Update")); #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); - QFutureWatcher watcher; + + QProcess *command = new QProcess(); + QObject::connect(command, QOverload::of(&QProcess::finished), + [=](int exitCode, QProcess::ExitStatus exitStatus){ + btn->setEnabled(true); + update_btn->setEnabled(true); + update_btn->setText("Ready to Reboot"); + command->deleteLater(); // Delete QProcess after it finishes + }); + QObject::connect(command, &QProcess::readyReadStandardOutput, [=]() { + QByteArray result = command->readAllStandardOutput(); + label->setText(label->text() + "\n" + result); // appends the text to the QLabel + scroll->update(); + }); + QObject::connect(command, &QProcess::readyReadStandardError, [=]() { + QByteArray result = command->readAllStandardError(); + label->setText(label->text() + "\n" + result); // appends the text to the QLabel + scroll->update(); + }); + QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); @@ -78,18 +98,8 @@ int main(int argc, char *argv[]) { } const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; - QFuture future = QtConcurrent::run([=]() { - std::string output = executeCommand(cmd.c_str()); - LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); - QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(output))); - }); - QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { - btn->setEnabled(true); - update_btn->setEnabled(true); - update_btn->setText("Ready to Reboot"); - }); - watcher.setFuture(future); + QString c = QString::fromStdString(cmd); + command->start(c); }); #else update_btn->setEnabled(false); From 8225779bf04b133b37c6ffe7fe2078ff2f5219ec Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 31 May 2024 23:59:31 -0400 Subject: [PATCH 216/575] deprecated --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 011ed8189c..e87db09a9a 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -79,7 +79,7 @@ int main(int argc, char *argv[]) { QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() { + QObject::connect(update_btn, &QPushButton::clicked, [=]() { btn->setEnabled(false); update_btn->setEnabled(false); update_btn->setText(QObject::tr("Updating...")); From 09be75814bce968fab811ff9b4ac295aa66a3b58 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 1 Jun 2024 00:01:14 -0400 Subject: [PATCH 217/575] do it like this --- selfdrive/ui/qt/text.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index e87db09a9a..eff533d43e 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) { #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); - QProcess *command = new QProcess(); + QProcess *command; QObject::connect(command, QOverload::of(&QProcess::finished), [=](int exitCode, QProcess::ExitStatus exitStatus){ btn->setEnabled(true); @@ -98,6 +98,7 @@ int main(int argc, char *argv[]) { } const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; + command = new QProcess() QString c = QString::fromStdString(cmd); command->start(c); }); From a7e9f093389d4bd3c8400d6ed63d452fe555b6c9 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 1 Jun 2024 00:02:45 -0400 Subject: [PATCH 218/575] Revert "do it like this" This reverts commit 09be75814bce968fab811ff9b4ac295aa66a3b58. --- selfdrive/ui/qt/text.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index eff533d43e..e87db09a9a 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) { #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); - QProcess *command; + QProcess *command = new QProcess(); QObject::connect(command, QOverload::of(&QProcess::finished), [=](int exitCode, QProcess::ExitStatus exitStatus){ btn->setEnabled(true); @@ -98,7 +98,6 @@ int main(int argc, char *argv[]) { } const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; - command = new QProcess() QString c = QString::fromStdString(cmd); command->start(c); }); From b0e8fddfedefb7b10114071eb8cae2320b8e9843 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 1 Jun 2024 00:02:46 -0400 Subject: [PATCH 219/575] Revert "deprecated" This reverts commit 8225779bf04b133b37c6ffe7fe2078ff2f5219ec. --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index e87db09a9a..011ed8189c 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -79,7 +79,7 @@ int main(int argc, char *argv[]) { QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - QObject::connect(update_btn, &QPushButton::clicked, [=]() { + QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() { btn->setEnabled(false); update_btn->setEnabled(false); update_btn->setText(QObject::tr("Updating...")); From 6f619aa48d6edc52d4efa29a64f38e4bd28a90c2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 1 Jun 2024 00:02:46 -0400 Subject: [PATCH 220/575] Revert "use qprocess so text is printed live" This reverts commit 7703ea8c514aaf8e49f5fdfdc88942cdd9e27bf0. --- selfdrive/ui/qt/text.cc | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 011ed8189c..6cdd4b830e 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -56,26 +55,7 @@ int main(int argc, char *argv[]) { update_btn->setText(QObject::tr("Update")); #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); - - QProcess *command = new QProcess(); - QObject::connect(command, QOverload::of(&QProcess::finished), - [=](int exitCode, QProcess::ExitStatus exitStatus){ - btn->setEnabled(true); - update_btn->setEnabled(true); - update_btn->setText("Ready to Reboot"); - command->deleteLater(); // Delete QProcess after it finishes - }); - QObject::connect(command, &QProcess::readyReadStandardOutput, [=]() { - QByteArray result = command->readAllStandardOutput(); - label->setText(label->text() + "\n" + result); // appends the text to the QLabel - scroll->update(); - }); - QObject::connect(command, &QProcess::readyReadStandardError, [=]() { - QByteArray result = command->readAllStandardError(); - label->setText(label->text() + "\n" + result); // appends the text to the QLabel - scroll->update(); - }); - + QFutureWatcher watcher; QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); @@ -98,8 +78,18 @@ int main(int argc, char *argv[]) { } const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; - QString c = QString::fromStdString(cmd); - command->start(c); + QFuture future = QtConcurrent::run([=]() { + std::string output = executeCommand(cmd.c_str()); + LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); + QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(output))); + }); + QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { + btn->setEnabled(true); + update_btn->setEnabled(true); + update_btn->setText("Ready to Reboot"); + }); + watcher.setFuture(future); }); #else update_btn->setEnabled(false); From 318fc690fa358f5d31631671a8f25d0d8003e7aa Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 1 Jun 2024 00:06:19 -0400 Subject: [PATCH 221/575] Revert "have this so no need to manually do it" This reverts commit a4f65ab740db72e197e23ad911c852ea9b0022bf. --- selfdrive/controls/lib/drive_helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index b04dde92b0..61edf7aade 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -5,7 +5,6 @@ from cereal import car, log, custom from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import clip, interp from openpilot.common.realtime import DT_MDL, DT_CTRL -from openpilot.selfdrive.controls.lib.desire_helper import LANE_CHANGE_SPEED_MIN from openpilot.selfdrive.modeld.constants import ModelConstants # WARNING: this value was determined based on the model's training distribution, From b66da7dd1a285eba34af127e4ff5a0cca40695b8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 1 Jun 2024 00:12:17 -0400 Subject: [PATCH 222/575] disable log --- selfdrive/ui/qt/text.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 6cdd4b830e..60b58f5134 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -80,7 +80,7 @@ int main(int argc, char *argv[]) { QFuture future = QtConcurrent::run([=]() { std::string output = executeCommand(cmd.c_str()); - LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); + //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(output))); }); From 910a4f6c8f6ae3c58192d53e8bcb3204013e9e38 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 4 Jun 2024 21:25:04 -0400 Subject: [PATCH 223/575] Update CHANGELOGS.md --- CHANGELOGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 954005fd4d..0f997e08af 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,4 +1,4 @@ -sunnypilot - 0.9.7.0 (2024-05-xx) +sunnypilot - 0.9.7.0 (2024-05-12) ======================== * New driving model * Adjust driving personality with the follow distance button From c740ab2a86fd5ee761dad27eaa68654210bccb32 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 4 Jun 2024 21:25:07 -0400 Subject: [PATCH 224/575] Revert "Update CHANGELOGS.md" This reverts commit 910a4f6c8f6ae3c58192d53e8bcb3204013e9e38. --- CHANGELOGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 0f997e08af..954005fd4d 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,4 +1,4 @@ -sunnypilot - 0.9.7.0 (2024-05-12) +sunnypilot - 0.9.7.0 (2024-05-xx) ======================== * New driving model * Adjust driving personality with the follow distance button From 21d3fd338591034b9a068d4dec192a022a0c2188 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 8 Jun 2024 21:59:31 +0200 Subject: [PATCH 225/575] Add logging for Sunnylink offline issue The code updates include error logging for debugging when Sunnylink goes offline. Log includes information such as the last ping time and the current time to better understand the cause of possible issues. This logging will aid in resolving status discrepancies with the Sunnylink feature. --- selfdrive/ui/qt/sidebar.cc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index df9ebc45c0..171058b48f 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -3,6 +3,7 @@ #include #include +#include #include "selfdrive/ui/qt/util.h" #include "common/params.h" @@ -156,16 +157,21 @@ void Sidebar::updateState(const UIState &s) { ItemStatus sunnylinkStatus; auto last_sunnylink_ping = std::strtol(params.get("LastSunnylinkPingTime").c_str(), nullptr, 10); + auto current_nanos = nanos_since_boot(); + auto elapsed_sunnylink_ping = current_nanos - last_sunnylink_ping; auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); if (!sunnylink_enabled) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("DISABLED")}, disabled_color}; - } else if (last_ping == 0) { + } else if (last_sunnylink_ping == 0) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("OFFLINE")}, warning_color}; } else { - if (nanos_since_boot() - last_sunnylink_ping < 80e9) + if (static_cast(elapsed_sunnylink_ping) < 80e9) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ONLINE")}, good_color}; - else + } + else { + LOGE("Sunnylink is offline, last ping: [%ld]. Current time: [%ld], diff: [%ld]", last_sunnylink_ping, current_nanos, elapsed_sunnylink_ping); sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ERROR")}, danger_color}; + } } setProperty("sunnylinkStatus", QVariant::fromValue(sunnylinkStatus)); } From 609b2d8a2134140513b1aa870cd8a7eb37da6839 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 9 Jun 2024 10:32:02 +0200 Subject: [PATCH 226/575] Refactor sunnylink code for logs and ping timeouts The sunnylink ping and log statements have been refactored for clarity and optimization. Log levels were changed from debug to info, resulting in more visibility for important processes in sunnylinkd.py. The timeout before re-pinging in ws_ping() was also updated from 80% to 70% to ensure more consistent connections. --- selfdrive/athena/sunnylinkd.py | 8 ++++---- selfdrive/ui/qt/sidebar.cc | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index 9c26dd42a1..1b993a534b 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -55,7 +55,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: raise finally: for thread in threads: - cloudlog.debug(f"athena.joining {thread.name}") + cloudlog.info(f"sunnylinkd athena.joining {thread.name}") thread.join() @@ -70,7 +70,7 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: recv_queue.put_nowait(data) cloudlog.debug(f"sunnylinkd.ws_recv.recv {data}") elif opcode in (ABNF.OPCODE_PING, ABNF.OPCODE_PONG): - cloudlog.debug(f"sunnylinkd.ws_recv.pong {opcode}") + cloudlog.info(f"sunnylinkd.ws_recv.pong {opcode}") last_ping = int(time.monotonic() * 1e9) Params().put("LastSunnylinkPingTime", str(last_ping)) except WebSocketTimeoutException: @@ -88,11 +88,11 @@ def ws_ping(ws: WebSocket, end_event: threading.Event) -> None: while not end_event.is_set(): try: ws.ping() - cloudlog.debug(f"sunnylinkd.ws_recv.ws_ping: Pinging") + cloudlog.info(f"sunnylinkd.ws_recv.ws_ping: Pinging") except Exception: cloudlog.exception("sunnylinkd.ws_ping.exception") end_event.set() - time.sleep(RECONNECT_TIMEOUT_S * 0.8) # Sleep about 80% before a timeout + time.sleep(RECONNECT_TIMEOUT_S * 0.7) # Sleep about 70% before a timeout def ws_queue(end_event: threading.Event) -> None: resume_requested = False diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 171058b48f..bc7a236b8b 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -156,7 +156,7 @@ void Sidebar::updateState(const UIState &s) { setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); ItemStatus sunnylinkStatus; - auto last_sunnylink_ping = std::strtol(params.get("LastSunnylinkPingTime").c_str(), nullptr, 10); + auto last_sunnylink_ping = std::stoull(params.get("LastSunnylinkPingTime")); auto current_nanos = nanos_since_boot(); auto elapsed_sunnylink_ping = current_nanos - last_sunnylink_ping; auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); @@ -165,7 +165,7 @@ void Sidebar::updateState(const UIState &s) { } else if (last_sunnylink_ping == 0) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("OFFLINE")}, warning_color}; } else { - if (static_cast(elapsed_sunnylink_ping) < 80e9) { + if (elapsed_sunnylink_ping < 80000000000ULL) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ONLINE")}, good_color}; } else { From 6b70ea3de60ad3c8c963ee9e78aef90fafb47728 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 9 Jun 2024 11:32:43 +0200 Subject: [PATCH 227/575] Comment out error logging for Sunnylink status The commit comments out a log statement that outputs an error message if Sunnylink is offline. This change is probably a temporary measure to avoid unnecessary log messages in the output, although it may affect troubleshooting of Sunnylink connection issues. --- selfdrive/ui/qt/sidebar.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index bc7a236b8b..b5198c2a17 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -169,7 +169,7 @@ void Sidebar::updateState(const UIState &s) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ONLINE")}, good_color}; } else { - LOGE("Sunnylink is offline, last ping: [%ld]. Current time: [%ld], diff: [%ld]", last_sunnylink_ping, current_nanos, elapsed_sunnylink_ping); + //LOGE("Sunnylink is offline, last ping: [%ld]. Current time: [%ld], diff: [%ld]", last_sunnylink_ping, current_nanos, elapsed_sunnylink_ping); sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ERROR")}, danger_color}; } } From 3f7403318e2948aea468eb78efa328ad75ad2000 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 10 Jun 2024 07:47:20 +0200 Subject: [PATCH 228/575] Add LastSunnylinkPingTime to manager and update sunnylinkStatus calculation A new parameter "LastSunnylinkPingTime" has been introduced to the manager script, it will now be tracked and stored alongside other car status parameters. Additionally, unnecessary comments and includes have been removed in the sidebar script. The "sunnylinkStatus" calculation has also been updated to accommodate empty strings and prevent potential crashes. --- selfdrive/manager/manager.py | 1 + selfdrive/ui/qt/sidebar.cc | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index ef8cd286a9..07bd5f5848 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -107,6 +107,7 @@ def manager_init() -> None: ("SunnylinkEnabled", "1"), ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), + ("LastSunnylinkPingTime", "0"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index b5198c2a17..5d8245acc5 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -3,7 +3,6 @@ #include #include -#include #include "selfdrive/ui/qt/util.h" #include "common/params.h" @@ -156,7 +155,8 @@ void Sidebar::updateState(const UIState &s) { setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); ItemStatus sunnylinkStatus; - auto last_sunnylink_ping = std::stoull(params.get("LastSunnylinkPingTime")); + auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime"); + auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str); auto current_nanos = nanos_since_boot(); auto elapsed_sunnylink_ping = current_nanos - last_sunnylink_ping; auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); @@ -169,7 +169,6 @@ void Sidebar::updateState(const UIState &s) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ONLINE")}, good_color}; } else { - //LOGE("Sunnylink is offline, last ping: [%ld]. Current time: [%ld], diff: [%ld]", last_sunnylink_ping, current_nanos, elapsed_sunnylink_ping); sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ERROR")}, danger_color}; } } From f4415169c04b9bd9952a4d0f766a042b1271e36b Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 10 Jun 2024 08:17:28 +0200 Subject: [PATCH 229/575] Add error logging for invalid requests in athenad.py An error logging line was added in athenad.py to capture and log the details of any invalid requests received. This additional error handling measure will help to maintain the standards of incoming requests and provide clearer debugging information. --- selfdrive/athena/athenad.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index d5b1e37ce1..37f74bc096 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -178,6 +178,7 @@ def jsonrpc_handler(end_event: threading.Event) -> None: elif "id" in data and ("result" in data or "error" in data): log_recv_queue.put_nowait(data) else: + cloudlog.error("athena.jsonrpc_handler.invalid_request", data=data) raise Exception("not a valid request or response") except queue.Empty: pass From 85fb840575140026098cfc42e69baef7e0824c5b Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 10 Jun 2024 08:55:23 +0200 Subject: [PATCH 230/575] Add debug message for non-supported PC in log_handler A debug message has been added to the log_handler function in the athenad.py file. This message will get triggered when the function is being invoked on a non-supported PC, providing a clearer indication of potential errors due to platform incompatibility. --- selfdrive/athena/athenad.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 0c5e03c79b..15672b92df 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -632,6 +632,7 @@ def add_log_to_queue(log_path, log_id, is_sunnylink = False): def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None: is_sunnylink = log_attr_name != LOG_ATTR_NAME if PC: + cloudlog.debug("athena.log_handler: Not supported on PC") return log_files = [] From 6d8348ccf0756c9a22b23d3d0dc81d88205098df Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 10 Jun 2024 09:39:32 +0200 Subject: [PATCH 231/575] Change error logging to event logging in athenad.py The logging for an invalid JSON RPC request in athenad.py has been changed from an error log to an event log. The event log still records the request as an error, improving clarity of error handling in the system. --- selfdrive/athena/athenad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 37f74bc096..443173e36d 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -178,7 +178,7 @@ def jsonrpc_handler(end_event: threading.Event) -> None: elif "id" in data and ("result" in data or "error" in data): log_recv_queue.put_nowait(data) else: - cloudlog.error("athena.jsonrpc_handler.invalid_request", data=data) + cloudlog.event("athena.jsonrpc_handler.invalid_request", error=True, data=data) raise Exception("not a valid request or response") except queue.Empty: pass From 12841b7bb4091cfeb437eaa815404fabad5548a8 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 10 Jun 2024 09:40:08 +0200 Subject: [PATCH 232/575] Update sunnylinkd.py to improve logging and connection timeout The commit enhances the logging in the selfdrive/athena/sunnylinkd.py by adding an info statement when the handle_long_poll function starts. It also modifies the create_connection function call, removing the fixed 30 seconds timeout, providing more flexibility for connection time. --- selfdrive/athena/sunnylinkd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index 1b993a534b..ca537149d9 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -28,6 +28,7 @@ LOCAL_PORT_WHITELIST = {8022} params = Params() sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8')) def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: + cloudlog.info("sunnylinkd.handle_long_poll started") end_event = threading.Event() threads = [ @@ -169,8 +170,7 @@ def main(exit_event: threading.Event = None): cloudlog.event("sunnylinkd.main.connecting_ws", ws_uri=ws_uri, retries=conn_retries) ws = create_connection(ws_uri, cookie="jwt=" + sunnylink_api.get_token(), - enable_multithread=True, - timeout=30.0) + enable_multithread=True) cloudlog.event("sunnylinkd.main.connected_ws", ws_uri=ws_uri, retries=conn_retries, duration=time.monotonic() - conn_start) conn_start = None From a7699c280f9e8a237d95ee16e73edaf19e0de799 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 10 Jun 2024 11:14:35 +0200 Subject: [PATCH 233/575] Change log level from info to debug in sunnylinkd.py The logging level for 'pong' and 'ping' messages in sunnylinkd.py has been changed from 'info' to 'debug'. This adjustment will help reduce the level of verbosity in the logs and focus more on significant events. --- selfdrive/athena/sunnylinkd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/athena/sunnylinkd.py b/selfdrive/athena/sunnylinkd.py index ca537149d9..b0c6e8d4dc 100755 --- a/selfdrive/athena/sunnylinkd.py +++ b/selfdrive/athena/sunnylinkd.py @@ -71,7 +71,7 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: recv_queue.put_nowait(data) cloudlog.debug(f"sunnylinkd.ws_recv.recv {data}") elif opcode in (ABNF.OPCODE_PING, ABNF.OPCODE_PONG): - cloudlog.info(f"sunnylinkd.ws_recv.pong {opcode}") + cloudlog.debug(f"sunnylinkd.ws_recv.pong {opcode}") last_ping = int(time.monotonic() * 1e9) Params().put("LastSunnylinkPingTime", str(last_ping)) except WebSocketTimeoutException: @@ -89,7 +89,7 @@ def ws_ping(ws: WebSocket, end_event: threading.Event) -> None: while not end_event.is_set(): try: ws.ping() - cloudlog.info(f"sunnylinkd.ws_recv.ws_ping: Pinging") + cloudlog.debug(f"sunnylinkd.ws_recv.ws_ping: Pinging") except Exception: cloudlog.exception("sunnylinkd.ws_ping.exception") end_event.set() From 7484d752052ed5dca9514c40cb4ed2a8c640e6ca Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 9 Jun 2024 00:00:12 -0400 Subject: [PATCH 234/575] GitLab Pipelines: Add script for syncing LFS between comma and sunnypilot This commit adds a new script, 'sync-lfs.sh'. This script assists by syncing the git large file storage (LFS) system. It backs up the current LFS configuration, uses the comma-separated configuration for operations like fetch and pull, and then restores the original configuration before pushing to the origin's main branch. --- release/ci/sync-lfs.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 release/ci/sync-lfs.sh diff --git a/release/ci/sync-lfs.sh b/release/ci/sync-lfs.sh new file mode 100644 index 0000000000..a328724351 --- /dev/null +++ b/release/ci/sync-lfs.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +mv .lfsconfig .lfsconfig.bak +mv .lfsconfig-comma .lfsconfig +git lfs fetch --all; git lfs pull +mv .lfsconfig .lfsconfig-comma +mv .lfsconfig.bak .lfsconfig +git lfs fetch --all; git lfs push origin main From 899b5a6e33630f5b6fed8d02a02da7e8e9ceeed3 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 10 Jun 2024 21:32:15 -0400 Subject: [PATCH 235/575] Update CHANGELOGS.md --- CHANGELOGS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 4eeeb94121..576a89aafa 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,15 +1,15 @@ -sunnypilot - 0.9.7.0 (2024-05-xx) +sunnypilot - 0.9.7.0 (2024-06-11) ======================== * New driving model * Adjust driving personality with the follow distance button -* Support for hybrid variants of supported Ford models * Added toggle to enable driver monitoring even when openpilot is not engaged +* Support for hybrid variants of supported Ford models * Fingerprinting without the OBD-II port on all cars ************************ * UPDATED: Synced with commaai's openpilot - * master commit 2e6b2ef (May 9, 2024) + * master commit ce2a686 (June 1, 2024) * NEW❗: sunnylink (Alpha early access) - * NEW❗: Config Backup + * NEW❗: Config/Settings Backup * Remotely back up and restore sunnypilot settings easily * Device registration with sunnylink ensures a secure, integrated experience across services * AES encryption derived from the device's RSA private key is used for utmost security @@ -27,6 +27,7 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * 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 + * To engage/disengage Force Offroad, go to Settings -> Device panel * UPDATED: Auto Lane Change Timer -> Auto Lane Change by Blinker * NEW❗: New "Off" option to disable lane change by blinker * UPDATED: Pause Lateral Below Speed with Blinker @@ -38,7 +39,7 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * 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 + * openpilot Longitudinal Control available cars * Custom Stock Longitudinal Control available cars * UPDATED: Continued support for comma Pedal * In response to the official deprecation of support for comma Pedal in the upstream, sunnypilot will continue maintaining software support for comma Pedal From ba3475a02cd8f1829c6736f510ca2bc32781a523 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 09:32:15 +0200 Subject: [PATCH 236/575] Add 'sunnylink_uploader.py' to temporary blacklist We are temporarily blacklisting 'sunnylink_uploader.py' by adding it under 'sunnypilot_blacklist' in 'release/release_files.py' until it's ready for widespread roll-out. Alongside, the existing path in 'system/manager/process_config.py' has been updated to reflect the correct location of 'sunnylink_uploader.py'. This will ensure the processes are correctly managed based on its existence. --- release/release_files.py | 1 + system/manager/process_config.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/release/release_files.py b/release/release_files.py index 74ef64933f..07fb41106b 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -37,6 +37,7 @@ blacklist = [ sunnypilot_blacklist = [ ".idea/", ".run/", + "system/loggerd/sunnylink_uploader.py", # Temporarily, until we are ready to roll it out widely ] # Merge the blacklists diff --git a/system/manager/process_config.py b/system/manager/process_config.py index deeb75791d..748e149955 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -109,7 +109,7 @@ if Params().get_bool("SunnylinkEnabled"): procs += [ DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), ] - if os.path.exists("../../system/loggerd/sunnylink_uploader.py"): + if os.path.exists("../loggerd/sunnylink_uploader.py"): procs += [ PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", always_run), ] From 702643188ca8bcc8a75f5d9e4b83b74279fc0b8c Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 10:49:35 +0200 Subject: [PATCH 237/575] Updating panda to point to the proper branch for dev-c3 --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 2ea3f78967..069a31b638 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 2ea3f7896751e89ccd06013e106264a2e13aaa59 +Subproject commit 069a31b6382fa3ac889d1e57725e59a6277dec0e From de4cefdc1ef5be75be6ae58785b9b9db81cfbee9 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 11:59:46 +0200 Subject: [PATCH 238/575] Update import paths in athena files The changes here correct a misalignment in the import paths in the sunnylinkd.py and manage_sunnylinkd.py files. These updates ensure that system.athena modules are accurately referenced, which is critical for the function of these scripts. --- system/athena/manage_sunnylinkd.py | 2 +- system/athena/sunnylinkd.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/system/athena/manage_sunnylinkd.py b/system/athena/manage_sunnylinkd.py index c785136889..8a912a8a73 100755 --- a/system/athena/manage_sunnylinkd.py +++ b/system/athena/manage_sunnylinkd.py @@ -2,4 +2,4 @@ from system.athena.manage_athenad import manage_athenad if __name__ == '__main__': - manage_athenad("SunnylinkDongleId", "SunnylinkdPid", 'sunnylinkd', 'selfdrive.athena.sunnylinkd') + manage_athenad("SunnylinkDongleId", "SunnylinkdPid", 'sunnylinkd', 'system.athena.sunnylinkd') diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 0b784d0371..4150e8a475 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -10,7 +10,7 @@ import os import threading import time -from openpilot.selfdrive.athena.athenad import ws_send, jsonrpc_handler, \ +from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \ recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler from jsonrpc import dispatcher from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, From d9170548c9b19c353adf983e95eff544b948fa5b Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 12:07:59 +0200 Subject: [PATCH 239/575] Add new entries to sunnypilot_blacklist Two new entries have been added to the sunnypilot_blacklist array in the release_files.py script. This now includes ".run/" and "release/ci/scons_cache/". --- release/release_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release/release_files.py b/release/release_files.py index 07fb41106b..7c3374a73f 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -37,6 +37,8 @@ blacklist = [ sunnypilot_blacklist = [ ".idea/", ".run/", + ".run/", + "release/ci/scons_cache/", "system/loggerd/sunnylink_uploader.py", # Temporarily, until we are ready to roll it out widely ] From 6a6f5f4860588db39c830603bbedb044035ed14d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 12:24:22 +0200 Subject: [PATCH 240/575] Add various files to release_files.py The commit adds a list of files related to GitLab CI, Docker, code formatting, and security guidelines to the release_files.py. These additions will provide more control and configuration for the release process. --- release/release_files.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/release/release_files.py b/release/release_files.py index 7c3374a73f..26ed7f32b1 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -40,6 +40,19 @@ sunnypilot_blacklist = [ ".run/", "release/ci/scons_cache/", "system/loggerd/sunnylink_uploader.py", # Temporarily, until we are ready to roll it out widely + ".gitlab-ci.yml", + ".clang-tidy", + ".dockerignore", + ".editorconfig", + ".gitmodules", + ".pre-commit-config.yaml", + ".python-version", + "Dockerfile.openpilot", + "Dockerfile.openpilot_base", + "SECURITY.md", + "codecov.yml", + "conftest.py", + "poetry.lock", ] # Merge the blacklists From 7a51cfcd13f32da101e8169e68cbbc85cfb83c21 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 12:37:41 +0200 Subject: [PATCH 241/575] Add numerous files to release_files.py This commit majorly expands the list of files to be tracked in release_files.py. The additions range from Dockerfile configurations, README documents, test files, builder scripts and many more. This might enhance the overall tracking and versioning capability for a wider scope of files across the project. --- release/release_files.py | 590 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 590 insertions(+) diff --git a/release/release_files.py b/release/release_files.py index 26ed7f32b1..e0e338c8cd 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -47,12 +47,602 @@ sunnypilot_blacklist = [ ".gitmodules", ".pre-commit-config.yaml", ".python-version", + "Dockerfile", + "dockerfile", "Dockerfile.openpilot", "Dockerfile.openpilot_base", "SECURITY.md", "codecov.yml", "conftest.py", "poetry.lock", + ".clang-tidy", + ".dockerignore", + ".editorconfig", + ".gitlab-ci.yml", + ".gitmodules", + ".pre-commit-config.yaml", + ".python-version", + "body/.pre-commit-config.yaml", + "body/board/canloader.py", + "body/board/flash_base.sh", + "body/board/flash_knee.sh", + "body/board/recover.sh", + "body/Dockerfile", + "body/LICENSE", + "body/pyproject.toml", + "body/README.md", + "body/requirements.txt", + "body/SConstruct", + "cereal/.dockerignore", + "cereal/.pre-commit-config.yaml", + "cereal/codecov.yml", + "cereal/Dockerfile", + "cereal/generate_javascript.sh", + "cereal/LICENSE", + "cereal/maptile.capnp", + "cereal/messaging/demo.py", + "cereal/messaging/msgq.md", + "cereal/messaging/stress.py", + "cereal/messaging/test_runner", + "cereal/messaging/tests/", + "cereal/messaging/tests/__init__.py", + "cereal/messaging/tests/test_fake.py", + "cereal/messaging/tests/test_messaging.py", + "cereal/messaging/tests/test_poller.py", + "cereal/messaging/tests/test_pub_sub_master.py", + "cereal/messaging/tests/test_services.py", + "cereal/pyproject.toml", + "cereal/README.md", + "cereal/SConstruct", + "cereal/site_scons/", + "cereal/site_scons/site_tools/", + "cereal/site_scons/site_tools/cython.py", + "cereal/visionipc/test_runner", + "cereal/visionipc/tests/", + "cereal/visionipc/tests/__init__.py", + "cereal/visionipc/tests/test_visionipc.py", + "codecov.yml", + "common/tests/", + "common/tests/__init__.py", + "common/tests/.gitignore", + "common/tests/test_common", + "common/tests/test_file_helpers.py", + "common/tests/test_numpy_fast.py", + "common/tests/test_params.py", + "common/tests/test_simple_kalman.py", + "common/transformations/.gitignore", + "common/transformations/README.md", + "common/transformations/tests/", + "common/transformations/tests/__init__.py", + "common/transformations/tests/test_coordinates.py", + "common/transformations/tests/test_orientation.py", + "conftest.py", + "Dockerfile.openpilot", + "Dockerfile.openpilot_base", + "docs/_static/", + "docs/_static/favicon.ico", + "docs/_static/logo.png", + "docs/_static/robots.txt", + "docs/assets/", + "docs/assets/icon-star-empty.svg", + "docs/assets/icon-star-full.svg", + "docs/assets/icon-star-half.svg", + "docs/assets/icon-youtube.svg", + "docs/BOUNTIES.md", + "docs/c_docs.rst", + "docs/conf.py", + "docs/index.md", + "docs/Makefile", + "docs/overview.rst", + "docs/README.md", + "docs/SAFETY.md", + "docs/WORKFLOW.md", + "opendbc/.pre-commit-config.yaml", + "opendbc/bmw_e9x_e8x.dbc", + "opendbc/cadillac_ct6_chassis.dbc", + "opendbc/cadillac_ct6_object.dbc", + "opendbc/cadillac_ct6_powertrain.dbc", + "opendbc/can/tests/", + "opendbc/can/tests/__init__.py", + "opendbc/can/tests/.gitignore", + "opendbc/can/tests/test_checksums.py", + "opendbc/can/tests/test_dbc_exceptions.py", + "opendbc/can/tests/test_dbc_parser.py", + "opendbc/can/tests/test_define.py", + "opendbc/can/tests/test_packer_parser.py", + "opendbc/can/tests/test_parser_performance.py", + "opendbc/can/tests/test.dbc", + "opendbc/chrysler_cusw.dbc", + "opendbc/Dockerfile", + "opendbc/ESR.dbc", + "opendbc/ford_cgea1_2_bodycan_2011.dbc", + "opendbc/ford_cgea1_2_ptcan_2011.dbc", + "opendbc/ford_fusion_2018_pt.dbc", + "opendbc/gm_global_a_high_voltage_management.dbc", + "opendbc/gm_global_a_lowspeed_1818125.dbc", + "opendbc/gm_global_a_lowspeed.dbc", + "opendbc/gm_global_a_powertrain_expansion.dbc", + "opendbc/honda_fit_hybrid_2018_can_generated.dbc", + "opendbc/honda_pilot_2023_can_generated.dbc", + "opendbc/hyundai_2015_ccan.dbc", + "opendbc/hyundai_2015_mcan.dbc", + "opendbc/hyundai_i30_2014.dbc", + "opendbc/hyundai_kia_mando_corner_radar_generated.dbc", + "opendbc/hyundai_santafe_2007.dbc", + "opendbc/LICENSE", + "opendbc/luxgen_s5_2015.dbc", + "opendbc/mazda_3_2019.dbc", + "opendbc/mazda_radar.dbc", + "opendbc/mazda_rx8.dbc", + "opendbc/mercedes_benz_e350_2010.dbc", + "opendbc/nissan_xterra_2011.dbc", + "opendbc/opel_omega_2001.dbc", + "opendbc/pyproject.toml", + "opendbc/README.md", + "opendbc/requirements.txt", + "opendbc/SConstruct", + "opendbc/site_scons/", + "opendbc/site_scons/site_tools/", + "opendbc/site_scons/site_tools/cython.py", + "opendbc/tesla_model3_party.dbc", + "opendbc/tesla_model3_vehicle.dbc", + "opendbc/toyota_2017_ref_pt.dbc", + "opendbc/toyota_iQ_2009_can.dbc", + "opendbc/toyota_prius_2010_pt.dbc", + "opendbc/toyota_radar_dsu_tssp.dbc", + "opendbc/volvo_v40_2017_pt.dbc", + "opendbc/volvo_v60_2015_pt.dbc", + "panda/.pre-commit-config.yaml", + "panda/Dockerfile", + "panda/docs/", + "panda/docs/CANPacket_structure.png", + "panda/docs/USB_packet_structure.png", + "panda/drivers/", + "panda/drivers/linux/", + "panda/drivers/linux/.gitignore", + "panda/drivers/linux/dkms.conf", + "panda/drivers/linux/Makefile", + "panda/drivers/linux/panda.c", + "panda/drivers/linux/README.md", + "panda/drivers/linux/test/", + "panda/drivers/linux/test/main.c", + "panda/drivers/linux/test/Makefile", + "panda/drivers/linux/test/run.sh", + "panda/drivers/spi/", + "panda/drivers/spi/.gitignore", + "panda/drivers/spi/load.sh", + "panda/drivers/spi/Makefile", + "panda/drivers/spi/patch", + "panda/drivers/spi/pull-src.sh", + "panda/drivers/spi/spi_panda.h", + "panda/drivers/spi/spidev_panda.c", + "panda/LICENSE", + "panda/mypy.ini", + "panda/panda.png", + "panda/pyproject.toml", + "panda/README.md", + "panda/requirements.txt", + "panda/SConstruct", + "panda/setup.cfg", + "panda/setup.py", + "panda/tests/", + "panda/tests/__init__.py", + "panda/tests/benchmark.py", + "panda/tests/black_white_loopback_test.py", + "panda/tests/black_white_relay_endurance.py", + "panda/tests/black_white_relay_test.py", + "panda/tests/bulk_write_test.py", + "panda/tests/can_printer.py", + "panda/tests/canfd/", + "panda/tests/canfd/test_canfd.py", + "panda/tests/check_fw_size.py", + "panda/tests/ci_shell.sh", + "panda/tests/debug_console.py", + "panda/tests/development/", + "panda/tests/development/register_hashmap_spread.py", + "panda/tests/echo.py", + "panda/tests/elm_car_simulator.py", + "panda/tests/elm_throughput.py", + "panda/tests/fan/", + "panda/tests/fan/fan_test.py", + "panda/tests/fan/fan_tuning.py", + "panda/tests/get_version.py", + "panda/tests/health_test.py", + "panda/tests/hitl/", + "panda/tests/hitl/__init__.py", + "panda/tests/hitl/1_program.py", + "panda/tests/hitl/2_health.py", + "panda/tests/hitl/3_usb_to_can.py", + "panda/tests/hitl/4_can_loopback.py", + "panda/tests/hitl/5_spi.py", + "panda/tests/hitl/6_safety.py", + "panda/tests/hitl/7_internal.py", + "panda/tests/hitl/8_misc.py", + "panda/tests/hitl/9_harness.py", + "panda/tests/hitl/conftest.py", + "panda/tests/hitl/helpers.py", + "panda/tests/hitl/known_bootstub/", + "panda/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin", + "panda/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin", + "panda/tests/hitl/known_bootstub/bootstub.panda_h7.bin", + "panda/tests/hitl/reset_jungles.py", + "panda/tests/hitl/run_parallel_tests.sh", + "panda/tests/hitl/run_serial_tests.sh", + "panda/tests/ir_test.py", + "panda/tests/libpanda/", + "panda/tests/libpanda/libpanda_py.py", + "panda/tests/libpanda/libpanda.so", + "panda/tests/libpanda/panda.c", + "panda/tests/libpanda/safety_helpers.h", + "panda/tests/libpanda/safety_helpers.py", + "panda/tests/libpanda/SConscript", + "panda/tests/libs/", + "panda/tests/libs/resetter.py", + "panda/tests/loopback_test.py", + "panda/tests/message_drop_test.py", + "panda/tests/misra/", + "panda/tests/misra/.gitignore", + "panda/tests/misra/coverage_table", + "panda/tests/misra/install.sh", + "panda/tests/misra/suppressions.txt", + "panda/tests/misra/test_misra.sh", + "panda/tests/misra/test_mutation.py", + "panda/tests/read_flash_spi.py", + "panda/tests/read_st_flash.sh", + "panda/tests/read_winusb_descriptors.py", + "panda/tests/reflash_internal_panda.py", + "panda/tests/relay_test.py", + "panda/tests/restore_flash_spi.py", + "panda/tests/safety_replay/", + "panda/tests/safety_replay/__init__.py", + "panda/tests/safety_replay/.gitignore", + "panda/tests/safety_replay/helpers.py", + "panda/tests/safety_replay/replay_drive.py", + "panda/tests/safety/", + "panda/tests/safety/__init__.py", + "panda/tests/safety/common.py", + "panda/tests/safety/hyundai_common.py", + "panda/tests/safety/test_body.py", + "panda/tests/safety/test_chrysler.py", + "panda/tests/safety/test_defaults.py", + "panda/tests/safety/test_elm327.py", + "panda/tests/safety/test_ford.py", + "panda/tests/safety/test_gm.py", + "panda/tests/safety/test_honda.py", + "panda/tests/safety/test_hyundai_canfd.py", + "panda/tests/safety/test_hyundai.py", + "panda/tests/safety/test_mazda.py", + "panda/tests/safety/test_nissan.py", + "panda/tests/safety/test_subaru_preglobal.py", + "panda/tests/safety/test_subaru.py", + "panda/tests/safety/test_tesla.py", + "panda/tests/safety/test_toyota.py", + "panda/tests/safety/test_volkswagen_mqb.py", + "panda/tests/safety/test_volkswagen_pq.py", + "panda/tests/safety/test.sh", + "panda/tests/setup_device_ci.sh", + "panda/tests/som_debug.sh", + "panda/tests/som/", + "panda/tests/som/on-device.py", + "panda/tests/som/test_bootkick.py", + "panda/tests/spam_can.py", + "panda/tests/standalone_test.py", + "panda/tests/test_rsa.c", + "panda/tests/usbprotocol/", + "panda/tests/usbprotocol/test_comms.py", + "panda/tests/usbprotocol/test_pandalib.py", + "panda/tests/usbprotocol/test.sh", + "poetry.lock", + "rednose -> rednose_repo/rednose", + "rednose_repo/.dockerignore", + "rednose_repo/.editorconfig", + "rednose_repo/.gitignore", + "rednose_repo/.pre-commit-config.yaml", + "rednose_repo/Dockerfile", + "rednose_repo/examples/", + "rednose_repo/examples/__init__.py", + "rednose_repo/examples/kinematic_kf.png", + "rednose_repo/examples/kinematic_kf.py", + "rednose_repo/examples/live_kf.py", + "rednose_repo/examples/SConscript", + "rednose_repo/examples/test_compare.py", + "rednose_repo/examples/test_kinematic_kf.py", + "rednose_repo/LICENSE", + "rednose_repo/pyproject.toml", + "rednose_repo/README.md", + "rednose_repo/rednose/", + "rednose_repo/rednose/__init__.py", + "rednose_repo/rednose/.gitignore", + "rednose_repo/rednose/helpers/", + "rednose_repo/rednose/helpers/__init__.py", + "rednose_repo/rednose/helpers/chi2_lookup_table.npy", + "rednose_repo/rednose/helpers/chi2_lookup.py", + "rednose_repo/rednose/helpers/ekf_load.h", + "rednose_repo/rednose/helpers/ekf_sym_pyx.cpp", + "rednose_repo/rednose/helpers/ekf_sym_pyx.pyx", + "rednose_repo/rednose/helpers/ekf_sym_pyx.so", + "rednose_repo/rednose/helpers/ekf_sym.h", + "rednose_repo/rednose/helpers/ekf_sym.py", + "rednose_repo/rednose/helpers/ekf.h", + "rednose_repo/rednose/helpers/kalmanfilter.py", + "rednose_repo/rednose/helpers/sympy_helpers.py", + "rednose_repo/rednose/logger/", + "rednose_repo/rednose/logger/logger.h", + "rednose_repo/rednose/SConscript", + "rednose_repo/rednose/templates/", + "rednose_repo/rednose/templates/compute_pos.c", + "rednose_repo/rednose/templates/ekf_c.c", + "rednose_repo/rednose/templates/feature_handler.c", + "rednose_repo/requirements.txt", + "rednose_repo/SConstruct", + "rednose_repo/setup.py", + "rednose_repo/site_scons/site_tools/cython.py", + "SECURITY.md", + "selfdrive/assets/compress-images.sh", + "selfdrive/assets/strip-svg-metadata.sh", + "selfdrive/boardd/tests/__init__.py", + "selfdrive/boardd/tests/bootstub.panda_h7_spiv0.bin", + "selfdrive/boardd/tests/bootstub.panda_h7.bin", + "selfdrive/boardd/tests/bootstub.panda.bin", + "selfdrive/boardd/tests/test_boardd_spi.py", + "selfdrive/boardd/tests/test_boardd_usbprotocol", + "selfdrive/boardd/tests/test_pandad.py", + "selfdrive/car/CARS_template.md", + "selfdrive/car/docs.py", + "selfdrive/car/ford/tests/", + "selfdrive/car/ford/tests/__init__.py", + "selfdrive/car/ford/tests/print_platform_codes.py", + "selfdrive/car/ford/tests/test_ford.py", + "selfdrive/car/gm/tests/", + "selfdrive/car/gm/tests/__init__.py", + "selfdrive/car/gm/tests/test_gm.py", + "selfdrive/car/honda/tests/", + "selfdrive/car/honda/tests/__init__.py", + "selfdrive/car/honda/tests/test_honda.py", + "selfdrive/car/hyundai/tests/", + "selfdrive/car/hyundai/tests/__init__.py", + "selfdrive/car/hyundai/tests/print_platform_codes.py", + "selfdrive/car/hyundai/tests/test_hyundai.py", + "selfdrive/car/README.md", + "selfdrive/car/subaru/tests/", + "selfdrive/car/subaru/tests/test_subaru.py", + "selfdrive/car/tests/.gitignore", + "selfdrive/car/tests/big_cars_test.sh", + "selfdrive/car/tests/routes.py", + "selfdrive/car/tests/test_can_fingerprint.py", + "selfdrive/car/tests/test_docs.py", + "selfdrive/car/tests/test_fingerprints.py", + "selfdrive/car/tests/test_fw_fingerprint.py", + "selfdrive/car/tests/test_lateral_limits.py", + "selfdrive/car/tests/test_models_segs.txt", + "selfdrive/car/tests/test_models.py", + "selfdrive/car/tests/test_platform_configs.py", + "selfdrive/car/toyota/tests/", + "selfdrive/car/toyota/tests/__init__.py", + "selfdrive/car/toyota/tests/print_platform_codes.py", + "selfdrive/car/toyota/tests/test_toyota.py", + "selfdrive/car/volkswagen/tests/", + "selfdrive/car/volkswagen/tests/test_volkswagen.py", + "selfdrive/controls/.gitignore", + "selfdrive/controls/lib/sunnypilot/tests/", + "selfdrive/controls/lib/sunnypilot/tests/test_speed_limit_resolver.py", + "selfdrive/controls/lib/tests/", + "selfdrive/controls/lib/tests/__init__.py", + "selfdrive/controls/lib/tests/test_alertmanager.py", + "selfdrive/controls/lib/tests/test_latcontrol.py", + "selfdrive/controls/lib/tests/test_vehicle_model.py", + "selfdrive/controls/tests/", + "selfdrive/controls/tests/__init__.py", + "selfdrive/controls/tests/test_alerts.py", + "selfdrive/controls/tests/test_cruise_speed.py", + "selfdrive/controls/tests/test_following_distance.py", + "selfdrive/controls/tests/test_lateral_mpc.py", + "selfdrive/controls/tests/test_leads.py", + "selfdrive/controls/tests/test_startup.py", + "selfdrive/controls/tests/test_state_machine.py", + "selfdrive/debug/__init__.py", + "selfdrive/debug/adb.sh", + "selfdrive/debug/can_print_changes.py", + "selfdrive/debug/can_table.py", + "selfdrive/debug/check_can_parser_performance.py", + "selfdrive/debug/check_lag.py", + "selfdrive/debug/check_timings.py", + "selfdrive/debug/clear_dtc.py", + "selfdrive/debug/count_events.py", + "selfdrive/debug/cpu_usage_stat.py", + "selfdrive/debug/cycle_alerts.py", + "selfdrive/debug/debug_fw_fingerprinting_offline.py", + "selfdrive/debug/dump_car_docs.py", + "selfdrive/debug/fingerprint_from_route.py", + "selfdrive/debug/internal/", + "selfdrive/debug/internal/__init__.py", + "selfdrive/debug/internal/fuzz_fw_fingerprint.py", + "selfdrive/debug/internal/hands_on_wheel_moniotr.py", + "selfdrive/debug/internal/measure_modeld_packet_drop.py", + "selfdrive/debug/internal/measure_torque_time_to_max.py", + "selfdrive/debug/internal/qlog_size.py", + "selfdrive/debug/live_cpu_and_temp.py", + "selfdrive/debug/print_docs_diff.py", + "selfdrive/debug/print_flags.py", + "selfdrive/debug/read_dtc_status.py", + "selfdrive/debug/README.md", + "selfdrive/debug/run_process_on_route.py", + "selfdrive/debug/set_car_params.py", + "selfdrive/debug/show_matching_cars.py", + "selfdrive/debug/test_fw_query_on_routes.py", + "selfdrive/debug/toyota_eps_factor.py", + "selfdrive/locationd/test/", + "selfdrive/locationd/test/__init__.py", + "selfdrive/locationd/test/.gitignore", + "selfdrive/locationd/test/test_calibrationd.py", + "selfdrive/locationd/test/test_locationd_scenarios.py", + "selfdrive/locationd/test/test_locationd.py", + "selfdrive/modeld/models/dmonitoring_model.current", + "selfdrive/modeld/models/dmonitoring_model.onnx", + "selfdrive/modeld/models/README.md", + "selfdrive/modeld/tests/", + "selfdrive/modeld/tests/__init__.py", + "selfdrive/modeld/tests/dmon_lag/", + "selfdrive/modeld/tests/snpe_benchmark/", + "selfdrive/modeld/tests/snpe_benchmark/.gitignore", + "selfdrive/modeld/tests/snpe_benchmark/benchmark.sh", + "selfdrive/modeld/tests/test_modeld.py", + "selfdrive/modeld/tests/tf_test/", + "selfdrive/modeld/tests/tf_test/build.sh", + "selfdrive/modeld/tests/tf_test/pb_loader.py", + "selfdrive/modeld/tests/timing/", + "selfdrive/modeld/tests/timing/benchmark.py", + "selfdrive/modeld/thneed/README", + "selfdrive/monitoring/helpers.py", + "selfdrive/monitoring/README.md", + "selfdrive/monitoring/test_hands_monitoring.py", + "selfdrive/monitoring/test_monitoring.py", + "selfdrive/test/.gitignore", + "selfdrive/test/ci_shell.sh", + "selfdrive/test/ciui.py", + "selfdrive/test/cpp_harness.py", + "selfdrive/test/docker_build.sh", + "selfdrive/test/docker_common.sh", + "selfdrive/test/docker_tag_multiarch.sh", + "selfdrive/test/longitudinal_maneuvers/", + "selfdrive/test/longitudinal_maneuvers/__init__.py", + "selfdrive/test/longitudinal_maneuvers/.gitignore", + "selfdrive/test/longitudinal_maneuvers/maneuver.py", + "selfdrive/test/longitudinal_maneuvers/plant.py", + "selfdrive/test/longitudinal_maneuvers/test_accel.py", + "selfdrive/test/longitudinal_maneuvers/test_longitudinal.py", + "selfdrive/test/loop_until_fail.sh", + "selfdrive/test/process_replay/", + "selfdrive/test/process_replay/__init__.py", + "selfdrive/test/process_replay/.gitignore", + "selfdrive/test/process_replay/capture.py", + "selfdrive/test/process_replay/compare_logs.py", + "selfdrive/test/process_replay/imgproc_replay_ref_hash", + "selfdrive/test/process_replay/migration.py", + "selfdrive/test/process_replay/model_replay_ref_commit", + "selfdrive/test/process_replay/model_replay.py", + "selfdrive/test/process_replay/process_replay.py", + "selfdrive/test/process_replay/README.md", + "selfdrive/test/process_replay/ref_commit", + "selfdrive/test/process_replay/regen_all.py", + "selfdrive/test/process_replay/regen.py", + "selfdrive/test/process_replay/test_fuzzy.py", + "selfdrive/test/process_replay/test_imgproc.py", + "selfdrive/test/process_replay/test_processes.py", + "selfdrive/test/process_replay/test_regen.py", + "selfdrive/test/process_replay/vision_meta.py", + "selfdrive/test/profiling/", + "selfdrive/test/profiling/__init__.py", + "selfdrive/test/profiling/.gitignore", + "selfdrive/test/profiling/lib.py", + "selfdrive/test/profiling/profiler.py", + "selfdrive/test/scons_build_test.sh", + "selfdrive/test/setup_vsound.sh", + "selfdrive/test/setup_xvfb.sh", + "selfdrive/test/test_updated.py", + "selfdrive/test/update_ci_routes.py", + "selfdrive/ui/__init__.py", + "selfdrive/ui/installer/continue_openpilot.sh", + "selfdrive/ui/installer/installers/", + "selfdrive/ui/installer/installers/installer_openpilot", + "selfdrive/ui/installer/installers/installer_openpilot_internal", + "selfdrive/ui/installer/installers/installer_openpilot_nightly", + "selfdrive/ui/installer/installers/installer_openpilot_test", + "selfdrive/ui/mui", + "selfdrive/ui/qt/python_helpers.py", + "selfdrive/ui/qt/setup/reset", + "selfdrive/ui/qt/setup/setup", + "selfdrive/ui/qt/setup/updater", + "selfdrive/ui/tests/__init__.py", + "selfdrive/ui/tests/.gitignore", + "selfdrive/ui/tests/body.py", + "selfdrive/ui/tests/create_test_translations.sh", + "selfdrive/ui/tests/cycle_offroad_alerts.py", + "selfdrive/ui/tests/test_soundd.py", + "selfdrive/ui/tests/test_ui/", + "selfdrive/ui/tests/test_ui/run.py", + "selfdrive/ui/tests/test_ui/template.html", + "selfdrive/ui/tests/ui_snapshot", + "selfdrive/ui/translations/auto_translate.py", + "selfdrive/ui/translations/create_badges.py", + "selfdrive/ui/translations/README.md", + "selfdrive/ui/ui.py", + "selfdrive/ui/watch3", + "teleoprtc -> teleoprtc_repo/teleoprtc", + "teleoprtc_repo/", + "teleoprtc_repo/.gitignore", + "teleoprtc_repo/.pre-commit-config.yaml", + "teleoprtc_repo/examples/", + "teleoprtc_repo/examples/face_detection/", + "teleoprtc_repo/examples/face_detection/face_detection.py", + "teleoprtc_repo/examples/face_detection/README.md", + "teleoprtc_repo/examples/videostream_cli/", + "teleoprtc_repo/examples/videostream_cli/cli.py", + "teleoprtc_repo/examples/videostream_cli/README.md", + "teleoprtc_repo/LICENSE", + "teleoprtc_repo/pyproject.toml", + "teleoprtc_repo/README.md", + "teleoprtc_repo/scripts/", + "teleoprtc_repo/scripts/bump_tag.sh", + "teleoprtc_repo/scripts/publish_pypi.sh", + "teleoprtc_repo/teleoprtc/", + "teleoprtc_repo/teleoprtc/__init__.py", + "teleoprtc_repo/teleoprtc/builder.py", + "teleoprtc_repo/teleoprtc/info.py", + "teleoprtc_repo/teleoprtc/stream.py", + "teleoprtc_repo/teleoprtc/tracks.py", + "teleoprtc_repo/tests/", + "teleoprtc_repo/tests/test_info.py", + "teleoprtc_repo/tests/test_integration.py", + "teleoprtc_repo/tests/test_stream.py", + "teleoprtc_repo/tests/test_track.py", + "third_party/acados/aarch64 -> larch64/", + "third_party/acados/acados_template/.gitignore", + "third_party/acados/build.sh", + "third_party/acados/x86_64/", + "third_party/acados/x86_64/lib/", + "third_party/acados/x86_64/lib/libacados.so", + "third_party/acados/x86_64/lib/libblasfeo.so", + "third_party/acados/x86_64/lib/libhpipm.so", + "third_party/acados/x86_64/lib/libqpOASES_e.so -> libqpOASES_e.so.3.1", + "third_party/acados/x86_64/lib/libqpOASES_e.so.3.1", + "third_party/acados/x86_64/t_renderer", + "third_party/bootstrap/.gitignore", + "third_party/catch2/", + "third_party/catch2/include/", + "third_party/catch2/include/catch2/", + "third_party/catch2/include/catch2/catch_reporter_automake.hpp", + "third_party/catch2/include/catch2/catch_reporter_sonarqube.hpp", + "third_party/catch2/include/catch2/catch_reporter_tap.hpp", + "third_party/catch2/include/catch2/catch_reporter_teamcity.hpp", + "third_party/catch2/include/catch2/catch.hpp", + "third_party/libyuv/.gitignore", + "third_party/libyuv/aarch64 -> larch64/", + "third_party/libyuv/build.sh", + "third_party/libyuv/LICENSE", + "third_party/libyuv/x86_64/", + "third_party/libyuv/x86_64/include -> ../include", + "third_party/libyuv/x86_64/lib/", + "third_party/maplibre-native-qt/.gitignore", + "third_party/snpe/x86_64 -> x86_64-linux-clang", + "third_party/snpe/x86_64-linux-clang/", + "third_party/snpe/x86_64-linux-clang/libHtpPrepare.so", + "third_party/snpe/x86_64-linux-clang/libomp.so", + "third_party/snpe/x86_64-linux-clang/libSNPE.so", + "tinygrad -> tinygrad_repo/tinygrad", + "tinygrad_repo/tinygrad/renderer/cuda.py", + "tinygrad_repo/tinygrad/renderer/llvmir.py", + "tinygrad_repo/tinygrad/renderer/metal.py", + "tinygrad_repo/tinygrad/renderer/triton.py", + "tinygrad_repo/tinygrad/renderer/wgsl.py", + "tinygrad_repo/tinygrad/runtime/ops_clang.py", + "tinygrad_repo/tinygrad/runtime/ops_cuda.py", + "tinygrad_repo/tinygrad/runtime/ops_hip.py", + "tinygrad_repo/tinygrad/runtime/ops_llvm.py", + "tinygrad_repo/tinygrad/runtime/ops_metal.py", + "tinygrad_repo/tinygrad/runtime/ops_shm.py", + "tinygrad_repo/tinygrad/runtime/ops_torch.py", + "tinygrad_repo/tinygrad/runtime/ops_webgpu.py", ] # Merge the blacklists From 135eb2f17bfa10291a56fca7d4417e3d55c11560 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 12:56:26 +0200 Subject: [PATCH 242/575] Remove unused files from release The Docker-related files, SECURITY.md, codecov.yml, conftest.py, poetry.lock and .clang-tidy were removed from the release_files.py. This cleanup was made to ensure the release contains only the necessary files, reducing potential clutter and confusion. --- release/release_files.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/release/release_files.py b/release/release_files.py index e0e338c8cd..b4efbd3a79 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -47,15 +47,6 @@ sunnypilot_blacklist = [ ".gitmodules", ".pre-commit-config.yaml", ".python-version", - "Dockerfile", - "dockerfile", - "Dockerfile.openpilot", - "Dockerfile.openpilot_base", - "SECURITY.md", - "codecov.yml", - "conftest.py", - "poetry.lock", - ".clang-tidy", ".dockerignore", ".editorconfig", ".gitlab-ci.yml", From a8bc3d30ca8efbd2a3abab5bfe9b954c783fe0c8 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 14:27:33 +0200 Subject: [PATCH 243/575] Update release files and remove unnecessary tests This commit significantly updates the list of release files, adding several new ones and removing numerous others. In addition, a large number of test files deemed unnecessary have been removed. This is part of a broader effort to optimize the codebase and improve efficiency. --- release/release_files.py | 332 +-------------------------------------- 1 file changed, 7 insertions(+), 325 deletions(-) diff --git a/release/release_files.py b/release/release_files.py index b4efbd3a79..64beb2f09d 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -53,6 +53,12 @@ sunnypilot_blacklist = [ ".gitmodules", ".pre-commit-config.yaml", ".python-version", + ".dockerignore", + ".editorconfig", + ".gitlab-ci.yml", + ".gitmodules", + ".pre-commit-config.yaml", + ".python-version", "body/.pre-commit-config.yaml", "body/board/canloader.py", "body/board/flash_base.sh", @@ -76,49 +82,22 @@ sunnypilot_blacklist = [ "cereal/messaging/stress.py", "cereal/messaging/test_runner", "cereal/messaging/tests/", - "cereal/messaging/tests/__init__.py", - "cereal/messaging/tests/test_fake.py", - "cereal/messaging/tests/test_messaging.py", - "cereal/messaging/tests/test_poller.py", - "cereal/messaging/tests/test_pub_sub_master.py", - "cereal/messaging/tests/test_services.py", "cereal/pyproject.toml", "cereal/README.md", "cereal/SConstruct", "cereal/site_scons/", - "cereal/site_scons/site_tools/", - "cereal/site_scons/site_tools/cython.py", "cereal/visionipc/test_runner", "cereal/visionipc/tests/", - "cereal/visionipc/tests/__init__.py", - "cereal/visionipc/tests/test_visionipc.py", "codecov.yml", "common/tests/", - "common/tests/__init__.py", - "common/tests/.gitignore", - "common/tests/test_common", - "common/tests/test_file_helpers.py", - "common/tests/test_numpy_fast.py", - "common/tests/test_params.py", - "common/tests/test_simple_kalman.py", "common/transformations/.gitignore", "common/transformations/README.md", "common/transformations/tests/", - "common/transformations/tests/__init__.py", - "common/transformations/tests/test_coordinates.py", - "common/transformations/tests/test_orientation.py", "conftest.py", "Dockerfile.openpilot", "Dockerfile.openpilot_base", "docs/_static/", - "docs/_static/favicon.ico", - "docs/_static/logo.png", - "docs/_static/robots.txt", "docs/assets/", - "docs/assets/icon-star-empty.svg", - "docs/assets/icon-star-full.svg", - "docs/assets/icon-star-half.svg", - "docs/assets/icon-youtube.svg", "docs/BOUNTIES.md", "docs/c_docs.rst", "docs/conf.py", @@ -134,15 +113,6 @@ sunnypilot_blacklist = [ "opendbc/cadillac_ct6_object.dbc", "opendbc/cadillac_ct6_powertrain.dbc", "opendbc/can/tests/", - "opendbc/can/tests/__init__.py", - "opendbc/can/tests/.gitignore", - "opendbc/can/tests/test_checksums.py", - "opendbc/can/tests/test_dbc_exceptions.py", - "opendbc/can/tests/test_dbc_parser.py", - "opendbc/can/tests/test_define.py", - "opendbc/can/tests/test_packer_parser.py", - "opendbc/can/tests/test_parser_performance.py", - "opendbc/can/tests/test.dbc", "opendbc/chrysler_cusw.dbc", "opendbc/Dockerfile", "opendbc/ESR.dbc", @@ -173,8 +143,6 @@ sunnypilot_blacklist = [ "opendbc/requirements.txt", "opendbc/SConstruct", "opendbc/site_scons/", - "opendbc/site_scons/site_tools/", - "opendbc/site_scons/site_tools/cython.py", "opendbc/tesla_model3_party.dbc", "opendbc/tesla_model3_vehicle.dbc", "opendbc/toyota_2017_ref_pt.dbc", @@ -186,27 +154,7 @@ sunnypilot_blacklist = [ "panda/.pre-commit-config.yaml", "panda/Dockerfile", "panda/docs/", - "panda/docs/CANPacket_structure.png", - "panda/docs/USB_packet_structure.png", "panda/drivers/", - "panda/drivers/linux/", - "panda/drivers/linux/.gitignore", - "panda/drivers/linux/dkms.conf", - "panda/drivers/linux/Makefile", - "panda/drivers/linux/panda.c", - "panda/drivers/linux/README.md", - "panda/drivers/linux/test/", - "panda/drivers/linux/test/main.c", - "panda/drivers/linux/test/Makefile", - "panda/drivers/linux/test/run.sh", - "panda/drivers/spi/", - "panda/drivers/spi/.gitignore", - "panda/drivers/spi/load.sh", - "panda/drivers/spi/Makefile", - "panda/drivers/spi/patch", - "panda/drivers/spi/pull-src.sh", - "panda/drivers/spi/spi_panda.h", - "panda/drivers/spi/spidev_panda.c", "panda/LICENSE", "panda/mypy.ini", "panda/panda.png", @@ -217,157 +165,17 @@ sunnypilot_blacklist = [ "panda/setup.cfg", "panda/setup.py", "panda/tests/", - "panda/tests/__init__.py", - "panda/tests/benchmark.py", - "panda/tests/black_white_loopback_test.py", - "panda/tests/black_white_relay_endurance.py", - "panda/tests/black_white_relay_test.py", - "panda/tests/bulk_write_test.py", - "panda/tests/can_printer.py", - "panda/tests/canfd/", - "panda/tests/canfd/test_canfd.py", - "panda/tests/check_fw_size.py", - "panda/tests/ci_shell.sh", - "panda/tests/debug_console.py", - "panda/tests/development/", - "panda/tests/development/register_hashmap_spread.py", - "panda/tests/echo.py", - "panda/tests/elm_car_simulator.py", - "panda/tests/elm_throughput.py", - "panda/tests/fan/", - "panda/tests/fan/fan_test.py", - "panda/tests/fan/fan_tuning.py", - "panda/tests/get_version.py", - "panda/tests/health_test.py", - "panda/tests/hitl/", - "panda/tests/hitl/__init__.py", - "panda/tests/hitl/1_program.py", - "panda/tests/hitl/2_health.py", - "panda/tests/hitl/3_usb_to_can.py", - "panda/tests/hitl/4_can_loopback.py", - "panda/tests/hitl/5_spi.py", - "panda/tests/hitl/6_safety.py", - "panda/tests/hitl/7_internal.py", - "panda/tests/hitl/8_misc.py", - "panda/tests/hitl/9_harness.py", - "panda/tests/hitl/conftest.py", - "panda/tests/hitl/helpers.py", - "panda/tests/hitl/known_bootstub/", - "panda/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin", - "panda/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin", - "panda/tests/hitl/known_bootstub/bootstub.panda_h7.bin", - "panda/tests/hitl/reset_jungles.py", - "panda/tests/hitl/run_parallel_tests.sh", - "panda/tests/hitl/run_serial_tests.sh", - "panda/tests/ir_test.py", - "panda/tests/libpanda/", - "panda/tests/libpanda/libpanda_py.py", - "panda/tests/libpanda/libpanda.so", - "panda/tests/libpanda/panda.c", - "panda/tests/libpanda/safety_helpers.h", - "panda/tests/libpanda/safety_helpers.py", - "panda/tests/libpanda/SConscript", - "panda/tests/libs/", - "panda/tests/libs/resetter.py", - "panda/tests/loopback_test.py", - "panda/tests/message_drop_test.py", - "panda/tests/misra/", - "panda/tests/misra/.gitignore", - "panda/tests/misra/coverage_table", - "panda/tests/misra/install.sh", - "panda/tests/misra/suppressions.txt", - "panda/tests/misra/test_misra.sh", - "panda/tests/misra/test_mutation.py", - "panda/tests/read_flash_spi.py", - "panda/tests/read_st_flash.sh", - "panda/tests/read_winusb_descriptors.py", - "panda/tests/reflash_internal_panda.py", - "panda/tests/relay_test.py", - "panda/tests/restore_flash_spi.py", - "panda/tests/safety_replay/", - "panda/tests/safety_replay/__init__.py", - "panda/tests/safety_replay/.gitignore", - "panda/tests/safety_replay/helpers.py", - "panda/tests/safety_replay/replay_drive.py", - "panda/tests/safety/", - "panda/tests/safety/__init__.py", - "panda/tests/safety/common.py", - "panda/tests/safety/hyundai_common.py", - "panda/tests/safety/test_body.py", - "panda/tests/safety/test_chrysler.py", - "panda/tests/safety/test_defaults.py", - "panda/tests/safety/test_elm327.py", - "panda/tests/safety/test_ford.py", - "panda/tests/safety/test_gm.py", - "panda/tests/safety/test_honda.py", - "panda/tests/safety/test_hyundai_canfd.py", - "panda/tests/safety/test_hyundai.py", - "panda/tests/safety/test_mazda.py", - "panda/tests/safety/test_nissan.py", - "panda/tests/safety/test_subaru_preglobal.py", - "panda/tests/safety/test_subaru.py", - "panda/tests/safety/test_tesla.py", - "panda/tests/safety/test_toyota.py", - "panda/tests/safety/test_volkswagen_mqb.py", - "panda/tests/safety/test_volkswagen_pq.py", - "panda/tests/safety/test.sh", - "panda/tests/setup_device_ci.sh", - "panda/tests/som_debug.sh", - "panda/tests/som/", - "panda/tests/som/on-device.py", - "panda/tests/som/test_bootkick.py", - "panda/tests/spam_can.py", - "panda/tests/standalone_test.py", - "panda/tests/test_rsa.c", - "panda/tests/usbprotocol/", - "panda/tests/usbprotocol/test_comms.py", - "panda/tests/usbprotocol/test_pandalib.py", - "panda/tests/usbprotocol/test.sh", "poetry.lock", - "rednose -> rednose_repo/rednose", "rednose_repo/.dockerignore", "rednose_repo/.editorconfig", "rednose_repo/.gitignore", "rednose_repo/.pre-commit-config.yaml", "rednose_repo/Dockerfile", "rednose_repo/examples/", - "rednose_repo/examples/__init__.py", - "rednose_repo/examples/kinematic_kf.png", - "rednose_repo/examples/kinematic_kf.py", - "rednose_repo/examples/live_kf.py", - "rednose_repo/examples/SConscript", - "rednose_repo/examples/test_compare.py", - "rednose_repo/examples/test_kinematic_kf.py", "rednose_repo/LICENSE", "rednose_repo/pyproject.toml", "rednose_repo/README.md", "rednose_repo/rednose/", - "rednose_repo/rednose/__init__.py", - "rednose_repo/rednose/.gitignore", - "rednose_repo/rednose/helpers/", - "rednose_repo/rednose/helpers/__init__.py", - "rednose_repo/rednose/helpers/chi2_lookup_table.npy", - "rednose_repo/rednose/helpers/chi2_lookup.py", - "rednose_repo/rednose/helpers/ekf_load.h", - "rednose_repo/rednose/helpers/ekf_sym_pyx.cpp", - "rednose_repo/rednose/helpers/ekf_sym_pyx.pyx", - "rednose_repo/rednose/helpers/ekf_sym_pyx.so", - "rednose_repo/rednose/helpers/ekf_sym.h", - "rednose_repo/rednose/helpers/ekf_sym.py", - "rednose_repo/rednose/helpers/ekf.h", - "rednose_repo/rednose/helpers/kalmanfilter.py", - "rednose_repo/rednose/helpers/sympy_helpers.py", - "rednose_repo/rednose/logger/", - "rednose_repo/rednose/logger/logger.h", - "rednose_repo/rednose/SConscript", - "rednose_repo/rednose/templates/", - "rednose_repo/rednose/templates/compute_pos.c", - "rednose_repo/rednose/templates/ekf_c.c", - "rednose_repo/rednose/templates/feature_handler.c", - "rednose_repo/requirements.txt", - "rednose_repo/SConstruct", - "rednose_repo/setup.py", - "rednose_repo/site_scons/site_tools/cython.py", "SECURITY.md", "selfdrive/assets/compress-images.sh", "selfdrive/assets/strip-svg-metadata.sh", @@ -381,22 +189,11 @@ sunnypilot_blacklist = [ "selfdrive/car/CARS_template.md", "selfdrive/car/docs.py", "selfdrive/car/ford/tests/", - "selfdrive/car/ford/tests/__init__.py", - "selfdrive/car/ford/tests/print_platform_codes.py", - "selfdrive/car/ford/tests/test_ford.py", "selfdrive/car/gm/tests/", - "selfdrive/car/gm/tests/__init__.py", - "selfdrive/car/gm/tests/test_gm.py", "selfdrive/car/honda/tests/", - "selfdrive/car/honda/tests/__init__.py", - "selfdrive/car/honda/tests/test_honda.py", "selfdrive/car/hyundai/tests/", - "selfdrive/car/hyundai/tests/__init__.py", - "selfdrive/car/hyundai/tests/print_platform_codes.py", - "selfdrive/car/hyundai/tests/test_hyundai.py", "selfdrive/car/README.md", "selfdrive/car/subaru/tests/", - "selfdrive/car/subaru/tests/test_subaru.py", "selfdrive/car/tests/.gitignore", "selfdrive/car/tests/big_cars_test.sh", "selfdrive/car/tests/routes.py", @@ -409,28 +206,11 @@ sunnypilot_blacklist = [ "selfdrive/car/tests/test_models.py", "selfdrive/car/tests/test_platform_configs.py", "selfdrive/car/toyota/tests/", - "selfdrive/car/toyota/tests/__init__.py", - "selfdrive/car/toyota/tests/print_platform_codes.py", - "selfdrive/car/toyota/tests/test_toyota.py", "selfdrive/car/volkswagen/tests/", - "selfdrive/car/volkswagen/tests/test_volkswagen.py", "selfdrive/controls/.gitignore", "selfdrive/controls/lib/sunnypilot/tests/", - "selfdrive/controls/lib/sunnypilot/tests/test_speed_limit_resolver.py", "selfdrive/controls/lib/tests/", - "selfdrive/controls/lib/tests/__init__.py", - "selfdrive/controls/lib/tests/test_alertmanager.py", - "selfdrive/controls/lib/tests/test_latcontrol.py", - "selfdrive/controls/lib/tests/test_vehicle_model.py", "selfdrive/controls/tests/", - "selfdrive/controls/tests/__init__.py", - "selfdrive/controls/tests/test_alerts.py", - "selfdrive/controls/tests/test_cruise_speed.py", - "selfdrive/controls/tests/test_following_distance.py", - "selfdrive/controls/tests/test_lateral_mpc.py", - "selfdrive/controls/tests/test_leads.py", - "selfdrive/controls/tests/test_startup.py", - "selfdrive/controls/tests/test_state_machine.py", "selfdrive/debug/__init__.py", "selfdrive/debug/adb.sh", "selfdrive/debug/can_print_changes.py", @@ -446,12 +226,6 @@ sunnypilot_blacklist = [ "selfdrive/debug/dump_car_docs.py", "selfdrive/debug/fingerprint_from_route.py", "selfdrive/debug/internal/", - "selfdrive/debug/internal/__init__.py", - "selfdrive/debug/internal/fuzz_fw_fingerprint.py", - "selfdrive/debug/internal/hands_on_wheel_moniotr.py", - "selfdrive/debug/internal/measure_modeld_packet_drop.py", - "selfdrive/debug/internal/measure_torque_time_to_max.py", - "selfdrive/debug/internal/qlog_size.py", "selfdrive/debug/live_cpu_and_temp.py", "selfdrive/debug/print_docs_diff.py", "selfdrive/debug/print_flags.py", @@ -463,26 +237,10 @@ sunnypilot_blacklist = [ "selfdrive/debug/test_fw_query_on_routes.py", "selfdrive/debug/toyota_eps_factor.py", "selfdrive/locationd/test/", - "selfdrive/locationd/test/__init__.py", - "selfdrive/locationd/test/.gitignore", - "selfdrive/locationd/test/test_calibrationd.py", - "selfdrive/locationd/test/test_locationd_scenarios.py", - "selfdrive/locationd/test/test_locationd.py", "selfdrive/modeld/models/dmonitoring_model.current", "selfdrive/modeld/models/dmonitoring_model.onnx", "selfdrive/modeld/models/README.md", "selfdrive/modeld/tests/", - "selfdrive/modeld/tests/__init__.py", - "selfdrive/modeld/tests/dmon_lag/", - "selfdrive/modeld/tests/snpe_benchmark/", - "selfdrive/modeld/tests/snpe_benchmark/.gitignore", - "selfdrive/modeld/tests/snpe_benchmark/benchmark.sh", - "selfdrive/modeld/tests/test_modeld.py", - "selfdrive/modeld/tests/tf_test/", - "selfdrive/modeld/tests/tf_test/build.sh", - "selfdrive/modeld/tests/tf_test/pb_loader.py", - "selfdrive/modeld/tests/timing/", - "selfdrive/modeld/tests/timing/benchmark.py", "selfdrive/modeld/thneed/README", "selfdrive/monitoring/helpers.py", "selfdrive/monitoring/README.md", @@ -496,49 +254,16 @@ sunnypilot_blacklist = [ "selfdrive/test/docker_common.sh", "selfdrive/test/docker_tag_multiarch.sh", "selfdrive/test/longitudinal_maneuvers/", - "selfdrive/test/longitudinal_maneuvers/__init__.py", - "selfdrive/test/longitudinal_maneuvers/.gitignore", - "selfdrive/test/longitudinal_maneuvers/maneuver.py", - "selfdrive/test/longitudinal_maneuvers/plant.py", - "selfdrive/test/longitudinal_maneuvers/test_accel.py", - "selfdrive/test/longitudinal_maneuvers/test_longitudinal.py", "selfdrive/test/loop_until_fail.sh", "selfdrive/test/process_replay/", - "selfdrive/test/process_replay/__init__.py", - "selfdrive/test/process_replay/.gitignore", - "selfdrive/test/process_replay/capture.py", - "selfdrive/test/process_replay/compare_logs.py", - "selfdrive/test/process_replay/imgproc_replay_ref_hash", - "selfdrive/test/process_replay/migration.py", - "selfdrive/test/process_replay/model_replay_ref_commit", - "selfdrive/test/process_replay/model_replay.py", - "selfdrive/test/process_replay/process_replay.py", - "selfdrive/test/process_replay/README.md", - "selfdrive/test/process_replay/ref_commit", - "selfdrive/test/process_replay/regen_all.py", - "selfdrive/test/process_replay/regen.py", - "selfdrive/test/process_replay/test_fuzzy.py", - "selfdrive/test/process_replay/test_imgproc.py", - "selfdrive/test/process_replay/test_processes.py", - "selfdrive/test/process_replay/test_regen.py", - "selfdrive/test/process_replay/vision_meta.py", "selfdrive/test/profiling/", - "selfdrive/test/profiling/__init__.py", - "selfdrive/test/profiling/.gitignore", - "selfdrive/test/profiling/lib.py", - "selfdrive/test/profiling/profiler.py", "selfdrive/test/scons_build_test.sh", "selfdrive/test/setup_vsound.sh", "selfdrive/test/setup_xvfb.sh", "selfdrive/test/test_updated.py", "selfdrive/test/update_ci_routes.py", "selfdrive/ui/__init__.py", - "selfdrive/ui/installer/continue_openpilot.sh", - "selfdrive/ui/installer/installers/", - "selfdrive/ui/installer/installers/installer_openpilot", - "selfdrive/ui/installer/installers/installer_openpilot_internal", - "selfdrive/ui/installer/installers/installer_openpilot_nightly", - "selfdrive/ui/installer/installers/installer_openpilot_test", + "selfdrive/ui/installer/", "selfdrive/ui/mui", "selfdrive/ui/qt/python_helpers.py", "selfdrive/ui/qt/setup/reset", @@ -551,76 +276,33 @@ sunnypilot_blacklist = [ "selfdrive/ui/tests/cycle_offroad_alerts.py", "selfdrive/ui/tests/test_soundd.py", "selfdrive/ui/tests/test_ui/", - "selfdrive/ui/tests/test_ui/run.py", - "selfdrive/ui/tests/test_ui/template.html", "selfdrive/ui/tests/ui_snapshot", "selfdrive/ui/translations/auto_translate.py", "selfdrive/ui/translations/create_badges.py", "selfdrive/ui/translations/README.md", "selfdrive/ui/ui.py", "selfdrive/ui/watch3", - "teleoprtc -> teleoprtc_repo/teleoprtc", "teleoprtc_repo/", "teleoprtc_repo/.gitignore", "teleoprtc_repo/.pre-commit-config.yaml", "teleoprtc_repo/examples/", - "teleoprtc_repo/examples/face_detection/", - "teleoprtc_repo/examples/face_detection/face_detection.py", - "teleoprtc_repo/examples/face_detection/README.md", - "teleoprtc_repo/examples/videostream_cli/", - "teleoprtc_repo/examples/videostream_cli/cli.py", - "teleoprtc_repo/examples/videostream_cli/README.md", "teleoprtc_repo/LICENSE", "teleoprtc_repo/pyproject.toml", "teleoprtc_repo/README.md", "teleoprtc_repo/scripts/", - "teleoprtc_repo/scripts/bump_tag.sh", - "teleoprtc_repo/scripts/publish_pypi.sh", "teleoprtc_repo/teleoprtc/", - "teleoprtc_repo/teleoprtc/__init__.py", - "teleoprtc_repo/teleoprtc/builder.py", - "teleoprtc_repo/teleoprtc/info.py", - "teleoprtc_repo/teleoprtc/stream.py", - "teleoprtc_repo/teleoprtc/tracks.py", "teleoprtc_repo/tests/", - "teleoprtc_repo/tests/test_info.py", - "teleoprtc_repo/tests/test_integration.py", - "teleoprtc_repo/tests/test_stream.py", - "teleoprtc_repo/tests/test_track.py", - "third_party/acados/aarch64 -> larch64/", "third_party/acados/acados_template/.gitignore", "third_party/acados/build.sh", "third_party/acados/x86_64/", - "third_party/acados/x86_64/lib/", - "third_party/acados/x86_64/lib/libacados.so", - "third_party/acados/x86_64/lib/libblasfeo.so", - "third_party/acados/x86_64/lib/libhpipm.so", - "third_party/acados/x86_64/lib/libqpOASES_e.so -> libqpOASES_e.so.3.1", - "third_party/acados/x86_64/lib/libqpOASES_e.so.3.1", - "third_party/acados/x86_64/t_renderer", "third_party/bootstrap/.gitignore", "third_party/catch2/", - "third_party/catch2/include/", - "third_party/catch2/include/catch2/", - "third_party/catch2/include/catch2/catch_reporter_automake.hpp", - "third_party/catch2/include/catch2/catch_reporter_sonarqube.hpp", - "third_party/catch2/include/catch2/catch_reporter_tap.hpp", - "third_party/catch2/include/catch2/catch_reporter_teamcity.hpp", - "third_party/catch2/include/catch2/catch.hpp", "third_party/libyuv/.gitignore", - "third_party/libyuv/aarch64 -> larch64/", "third_party/libyuv/build.sh", "third_party/libyuv/LICENSE", "third_party/libyuv/x86_64/", - "third_party/libyuv/x86_64/include -> ../include", - "third_party/libyuv/x86_64/lib/", "third_party/maplibre-native-qt/.gitignore", - "third_party/snpe/x86_64 -> x86_64-linux-clang", "third_party/snpe/x86_64-linux-clang/", - "third_party/snpe/x86_64-linux-clang/libHtpPrepare.so", - "third_party/snpe/x86_64-linux-clang/libomp.so", - "third_party/snpe/x86_64-linux-clang/libSNPE.so", - "tinygrad -> tinygrad_repo/tinygrad", "tinygrad_repo/tinygrad/renderer/cuda.py", "tinygrad_repo/tinygrad/renderer/llvmir.py", "tinygrad_repo/tinygrad/renderer/metal.py", From 489ab0efe3fde97670d66f66817f678f53167ded Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 14:29:08 +0200 Subject: [PATCH 244/575] more cleanup --- release/release_files.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/release/release_files.py b/release/release_files.py index 64beb2f09d..5331512e20 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -35,11 +35,11 @@ blacklist = [ # Sunnypilot blacklist sunnypilot_blacklist = [ + "system/loggerd/sunnylink_uploader.py", # Temporarily, until we are ready to roll it out widely ".idea/", ".run/", ".run/", "release/ci/scons_cache/", - "system/loggerd/sunnylink_uploader.py", # Temporarily, until we are ready to roll it out widely ".gitlab-ci.yml", ".clang-tidy", ".dockerignore", @@ -48,6 +48,10 @@ sunnypilot_blacklist = [ ".pre-commit-config.yaml", ".python-version", ".dockerignore", + "rednose/", + "teleoprtc/", + "tinygrad/", + # Below needs to be revisited ".editorconfig", ".gitlab-ci.yml", ".gitmodules", From 86242fa57ffb3af917499b8bab2d021becad36d3 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 14:56:15 +0200 Subject: [PATCH 245/575] more cleanup adjustment --- release/release_files.py | 437 +++++++++++++++++++++++++++++++++------ 1 file changed, 375 insertions(+), 62 deletions(-) diff --git a/release/release_files.py b/release/release_files.py index 5331512e20..75edefc91d 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -52,11 +52,7 @@ sunnypilot_blacklist = [ "teleoprtc/", "tinygrad/", # Below needs to be revisited - ".editorconfig", - ".gitlab-ci.yml", - ".gitmodules", - ".pre-commit-config.yaml", - ".python-version", + ".clang-tidy", ".dockerignore", ".editorconfig", ".gitlab-ci.yml", @@ -67,6 +63,12 @@ sunnypilot_blacklist = [ "body/board/canloader.py", "body/board/flash_base.sh", "body/board/flash_knee.sh", + "body/board/obj/.placeholder", + "body/board/obj/body.bin", + "body/board/obj/body.bin.signed", + "body/board/obj/body.elf", + "body/board/obj/bootstub.body.bin", + "body/board/obj/bootstub.body.elf", "body/board/recover.sh", "body/Dockerfile", "body/LICENSE", @@ -78,30 +80,61 @@ sunnypilot_blacklist = [ "cereal/.pre-commit-config.yaml", "cereal/codecov.yml", "cereal/Dockerfile", + "cereal/gen/c/include/c++.capnp.h", + "cereal/gen/cpp/car.capnp.c++", + "cereal/gen/cpp/car.capnp.h", + "cereal/gen/cpp/custom.capnp.c++", + "cereal/gen/cpp/custom.capnp.h", + "cereal/gen/cpp/legacy.capnp.c++", + "cereal/gen/cpp/legacy.capnp.h", + "cereal/gen/cpp/log.capnp.c++", + "cereal/gen/cpp/log.capnp.h", "cereal/generate_javascript.sh", + "cereal/libcereal_shared.so", "cereal/LICENSE", "cereal/maptile.capnp", + "cereal/messaging/bridge", "cereal/messaging/demo.py", + "cereal/messaging/messaging_pyx.cpp", + "cereal/messaging/messaging_pyx.so", "cereal/messaging/msgq.md", "cereal/messaging/stress.py", "cereal/messaging/test_runner", - "cereal/messaging/tests/", + "cereal/messaging/tests/__init__.py", + "cereal/messaging/tests/test_fake.py", + "cereal/messaging/tests/test_messaging.py", + "cereal/messaging/tests/test_poller.py", + "cereal/messaging/tests/test_pub_sub_master.py", + "cereal/messaging/tests/test_services.py", "cereal/pyproject.toml", "cereal/README.md", "cereal/SConstruct", - "cereal/site_scons/", + "cereal/services.h", + "cereal/site_scons/site_tools/cython.py", "cereal/visionipc/test_runner", - "cereal/visionipc/tests/", + "cereal/visionipc/tests/__init__.py", + "cereal/visionipc/tests/test_visionipc.py", + "cereal/visionipc/visionipc_pyx.cpp", + "cereal/visionipc/visionipc_pyx.so", "codecov.yml", - "common/tests/", + "common/params_pyx.cpp", + "common/params_pyx.so", + "common/tests/.gitignore", + "common/tests/test_common", "common/transformations/.gitignore", "common/transformations/README.md", - "common/transformations/tests/", + "common/transformations/transformations.cpp", + "common/transformations/transformations.so", "conftest.py", "Dockerfile.openpilot", "Dockerfile.openpilot_base", - "docs/_static/", - "docs/assets/", + "docs/_static/favicon.ico", + "docs/_static/logo.png", + "docs/_static/robots.txt", + "docs/assets/icon-star-empty.svg", + "docs/assets/icon-star-full.svg", + "docs/assets/icon-star-half.svg", + "docs/assets/icon-youtube.svg", "docs/BOUNTIES.md", "docs/c_docs.rst", "docs/conf.py", @@ -116,7 +149,20 @@ sunnypilot_blacklist = [ "opendbc/cadillac_ct6_chassis.dbc", "opendbc/cadillac_ct6_object.dbc", "opendbc/cadillac_ct6_powertrain.dbc", - "opendbc/can/tests/", + "opendbc/can/libdbc.so", + "opendbc/can/packer_pyx.cpp", + "opendbc/can/packer_pyx.so", + "opendbc/can/parser_pyx.cpp", + "opendbc/can/parser_pyx.so", + "opendbc/can/tests/__init__.py", + "opendbc/can/tests/.gitignore", + "opendbc/can/tests/test_checksums.py", + "opendbc/can/tests/test_dbc_exceptions.py", + "opendbc/can/tests/test_dbc_parser.py", + "opendbc/can/tests/test_define.py", + "opendbc/can/tests/test_packer_parser.py", + "opendbc/can/tests/test_parser_performance.py", + "opendbc/can/tests/test.dbc", "opendbc/chrysler_cusw.dbc", "opendbc/Dockerfile", "opendbc/ESR.dbc", @@ -146,7 +192,7 @@ sunnypilot_blacklist = [ "opendbc/README.md", "opendbc/requirements.txt", "opendbc/SConstruct", - "opendbc/site_scons/", + "opendbc/site_scons/site_tools/cython.py", "opendbc/tesla_model3_party.dbc", "opendbc/tesla_model3_vehicle.dbc", "opendbc/toyota_2017_ref_pt.dbc", @@ -155,10 +201,30 @@ sunnypilot_blacklist = [ "opendbc/toyota_radar_dsu_tssp.dbc", "opendbc/volvo_v40_2017_pt.dbc", "opendbc/volvo_v60_2015_pt.dbc", + "openpilot/common", + "openpilot/selfdrive", + "openpilot/system", + "openpilot/third_party", + "openpilot/tools", "panda/.pre-commit-config.yaml", "panda/Dockerfile", - "panda/docs/", - "panda/drivers/", + "panda/docs/CANPacket_structure.png", + "panda/docs/USB_packet_structure.png", + "panda/drivers/linux/.gitignore", + "panda/drivers/linux/dkms.conf", + "panda/drivers/linux/Makefile", + "panda/drivers/linux/panda.c", + "panda/drivers/linux/README.md", + "panda/drivers/linux/test/main.c", + "panda/drivers/linux/test/Makefile", + "panda/drivers/linux/test/run.sh", + "panda/drivers/spi/.gitignore", + "panda/drivers/spi/load.sh", + "panda/drivers/spi/Makefile", + "panda/drivers/spi/patch", + "panda/drivers/spi/pull-src.sh", + "panda/drivers/spi/spi_panda.h", + "panda/drivers/spi/spidev_panda.c", "panda/LICENSE", "panda/mypy.ini", "panda/panda.png", @@ -168,21 +234,148 @@ sunnypilot_blacklist = [ "panda/SConstruct", "panda/setup.cfg", "panda/setup.py", - "panda/tests/", + "panda/tests/__init__.py", + "panda/tests/benchmark.py", + "panda/tests/black_white_loopback_test.py", + "panda/tests/black_white_relay_endurance.py", + "panda/tests/black_white_relay_test.py", + "panda/tests/bulk_write_test.py", + "panda/tests/can_printer.py", + "panda/tests/canfd/test_canfd.py", + "panda/tests/check_fw_size.py", + "panda/tests/ci_shell.sh", + "panda/tests/debug_console.py", + "panda/tests/development/register_hashmap_spread.py", + "panda/tests/echo.py", + "panda/tests/elm_car_simulator.py", + "panda/tests/elm_throughput.py", + "panda/tests/fan/fan_test.py", + "panda/tests/fan/fan_tuning.py", + "panda/tests/get_version.py", + "panda/tests/health_test.py", + "panda/tests/hitl/__init__.py", + "panda/tests/hitl/1_program.py", + "panda/tests/hitl/2_health.py", + "panda/tests/hitl/3_usb_to_can.py", + "panda/tests/hitl/4_can_loopback.py", + "panda/tests/hitl/5_spi.py", + "panda/tests/hitl/6_safety.py", + "panda/tests/hitl/7_internal.py", + "panda/tests/hitl/8_misc.py", + "panda/tests/hitl/9_harness.py", + "panda/tests/hitl/conftest.py", + "panda/tests/hitl/helpers.py", + "panda/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin", + "panda/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin", + "panda/tests/hitl/known_bootstub/bootstub.panda_h7.bin", + "panda/tests/hitl/reset_jungles.py", + "panda/tests/hitl/run_parallel_tests.sh", + "panda/tests/hitl/run_serial_tests.sh", + "panda/tests/ir_test.py", + "panda/tests/libpanda/libpanda_py.py", + "panda/tests/libpanda/libpanda.so", + "panda/tests/libpanda/panda.c", + "panda/tests/libpanda/safety_helpers.h", + "panda/tests/libpanda/safety_helpers.py", + "panda/tests/libpanda/SConscript", + "panda/tests/libs/resetter.py", + "panda/tests/loopback_test.py", + "panda/tests/message_drop_test.py", + "panda/tests/misra/.gitignore", + "panda/tests/misra/coverage_table", + "panda/tests/misra/install.sh", + "panda/tests/misra/suppressions.txt", + "panda/tests/misra/test_misra.sh", + "panda/tests/misra/test_mutation.py", + "panda/tests/read_flash_spi.py", + "panda/tests/read_st_flash.sh", + "panda/tests/read_winusb_descriptors.py", + "panda/tests/reflash_internal_panda.py", + "panda/tests/relay_test.py", + "panda/tests/restore_flash_spi.py", + "panda/tests/safety_replay/__init__.py", + "panda/tests/safety_replay/.gitignore", + "panda/tests/safety_replay/helpers.py", + "panda/tests/safety_replay/replay_drive.py", + "panda/tests/safety/__init__.py", + "panda/tests/safety/common.py", + "panda/tests/safety/hyundai_common.py", + "panda/tests/safety/test_body.py", + "panda/tests/safety/test_chrysler.py", + "panda/tests/safety/test_defaults.py", + "panda/tests/safety/test_elm327.py", + "panda/tests/safety/test_ford.py", + "panda/tests/safety/test_gm.py", + "panda/tests/safety/test_honda.py", + "panda/tests/safety/test_hyundai_canfd.py", + "panda/tests/safety/test_hyundai.py", + "panda/tests/safety/test_mazda.py", + "panda/tests/safety/test_nissan.py", + "panda/tests/safety/test_subaru_preglobal.py", + "panda/tests/safety/test_subaru.py", + "panda/tests/safety/test_tesla.py", + "panda/tests/safety/test_toyota.py", + "panda/tests/safety/test_volkswagen_mqb.py", + "panda/tests/safety/test_volkswagen_pq.py", + "panda/tests/safety/test.sh", + "panda/tests/setup_device_ci.sh", + "panda/tests/som_debug.sh", + "panda/tests/som/on-device.py", + "panda/tests/som/test_bootkick.py", + "panda/tests/spam_can.py", + "panda/tests/standalone_test.py", + "panda/tests/test_rsa.c", + "panda/tests/usbprotocol/test_comms.py", + "panda/tests/usbprotocol/test_pandalib.py", + "panda/tests/usbprotocol/test.sh", "poetry.lock", + "prebuilt", + "rednose/", "rednose_repo/.dockerignore", "rednose_repo/.editorconfig", "rednose_repo/.gitignore", "rednose_repo/.pre-commit-config.yaml", "rednose_repo/Dockerfile", - "rednose_repo/examples/", + "rednose_repo/examples/__init__.py", + "rednose_repo/examples/kinematic_kf.png", + "rednose_repo/examples/kinematic_kf.py", + "rednose_repo/examples/live_kf.py", + "rednose_repo/examples/SConscript", + "rednose_repo/examples/test_compare.py", + "rednose_repo/examples/test_kinematic_kf.py", "rednose_repo/LICENSE", "rednose_repo/pyproject.toml", "rednose_repo/README.md", - "rednose_repo/rednose/", + "rednose_repo/rednose/__init__.py", + "rednose_repo/rednose/.gitignore", + "rednose_repo/rednose/helpers/__init__.py", + "rednose_repo/rednose/helpers/chi2_lookup_table.npy", + "rednose_repo/rednose/helpers/chi2_lookup.py", + "rednose_repo/rednose/helpers/ekf_load.h", + "rednose_repo/rednose/helpers/ekf_sym_pyx.cpp", + "rednose_repo/rednose/helpers/ekf_sym_pyx.pyx", + "rednose_repo/rednose/helpers/ekf_sym_pyx.so", + "rednose_repo/rednose/helpers/ekf_sym.h", + "rednose_repo/rednose/helpers/ekf_sym.py", + "rednose_repo/rednose/helpers/ekf.h", + "rednose_repo/rednose/helpers/kalmanfilter.py", + "rednose_repo/rednose/helpers/sympy_helpers.py", + "rednose_repo/rednose/logger/logger.h", + "rednose_repo/rednose/SConscript", + "rednose_repo/rednose/templates/compute_pos.c", + "rednose_repo/rednose/templates/ekf_c.c", + "rednose_repo/rednose/templates/feature_handler.c", + "rednose_repo/requirements.txt", + "rednose_repo/SConstruct", + "rednose_repo/setup.py", + "rednose_repo/site_scons/site_tools/cython.py", "SECURITY.md", "selfdrive/assets/compress-images.sh", "selfdrive/assets/strip-svg-metadata.sh", + "selfdrive/assets/translations_assets.qrc", + "selfdrive/boardd/boardd", + "selfdrive/boardd/boardd_api_impl.cpp", + "selfdrive/boardd/boardd_api_impl.so", "selfdrive/boardd/tests/__init__.py", "selfdrive/boardd/tests/bootstub.panda_h7_spiv0.bin", "selfdrive/boardd/tests/bootstub.panda_h7.bin", @@ -192,12 +385,7 @@ sunnypilot_blacklist = [ "selfdrive/boardd/tests/test_pandad.py", "selfdrive/car/CARS_template.md", "selfdrive/car/docs.py", - "selfdrive/car/ford/tests/", - "selfdrive/car/gm/tests/", - "selfdrive/car/honda/tests/", - "selfdrive/car/hyundai/tests/", "selfdrive/car/README.md", - "selfdrive/car/subaru/tests/", "selfdrive/car/tests/.gitignore", "selfdrive/car/tests/big_cars_test.sh", "selfdrive/car/tests/routes.py", @@ -209,12 +397,22 @@ sunnypilot_blacklist = [ "selfdrive/car/tests/test_models_segs.txt", "selfdrive/car/tests/test_models.py", "selfdrive/car/tests/test_platform_configs.py", - "selfdrive/car/toyota/tests/", - "selfdrive/car/volkswagen/tests/", "selfdrive/controls/.gitignore", - "selfdrive/controls/lib/sunnypilot/tests/", - "selfdrive/controls/lib/tests/", - "selfdrive/controls/tests/", + "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so", + "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so", + "selfdrive/controls/lib/sunnypilot/tests/test_speed_limit_resolver.py", + "selfdrive/controls/lib/tests/__init__.py", + "selfdrive/controls/lib/tests/test_alertmanager.py", + "selfdrive/controls/lib/tests/test_latcontrol.py", + "selfdrive/controls/lib/tests/test_vehicle_model.py", + "selfdrive/controls/tests/__init__.py", + "selfdrive/controls/tests/test_alerts.py", + "selfdrive/controls/tests/test_cruise_speed.py", + "selfdrive/controls/tests/test_following_distance.py", + "selfdrive/controls/tests/test_lateral_mpc.py", + "selfdrive/controls/tests/test_leads.py", + "selfdrive/controls/tests/test_startup.py", + "selfdrive/controls/tests/test_state_machine.py", "selfdrive/debug/__init__.py", "selfdrive/debug/adb.sh", "selfdrive/debug/can_print_changes.py", @@ -229,7 +427,12 @@ sunnypilot_blacklist = [ "selfdrive/debug/debug_fw_fingerprinting_offline.py", "selfdrive/debug/dump_car_docs.py", "selfdrive/debug/fingerprint_from_route.py", - "selfdrive/debug/internal/", + "selfdrive/debug/internal/__init__.py", + "selfdrive/debug/internal/fuzz_fw_fingerprint.py", + "selfdrive/debug/internal/hands_on_wheel_moniotr.py", + "selfdrive/debug/internal/measure_modeld_packet_drop.py", + "selfdrive/debug/internal/measure_torque_time_to_max.py", + "selfdrive/debug/internal/qlog_size.py", "selfdrive/debug/live_cpu_and_temp.py", "selfdrive/debug/print_docs_diff.py", "selfdrive/debug/print_flags.py", @@ -240,16 +443,47 @@ sunnypilot_blacklist = [ "selfdrive/debug/show_matching_cars.py", "selfdrive/debug/test_fw_query_on_routes.py", "selfdrive/debug/toyota_eps_factor.py", - "selfdrive/locationd/test/", + "selfdrive/locationd/locationd", + "selfdrive/locationd/models/generated/car.cpp", + "selfdrive/locationd/models/generated/car.h", + "selfdrive/locationd/models/generated/libcar.so", + "selfdrive/locationd/models/generated/liblive.so", + "selfdrive/locationd/models/generated/live_kf_constants.h", + "selfdrive/locationd/models/generated/live.cpp", + "selfdrive/locationd/models/generated/live.h", + "selfdrive/locationd/test/__init__.py", + "selfdrive/locationd/test/.gitignore", + "selfdrive/locationd/test/test_calibrationd.py", + "selfdrive/locationd/test/test_locationd_scenarios.py", + "selfdrive/locationd/test/test_locationd.py", + "selfdrive/modeld/libthneed.so", + "selfdrive/modeld/models/commonmodel_pyx.cpp", + "selfdrive/modeld/models/commonmodel_pyx.so", "selfdrive/modeld/models/dmonitoring_model.current", "selfdrive/modeld/models/dmonitoring_model.onnx", "selfdrive/modeld/models/README.md", - "selfdrive/modeld/tests/", + "selfdrive/modeld/models/supercombo_metadata.pkl", + "selfdrive/modeld/models/supercombo.thneed", + "selfdrive/modeld/runners/runmodel_pyx.cpp", + "selfdrive/modeld/runners/runmodel_pyx.so", + "selfdrive/modeld/runners/snpemodel_pyx.cpp", + "selfdrive/modeld/runners/snpemodel_pyx.so", + "selfdrive/modeld/runners/thneedmodel_pyx.cpp", + "selfdrive/modeld/runners/thneedmodel_pyx.so", + "selfdrive/modeld/tests/__init__.py", + "selfdrive/modeld/tests/snpe_benchmark/.gitignore", + "selfdrive/modeld/tests/snpe_benchmark/benchmark.sh", + "selfdrive/modeld/tests/test_modeld.py", + "selfdrive/modeld/tests/tf_test/build.sh", + "selfdrive/modeld/tests/tf_test/pb_loader.py", + "selfdrive/modeld/tests/timing/benchmark.py", "selfdrive/modeld/thneed/README", "selfdrive/monitoring/helpers.py", "selfdrive/monitoring/README.md", "selfdrive/monitoring/test_hands_monitoring.py", "selfdrive/monitoring/test_monitoring.py", + "selfdrive/navd/libmaprender.so", + "selfdrive/navd/mapsd", "selfdrive/test/.gitignore", "selfdrive/test/ci_shell.sh", "selfdrive/test/ciui.py", @@ -257,18 +491,50 @@ sunnypilot_blacklist = [ "selfdrive/test/docker_build.sh", "selfdrive/test/docker_common.sh", "selfdrive/test/docker_tag_multiarch.sh", - "selfdrive/test/longitudinal_maneuvers/", + "selfdrive/test/longitudinal_maneuvers/__init__.py", + "selfdrive/test/longitudinal_maneuvers/.gitignore", + "selfdrive/test/longitudinal_maneuvers/maneuver.py", + "selfdrive/test/longitudinal_maneuvers/plant.py", + "selfdrive/test/longitudinal_maneuvers/test_accel.py", + "selfdrive/test/longitudinal_maneuvers/test_longitudinal.py", "selfdrive/test/loop_until_fail.sh", - "selfdrive/test/process_replay/", - "selfdrive/test/profiling/", + "selfdrive/test/process_replay/__init__.py", + "selfdrive/test/process_replay/.gitignore", + "selfdrive/test/process_replay/capture.py", + "selfdrive/test/process_replay/compare_logs.py", + "selfdrive/test/process_replay/imgproc_replay_ref_hash", + "selfdrive/test/process_replay/migration.py", + "selfdrive/test/process_replay/model_replay_ref_commit", + "selfdrive/test/process_replay/model_replay.py", + "selfdrive/test/process_replay/process_replay.py", + "selfdrive/test/process_replay/README.md", + "selfdrive/test/process_replay/ref_commit", + "selfdrive/test/process_replay/regen_all.py", + "selfdrive/test/process_replay/regen.py", + "selfdrive/test/process_replay/test_fuzzy.py", + "selfdrive/test/process_replay/test_imgproc.py", + "selfdrive/test/process_replay/test_processes.py", + "selfdrive/test/process_replay/test_regen.py", + "selfdrive/test/process_replay/vision_meta.py", + "selfdrive/test/profiling/__init__.py", + "selfdrive/test/profiling/.gitignore", + "selfdrive/test/profiling/lib.py", + "selfdrive/test/profiling/profiler.py", "selfdrive/test/scons_build_test.sh", "selfdrive/test/setup_vsound.sh", "selfdrive/test/setup_xvfb.sh", "selfdrive/test/test_updated.py", "selfdrive/test/update_ci_routes.py", "selfdrive/ui/__init__.py", - "selfdrive/ui/installer/", + "selfdrive/ui/_spinner", + "selfdrive/ui/_text", + "selfdrive/ui/installer/continue_openpilot.sh", + "selfdrive/ui/installer/installers/installer_openpilot", + "selfdrive/ui/installer/installers/installer_openpilot_internal", + "selfdrive/ui/installer/installers/installer_openpilot_nightly", + "selfdrive/ui/installer/installers/installer_openpilot_test", "selfdrive/ui/mui", + "selfdrive/ui/qt/libpython_helpers.so", "selfdrive/ui/qt/python_helpers.py", "selfdrive/ui/qt/setup/reset", "selfdrive/ui/qt/setup/setup", @@ -279,47 +545,94 @@ sunnypilot_blacklist = [ "selfdrive/ui/tests/create_test_translations.sh", "selfdrive/ui/tests/cycle_offroad_alerts.py", "selfdrive/ui/tests/test_soundd.py", - "selfdrive/ui/tests/test_ui/", + "selfdrive/ui/tests/test_ui/run.py", + "selfdrive/ui/tests/test_ui/template.html", "selfdrive/ui/tests/ui_snapshot", "selfdrive/ui/translations/auto_translate.py", "selfdrive/ui/translations/create_badges.py", + "selfdrive/ui/translations/main_ar.qm", + "selfdrive/ui/translations/main_de.qm", + "selfdrive/ui/translations/main_en.qm", + "selfdrive/ui/translations/main_fr.qm", + "selfdrive/ui/translations/main_ja.qm", + "selfdrive/ui/translations/main_ko.qm", + "selfdrive/ui/translations/main_pt-BR.qm", + "selfdrive/ui/translations/main_th.qm", + "selfdrive/ui/translations/main_tr.qm", + "selfdrive/ui/translations/main_zh-CHS.qm", + "selfdrive/ui/translations/main_zh-CHT.qm", "selfdrive/ui/translations/README.md", + "selfdrive/ui/ui", "selfdrive/ui/ui.py", "selfdrive/ui/watch3", - "teleoprtc_repo/", + "system/camerad/camerad", + "system/camerad/test/.gitignore", + "system/camerad/test/stress_restart.sh", + "system/hardware/.gitignore", + "system/hardware/tici/restart_modem.sh", + "system/logcatd/logcatd", + "system/loggerd/bootlog", + "system/loggerd/encoderd", + "system/loggerd/loggerd", + "system/loggerd/README.md", + "system/loggerd/tests/test_logger", + "system/manager/gitlab_runner.sh", + "system/proclogd/proclogd", + "system/proclogd/tests/.gitignore", + "system/proclogd/tests/test_proclog", + "system/sensord/sensord", + "system/ubloxd/glonass_fix.patch", + "system/ubloxd/glonass.ksy", + "system/ubloxd/gps.ksy", + "system/ubloxd/tests/test_glonass_runner", + "system/ubloxd/ubloxd", + "system/ubloxd/ubx.ksy", + "teleoprtc/", "teleoprtc_repo/.gitignore", "teleoprtc_repo/.pre-commit-config.yaml", - "teleoprtc_repo/examples/", + "teleoprtc_repo/examples/face_detection/face_detection.py", + "teleoprtc_repo/examples/face_detection/README.md", + "teleoprtc_repo/examples/videostream_cli/cli.py", + "teleoprtc_repo/examples/videostream_cli/README.md", "teleoprtc_repo/LICENSE", "teleoprtc_repo/pyproject.toml", "teleoprtc_repo/README.md", - "teleoprtc_repo/scripts/", - "teleoprtc_repo/teleoprtc/", - "teleoprtc_repo/tests/", - "third_party/acados/acados_template/.gitignore", + "teleoprtc_repo/scripts/bump_tag.sh", + "teleoprtc_repo/scripts/publish_pypi.sh", + "teleoprtc_repo/teleoprtc/__init__.py", + "teleoprtc_repo/teleoprtc/builder.py", + "teleoprtc_repo/teleoprtc/info.py", + "teleoprtc_repo/teleoprtc/stream.py", + "teleoprtc_repo/teleoprtc/tracks.py", + "teleoprtc_repo/tests/test_info.py", + "teleoprtc_repo/tests/test_integration.py", + "teleoprtc_repo/tests/test_stream.py", + "teleoprtc_repo/tests/test_track.py", + "third_party/acados/aarch64", "third_party/acados/build.sh", - "third_party/acados/x86_64/", - "third_party/bootstrap/.gitignore", - "third_party/catch2/", + "third_party/acados/x86_64/lib/libacados.so", + "third_party/acados/x86_64/lib/libblasfeo.so", + "third_party/acados/x86_64/lib/libhpipm.so", + "third_party/acados/x86_64/lib/libqpOASES_e.so", + "third_party/acados/x86_64/lib/libqpOASES_e.so.3.1", + "third_party/acados/x86_64/t_renderer", + "third_party/catch2/include/catch2/catch_reporter_automake.hpp", + "third_party/catch2/include/catch2/catch_reporter_sonarqube.hpp", + "third_party/catch2/include/catch2/catch_reporter_tap.hpp", + "third_party/catch2/include/catch2/catch_reporter_teamcity.hpp", + "third_party/catch2/include/catch2/catch.hpp", "third_party/libyuv/.gitignore", + "third_party/libyuv/aarch64", "third_party/libyuv/build.sh", "third_party/libyuv/LICENSE", - "third_party/libyuv/x86_64/", - "third_party/maplibre-native-qt/.gitignore", - "third_party/snpe/x86_64-linux-clang/", - "tinygrad_repo/tinygrad/renderer/cuda.py", - "tinygrad_repo/tinygrad/renderer/llvmir.py", - "tinygrad_repo/tinygrad/renderer/metal.py", - "tinygrad_repo/tinygrad/renderer/triton.py", - "tinygrad_repo/tinygrad/renderer/wgsl.py", - "tinygrad_repo/tinygrad/runtime/ops_clang.py", - "tinygrad_repo/tinygrad/runtime/ops_cuda.py", - "tinygrad_repo/tinygrad/runtime/ops_hip.py", - "tinygrad_repo/tinygrad/runtime/ops_llvm.py", - "tinygrad_repo/tinygrad/runtime/ops_metal.py", - "tinygrad_repo/tinygrad/runtime/ops_shm.py", - "tinygrad_repo/tinygrad/runtime/ops_torch.py", - "tinygrad_repo/tinygrad/runtime/ops_webgpu.py", + "third_party/libyuv/x86_64/include", + "third_party/maplibre-native-qt/aarch64", + "third_party/snpe/larch64", + "third_party/snpe/x86_64", + "third_party/snpe/x86_64-linux-clang/libHtpPrepare.so", + "third_party/snpe/x86_64-linux-clang/libomp.so", + "third_party/snpe/x86_64-linux-clang/libSNPE.so", + "tinygrad/", ] # Merge the blacklists From f264b49c7e879d9001fe68e3afbee0bf68faeb58 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 15:26:04 +0200 Subject: [PATCH 246/575] more cleanup adjustment --- release/release_files.py | 586 +-------------------------------------- 1 file changed, 7 insertions(+), 579 deletions(-) diff --git a/release/release_files.py b/release/release_files.py index 75edefc91d..87fbf880b8 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -51,587 +51,15 @@ sunnypilot_blacklist = [ "rednose/", "teleoprtc/", "tinygrad/", - # Below needs to be revisited - ".clang-tidy", - ".dockerignore", - ".editorconfig", - ".gitlab-ci.yml", - ".gitmodules", - ".pre-commit-config.yaml", - ".python-version", - "body/.pre-commit-config.yaml", - "body/board/canloader.py", - "body/board/flash_base.sh", - "body/board/flash_knee.sh", - "body/board/obj/.placeholder", - "body/board/obj/body.bin", - "body/board/obj/body.bin.signed", - "body/board/obj/body.elf", - "body/board/obj/bootstub.body.bin", - "body/board/obj/bootstub.body.elf", - "body/board/recover.sh", - "body/Dockerfile", - "body/LICENSE", - "body/pyproject.toml", - "body/README.md", - "body/requirements.txt", - "body/SConstruct", - "cereal/.dockerignore", - "cereal/.pre-commit-config.yaml", - "cereal/codecov.yml", - "cereal/Dockerfile", - "cereal/gen/c/include/c++.capnp.h", - "cereal/gen/cpp/car.capnp.c++", - "cereal/gen/cpp/car.capnp.h", - "cereal/gen/cpp/custom.capnp.c++", - "cereal/gen/cpp/custom.capnp.h", - "cereal/gen/cpp/legacy.capnp.c++", - "cereal/gen/cpp/legacy.capnp.h", - "cereal/gen/cpp/log.capnp.c++", - "cereal/gen/cpp/log.capnp.h", - "cereal/generate_javascript.sh", - "cereal/libcereal_shared.so", - "cereal/LICENSE", - "cereal/maptile.capnp", - "cereal/messaging/bridge", - "cereal/messaging/demo.py", - "cereal/messaging/messaging_pyx.cpp", - "cereal/messaging/messaging_pyx.so", - "cereal/messaging/msgq.md", - "cereal/messaging/stress.py", - "cereal/messaging/test_runner", - "cereal/messaging/tests/__init__.py", - "cereal/messaging/tests/test_fake.py", - "cereal/messaging/tests/test_messaging.py", - "cereal/messaging/tests/test_poller.py", - "cereal/messaging/tests/test_pub_sub_master.py", - "cereal/messaging/tests/test_services.py", - "cereal/pyproject.toml", - "cereal/README.md", - "cereal/SConstruct", - "cereal/services.h", - "cereal/site_scons/site_tools/cython.py", - "cereal/visionipc/test_runner", - "cereal/visionipc/tests/__init__.py", - "cereal/visionipc/tests/test_visionipc.py", - "cereal/visionipc/visionipc_pyx.cpp", - "cereal/visionipc/visionipc_pyx.so", - "codecov.yml", - "common/params_pyx.cpp", - "common/params_pyx.so", - "common/tests/.gitignore", - "common/tests/test_common", - "common/transformations/.gitignore", - "common/transformations/README.md", - "common/transformations/transformations.cpp", - "common/transformations/transformations.so", - "conftest.py", - "Dockerfile.openpilot", - "Dockerfile.openpilot_base", - "docs/_static/favicon.ico", - "docs/_static/logo.png", - "docs/_static/robots.txt", - "docs/assets/icon-star-empty.svg", - "docs/assets/icon-star-full.svg", - "docs/assets/icon-star-half.svg", - "docs/assets/icon-youtube.svg", - "docs/BOUNTIES.md", - "docs/c_docs.rst", - "docs/conf.py", - "docs/index.md", - "docs/Makefile", - "docs/overview.rst", - "docs/README.md", - "docs/SAFETY.md", - "docs/WORKFLOW.md", - "opendbc/.pre-commit-config.yaml", - "opendbc/bmw_e9x_e8x.dbc", - "opendbc/cadillac_ct6_chassis.dbc", - "opendbc/cadillac_ct6_object.dbc", - "opendbc/cadillac_ct6_powertrain.dbc", - "opendbc/can/libdbc.so", - "opendbc/can/packer_pyx.cpp", - "opendbc/can/packer_pyx.so", - "opendbc/can/parser_pyx.cpp", - "opendbc/can/parser_pyx.so", - "opendbc/can/tests/__init__.py", - "opendbc/can/tests/.gitignore", - "opendbc/can/tests/test_checksums.py", - "opendbc/can/tests/test_dbc_exceptions.py", - "opendbc/can/tests/test_dbc_parser.py", - "opendbc/can/tests/test_define.py", - "opendbc/can/tests/test_packer_parser.py", - "opendbc/can/tests/test_parser_performance.py", - "opendbc/can/tests/test.dbc", - "opendbc/chrysler_cusw.dbc", - "opendbc/Dockerfile", - "opendbc/ESR.dbc", - "opendbc/ford_cgea1_2_bodycan_2011.dbc", - "opendbc/ford_cgea1_2_ptcan_2011.dbc", - "opendbc/ford_fusion_2018_pt.dbc", - "opendbc/gm_global_a_high_voltage_management.dbc", - "opendbc/gm_global_a_lowspeed_1818125.dbc", - "opendbc/gm_global_a_lowspeed.dbc", - "opendbc/gm_global_a_powertrain_expansion.dbc", - "opendbc/honda_fit_hybrid_2018_can_generated.dbc", - "opendbc/honda_pilot_2023_can_generated.dbc", - "opendbc/hyundai_2015_ccan.dbc", - "opendbc/hyundai_2015_mcan.dbc", - "opendbc/hyundai_i30_2014.dbc", - "opendbc/hyundai_kia_mando_corner_radar_generated.dbc", - "opendbc/hyundai_santafe_2007.dbc", - "opendbc/LICENSE", - "opendbc/luxgen_s5_2015.dbc", - "opendbc/mazda_3_2019.dbc", - "opendbc/mazda_radar.dbc", - "opendbc/mazda_rx8.dbc", - "opendbc/mercedes_benz_e350_2010.dbc", - "opendbc/nissan_xterra_2011.dbc", - "opendbc/opel_omega_2001.dbc", - "opendbc/pyproject.toml", - "opendbc/README.md", - "opendbc/requirements.txt", - "opendbc/SConstruct", - "opendbc/site_scons/site_tools/cython.py", - "opendbc/tesla_model3_party.dbc", - "opendbc/tesla_model3_vehicle.dbc", - "opendbc/toyota_2017_ref_pt.dbc", - "opendbc/toyota_iQ_2009_can.dbc", - "opendbc/toyota_prius_2010_pt.dbc", - "opendbc/toyota_radar_dsu_tssp.dbc", - "opendbc/volvo_v40_2017_pt.dbc", - "opendbc/volvo_v60_2015_pt.dbc", - "openpilot/common", - "openpilot/selfdrive", - "openpilot/system", - "openpilot/third_party", - "openpilot/tools", - "panda/.pre-commit-config.yaml", - "panda/Dockerfile", - "panda/docs/CANPacket_structure.png", - "panda/docs/USB_packet_structure.png", - "panda/drivers/linux/.gitignore", - "panda/drivers/linux/dkms.conf", - "panda/drivers/linux/Makefile", - "panda/drivers/linux/panda.c", - "panda/drivers/linux/README.md", - "panda/drivers/linux/test/main.c", - "panda/drivers/linux/test/Makefile", - "panda/drivers/linux/test/run.sh", - "panda/drivers/spi/.gitignore", - "panda/drivers/spi/load.sh", - "panda/drivers/spi/Makefile", - "panda/drivers/spi/patch", - "panda/drivers/spi/pull-src.sh", - "panda/drivers/spi/spi_panda.h", - "panda/drivers/spi/spidev_panda.c", - "panda/LICENSE", - "panda/mypy.ini", - "panda/panda.png", - "panda/pyproject.toml", - "panda/README.md", - "panda/requirements.txt", - "panda/SConstruct", - "panda/setup.cfg", - "panda/setup.py", - "panda/tests/__init__.py", - "panda/tests/benchmark.py", - "panda/tests/black_white_loopback_test.py", - "panda/tests/black_white_relay_endurance.py", - "panda/tests/black_white_relay_test.py", - "panda/tests/bulk_write_test.py", - "panda/tests/can_printer.py", - "panda/tests/canfd/test_canfd.py", - "panda/tests/check_fw_size.py", - "panda/tests/ci_shell.sh", - "panda/tests/debug_console.py", - "panda/tests/development/register_hashmap_spread.py", - "panda/tests/echo.py", - "panda/tests/elm_car_simulator.py", - "panda/tests/elm_throughput.py", - "panda/tests/fan/fan_test.py", - "panda/tests/fan/fan_tuning.py", - "panda/tests/get_version.py", - "panda/tests/health_test.py", - "panda/tests/hitl/__init__.py", - "panda/tests/hitl/1_program.py", - "panda/tests/hitl/2_health.py", - "panda/tests/hitl/3_usb_to_can.py", - "panda/tests/hitl/4_can_loopback.py", - "panda/tests/hitl/5_spi.py", - "panda/tests/hitl/6_safety.py", - "panda/tests/hitl/7_internal.py", - "panda/tests/hitl/8_misc.py", - "panda/tests/hitl/9_harness.py", - "panda/tests/hitl/conftest.py", - "panda/tests/hitl/helpers.py", - "panda/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin", - "panda/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin", - "panda/tests/hitl/known_bootstub/bootstub.panda_h7.bin", - "panda/tests/hitl/reset_jungles.py", - "panda/tests/hitl/run_parallel_tests.sh", - "panda/tests/hitl/run_serial_tests.sh", - "panda/tests/ir_test.py", - "panda/tests/libpanda/libpanda_py.py", - "panda/tests/libpanda/libpanda.so", - "panda/tests/libpanda/panda.c", - "panda/tests/libpanda/safety_helpers.h", - "panda/tests/libpanda/safety_helpers.py", - "panda/tests/libpanda/SConscript", - "panda/tests/libs/resetter.py", - "panda/tests/loopback_test.py", - "panda/tests/message_drop_test.py", - "panda/tests/misra/.gitignore", - "panda/tests/misra/coverage_table", - "panda/tests/misra/install.sh", - "panda/tests/misra/suppressions.txt", - "panda/tests/misra/test_misra.sh", - "panda/tests/misra/test_mutation.py", - "panda/tests/read_flash_spi.py", - "panda/tests/read_st_flash.sh", - "panda/tests/read_winusb_descriptors.py", - "panda/tests/reflash_internal_panda.py", - "panda/tests/relay_test.py", - "panda/tests/restore_flash_spi.py", - "panda/tests/safety_replay/__init__.py", - "panda/tests/safety_replay/.gitignore", - "panda/tests/safety_replay/helpers.py", - "panda/tests/safety_replay/replay_drive.py", - "panda/tests/safety/__init__.py", - "panda/tests/safety/common.py", - "panda/tests/safety/hyundai_common.py", - "panda/tests/safety/test_body.py", - "panda/tests/safety/test_chrysler.py", - "panda/tests/safety/test_defaults.py", - "panda/tests/safety/test_elm327.py", - "panda/tests/safety/test_ford.py", - "panda/tests/safety/test_gm.py", - "panda/tests/safety/test_honda.py", - "panda/tests/safety/test_hyundai_canfd.py", - "panda/tests/safety/test_hyundai.py", - "panda/tests/safety/test_mazda.py", - "panda/tests/safety/test_nissan.py", - "panda/tests/safety/test_subaru_preglobal.py", - "panda/tests/safety/test_subaru.py", - "panda/tests/safety/test_tesla.py", - "panda/tests/safety/test_toyota.py", - "panda/tests/safety/test_volkswagen_mqb.py", - "panda/tests/safety/test_volkswagen_pq.py", - "panda/tests/safety/test.sh", - "panda/tests/setup_device_ci.sh", - "panda/tests/som_debug.sh", - "panda/tests/som/on-device.py", - "panda/tests/som/test_bootkick.py", - "panda/tests/spam_can.py", - "panda/tests/standalone_test.py", - "panda/tests/test_rsa.c", - "panda/tests/usbprotocol/test_comms.py", - "panda/tests/usbprotocol/test_pandalib.py", - "panda/tests/usbprotocol/test.sh", - "poetry.lock", - "prebuilt", + "__pycache__/", + ".pytest_cache/", + ".sconsign.dblite", + ".DS_Store", + ".env", + ".*/.gitignore", + "Jenkinsfile", "rednose/", - "rednose_repo/.dockerignore", - "rednose_repo/.editorconfig", - "rednose_repo/.gitignore", - "rednose_repo/.pre-commit-config.yaml", - "rednose_repo/Dockerfile", - "rednose_repo/examples/__init__.py", - "rednose_repo/examples/kinematic_kf.png", - "rednose_repo/examples/kinematic_kf.py", - "rednose_repo/examples/live_kf.py", - "rednose_repo/examples/SConscript", - "rednose_repo/examples/test_compare.py", - "rednose_repo/examples/test_kinematic_kf.py", - "rednose_repo/LICENSE", - "rednose_repo/pyproject.toml", - "rednose_repo/README.md", - "rednose_repo/rednose/__init__.py", - "rednose_repo/rednose/.gitignore", - "rednose_repo/rednose/helpers/__init__.py", - "rednose_repo/rednose/helpers/chi2_lookup_table.npy", - "rednose_repo/rednose/helpers/chi2_lookup.py", - "rednose_repo/rednose/helpers/ekf_load.h", - "rednose_repo/rednose/helpers/ekf_sym_pyx.cpp", - "rednose_repo/rednose/helpers/ekf_sym_pyx.pyx", - "rednose_repo/rednose/helpers/ekf_sym_pyx.so", - "rednose_repo/rednose/helpers/ekf_sym.h", - "rednose_repo/rednose/helpers/ekf_sym.py", - "rednose_repo/rednose/helpers/ekf.h", - "rednose_repo/rednose/helpers/kalmanfilter.py", - "rednose_repo/rednose/helpers/sympy_helpers.py", - "rednose_repo/rednose/logger/logger.h", - "rednose_repo/rednose/SConscript", - "rednose_repo/rednose/templates/compute_pos.c", - "rednose_repo/rednose/templates/ekf_c.c", - "rednose_repo/rednose/templates/feature_handler.c", - "rednose_repo/requirements.txt", - "rednose_repo/SConstruct", - "rednose_repo/setup.py", - "rednose_repo/site_scons/site_tools/cython.py", - "SECURITY.md", - "selfdrive/assets/compress-images.sh", - "selfdrive/assets/strip-svg-metadata.sh", - "selfdrive/assets/translations_assets.qrc", - "selfdrive/boardd/boardd", - "selfdrive/boardd/boardd_api_impl.cpp", - "selfdrive/boardd/boardd_api_impl.so", - "selfdrive/boardd/tests/__init__.py", - "selfdrive/boardd/tests/bootstub.panda_h7_spiv0.bin", - "selfdrive/boardd/tests/bootstub.panda_h7.bin", - "selfdrive/boardd/tests/bootstub.panda.bin", - "selfdrive/boardd/tests/test_boardd_spi.py", - "selfdrive/boardd/tests/test_boardd_usbprotocol", - "selfdrive/boardd/tests/test_pandad.py", - "selfdrive/car/CARS_template.md", - "selfdrive/car/docs.py", - "selfdrive/car/README.md", - "selfdrive/car/tests/.gitignore", - "selfdrive/car/tests/big_cars_test.sh", - "selfdrive/car/tests/routes.py", - "selfdrive/car/tests/test_can_fingerprint.py", - "selfdrive/car/tests/test_docs.py", - "selfdrive/car/tests/test_fingerprints.py", - "selfdrive/car/tests/test_fw_fingerprint.py", - "selfdrive/car/tests/test_lateral_limits.py", - "selfdrive/car/tests/test_models_segs.txt", - "selfdrive/car/tests/test_models.py", - "selfdrive/car/tests/test_platform_configs.py", - "selfdrive/controls/.gitignore", - "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so", - "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so", - "selfdrive/controls/lib/sunnypilot/tests/test_speed_limit_resolver.py", - "selfdrive/controls/lib/tests/__init__.py", - "selfdrive/controls/lib/tests/test_alertmanager.py", - "selfdrive/controls/lib/tests/test_latcontrol.py", - "selfdrive/controls/lib/tests/test_vehicle_model.py", - "selfdrive/controls/tests/__init__.py", - "selfdrive/controls/tests/test_alerts.py", - "selfdrive/controls/tests/test_cruise_speed.py", - "selfdrive/controls/tests/test_following_distance.py", - "selfdrive/controls/tests/test_lateral_mpc.py", - "selfdrive/controls/tests/test_leads.py", - "selfdrive/controls/tests/test_startup.py", - "selfdrive/controls/tests/test_state_machine.py", - "selfdrive/debug/__init__.py", - "selfdrive/debug/adb.sh", - "selfdrive/debug/can_print_changes.py", - "selfdrive/debug/can_table.py", - "selfdrive/debug/check_can_parser_performance.py", - "selfdrive/debug/check_lag.py", - "selfdrive/debug/check_timings.py", - "selfdrive/debug/clear_dtc.py", - "selfdrive/debug/count_events.py", - "selfdrive/debug/cpu_usage_stat.py", - "selfdrive/debug/cycle_alerts.py", - "selfdrive/debug/debug_fw_fingerprinting_offline.py", - "selfdrive/debug/dump_car_docs.py", - "selfdrive/debug/fingerprint_from_route.py", - "selfdrive/debug/internal/__init__.py", - "selfdrive/debug/internal/fuzz_fw_fingerprint.py", - "selfdrive/debug/internal/hands_on_wheel_moniotr.py", - "selfdrive/debug/internal/measure_modeld_packet_drop.py", - "selfdrive/debug/internal/measure_torque_time_to_max.py", - "selfdrive/debug/internal/qlog_size.py", - "selfdrive/debug/live_cpu_and_temp.py", - "selfdrive/debug/print_docs_diff.py", - "selfdrive/debug/print_flags.py", - "selfdrive/debug/read_dtc_status.py", - "selfdrive/debug/README.md", - "selfdrive/debug/run_process_on_route.py", - "selfdrive/debug/set_car_params.py", - "selfdrive/debug/show_matching_cars.py", - "selfdrive/debug/test_fw_query_on_routes.py", - "selfdrive/debug/toyota_eps_factor.py", - "selfdrive/locationd/locationd", - "selfdrive/locationd/models/generated/car.cpp", - "selfdrive/locationd/models/generated/car.h", - "selfdrive/locationd/models/generated/libcar.so", - "selfdrive/locationd/models/generated/liblive.so", - "selfdrive/locationd/models/generated/live_kf_constants.h", - "selfdrive/locationd/models/generated/live.cpp", - "selfdrive/locationd/models/generated/live.h", - "selfdrive/locationd/test/__init__.py", - "selfdrive/locationd/test/.gitignore", - "selfdrive/locationd/test/test_calibrationd.py", - "selfdrive/locationd/test/test_locationd_scenarios.py", - "selfdrive/locationd/test/test_locationd.py", - "selfdrive/modeld/libthneed.so", - "selfdrive/modeld/models/commonmodel_pyx.cpp", - "selfdrive/modeld/models/commonmodel_pyx.so", - "selfdrive/modeld/models/dmonitoring_model.current", - "selfdrive/modeld/models/dmonitoring_model.onnx", - "selfdrive/modeld/models/README.md", - "selfdrive/modeld/models/supercombo_metadata.pkl", - "selfdrive/modeld/models/supercombo.thneed", - "selfdrive/modeld/runners/runmodel_pyx.cpp", - "selfdrive/modeld/runners/runmodel_pyx.so", - "selfdrive/modeld/runners/snpemodel_pyx.cpp", - "selfdrive/modeld/runners/snpemodel_pyx.so", - "selfdrive/modeld/runners/thneedmodel_pyx.cpp", - "selfdrive/modeld/runners/thneedmodel_pyx.so", - "selfdrive/modeld/tests/__init__.py", - "selfdrive/modeld/tests/snpe_benchmark/.gitignore", - "selfdrive/modeld/tests/snpe_benchmark/benchmark.sh", - "selfdrive/modeld/tests/test_modeld.py", - "selfdrive/modeld/tests/tf_test/build.sh", - "selfdrive/modeld/tests/tf_test/pb_loader.py", - "selfdrive/modeld/tests/timing/benchmark.py", - "selfdrive/modeld/thneed/README", - "selfdrive/monitoring/helpers.py", - "selfdrive/monitoring/README.md", - "selfdrive/monitoring/test_hands_monitoring.py", - "selfdrive/monitoring/test_monitoring.py", - "selfdrive/navd/libmaprender.so", - "selfdrive/navd/mapsd", - "selfdrive/test/.gitignore", - "selfdrive/test/ci_shell.sh", - "selfdrive/test/ciui.py", - "selfdrive/test/cpp_harness.py", - "selfdrive/test/docker_build.sh", - "selfdrive/test/docker_common.sh", - "selfdrive/test/docker_tag_multiarch.sh", - "selfdrive/test/longitudinal_maneuvers/__init__.py", - "selfdrive/test/longitudinal_maneuvers/.gitignore", - "selfdrive/test/longitudinal_maneuvers/maneuver.py", - "selfdrive/test/longitudinal_maneuvers/plant.py", - "selfdrive/test/longitudinal_maneuvers/test_accel.py", - "selfdrive/test/longitudinal_maneuvers/test_longitudinal.py", - "selfdrive/test/loop_until_fail.sh", - "selfdrive/test/process_replay/__init__.py", - "selfdrive/test/process_replay/.gitignore", - "selfdrive/test/process_replay/capture.py", - "selfdrive/test/process_replay/compare_logs.py", - "selfdrive/test/process_replay/imgproc_replay_ref_hash", - "selfdrive/test/process_replay/migration.py", - "selfdrive/test/process_replay/model_replay_ref_commit", - "selfdrive/test/process_replay/model_replay.py", - "selfdrive/test/process_replay/process_replay.py", - "selfdrive/test/process_replay/README.md", - "selfdrive/test/process_replay/ref_commit", - "selfdrive/test/process_replay/regen_all.py", - "selfdrive/test/process_replay/regen.py", - "selfdrive/test/process_replay/test_fuzzy.py", - "selfdrive/test/process_replay/test_imgproc.py", - "selfdrive/test/process_replay/test_processes.py", - "selfdrive/test/process_replay/test_regen.py", - "selfdrive/test/process_replay/vision_meta.py", - "selfdrive/test/profiling/__init__.py", - "selfdrive/test/profiling/.gitignore", - "selfdrive/test/profiling/lib.py", - "selfdrive/test/profiling/profiler.py", - "selfdrive/test/scons_build_test.sh", - "selfdrive/test/setup_vsound.sh", - "selfdrive/test/setup_xvfb.sh", - "selfdrive/test/test_updated.py", - "selfdrive/test/update_ci_routes.py", - "selfdrive/ui/__init__.py", - "selfdrive/ui/_spinner", - "selfdrive/ui/_text", - "selfdrive/ui/installer/continue_openpilot.sh", - "selfdrive/ui/installer/installers/installer_openpilot", - "selfdrive/ui/installer/installers/installer_openpilot_internal", - "selfdrive/ui/installer/installers/installer_openpilot_nightly", - "selfdrive/ui/installer/installers/installer_openpilot_test", - "selfdrive/ui/mui", - "selfdrive/ui/qt/libpython_helpers.so", - "selfdrive/ui/qt/python_helpers.py", - "selfdrive/ui/qt/setup/reset", - "selfdrive/ui/qt/setup/setup", - "selfdrive/ui/qt/setup/updater", - "selfdrive/ui/tests/__init__.py", - "selfdrive/ui/tests/.gitignore", - "selfdrive/ui/tests/body.py", - "selfdrive/ui/tests/create_test_translations.sh", - "selfdrive/ui/tests/cycle_offroad_alerts.py", - "selfdrive/ui/tests/test_soundd.py", - "selfdrive/ui/tests/test_ui/run.py", - "selfdrive/ui/tests/test_ui/template.html", - "selfdrive/ui/tests/ui_snapshot", - "selfdrive/ui/translations/auto_translate.py", - "selfdrive/ui/translations/create_badges.py", - "selfdrive/ui/translations/main_ar.qm", - "selfdrive/ui/translations/main_de.qm", - "selfdrive/ui/translations/main_en.qm", - "selfdrive/ui/translations/main_fr.qm", - "selfdrive/ui/translations/main_ja.qm", - "selfdrive/ui/translations/main_ko.qm", - "selfdrive/ui/translations/main_pt-BR.qm", - "selfdrive/ui/translations/main_th.qm", - "selfdrive/ui/translations/main_tr.qm", - "selfdrive/ui/translations/main_zh-CHS.qm", - "selfdrive/ui/translations/main_zh-CHT.qm", - "selfdrive/ui/translations/README.md", - "selfdrive/ui/ui", - "selfdrive/ui/ui.py", - "selfdrive/ui/watch3", - "system/camerad/camerad", - "system/camerad/test/.gitignore", - "system/camerad/test/stress_restart.sh", - "system/hardware/.gitignore", - "system/hardware/tici/restart_modem.sh", - "system/logcatd/logcatd", - "system/loggerd/bootlog", - "system/loggerd/encoderd", - "system/loggerd/loggerd", - "system/loggerd/README.md", - "system/loggerd/tests/test_logger", - "system/manager/gitlab_runner.sh", - "system/proclogd/proclogd", - "system/proclogd/tests/.gitignore", - "system/proclogd/tests/test_proclog", - "system/sensord/sensord", - "system/ubloxd/glonass_fix.patch", - "system/ubloxd/glonass.ksy", - "system/ubloxd/gps.ksy", - "system/ubloxd/tests/test_glonass_runner", - "system/ubloxd/ubloxd", - "system/ubloxd/ubx.ksy", "teleoprtc/", - "teleoprtc_repo/.gitignore", - "teleoprtc_repo/.pre-commit-config.yaml", - "teleoprtc_repo/examples/face_detection/face_detection.py", - "teleoprtc_repo/examples/face_detection/README.md", - "teleoprtc_repo/examples/videostream_cli/cli.py", - "teleoprtc_repo/examples/videostream_cli/README.md", - "teleoprtc_repo/LICENSE", - "teleoprtc_repo/pyproject.toml", - "teleoprtc_repo/README.md", - "teleoprtc_repo/scripts/bump_tag.sh", - "teleoprtc_repo/scripts/publish_pypi.sh", - "teleoprtc_repo/teleoprtc/__init__.py", - "teleoprtc_repo/teleoprtc/builder.py", - "teleoprtc_repo/teleoprtc/info.py", - "teleoprtc_repo/teleoprtc/stream.py", - "teleoprtc_repo/teleoprtc/tracks.py", - "teleoprtc_repo/tests/test_info.py", - "teleoprtc_repo/tests/test_integration.py", - "teleoprtc_repo/tests/test_stream.py", - "teleoprtc_repo/tests/test_track.py", - "third_party/acados/aarch64", - "third_party/acados/build.sh", - "third_party/acados/x86_64/lib/libacados.so", - "third_party/acados/x86_64/lib/libblasfeo.so", - "third_party/acados/x86_64/lib/libhpipm.so", - "third_party/acados/x86_64/lib/libqpOASES_e.so", - "third_party/acados/x86_64/lib/libqpOASES_e.so.3.1", - "third_party/acados/x86_64/t_renderer", - "third_party/catch2/include/catch2/catch_reporter_automake.hpp", - "third_party/catch2/include/catch2/catch_reporter_sonarqube.hpp", - "third_party/catch2/include/catch2/catch_reporter_tap.hpp", - "third_party/catch2/include/catch2/catch_reporter_teamcity.hpp", - "third_party/catch2/include/catch2/catch.hpp", - "third_party/libyuv/.gitignore", - "third_party/libyuv/aarch64", - "third_party/libyuv/build.sh", - "third_party/libyuv/LICENSE", - "third_party/libyuv/x86_64/include", - "third_party/maplibre-native-qt/aarch64", - "third_party/snpe/larch64", - "third_party/snpe/x86_64", - "third_party/snpe/x86_64-linux-clang/libHtpPrepare.so", - "third_party/snpe/x86_64-linux-clang/libomp.so", - "third_party/snpe/x86_64-linux-clang/libSNPE.so", "tinygrad/", ] From 649aef3cc167b2f0402a09225a05e50fe90e96cd Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 15:32:09 +0200 Subject: [PATCH 247/575] more cleanup adjustment --- release/release_files.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/release/release_files.py b/release/release_files.py index 87fbf880b8..4166e38368 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -48,8 +48,6 @@ sunnypilot_blacklist = [ ".pre-commit-config.yaml", ".python-version", ".dockerignore", - "rednose/", - "teleoprtc/", "tinygrad/", "__pycache__/", ".pytest_cache/", @@ -58,9 +56,6 @@ sunnypilot_blacklist = [ ".env", ".*/.gitignore", "Jenkinsfile", - "rednose/", - "teleoprtc/", - "tinygrad/", ] # Merge the blacklists From 55cee14105a8c329512fe15f727a241c223bd59b Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 15:45:14 +0200 Subject: [PATCH 248/575] more cleanup adjustment --- release/release_files.py | 104 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/release/release_files.py b/release/release_files.py index 4166e38368..76c7e4d10c 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -56,6 +56,105 @@ sunnypilot_blacklist = [ ".env", ".*/.gitignore", "Jenkinsfile", + "poetry.lock", + "panda/SConstruct", + "panda/setup.cfg", + "panda/setup.py", + "panda/tests/__init__.py", + "panda/tests/benchmark.py", + "panda/tests/black_white_loopback_test.py", + "panda/tests/black_white_relay_endurance.py", + "panda/tests/black_white_relay_test.py", + "panda/tests/bulk_write_test.py", + "panda/tests/can_printer.py", + "panda/tests/canfd/test_canfd.py", + "panda/tests/check_fw_size.py", + "panda/tests/ci_shell.sh", + "panda/tests/debug_console.py", + "panda/tests/development/register_hashmap_spread.py", + "panda/tests/echo.py", + "panda/tests/elm_car_simulator.py", + "panda/tests/elm_throughput.py", + "panda/tests/fan/fan_test.py", + "panda/tests/fan/fan_tuning.py", + "panda/tests/get_version.py", + "panda/tests/health_test.py", + "panda/tests/hitl/__init__.py", + "panda/tests/hitl/1_program.py", + "panda/tests/hitl/2_health.py", + "panda/tests/hitl/3_usb_to_can.py", + "panda/tests/hitl/4_can_loopback.py", + "panda/tests/hitl/5_spi.py", + "panda/tests/hitl/6_safety.py", + "panda/tests/hitl/7_internal.py", + "panda/tests/hitl/8_misc.py", + "panda/tests/hitl/9_harness.py", + "panda/tests/hitl/conftest.py", + "panda/tests/hitl/helpers.py", + "panda/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin", + "panda/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin", + "panda/tests/hitl/known_bootstub/bootstub.panda_h7.bin", + "panda/tests/hitl/reset_jungles.py", + "panda/tests/hitl/run_parallel_tests.sh", + "panda/tests/hitl/run_serial_tests.sh", + "panda/tests/ir_test.py", + "panda/tests/libpanda/libpanda_py.py", + "panda/tests/libpanda/libpanda.so", + "panda/tests/libpanda/panda.c", + "panda/tests/libpanda/panda.os", + "panda/tests/libpanda/safety_helpers.h", + "panda/tests/libpanda/safety_helpers.py", + "panda/tests/libpanda/SConscript", + "panda/tests/libs/resetter.py", + "panda/tests/loopback_test.py", + "panda/tests/message_drop_test.py", + "panda/tests/misra/coverage_table", + "panda/tests/misra/install.sh", + "panda/tests/misra/suppressions.txt", + "panda/tests/misra/test_misra.sh", + "panda/tests/misra/test_mutation.py", + "panda/tests/read_flash_spi.py", + "panda/tests/read_st_flash.sh", + "panda/tests/read_winusb_descriptors.py", + "panda/tests/reflash_internal_panda.py", + "panda/tests/relay_test.py", + "panda/tests/restore_flash_spi.py", + "panda/tests/safety_replay/__init__.py", + "panda/tests/safety_replay/helpers.py", + "panda/tests/safety_replay/replay_drive.py", + "panda/tests/safety/__init__.py", + "panda/tests/safety/common.py", + "panda/tests/safety/hyundai_common.py", + "panda/tests/safety/test_body.py", + "panda/tests/safety/test_chrysler.py", + "panda/tests/safety/test_defaults.py", + "panda/tests/safety/test_elm327.py", + "panda/tests/safety/test_ford.py", + "panda/tests/safety/test_gm.py", + "panda/tests/safety/test_honda.py", + "panda/tests/safety/test_hyundai_canfd.py", + "panda/tests/safety/test_hyundai.py", + "panda/tests/safety/test_mazda.py", + "panda/tests/safety/test_nissan.py", + "panda/tests/safety/test_subaru_preglobal.py", + "panda/tests/safety/test_subaru.py", + "panda/tests/safety/test_tesla.py", + "panda/tests/safety/test_toyota.py", + "panda/tests/safety/test_volkswagen_mqb.py", + "panda/tests/safety/test_volkswagen_pq.py", + "panda/tests/safety/test.sh", + "panda/tests/setup_device_ci.sh", + "panda/tests/som_debug.sh", + "panda/tests/som/on-device.py", + "panda/tests/som/test_bootkick.py", + "panda/tests/spam_can.py", + "panda/tests/standalone_test.py", + "panda/tests/test_rsa.c", + "panda/tests/usbprotocol/test_comms.py", + "panda/tests/usbprotocol/test_pandalib.py", + "panda/tests/usbprotocol/test.sh", + + "selfdrive/boardd/tests/", ] # Merge the blacklists @@ -86,6 +185,11 @@ whitelist = [ "tinygrad_repo/tinygrad/.*.py", ] +# Sunnypilot whitelist +sunnypilot_blacklist = [ + "selfdrive/boardd/tests/test_boardd_loopback.py", +] + if __name__ == "__main__": for f in Path(ROOT).rglob("**/*"): if not (f.is_file() or f.is_symlink()): From 725768783f5fa764c877c4cbfdde4900ce08b102 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 15:47:38 +0200 Subject: [PATCH 249/575] Update release files copy command in CI pipeline The `.gitlab-ci.yml` file has been updated to directly use copy command instead of the output from release_files.py script, which was previously used in the pipeline. This change is implemented for security reasons - to validate the output and prevent exposure of unnecessary files. Warning outputs are also efficiently managed now, with the use of grep command. --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97108958bc..f02236428a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -86,7 +86,9 @@ build: - mkdir -p ${BUILD_DIR} - ls -la ${BUILD_DIR} - "echo Building Rest..." - - ./release/release_files.py | sort | uniq | rsync -vrRl --files-from=- . $BUILD_DIR/ + # The output below MUST be validated otherwise we expose files that we don't want to expose + # - ./release/release_files.py | sort | uniq | rsync -vrRl --files-from=- . $BUILD_DIR/ + - cp -pR --parents $(cat release/files_common release/files_tici | sort | uniq) $BUILD_DIR/ 2> >(grep -v 'warning:' >&2) - cd $BUILD_DIR - 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 From 2babfe790d7ceba48251822eaae80f7676600a85 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 15:53:59 +0200 Subject: [PATCH 250/575] Move mapd_manager.py to system directory The mapd_manager.py file has been relocated from the selfdrive directory to the system directory. This change in location reflects the file hierarchy restructuring and improves the organization of the codebase. --- release/files_common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/files_common b/release/files_common index 8af148a3b2..281bb49215 100644 --- a/release/files_common +++ b/release/files_common @@ -142,7 +142,7 @@ selfdrive/debug/uiview.py selfdrive/debug/hyundai_enable_radar_points.py selfdrive/debug/vw_mqb_config.py -selfdrive/mapd_manager.py +system/mapd_manager.py selfdrive/sunnypilot/*.py selfdrive/sunnypilot/live_map_data/*.py From 38a40b4db05973f257762286af8a05b7ec210fc0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 11 Jun 2024 12:17:28 -0400 Subject: [PATCH 251/575] Add monitoring/helpers.py --- release/files_common | 1 + 1 file changed, 1 insertion(+) diff --git a/release/files_common b/release/files_common index 281bb49215..52139f50f9 100644 --- a/release/files_common +++ b/release/files_common @@ -400,6 +400,7 @@ selfdrive/modeld/runners/*.h selfdrive/modeld/runners/*.py selfdrive/monitoring/dmonitoringd.py +selfdrive/monitoring/helpers.py selfdrive/monitoring/hands_on_wheel_monitor.py selfdrive/navd/.gitignore From d21a6126ac4b7eab915bb405c682245d7e83f0d9 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 20:48:55 +0200 Subject: [PATCH 252/575] Update file whitelists and blacklists in release_files.py The file blacklists and whitelists in release_files.py have been thoroughly updated to reflect changes in project structure and requirements. This includes additions to the sunnypilot_blacklist, such as "system/manager/gitlab_runner.sh" and "README.md", among others. The sunnypilot_whitelist has also been --- release/release_files.py | 172 ++++++++++++++------------------------- 1 file changed, 61 insertions(+), 111 deletions(-) diff --git a/release/release_files.py b/release/release_files.py index 76c7e4d10c..71cbd180ea 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -36,9 +36,27 @@ blacklist = [ # Sunnypilot blacklist sunnypilot_blacklist = [ "system/loggerd/sunnylink_uploader.py", # Temporarily, until we are ready to roll it out widely + "system/manager/gitlab_runner.sh", ".idea/", ".run/", ".run/", + ".*__pycache__/.*", + ".*\.pyc", + "tinygrad/*", + "teleoprtc/*" + "^body/.*" + "third_party/snpe/x86_64/*", + "body/board/canloader.py", + "body/board/flash_base.sh", + "body/board/flash_knee.sh", + "body/board/recover.sh", + ".*/test/", + ".*/tests/", + ".*tinygrad_repo/tinygrad/renderer/", + "README.md", + ".*internal/", + "docs/.*", + ".sconsign.dblite", "release/ci/scons_cache/", ".gitlab-ci.yml", ".clang-tidy", @@ -47,114 +65,12 @@ sunnypilot_blacklist = [ ".gitmodules", ".pre-commit-config.yaml", ".python-version", - ".dockerignore", - "tinygrad/", - "__pycache__/", - ".pytest_cache/", - ".sconsign.dblite", - ".DS_Store", - ".env", - ".*/.gitignore", - "Jenkinsfile", + "Dockerfile", + "dockerfile", + "SECURITY.md", + "codecov.yml", + "conftest.py", "poetry.lock", - "panda/SConstruct", - "panda/setup.cfg", - "panda/setup.py", - "panda/tests/__init__.py", - "panda/tests/benchmark.py", - "panda/tests/black_white_loopback_test.py", - "panda/tests/black_white_relay_endurance.py", - "panda/tests/black_white_relay_test.py", - "panda/tests/bulk_write_test.py", - "panda/tests/can_printer.py", - "panda/tests/canfd/test_canfd.py", - "panda/tests/check_fw_size.py", - "panda/tests/ci_shell.sh", - "panda/tests/debug_console.py", - "panda/tests/development/register_hashmap_spread.py", - "panda/tests/echo.py", - "panda/tests/elm_car_simulator.py", - "panda/tests/elm_throughput.py", - "panda/tests/fan/fan_test.py", - "panda/tests/fan/fan_tuning.py", - "panda/tests/get_version.py", - "panda/tests/health_test.py", - "panda/tests/hitl/__init__.py", - "panda/tests/hitl/1_program.py", - "panda/tests/hitl/2_health.py", - "panda/tests/hitl/3_usb_to_can.py", - "panda/tests/hitl/4_can_loopback.py", - "panda/tests/hitl/5_spi.py", - "panda/tests/hitl/6_safety.py", - "panda/tests/hitl/7_internal.py", - "panda/tests/hitl/8_misc.py", - "panda/tests/hitl/9_harness.py", - "panda/tests/hitl/conftest.py", - "panda/tests/hitl/helpers.py", - "panda/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin", - "panda/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin", - "panda/tests/hitl/known_bootstub/bootstub.panda_h7.bin", - "panda/tests/hitl/reset_jungles.py", - "panda/tests/hitl/run_parallel_tests.sh", - "panda/tests/hitl/run_serial_tests.sh", - "panda/tests/ir_test.py", - "panda/tests/libpanda/libpanda_py.py", - "panda/tests/libpanda/libpanda.so", - "panda/tests/libpanda/panda.c", - "panda/tests/libpanda/panda.os", - "panda/tests/libpanda/safety_helpers.h", - "panda/tests/libpanda/safety_helpers.py", - "panda/tests/libpanda/SConscript", - "panda/tests/libs/resetter.py", - "panda/tests/loopback_test.py", - "panda/tests/message_drop_test.py", - "panda/tests/misra/coverage_table", - "panda/tests/misra/install.sh", - "panda/tests/misra/suppressions.txt", - "panda/tests/misra/test_misra.sh", - "panda/tests/misra/test_mutation.py", - "panda/tests/read_flash_spi.py", - "panda/tests/read_st_flash.sh", - "panda/tests/read_winusb_descriptors.py", - "panda/tests/reflash_internal_panda.py", - "panda/tests/relay_test.py", - "panda/tests/restore_flash_spi.py", - "panda/tests/safety_replay/__init__.py", - "panda/tests/safety_replay/helpers.py", - "panda/tests/safety_replay/replay_drive.py", - "panda/tests/safety/__init__.py", - "panda/tests/safety/common.py", - "panda/tests/safety/hyundai_common.py", - "panda/tests/safety/test_body.py", - "panda/tests/safety/test_chrysler.py", - "panda/tests/safety/test_defaults.py", - "panda/tests/safety/test_elm327.py", - "panda/tests/safety/test_ford.py", - "panda/tests/safety/test_gm.py", - "panda/tests/safety/test_honda.py", - "panda/tests/safety/test_hyundai_canfd.py", - "panda/tests/safety/test_hyundai.py", - "panda/tests/safety/test_mazda.py", - "panda/tests/safety/test_nissan.py", - "panda/tests/safety/test_subaru_preglobal.py", - "panda/tests/safety/test_subaru.py", - "panda/tests/safety/test_tesla.py", - "panda/tests/safety/test_toyota.py", - "panda/tests/safety/test_volkswagen_mqb.py", - "panda/tests/safety/test_volkswagen_pq.py", - "panda/tests/safety/test.sh", - "panda/tests/setup_device_ci.sh", - "panda/tests/som_debug.sh", - "panda/tests/som/on-device.py", - "panda/tests/som/test_bootkick.py", - "panda/tests/spam_can.py", - "panda/tests/standalone_test.py", - "panda/tests/test_rsa.c", - "panda/tests/usbprotocol/test_comms.py", - "panda/tests/usbprotocol/test_pandalib.py", - "panda/tests/usbprotocol/test.sh", - - "selfdrive/boardd/tests/", ] # Merge the blacklists @@ -163,7 +79,7 @@ blacklist += sunnypilot_blacklist # gets you through the blacklist whitelist = [ "^tools/lib/(?!.*__pycache__).*$", - "tools/bodyteleop/", + "tools/bodyteleop/(?!.*__pycache__).*$", "tinygrad_repo/openpilot/compile2.py", "tinygrad_repo/extra/onnx.py", @@ -182,14 +98,48 @@ whitelist = [ "tinygrad_repo/tinygrad/runtime/ops_disk.py", "tinygrad_repo/tinygrad/runtime/ops_gpu.py", "tinygrad_repo/tinygrad/shape/*", - "tinygrad_repo/tinygrad/.*.py", + "tinygrad_repo/tinygrad/.*.py$", ] # Sunnypilot whitelist -sunnypilot_blacklist = [ - "selfdrive/boardd/tests/test_boardd_loopback.py", +sunnypilot_whitelist = [ + ".*selfdrive/test/fuzzy_generation.py", + ".*selfdrive/test/helpers.py", + ".*selfdrive/test/__init__.py", + ".*selfdrive/test/setup_device_ci.sh", + ".*selfdrive/test/test_time_to_onroad.py", + ".*selfdrive/test/test_onroad.py", + ".*system/manager/test/test_manager.py", + ".*system/manager/test/__init__.py", + ".*system/qcomgpsd/tests/test_qcomgpsd.py", + ".*system/updated/casync/tests/test_casync.py", + ".*system/updated/tests/test_git.py", + ".*system/updated/tests/test_base.py", + ".*tools/lib/tests/test_route_library.py", + ".*tools/lib/tests/test_caching.py", + ".*tools/lib/tests/test_logreader.py", + ".*tools/lib/tests/test_readers.py", + ".*tools/lib/tests/__init__.py", + ".*tools/lib/tests/test_comma_car_segments.py", + ".*selfdrive/ui/tests/test_translations.py", + ".*selfdrive/car/tests/__init__.py", + ".*selfdrive/car/tests/test_car_interfaces.py", + ".*selfdrive/navd/tests/test_navd.py", + ".*selfdrive/navd/tests/test_map_renderer.py", + ".*selfdrive/boardd/tests/test_boardd_loopback.py", + ".*tinygrad_repo/tinygrad/renderer/opencl.py", + ".*tinygrad_repo/tinygrad/renderer/cstyle.py", + ".*INTEGRATION.md", + ".*HOW-TOS.md", + ".*CARS.md", + ".*LIMITATIONS.md", + ".*CONTRIBUTING.md", + ".*sunnyhaibin0850_qrcode_paypal.me.png", ] +# Merge the whitelists +whitelist += sunnypilot_whitelist + if __name__ == "__main__": for f in Path(ROOT).rglob("**/*"): if not (f.is_file() or f.is_symlink()): From 0f9440309215afc7992699fafc5a2fc78d5fa98a Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 21:00:30 +0200 Subject: [PATCH 253/575] Optimize scons command and update release_files script The scons command in the GitLab CI file now includes the --minimal flag to boost performance by reducing unnecessary operations. In the release_files script, a redundant exclusion pattern has been removed, simplifying file matching. --- .gitlab-ci.yml | 2 +- release/release_files.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97108958bc..e7114f3840 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -89,7 +89,7 @@ build: - ./release/release_files.py | sort | uniq | rsync -vrRl --files-from=- . $BUILD_DIR/ - cd $BUILD_DIR - 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 + - scons -j$(nproc) cache_dir=${CI_DIR}/scons_cache --minimal - touch ${BUILD_DIR}/prebuilt - sudo rm -rf ${OUTPUT_DIR} - mkdir -p ${OUTPUT_DIR} diff --git a/release/release_files.py b/release/release_files.py index 71cbd180ea..bd503af0e4 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -43,8 +43,7 @@ sunnypilot_blacklist = [ ".*__pycache__/.*", ".*\.pyc", "tinygrad/*", - "teleoprtc/*" - "^body/.*" + "teleoprtc/*", "third_party/snpe/x86_64/*", "body/board/canloader.py", "body/board/flash_base.sh", From 556e2f55a0076a3d5b90079cb2ce1c2e8f6a6cd1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 21:27:01 +0200 Subject: [PATCH 254/575] Update rsync flag in GitLab CI configuration This change updates the rsync command in the GitLab CI configuration file (.gitlab-ci.yml) from '-av' to '-avm'. This introduces a more efficient synchronization by reducing the amount of file metadata transferred. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e7114f3840..417484433a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -94,7 +94,7 @@ build: - 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 -av + - rsync -avm --include='**/panda/board/' --include='**/panda/board/obj' --include='**/panda/board/obj/panda.bin.signed' From 1f5c04f80e11fc0c86585b511bb72ecca7c7bb03 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 15:47:38 +0200 Subject: [PATCH 255/575] Update release files copy command in CI pipeline The `.gitlab-ci.yml` file has been updated to directly use copy command instead of the output from release_files.py script, which was previously used in the pipeline. This change is implemented for security reasons - to validate the output and prevent exposure of unnecessary files. Warning outputs are also efficiently managed now, with the use of grep command. --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 417484433a..d5376bf4a4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -86,7 +86,9 @@ build: - mkdir -p ${BUILD_DIR} - ls -la ${BUILD_DIR} - "echo Building Rest..." - - ./release/release_files.py | sort | uniq | rsync -vrRl --files-from=- . $BUILD_DIR/ + # The output below MUST be validated otherwise we expose files that we don't want to expose + # - ./release/release_files.py | sort | uniq | rsync -vrRl --files-from=- . $BUILD_DIR/ + - cp -pR --parents $(cat release/files_common release/files_tici | sort | uniq) $BUILD_DIR/ 2> >(grep -v 'warning:' >&2) - cd $BUILD_DIR - 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 From 6c87faefdff93395bb4fa013ce5a208f388fb6c0 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 15:53:59 +0200 Subject: [PATCH 256/575] Move mapd_manager.py to system directory The mapd_manager.py file has been relocated from the selfdrive directory to the system directory. This change in location reflects the file hierarchy restructuring and improves the organization of the codebase. --- release/files_common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/files_common b/release/files_common index 8af148a3b2..281bb49215 100644 --- a/release/files_common +++ b/release/files_common @@ -142,7 +142,7 @@ selfdrive/debug/uiview.py selfdrive/debug/hyundai_enable_radar_points.py selfdrive/debug/vw_mqb_config.py -selfdrive/mapd_manager.py +system/mapd_manager.py selfdrive/sunnypilot/*.py selfdrive/sunnypilot/live_map_data/*.py From 90628c6e0f38d391afbd0147daf67b4f0dd5074f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 11 Jun 2024 12:17:28 -0400 Subject: [PATCH 257/575] Add monitoring/helpers.py --- release/files_common | 1 + 1 file changed, 1 insertion(+) diff --git a/release/files_common b/release/files_common index 281bb49215..52139f50f9 100644 --- a/release/files_common +++ b/release/files_common @@ -400,6 +400,7 @@ selfdrive/modeld/runners/*.h selfdrive/modeld/runners/*.py selfdrive/monitoring/dmonitoringd.py +selfdrive/monitoring/helpers.py selfdrive/monitoring/hands_on_wheel_monitor.py selfdrive/navd/.gitignore From dde55447d549e375a4970f324e175f357f490656 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 21:50:22 +0200 Subject: [PATCH 258/575] Update method for copying release files in CI The method to copy release files in the continuous integration pipeline has been changed. Instead of using a complex cp command, we are now simply using a Python script with rsync, which provides cleaner and more readable code. The old method is still present but commented out for history or possible future use. --- .gitlab-ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d5376bf4a4..4db7863880 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -86,9 +86,8 @@ build: - mkdir -p ${BUILD_DIR} - ls -la ${BUILD_DIR} - "echo Building Rest..." - # The output below MUST be validated otherwise we expose files that we don't want to expose - # - ./release/release_files.py | sort | uniq | rsync -vrRl --files-from=- . $BUILD_DIR/ - - cp -pR --parents $(cat release/files_common release/files_tici | sort | uniq) $BUILD_DIR/ 2> >(grep -v 'warning:' >&2) + - ./release/release_files.py | sort | uniq | rsync -vrRl --files-from=- . $BUILD_DIR/ + # - cp -pR --parents $(cat release/files_common release/files_tici | sort | uniq) $BUILD_DIR/ 2> >(grep -v 'warning:' >&2) - cd $BUILD_DIR - 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 From a2ad9cb6a4bd2f22407498d484d931693d140f05 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 11 Jun 2024 22:11:35 +0200 Subject: [PATCH 259/575] adding back the readme --- release/release_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/release/release_files.py b/release/release_files.py index bd503af0e4..d540ccf430 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -102,6 +102,7 @@ whitelist = [ # Sunnypilot whitelist sunnypilot_whitelist = [ + "^README.md", ".*selfdrive/test/fuzzy_generation.py", ".*selfdrive/test/helpers.py", ".*selfdrive/test/__init__.py", From 229dccd428b8e9c500e1ccd279ea55f6731719ae Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 9 Jun 2024 00:00:12 -0400 Subject: [PATCH 260/575] GitLab Pipelines: Add script for syncing LFS between comma and sunnypilot This commit adds a new script, 'sync-lfs.sh'. This script assists by syncing the git large file storage (LFS) system. It backs up the current LFS configuration, uses the comma-separated configuration for operations like fetch and pull, and then restores the original configuration before pushing to the origin's main branch. --- release/ci/sync-lfs.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 release/ci/sync-lfs.sh diff --git a/release/ci/sync-lfs.sh b/release/ci/sync-lfs.sh new file mode 100644 index 0000000000..a328724351 --- /dev/null +++ b/release/ci/sync-lfs.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +mv .lfsconfig .lfsconfig.bak +mv .lfsconfig-comma .lfsconfig +git lfs fetch --all; git lfs pull +mv .lfsconfig .lfsconfig-comma +mv .lfsconfig.bak .lfsconfig +git lfs fetch --all; git lfs push origin main From 436bc369c1f8957436cbe20df33ba8c256533b0c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 10 Jun 2024 21:32:15 -0400 Subject: [PATCH 261/575] Update CHANGELOGS.md --- CHANGELOGS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 4eeeb94121..576a89aafa 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,15 +1,15 @@ -sunnypilot - 0.9.7.0 (2024-05-xx) +sunnypilot - 0.9.7.0 (2024-06-11) ======================== * New driving model * Adjust driving personality with the follow distance button -* Support for hybrid variants of supported Ford models * Added toggle to enable driver monitoring even when openpilot is not engaged +* Support for hybrid variants of supported Ford models * Fingerprinting without the OBD-II port on all cars ************************ * UPDATED: Synced with commaai's openpilot - * master commit 2e6b2ef (May 9, 2024) + * master commit ce2a686 (June 1, 2024) * NEW❗: sunnylink (Alpha early access) - * NEW❗: Config Backup + * NEW❗: Config/Settings Backup * Remotely back up and restore sunnypilot settings easily * Device registration with sunnylink ensures a secure, integrated experience across services * AES encryption derived from the device's RSA private key is used for utmost security @@ -27,6 +27,7 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * 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 + * To engage/disengage Force Offroad, go to Settings -> Device panel * UPDATED: Auto Lane Change Timer -> Auto Lane Change by Blinker * NEW❗: New "Off" option to disable lane change by blinker * UPDATED: Pause Lateral Below Speed with Blinker @@ -38,7 +39,7 @@ sunnypilot - 0.9.7.0 (2024-05-xx) * 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 + * openpilot Longitudinal Control available cars * Custom Stock Longitudinal Control available cars * UPDATED: Continued support for comma Pedal * In response to the official deprecation of support for comma Pedal in the upstream, sunnypilot will continue maintaining software support for comma Pedal From b7df7298a166fb7f9444d96d493d2e4e005a9e79 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 12 Jun 2024 22:25:31 -0400 Subject: [PATCH 262/575] Update CHANGELOGS.md --- CHANGELOGS.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 576a89aafa..9c92569fda 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,13 +1,16 @@ sunnypilot - 0.9.7.0 (2024-06-11) ======================== * New driving model + * Inputs the past curvature for smoother and more accurate lateral control + * Simplified neural network architecture in the model's last layers + * Minor fixes to desire augmentation and weight decay * Adjust driving personality with the follow distance button * Added toggle to enable driver monitoring even when openpilot is not engaged * Support for hybrid variants of supported Ford models * Fingerprinting without the OBD-II port on all cars ************************ * UPDATED: Synced with commaai's openpilot - * master commit ce2a686 (June 1, 2024) + * master commit f8cb04e (June 10, 2024) * NEW❗: sunnylink (Alpha early access) * NEW❗: Config/Settings Backup * Remotely back up and restore sunnypilot settings easily From 55542d56f2a66c109274f7ad07635af6d966f444 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Jun 2024 02:02:58 -0400 Subject: [PATCH 263/575] ui: sunnypilot Terms of Service --- common/params.cc | 1 + selfdrive/assets/offroad/sp_tc.html | 44 +++++++++++++++++++++++++++ selfdrive/ui/qt/offroad/onboarding.cc | 9 +++--- system/hardware/hardwared.py | 4 +-- system/manager/manager.py | 3 +- system/version.py | 2 ++ 6 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 selfdrive/assets/offroad/sp_tc.html diff --git a/common/params.cc b/common/params.cc index 7762c5cfec..c45ecfa194 100644 --- a/common/params.cc +++ b/common/params.cc @@ -311,6 +311,7 @@ std::unordered_map keys = { {"SunnylinkDongleId", PERSISTENT}, {"SunnylinkEnabled", PERSISTENT}, {"SunnylinkdPid", PERSISTENT}, + {"TermsVersionSunnypilot", PERSISTENT}, {"TorqueDeadzoneDeg", PERSISTENT | BACKUP}, {"TorqueFriction", PERSISTENT | BACKUP}, {"TorqueMaxLatAccel", PERSISTENT | BACKUP}, diff --git a/selfdrive/assets/offroad/sp_tc.html b/selfdrive/assets/offroad/sp_tc.html new file mode 100644 index 0000000000..7bed194516 --- /dev/null +++ b/selfdrive/assets/offroad/sp_tc.html @@ -0,0 +1,44 @@ + + + + + sunnypilot Terms of Service + + + + +

The Terms and Conditions below are effective for all users

+

Last Updated on June 13, 2024

+

Please read these Terms of Use (“Terms”) carefully before using sunnypilot which is software developed by SUNNYPILOT LLC, a Limited Liability Company organized under the laws of Virginia ("sunnypilot," “us,” “we,” or “our”). sunnypilot is a software forked from openpilot which is open-sourced software developed by Comma.ai, Inc., a corporation organized under the laws of Delaware (“comma”). sunnypilot builds upon the foundation of openpilot by incorporating additional features and improvements. openpilot can be found on https://github.com/commaai/openpilot

+

Before using and by accessing sunnypilot, you indicate that you have read, understood, and agree to these Terms. These Terms apply to all users and others who access or use sunnypilot. If others use sunnypilot through your user account or vehicle, you are responsible to ensure that they only use sunnypilot when it is safe to do so, and in compliance with these Terms and with applicable law. If you disagree with any part of the Terms, you should not access or use sunnypilot.

+

Communications

+

You agree that sunnypilot may contact you by email or telephone in connection with sunnypilot or for other business purposes. You may opt out of receiving email messages at any time by contacting us at support@sunnypilot.ai.

+

We collect, use, and share information from and about you and your vehicle in connection with sunnypilot. You consent to sunnypilot accessing the systems associated with sunnypilot, without additional notice or consent, for the purposes of providing sunnypilot, data collection, software updates, safety and cybersecurity, suspension or removal of your account, and as disclosed in the Privacy Policy (available at https://sunnypilot.ai/legal/privacy/).

+

Safety

+

sunnypilot performs the functions of Adaptive Cruise Control (ACC) and Lane Keeping Assist System (LKAS) designed for use in compatible motor vehicles. While using sunnypilot, it is your responsibility to obey all laws, traffic rules, and traffic regulations governing your vehicle and its operation. Access to and use of sunnypilot is at your own risk and responsibility, and sunnypilot should be accessed and/or used only when you can do so safely.

+

sunnypilot does not make your vehicle “autonomous” or capable of operation without the active monitoring of a licensed driver. It is designed to assist a licensed driver. A licensed driver must pay attention to the road, remain aware of navigation at all times, and be prepared to take immediate action. Failure to do so can cause damage, injury, or death.

+

Supported Locations and Models

+

sunnypilot is compatible only with particular makes and models of vehicles. For a complete list of currently supported vehicles, visit https://comma.ai. sunnypilot will not function properly when installed in an incompatible vehicle. sunnypilot is compatible only within the geographical boundaries of the United States of America.

+

Indemnification

+

To the maximum extent allowable by law, you agree to defend, indemnify and hold harmless sunnypilot, and its employees, partners, suppliers, contractors, investors, agents, officers, directors, and affiliates, from and against any and all claims, damages, causes of action, penalties, interest, demands, obligations, losses, liabilities, costs or debt, additional taxes, and expenses (including but not limited to attorneys’ fees), resulting from or arising out of (i) your use and access of, or inability to use or access, sunnypilot, (ii) your breach of these Terms, (iii) the inaccuracy of any information, representation or warranty made by you, (iv) activities of anyone other than you in connection with sunnypilot conducted through your comma or other compatible device or account, (v) any other of your activities under or in connection with these Terms or sunnypilot.

+

Limitation of Liability

+

In no event shall sunnypilot, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your access to or use of or inability to access or use of the Software; or (ii) any conduct or content of any third party on the Software whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not we have been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose.

+

No Warranty or Obligations to Maintain or Service

+

sunnypilot provides sunnypilot without representations, conditions, or warranties of any kind. sunnypilot is provided on an “AS IS” and “AS AVAILABLE” basis, including with all faults and errors as may occur. To the extent permitted by law and unless prohibited by law, sunnypilot on behalf of itself and all persons and parties acting by, through, or for sunnypilot, explicitly disclaims all warranties or conditions, express, implied, or collateral, including any implied warranties of merchantability, satisfactory quality, and fitness for a particular purpose in respect of sunnypilot.

+

To the extent permitted by law, sunnypilot does not warrant the operation, performance, or availability of sunnypilot under all conditions. sunnypilot is not responsible for any failures caused by server errors, misdirected or redirected transmissions, failed internet connections, interruptions or failures in the transmission of data, any computer virus, or any acts or omissions of third parties that damage the network or impair wireless service.

+

We undertake reasonable measures to preserve and secure information collected through our sunnypilot. However, no data collection, transmission or storage system is 100% secure, and there is always a risk that your information may be intercepted without our consent. In using sunnypilot, you acknowledge that sunnypilot is not responsible for intercepted information, and you hereby release us from any and all claims arising out of or related to the use of intercepted information in any unauthorized manner.

+

By providing sunnypilot, sunnypilot does not transfer or license its intellectual property or grant rights in its brand names, nor does sunnypilot make representations with respect to third-party intellectual property rights.

+

We are not obligated to provide any maintenance or support for sunnypilot, technical or otherwise. If we voluntarily provide any maintenance or support for sunnypilot, we may stop any such maintenance, support, or services at any time in our sole discretion.

+

Modification of Software

+

In no event shall sunnypilot, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable if you choose to modify the software.

+

Changes

+

We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material we will provide at least 15 days’ notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.

+

By continuing to access or use our Software after any revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new terms, you are no longer authorized to use the Software.

+

Contact Us

+

If you have any questions about these Terms, please contact us at support@sunnypilot.ai.

+ + diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index b1219055fd..76fa78271e 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -106,7 +106,7 @@ void TermsPage::showEvent(QShowEvent *event) { text->setAttribute(Qt::WA_AlwaysStackOnTop); text->setClearColor(QColor("#1B1B1B")); - QString text_view = util::read_file("../assets/offroad/tc.html").c_str(); + QString text_view = util::read_file("../assets/offroad/sp_tc.html").c_str(); text->rootContext()->setContextProperty("text_view", text_view); text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml")); @@ -158,7 +158,7 @@ void DeclinePage::showEvent(QShowEvent *event) { main_layout->setSpacing(40); QLabel *text = new QLabel(this); - text->setText(tr("You must accept the Terms and Conditions in order to use openpilot.")); + text->setText(tr("You must accept the Terms and Conditions in order to use sunnypilot.")); text->setStyleSheet(R"(font-size: 80px; font-weight: 300; margin: 200px;)"); text->setWordWrap(true); main_layout->addWidget(text, 0, Qt::AlignCenter); @@ -192,14 +192,15 @@ void OnboardingWindow::updateActiveScreen() { OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { std::string current_terms_version = params.get("TermsVersion"); + std::string current_terms_version_sp = params.get("TermsVersionSunnypilot"); std::string current_training_version = params.get("TrainingVersion"); - accepted_terms = params.get("HasAcceptedTerms") == current_terms_version; + accepted_terms = params.get("HasAcceptedTerms") == current_terms_version_sp; training_done = params.get("CompletedTrainingVersion") == current_training_version; TermsPage* terms = new TermsPage(this); addWidget(terms); connect(terms, &TermsPage::acceptedTerms, [=]() { - params.put("HasAcceptedTerms", current_terms_version); + params.put("HasAcceptedTerms", current_terms_version_sp); accepted_terms = true; updateActiveScreen(); }); diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 4379aa7acd..14eb4f24e6 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -23,7 +23,7 @@ from openpilot.system.statsd import statlog from openpilot.common.swaglog import cloudlog from openpilot.system.hardware.power_monitoring import PowerMonitoring from openpilot.system.hardware.fan_controller import TiciFanController -from openpilot.system.version import terms_version, training_version +from openpilot.system.version import terms_version, terms_version_sp, training_version ThermalStatus = log.DeviceState.ThermalStatus NetworkType = log.DeviceState.NetworkType @@ -297,7 +297,7 @@ def hardware_thread(end_event, hw_queue) -> None: startup_conditions["up_to_date"] = params.get("Offroad_ConnectivityNeeded") is None or params.get_bool("DisableUpdates") or params.get_bool("SnoozeUpdate") startup_conditions["not_uninstalling"] = not params.get_bool("DoUninstall") - startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version + startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version_sp # with 2% left, we killall, otherwise the phone will take a long time to boot startup_conditions["free_space"] = msg.deviceState.freeSpacePercent > 2 diff --git a/system/manager/manager.py b/system/manager/manager.py index 6d1a2ce95f..273193b78c 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -18,7 +18,7 @@ from openpilot.system.manager.process_config import managed_processes from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID, is_registered_device from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.hardware.hw import Paths -from openpilot.system.version import get_build_metadata, terms_version, training_version +from openpilot.system.version import get_build_metadata, terms_version, terms_version_sp, training_version def manager_init() -> None: @@ -135,6 +135,7 @@ def manager_init() -> None: # set version params params.put("Version", build_metadata.openpilot.version) params.put("TermsVersion", terms_version) + params.put("TermsVersionSunnypilot", terms_version_sp) params.put("TrainingVersion", training_version) params.put("GitCommit", build_metadata.openpilot.git_commit) params.put("GitCommitDate", build_metadata.openpilot.git_commit_date) diff --git a/system/version.py b/system/version.py index 9873b50511..05b5ab9361 100755 --- a/system/version.py +++ b/system/version.py @@ -18,6 +18,7 @@ BUILD_METADATA_FILENAME = "build.json" training_version: bytes = b"0.2.0" terms_version: bytes = b"2" +terms_version_sp: bytes = b"1.0" def get_version(path: str = BASEDIR) -> str: @@ -179,6 +180,7 @@ if __name__ == "__main__": params = Params() params.put("TermsVersion", terms_version) + params.put("TermsVersionSunnypilot", terms_version_sp) params.put("TrainingVersion", training_version) print(get_build_metadata()) From fbaf8722d1a4f956e99233ac176422b03034ff9c Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 13 Jun 2024 09:56:54 +0200 Subject: [PATCH 264/575] Refactor athenad and sunnylinkd python code Several changes have been made to improve the code structure of the athenad and sunnylinkd scripts. The while loop in athenad now uses end_event.wait(5) for cleaner execution control. In sunnylinkd.py, there have been additions to manage exceptions and refinements in the handling of network checks. The final major change is the inclusion of a new constant for sunnylink reconnection timeout that plays major role in various parts of the script. --- system/athena/athenad.py | 6 ++---- system/athena/sunnylinkd.py | 36 ++++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/system/athena/athenad.py b/system/athena/athenad.py index e8ef682026..71678d534e 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -634,6 +634,7 @@ def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None is_sunnylink = log_attr_name != LOG_ATTR_NAME if PC: cloudlog.debug("athena.log_handler: Not supported on PC") + time.sleep(1) return log_files = [] @@ -808,7 +809,7 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None: onroad_prev = None sock = ws.sock - while True: + while not end_event.wait(5): onroad = params.get_bool("IsOnroad") if onroad != onroad_prev: onroad_prev = onroad @@ -826,9 +827,6 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3) - if end_event.wait(5): - break - def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 4150e8a475..67ae8a95af 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -1,17 +1,15 @@ #!/usr/bin/env python3 -#TODO: Add this to files_common to allow release to public from __future__ import annotations import base64 import gzip -import json import os import threading import time from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \ - recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler + recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler from jsonrpc import dispatcher from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection) @@ -26,6 +24,7 @@ SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sun HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) LOCAL_PORT_WHITELIST = {8022} SUNNYLINK_LOG_ATTR_NAME = "user.sunny.upload" +SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require a change on sidebar.cc params = Params() sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8')) @@ -58,24 +57,22 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: end_event.set() comma_prime_cellular_end_event.set() - prime_type = params.get("PrimeType", encoding='utf-8') + prime_type = params.get("PrimeType", encoding='utf-8') or 0 metered = sm['deviceState'].networkMetered - if int(prime_type) > 2 and metered: + if metered and int(prime_type) > 2: cloudlog.debug(f"sunnylinkd.handle_long_poll: PrimeType({prime_type}) > 2 and networkMetered({metered})") comma_prime_cellular_end_event.set() elif comma_prime_cellular_end_event.is_set(): cloudlog.debug(f"sunnylinkd.handle_long_poll: comma_prime_cellular_end_event is set and not PrimeType({prime_type}) > 2 or not networkMetered({metered})") comma_prime_cellular_end_event.clear() - - except (KeyboardInterrupt, SystemExit): + finally: end_event.set() comma_prime_cellular_end_event.set() - raise - finally: for thread in threads: cloudlog.debug(f"sunnylinkd athena.joining {thread.name}") thread.join() + cloudlog.debug(f"sunnylinkd athena.joined {thread.name}") def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: @@ -89,12 +86,12 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: recv_queue.put_nowait(data) cloudlog.debug(f"sunnylinkd.ws_recv.recv {data}") elif opcode in (ABNF.OPCODE_PING, ABNF.OPCODE_PONG): - cloudlog.debug(f"sunnylinkd.ws_recv.pong {opcode}") + cloudlog.debug(f"sunnylinkd.ws_recv.pong") last_ping = int(time.monotonic() * 1e9) Params().put("LastSunnylinkPingTime", str(last_ping)) except WebSocketTimeoutException: ns_since_last_ping = int(time.monotonic() * 1e9) - last_ping - if ns_since_last_ping > RECONNECT_TIMEOUT_S * 1e9: + if ns_since_last_ping > SUNNYLINK_RECONNECT_TIMEOUT_S * 1e9: cloudlog.exception("sunnylinkd.ws_recv.timeout") end_event.set() except Exception: @@ -103,15 +100,15 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: def ws_ping(ws: WebSocket, end_event: threading.Event) -> None: - # last_ping = int(time.monotonic() * 1e9) - while not end_event.is_set(): + ws.ping() # Send the first ping + while not end_event.wait(SUNNYLINK_RECONNECT_TIMEOUT_S * 0.7): # Sleep about 70% before a timeout try: ws.ping() cloudlog.debug(f"sunnylinkd.ws_recv.ws_ping: Pinging") except Exception: cloudlog.exception("sunnylinkd.ws_ping.exception") end_event.set() - time.sleep(RECONNECT_TIMEOUT_S * 0.7) # Sleep about 70% before a timeout + cloudlog.debug(f"sunnylinkd.ws_ping.end_event is set, exiting ws_ping thread") def ws_queue(end_event: threading.Event) -> None: resume_requested = False @@ -129,7 +126,13 @@ def ws_queue(end_event: threading.Event) -> None: resume_requested = False tries += 1 time.sleep(backoff(tries)) # Wait for the backoff time before the next attempt - cloudlog.debug("Resume requested or end_event is set, exiting ws_queue thread") + + if end_event.is_set(): + cloudlog.debug("end_event is set, exiting ws_queue thread") + elif resume_requested: + cloudlog.debug(f"Resume requested to server after {tries} tries") + else: + cloudlog.error(f"Reached end of ws_queue while end_event is not set and resume_requested is {resume_requested}") def sunny_log_handler(end_event: threading.Event, comma_prime_cellular_end_event: threading.Event) -> None: @@ -195,7 +198,8 @@ def main(exit_event: threading.Event = None): cloudlog.event("sunnylinkd.main.connecting_ws", ws_uri=ws_uri, retries=conn_retries) ws = create_connection(ws_uri, cookie="jwt=" + sunnylink_api.get_token(), - enable_multithread=True) + enable_multithread=True, + timeout=SUNNYLINK_RECONNECT_TIMEOUT_S) cloudlog.event("sunnylinkd.main.connected_ws", ws_uri=ws_uri, retries=conn_retries, duration=time.monotonic() - conn_start) conn_start = None From 252f06c5adee4ee8a6e2ad8a687fe9838d1bccca Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Jun 2024 10:40:15 -0400 Subject: [PATCH 265/575] make sure comma's ToS is retained --- common/params.cc | 1 + selfdrive/ui/qt/offroad/onboarding.cc | 12 +++++++++++- selfdrive/ui/qt/offroad/onboarding.h | 2 +- system/hardware/hardwared.py | 3 ++- system/manager/manager.py | 1 + 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/common/params.cc b/common/params.cc index c45ecfa194..73906d35f4 100644 --- a/common/params.cc +++ b/common/params.cc @@ -258,6 +258,7 @@ std::unordered_map keys = { {"ForceOffroad", CLEAR_ON_MANAGER_START}, {"GmapKey", PERSISTENT | BACKUP}, {"HandsOnWheelMonitoring", PERSISTENT | BACKUP}, + {"HasAcceptedTermsSP", PERSISTENT}, {"HideVEgoUi", PERSISTENT | BACKUP}, {"HkgSmoothStop", PERSISTENT | BACKUP}, {"HotspotOnBoot", PERSISTENT}, diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index 76fa78271e..c547ba564d 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -195,17 +195,27 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { std::string current_terms_version_sp = params.get("TermsVersionSunnypilot"); std::string current_training_version = params.get("TrainingVersion"); accepted_terms = params.get("HasAcceptedTerms") == current_terms_version_sp; + accepted_terms_sp = params.get("HasAcceptedTermsSP") == current_terms_version_sp; training_done = params.get("CompletedTrainingVersion") == current_training_version; TermsPage* terms = new TermsPage(this); addWidget(terms); connect(terms, &TermsPage::acceptedTerms, [=]() { - params.put("HasAcceptedTerms", current_terms_version_sp); + params.put("HasAcceptedTerms", current_terms_version); accepted_terms = true; updateActiveScreen(); }); connect(terms, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); }); + TermsPage* terms_sp = new TermsPage(this); + addWidget(terms_sp); + connect(terms_sp, &TermsPage::acceptedTerms, [=]() { + params.put("HasAcceptedTermsSP", current_terms_version_sp); + accepted_terms = true; + updateActiveScreen(); + }); + connect(terms_sp, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); }); + TrainingGuide* tr = new TrainingGuide(this); addWidget(tr); connect(tr, &TrainingGuide::completedTraining, [=]() { diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index a1b6895ba0..7580e63967 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -103,7 +103,7 @@ private: void updateActiveScreen(); Params params; - bool accepted_terms = false, training_done = false; + bool accepted_terms = false, accepted_terms_sp = false, training_done = false; signals: void onboardingDone(); diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 14eb4f24e6..dd03692770 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -297,7 +297,8 @@ def hardware_thread(end_event, hw_queue) -> None: startup_conditions["up_to_date"] = params.get("Offroad_ConnectivityNeeded") is None or params.get_bool("DisableUpdates") or params.get_bool("SnoozeUpdate") startup_conditions["not_uninstalling"] = not params.get_bool("DoUninstall") - startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version_sp + startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version + startup_conditions["accepted_terms"] = params.get("HasAcceptedTermsSP") == terms_version_sp # with 2% left, we killall, otherwise the phone will take a long time to boot startup_conditions["free_space"] = msg.deviceState.freeSpacePercent > 2 diff --git a/system/manager/manager.py b/system/manager/manager.py index 273193b78c..a48440f846 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -67,6 +67,7 @@ def manager_init() -> None: ("EnhancedScc", "0"), ("FeatureStatus", "1"), ("HandsOnWheelMonitoring", "0"), + ("HasAcceptedTermsSP", "0"), ("HideVEgoUi", "0"), ("LastSpeedLimitSignTap", "0"), ("LkasToggle", "0"), From 29791a71053fc0e43e54f50150c5e7d27048dce7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Jun 2024 10:44:21 -0400 Subject: [PATCH 266/575] fix index --- selfdrive/ui/qt/offroad/onboarding.cc | 20 +++++++++++--------- selfdrive/ui/qt/offroad/onboarding.h | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index c547ba564d..148d9e940a 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -185,6 +185,8 @@ void OnboardingWindow::updateActiveScreen() { setCurrentIndex(0); } else if (!training_done) { setCurrentIndex(1); + } else if (!accepted_terms_sp) { + setCurrentIndex(3); } else { emit onboardingDone(); } @@ -207,15 +209,6 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { }); connect(terms, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); }); - TermsPage* terms_sp = new TermsPage(this); - addWidget(terms_sp); - connect(terms_sp, &TermsPage::acceptedTerms, [=]() { - params.put("HasAcceptedTermsSP", current_terms_version_sp); - accepted_terms = true; - updateActiveScreen(); - }); - connect(terms_sp, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); }); - TrainingGuide* tr = new TrainingGuide(this); addWidget(tr); connect(tr, &TrainingGuide::completedTraining, [=]() { @@ -228,6 +221,15 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { addWidget(declinePage); connect(declinePage, &DeclinePage::getBack, [=]() { updateActiveScreen(); }); + TermsPage* terms_sp = new TermsPage(this); + addWidget(terms_sp); // index = 3 + connect(terms_sp, &TermsPage::acceptedTerms, [=]() { + params.put("HasAcceptedTermsSP", current_terms_version_sp); + accepted_terms_sp = true; + updateActiveScreen(); + }); + connect(terms_sp, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); }); + setStyleSheet(R"( * { color: white; diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 7580e63967..2c856270ab 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -97,7 +97,7 @@ class OnboardingWindow : public QStackedWidget { public: explicit OnboardingWindow(QWidget *parent = 0); inline void showTrainingGuide() { setCurrentIndex(1); } - inline bool completed() const { return accepted_terms && training_done; } + inline bool completed() const { return accepted_terms && accepted_terms_sp && training_done; } private: void updateActiveScreen(); From f48c7b22ae6081c1fc687ac44e4205092181260c Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 13 Jun 2024 17:28:36 +0200 Subject: [PATCH 267/575] Set default value for SunnylinkDongleId Removed the condition check for sunnylink_dongle_id and instead, directly assigned a default value in case it doesn't exist. This simplifies the code by removing an unnecessary if statement. --- common/api/sunnylink.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index 46f5715c88..5b171b0e29 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -121,8 +121,7 @@ class SunnylinkApi(BaseApi): time.sleep(3) break - if sunnylink_dongle_id: - self.params.put("SunnylinkDongleId", sunnylink_dongle_id) + self.params.put("SunnylinkDongleId", sunnylink_dongle_id or UNREGISTERED_SUNNYLINK_DONGLE_ID) self.spinner = None return sunnylink_dongle_id From 589a4de52270d983e4492d0956010c29a449c71a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Jun 2024 12:08:12 -0400 Subject: [PATCH 268/575] split comma's and sunnypilot's --- selfdrive/ui/qt/offroad/onboarding.cc | 7 ++++--- selfdrive/ui/qt/offroad/onboarding.h | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index 148d9e940a..dcb7349748 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -106,7 +106,8 @@ void TermsPage::showEvent(QShowEvent *event) { text->setAttribute(Qt::WA_AlwaysStackOnTop); text->setClearColor(QColor("#1B1B1B")); - QString text_view = util::read_file("../assets/offroad/sp_tc.html").c_str(); + std::string tc_text = sunnypilot_tc ? "../assets/offroad/sp_tc.html" : "../assets/offroad/tc.html"; + QString text_view = util::read_file(tc_text).c_str(); text->rootContext()->setContextProperty("text_view", text_view); text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml")); @@ -200,7 +201,7 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { accepted_terms_sp = params.get("HasAcceptedTermsSP") == current_terms_version_sp; training_done = params.get("CompletedTrainingVersion") == current_training_version; - TermsPage* terms = new TermsPage(this); + TermsPage* terms = new TermsPage(false, this); addWidget(terms); connect(terms, &TermsPage::acceptedTerms, [=]() { params.put("HasAcceptedTerms", current_terms_version); @@ -221,7 +222,7 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { addWidget(declinePage); connect(declinePage, &DeclinePage::getBack, [=]() { updateActiveScreen(); }); - TermsPage* terms_sp = new TermsPage(this); + TermsPage* terms_sp = new TermsPage(true, this); addWidget(terms_sp); // index = 3 connect(terms_sp, &TermsPage::acceptedTerms, [=]() { params.put("HasAcceptedTermsSP", current_terms_version_sp); diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 2c856270ab..008d86032c 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -63,7 +63,7 @@ class TermsPage : public QFrame { Q_OBJECT public: - explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {} + explicit TermsPage(bool sunnypilot = false, QWidget *parent = 0) : QFrame(parent), sunnypilot_tc(sunnypilot) {} public slots: void enableAccept(); @@ -72,6 +72,7 @@ private: void showEvent(QShowEvent *event) override; QPushButton *accept_btn; + bool sunnypilot_tc = false; signals: void acceptedTerms(); From bc749ff83c8f1441fc893fff9c1e5f7ff4e6df17 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Jun 2024 12:46:54 -0400 Subject: [PATCH 269/575] ui: Disallow QR code generation if sunnylink dongle ID does not exist --- selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc index 6c55bd1655..118aa42159 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc @@ -70,7 +70,11 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { ); list->addItem(pairSponsorBtn); connect(pairSponsorBtn, &ButtonControl::clicked, [=]() { - pair_popup->exec(); + if (getSunnylinkDongleId().value_or(tr("N/A")) == "N/A") { + ConfirmationDialog::alert(tr("sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again."), this); + } else { + pair_popup->exec(); + } }); list->addItem(horizontal_line()); @@ -410,7 +414,7 @@ void SunnylinkSponsorQRWidget::refresh() { if (sponsor_pair) { QString token = CommaApi::create_jwt({}, 3600, true); auto sl_dongle_id = getSunnylinkDongleId(); - QByteArray payload = QString("1|" + *sl_dongle_id + "|" + token).toUtf8().toBase64(); + QByteArray payload = QString("1|" + sl_dongle_id.value_or("") + "|" + token).toUtf8().toBase64(); qrString = SUNNYLINK_BASE_URL + "/sso?state=" + payload; } else { qrString = "https://github.com/sponsors/sunnyhaibin"; From 43a4218e7fd1c27ef15423122bc7405ef23790f6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Jun 2024 13:26:29 -0400 Subject: [PATCH 270/575] Explicit access token arg --- common/api/sunnylink.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index 5b171b0e29..e2761cc29f 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -22,11 +22,11 @@ class SunnylinkApi(BaseApi): self.spinner = None self.params = Params() - def api_get(self, endpoint, method='GET', timeout=10, **kwargs): + def api_get(self, endpoint, method='GET', timeout=10, access_token=None, **kwargs): if not self.params.get_bool("SunnylinkEnabled"): return None - return super().api_get(endpoint, method, timeout, **kwargs) + return super().api_get(endpoint, method, timeout, access_token, **kwargs) def resume_queued(self, timeout=10, **kwargs): sunnylinkId, commaId = self._resolve_dongle_ids() From 2110e9cfc9f57a15ce7f1d605b36c93b0b687483 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 14 Jun 2024 11:41:42 -0400 Subject: [PATCH 271/575] Sentry: Update fingerprinting events --- selfdrive/car/car_helpers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index eec3b72081..5fcfe34a4f 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -219,7 +219,7 @@ def crash_log(candidate): while True: if is_connected_to_internet(): sentry.get_init() - sentry.capture_warning("fingerprinted %s" % candidate) + sentry.capture_info("fingerprinted %s" % candidate) break else: no_internet += 1 @@ -233,8 +233,7 @@ def crash_log2(fingerprints, fw): while True: if is_connected_to_internet(): sentry.get_init() - sentry.capture_warning("car doesn't match any fingerprints: %s" % fingerprints) - sentry.capture_warning("car doesn't match any fw: %s" % fw) + sentry.capture_warning("car doesn't match any fingerprints: %s" % repr(fingerprints)) break else: no_internet += 1 From e1ac25bdd81448cc067ff80dfafe88b38bfd1ada Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 16 Jun 2024 01:17:53 -0400 Subject: [PATCH 272/575] Custom Stock Longitudinal Control: GM Support This commit adds the custom cruise control logic in the GM interface and provides custom minimum cruise speed for GM. It also provides comprehensive updates in GM car controller to manage different scenarios in cruise control. Additionally, the update includes button control for cruise speed modification, maintaining a steady speed and considering curve speed hysteresis. Further, safety checks were implemented in the panda safety module for the GM to check cruise control actions. --- panda | 2 +- selfdrive/car/gm/carcontroller.py | 190 ++++++++++++++++++++++++ selfdrive/car/gm/interface.py | 3 +- selfdrive/controls/lib/drive_helpers.py | 8 + 4 files changed, 201 insertions(+), 2 deletions(-) diff --git a/panda b/panda index b1eaf46501..5b6fb2ac53 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b1eaf465019be0b76d208a820face2a62a711a8f +Subproject commit 5b6fb2ac537554e65a0a1a0d7c97756d202a96a7 diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 2aa4811e4e..9f5c8ed9a8 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -1,4 +1,6 @@ from cereal import car +import cereal.messaging as messaging +from openpilot.common.params import Params from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import interp from openpilot.common.realtime import DT_CTRL @@ -7,6 +9,7 @@ from openpilot.selfdrive.car import apply_driver_steer_torque_limits from openpilot.selfdrive.car.gm import gmcan from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons from openpilot.selfdrive.car.interfaces import CarControllerBase +from selfdrive.controls.lib.drive_helpers import GM_V_CRUISE_MIN VisualAlert = car.CarControl.HUDControl.VisualAlert NetworkLocation = car.CarParams.NetworkLocation @@ -39,6 +42,36 @@ class CarController(CarControllerBase): self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar']) self.packer_ch = CANPacker(DBC[self.CP.carFingerprint]['chassis']) + self.sm = messaging.SubMaster(['longitudinalPlan']) + self.param_s = Params() + self.is_metric = self.param_s.get_bool("IsMetric") + self.speed_limit_control_enabled = False + self.last_speed_limit_sign_tap = False + self.last_speed_limit_sign_tap_prev = False + self.speed_limit = 0. + self.speed_limit_offset = 0 + self.timer = 0 + self.final_speed_kph = 0 + self.init_speed = 0 + self.current_speed = 0 + self.v_set_dis = 0 + self.v_cruise_min = 0 + self.button_type = 0 + self.button_select = 0 + self.button_count = 0 + self.target_speed = 0 + self.t_interval = 7 + self.slc_active_stock = False + self.sl_force_active_timer = 0 + self.v_tsc_state = 0 + self.slc_state = 0 + self.m_tsc_state = 0 + self.cruise_button = None + self.speed_diff = 0 + self.v_tsc = 0 + self.m_tsc = 0 + self.steady_speed = 0 + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl @@ -47,9 +80,40 @@ class CarController(CarControllerBase): if hud_v_cruise > 70: hud_v_cruise = 0 + if not self.CP.pcmCruiseSpeed: + self.sm.update(0) + + if self.sm.updated['longitudinalPlanSP']: + self.v_tsc_state = self.sm['longitudinalPlanSP'].visionTurnControllerState + self.slc_state = self.sm['longitudinalPlanSP'].speedLimitControlState + self.m_tsc_state = self.sm['longitudinalPlanSP'].turnSpeedControlState + self.speed_limit = self.sm['longitudinalPlanSP'].speedLimit + self.speed_limit_offset = self.sm['longitudinalPlanSP'].speedLimitOffset + self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed + self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed + + if self.frame % 200 == 0: + self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc") + self.is_metric = self.param_s.get_bool("IsMetric") + self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap") + self.v_cruise_min = GM_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1) + # Send CAN commands. can_sends = [] + if not self.CP.pcmCruiseSpeed: + if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap: + self.sl_force_active_timer = self.frame + self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False) + self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap + + sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) + sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) + sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) + slc_active = not sl_inactive and not sl_temp_inactive + + self.slc_active_stock = slc_active + # Steering (Active: 50Hz, inactive: 10Hz) steer_step = self.params.STEER_STEP if CC.latActive else self.params.INACTIVE_STEER_STEP @@ -150,6 +214,15 @@ class CarController(CarControllerBase): self.last_button_frame = self.frame can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, CS.buttons_counter, CruiseButtons.CANCEL)) + if not (CC.cruiseControl.cancel or CC.cruiseControl.resume) and not self.CP.pcmCruiseSpeed and CS.out.cruiseState.enabled: + self.cruise_button = self.get_cruise_buttons(CS, CC.vCruise) + if self.cruise_button is not None: + send_freq = 1 + if not (self.v_tsc_state != 0 or self.m_tsc_state > 1) and abs(self.target_speed - self.v_set_dis) <= 2: + send_freq = 3 + if self.frame % 12 < 6: # thanks to mochi86420 for the magic numbers + can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, (CS.button_counter + 2) % 4, self.cruise_button)) + if self.CP.networkLocation == NetworkLocation.fwdCamera: # Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1 if self.frame % 10 == 0: @@ -163,3 +236,120 @@ class CarController(CarControllerBase): self.frame += 1 return new_actuators, can_sends + + # multikyd methods, sunnyhaibin logic + def get_cruise_buttons_status(self, CS): + if not CS.out.cruiseState.enabled or CS.cruise_buttons != CruiseButtons.UNPRESS or CS.cruise_buttons != CruiseButtons.INIT: + self.timer = 40 + elif self.timer: + self.timer -= 1 + else: + return 1 + return 0 + + def get_target_speed(self, v_cruise_kph_prev): + v_cruise_kph = v_cruise_kph_prev + if self.slc_state > 1: + v_cruise_kph = (self.speed_limit + self.speed_limit_offset) * CV.MS_TO_KPH + if not self.slc_active_stock: + v_cruise_kph = v_cruise_kph_prev + return v_cruise_kph + + def get_button_type(self, button_type): + self.type_status = "type_" + str(button_type) + self.button_picker = getattr(self, self.type_status, lambda: "default") + return self.button_picker() + + def reset_button(self): + if self.button_type != 3: + self.button_type = 0 + + def type_default(self): + self.button_type = 0 + return None + + def type_0(self): + self.button_count = 0 + self.target_speed = self.init_speed + self.speed_diff = self.target_speed - self.v_set_dis + if self.target_speed > self.v_set_dis: + self.button_type = 1 + elif self.target_speed < self.v_set_dis and self.v_set_dis > self.v_cruise_min: + self.button_type = 2 + return None + + def type_1(self): + cruise_button = CruiseButtons.RES_ACCEL + self.button_count += 1 + if self.target_speed <= self.v_set_dis: + self.button_count = 0 + self.button_type = 3 + elif self.button_count > 5: + self.button_count = 0 + self.button_type = 3 + return cruise_button + + def type_2(self): + cruise_button = CruiseButtons.DECEL_SET + self.button_count += 1 + if self.target_speed >= self.v_set_dis or self.v_set_dis <= self.v_cruise_min: + self.button_count = 0 + self.button_type = 3 + elif self.button_count > 5: + self.button_count = 0 + self.button_type = 3 + return cruise_button + + def type_3(self): + cruise_button = None + self.button_count += 1 + if self.button_count > self.t_interval: + self.button_type = 0 + return cruise_button + + def get_curve_speed(self, target_speed_kph, v_cruise_kph_prev): + if self.v_tsc_state != 0: + vision_v_cruise_kph = self.v_tsc * CV.MS_TO_KPH + if int(vision_v_cruise_kph) == int(v_cruise_kph_prev): + vision_v_cruise_kph = 255 + else: + vision_v_cruise_kph = 255 + if self.m_tsc_state > 1: + map_v_cruise_kph = self.m_tsc * CV.MS_TO_KPH + if int(map_v_cruise_kph) == 0.0: + map_v_cruise_kph = 255 + else: + map_v_cruise_kph = 255 + curve_speed = self.curve_speed_hysteresis(min(vision_v_cruise_kph, map_v_cruise_kph) + 2 * CV.MPH_TO_KPH) + return min(target_speed_kph, curve_speed) + + def get_button_control(self, CS, final_speed, v_cruise_kph_prev): + self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1)) + self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH)) + cruise_button = self.get_button_type(self.button_type) + return cruise_button + + def curve_speed_hysteresis(self, cur_speed: float, hyst=(0.75 * CV.MPH_TO_KPH)): + if cur_speed > self.steady_speed: + self.steady_speed = cur_speed + elif cur_speed < self.steady_speed - hyst: + self.steady_speed = cur_speed + return self.steady_speed + + def get_cruise_buttons(self, CS, v_cruise_kph_prev): + cruise_button = None + if not self.get_cruise_buttons_status(CS): + pass + elif CS.out.cruiseState.enabled: + set_speed_kph = self.get_target_speed(v_cruise_kph_prev) + if self.slc_state > 1: + target_speed_kph = set_speed_kph + else: + target_speed_kph = min(v_cruise_kph_prev, set_speed_kph) + if self.v_tsc_state != 0 or self.m_tsc_state > 1: + self.final_speed_kph = self.get_curve_speed(target_speed_kph, v_cruise_kph_prev) + else: + self.final_speed_kph = target_speed_kph + + cruise_button = self.get_button_control(CS, self.final_speed_kph, v_cruise_kph_prev) # MPH/KPH based button presses + return cruise_button diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 465109eb4c..f68b31a291 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -88,6 +88,7 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)] ret.autoResumeSng = False ret.enableBsm = 0x142 in fingerprint[CanBus.POWERTRAIN] + ret.customStockLongAvailable = True if candidate in EV_CAR: ret.transmissionType = TransmissionType.direct @@ -239,7 +240,7 @@ class CarInterface(CarInterfaceBase): else: self.CS.madsEnabled = False - if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0): + 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): self.CS.madsEnabled, self.CS.accEnabled = self.get_sp_cancel_cruise_state(self.CS.madsEnabled) if self.get_sp_pedal_disengage(ret): diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 61edf7aade..89d165107b 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -65,6 +65,10 @@ VOLKSWAGEN_V_CRUISE_MIN = { True: 30, False: int(20 * CV.MPH_TO_KPH), } +GM_V_CRUISE_MIN = { + True: 30, + False: int(20 * CV.MPH_TO_KPH), +} SpeedLimitControlState = custom.LongitudinalPlanSP.SpeedLimitControlState @@ -202,6 +206,8 @@ class VCruiseHelper: initial = MAZDA_V_CRUISE_MIN[is_metric] elif self.CP.carName == "volkswagen": initial = VOLKSWAGEN_V_CRUISE_MIN[is_metric] + elif self.CP.carName == "gm": + initial = GM_V_CRUISE_MIN[is_metric] # 250kph or above probably means we never had a set speed if any(b.type in resume_buttons for b in CS.buttonEvents) and self.v_cruise_kph_last < 250: @@ -234,6 +240,8 @@ class VCruiseHelper: self.v_cruise_min = MAZDA_V_CRUISE_MIN[is_metric] elif self.CP.carName == "volkswagen": self.v_cruise_min = VOLKSWAGEN_V_CRUISE_MIN[is_metric] + elif self.CP.carName == "gm": + self.v_cruise_min = GM_V_CRUISE_MIN[is_metric] self.is_metric_prev = is_metric From 572abfb519b4ad96611402c6989e956c94b363d5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 16 Jun 2024 01:41:15 -0400 Subject: [PATCH 273/575] ui: Fix onroad bottom row buttons positions with RHD detection The `updateButtonsLayout` function now takes a boolean argument `is_rhd` which adjusts the alignment of the on-screen buttons according to the handedness of the driving. This change was necessary to cater to right-hand drive regions, making the application more user-friendly and adaptable to global user base. No behavior change for left-hand drive. --- selfdrive/ui/qt/onroad/annotated_camera.cc | 16 ++++++++-------- selfdrive/ui/qt/onroad/annotated_camera.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 8e99892be7..8a459a9083 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -67,7 +67,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par buttons_layout = new QHBoxLayout(); buttons_layout->setContentsMargins(0, 0, 10, 20); main_layout->addLayout(buttons_layout); - updateButtonsLayout(); + updateButtonsLayout(false); } void AnnotatedCameraWidget::mousePressEvent(QMouseEvent* e) { @@ -102,7 +102,7 @@ void AnnotatedCameraWidget::offroadTransition(bool offroad) { } #endif -void AnnotatedCameraWidget::updateButtonsLayout() { +void AnnotatedCameraWidget::updateButtonsLayout(bool is_rhd) { QLayoutItem *item; while ((item = buttons_layout->takeAt(0)) != nullptr) { delete item; @@ -111,16 +111,16 @@ void AnnotatedCameraWidget::updateButtonsLayout() { buttons_layout->setContentsMargins(0, 0, 10, rn_offset != 0 ? rn_offset + 10 : 20); buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); - buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); + buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | (is_rhd ? Qt::AlignRight : Qt::AlignLeft)); buttons_layout->addStretch(1); #ifdef ENABLE_DASHCAM - buttons_layout->addWidget(recorder, 0, Qt::AlignBottom | Qt::AlignRight); + buttons_layout->addWidget(recorder, 0, Qt::AlignBottom | (is_rhd ? Qt::AlignLeft : Qt::AlignRight)); #endif buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); - buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); + buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | (is_rhd ? Qt::AlignLeft : Qt::AlignRight)); } void AnnotatedCameraWidget::updateState(const UIState &s) { @@ -227,9 +227,6 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { recorder->updateState(s); #endif - // update buttons layout - updateButtonsLayout(); - // update DM icon auto dm_state = sm["driverMonitoringState"].getDriverMonitoringState(); dmActive = dm_state.getIsActiveMode(); @@ -237,6 +234,9 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { // DM icon transition dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0); + // update buttons layout + updateButtonsLayout(rightHandDM); + // hide map settings button for alerts and flip for right hand DM if (map_settings_btn->isEnabled()) { map_settings_btn->setVisible(!hideBottomIcons); diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h index 195056ecd4..1d35c0782f 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -76,7 +76,7 @@ private: void drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state); void drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state); int blinkerPulse(int frame); - void updateButtonsLayout(); + void updateButtonsLayout(bool is_rhd); void drawFeatureStatusText(QPainter &p, int x, int y); void speedLimitSignPulse(int frame); From 50b0a7a69a234434c4ea464fcf0f0b75fb3ecaba Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 16 Jun 2024 02:19:45 -0400 Subject: [PATCH 274/575] Only camera ACC for now --- selfdrive/car/gm/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index f68b31a291..4a74461f06 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -88,7 +88,6 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)] ret.autoResumeSng = False ret.enableBsm = 0x142 in fingerprint[CanBus.POWERTRAIN] - ret.customStockLongAvailable = True if candidate in EV_CAR: ret.transmissionType = TransmissionType.direct @@ -121,6 +120,7 @@ class CarInterface(CarInterfaceBase): ret.pcmCruise = False ret.openpilotLongitudinalControl = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM_LONG + ret.customStockLongAvailable = True else: # ASCM, OBD-II harness ret.openpilotLongitudinalControl = True From 779637fb0dac5df94690940d1565114f64626036 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 16 Jun 2024 14:51:32 -0400 Subject: [PATCH 275/575] Fix --- selfdrive/car/gm/carcontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 9f5c8ed9a8..d8b06e026f 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -42,7 +42,7 @@ class CarController(CarControllerBase): self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar']) self.packer_ch = CANPacker(DBC[self.CP.carFingerprint]['chassis']) - self.sm = messaging.SubMaster(['longitudinalPlan']) + self.sm = messaging.SubMaster(['longitudinalPlanSP']) self.param_s = Params() self.is_metric = self.param_s.get_bool("IsMetric") self.speed_limit_control_enabled = False From d00479b517436e1a0df616f56f00b3458bd4e039 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 17 Jun 2024 20:15:48 -0400 Subject: [PATCH 276/575] ui: Fix navigation path not updating color --- selfdrive/ui/qt/maps/map.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 55825d1d61..b5b731ef66 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -170,7 +170,7 @@ void MapWindow::updateState(const UIState &s) { } prev_time_valid = sm.valid("clocks"); - if (sm.updated("modelV2") && uiState()->scene.custom_driving_model && uiState()->scene.driving_model_gen != 4 && uiState()->scene.driving_model_gen != 0) { + if (sm.updated("modelV2")) { // set path color on change, and show map on rising edge of navigate on openpilot auto car_control = sm["carControl"].getCarControl(); bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() && From 7821c5371b76c20ebe01e1d50de71ae9f96189fe Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 19 Jun 2024 14:58:24 -0400 Subject: [PATCH 277/575] HKG: Car Port for Kia Ceed PHEV Non-SCC 2022 --- opendbc | 2 +- panda | 2 +- selfdrive/car/hyundai/carstate.py | 11 ++++++++--- selfdrive/car/hyundai/fingerprints.py | 8 ++++++++ selfdrive/car/hyundai/hyundaican.py | 2 +- selfdrive/car/hyundai/values.py | 6 ++++++ selfdrive/car/sunnypilot_carname.json | 1 + selfdrive/car/torque_data/substitute.toml | 1 + 8 files changed, 27 insertions(+), 6 deletions(-) diff --git a/opendbc b/opendbc index e0b8c109cd..7b3c2431fa 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit e0b8c109cd3571c407cb09b5edacc223fb78be10 +Subproject commit 7b3c2431fa2ba3c60f4d720a767a3b474ddb5cb3 diff --git a/panda b/panda index b1eaf46501..45b6e29dcc 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b1eaf465019be0b76d208a820face2a62a711a8f +Subproject commit 45b6e29dcc72229dd703d8daa68b427fdde37bd4 diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index f88ca1f3aa..5ba83f08eb 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -117,9 +117,12 @@ class CarState(CarStateBase): ret.cruiseState.standstill = False ret.cruiseState.nonAdaptive = False elif self.CP.carFingerprint in NON_SCC_CAR: - ret.cruiseState.available = cp.vl['EMS16']['CRUISE_LAMP_M'] != 0 - ret.cruiseState.enabled = cp.vl["LVR12"]['CF_Lvr_CruiseSet'] != 0 - ret.cruiseState.speed = cp.vl["LVR12"]["CF_Lvr_CruiseSet"] * speed_conv + cruise_available_msg = "E_CRUISE_CONTROL" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "EMS16" + cruise_enabled_msg = "E_CRUISE_CONTROL" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "LVR12" + cruise_speed_msg = "ELECT_GEAR" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "LVR12" + ret.cruiseState.available = cp.vl[cruise_available_msg]['CRUISE_LAMP_M'] != 0 + ret.cruiseState.enabled = cp.vl[cruise_enabled_msg]['CF_Lvr_CruiseSet'] != 0 + ret.cruiseState.speed = cp.vl[cruise_speed_msg]["CF_Lvr_CruiseSet"] * speed_conv ret.cruiseState.standstill = False ret.cruiseState.nonAdaptive = False else: @@ -364,6 +367,8 @@ class CarState(CarStateBase): if CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): messages.append(("ELECT_GEAR", 20)) + if CP.spFlags & HyundaiFlagsSP.SP_NON_SCC: + messages.append(("E_CRUISE_CONTROL", 10)) elif CP.carFingerprint in CAN_GEARS["use_cluster_gears"]: pass elif CP.carFingerprint in CAN_GEARS["use_tcu_gears"]: diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 7f53e63527..c3f0fc9b14 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -1190,6 +1190,14 @@ FW_VERSIONS = { b'\xf1\x006T6J0_C2\x00\x006T6K1051\x00\x00TOS4N20NS2\x00\x00\x00\x00', ], }, + CAR.KIA_CEED_PHEV_2022_NON_SCC: { + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00CD MDPS C 1.00 1.01 56310-XX000 4CPHC101', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00CDH LKAS AT EUR LHD 1.00 1.01 99211-CR700 931', + ], + }, CAR.KIA_FORTE_2019_NON_SCC: { (Ecu.eps, 0x7D4, None): [ b'\xf1\x00BD MDPS C 1.00 1.04 56310/M6000 4BDDC104', diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index bcfe88f484..a2f44716ef 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -41,7 +41,7 @@ def create_lkas11(packer, frame, CP, apply_steer, steer_req, CAR.HYUNDAI_SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_CEED, CAR.HYUNDAI_AZERA_6TH_GEN, CAR.HYUNDAI_AZERA_HEV_6TH_GEN, CAR.HYUNDAI_CUSTIN_1ST_GEN, CAR.HYUNDAI_ELANTRA_2022_NON_SCC, CAR.GENESIS_G70_2021_NON_SCC, CAR.KIA_SELTOS_2023_NON_SCC, - CAR.HYUNDAI_BAYON_1ST_GEN_NON_SCC): + CAR.HYUNDAI_BAYON_1ST_GEN_NON_SCC, CAR.KIA_CEED_PHEV_2022_NON_SCC): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 83aa8ce5d4..5676a76daa 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -573,6 +573,12 @@ class CAR(Platforms): HYUNDAI_KONA.specs, spFlags=HyundaiFlagsSP.SP_NON_SCC | HyundaiFlagsSP.SP_NON_SCC_FCA, ) + KIA_CEED_PHEV_2022_NON_SCC = HyundaiPlatformConfig( + [HyundaiCarDocs("Kia Ceed PHEV Non-SCC 2022", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_i]))], + CarSpecs(mass=1650, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5), + flags=HyundaiFlags.HYBRID, + spFlags=HyundaiFlagsSP.SP_NON_SCC | HyundaiFlagsSP.SP_NON_SCC_FCA, + ) KIA_FORTE_2019_NON_SCC = HyundaiPlatformConfig( [HyundaiCarDocs("Kia Forte Non-SCC 2019", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_g]))], KIA_FORTE.specs, diff --git a/selfdrive/car/sunnypilot_carname.json b/selfdrive/car/sunnypilot_carname.json index 4cf1737c45..41f167302e 100644 --- a/selfdrive/car/sunnypilot_carname.json +++ b/selfdrive/car/sunnypilot_carname.json @@ -125,6 +125,7 @@ "Kia Carnival 2022-24": "KIA_CARNIVAL_4TH_GEN", "Kia Carnival (China only) 2023": "KIA_CARNIVAL_4TH_GEN", "Kia Ceed 2019": "KIA_CEED", + "Kia Ceed PHEV Non-SCC 2022": "KIA_CEED_PHEV_2022_NON_SCC", "Kia EV6 (Southeast Asia only) 2022-24": "KIA_EV6", "Kia EV6 (with HDA II) 2022-24": "KIA_EV6", "Kia EV6 (without HDA II) 2022-24": "KIA_EV6", diff --git a/selfdrive/car/torque_data/substitute.toml b/selfdrive/car/torque_data/substitute.toml index 44ce127fb8..921846667b 100644 --- a/selfdrive/car/torque_data/substitute.toml +++ b/selfdrive/car/torque_data/substitute.toml @@ -88,3 +88,4 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "HYUNDAI_KONA_NON_SCC" = "HYUNDAI_KONA_EV" "HYUNDAI_ELANTRA_2022_NON_SCC" = "HYUNDAI_ELANTRA_2021" "GENESIS_G70_2021_NON_SCC" = "HYUNDAI_SONATA" +"KIA_CEED_PHEV_2022_NON_SCC" = "HYUNDAI_SONATA" From 2952d54a2bf6fa62a49d46378ee03251040c11ed Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 19 Jun 2024 15:00:56 -0400 Subject: [PATCH 278/575] Bump submodule for SP-162 --- opendbc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc b/opendbc index e0b8c109cd..7b3c2431fa 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit e0b8c109cd3571c407cb09b5edacc223fb78be10 +Subproject commit 7b3c2431fa2ba3c60f4d720a767a3b474ddb5cb3 From 5d440cf62a9909c6654dadffd94315bc543818c1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 16 Jun 2024 20:36:05 +0200 Subject: [PATCH 279/575] Refactor Sunnylink integration and add dongle registration check The Sunnylink integration has been refactored to improve handling of scenarios where Sunnylink is enabled but the dongle is not registered. Added checks for the registration status of the Sunnylink dongle and updated related defaults and UI indicators correspondingly. Additionally, moved the execution of the sunnylink.py script to the background during launch to prevent blocking other tasks. --- common/api/sunnylink.py | 3 ++- launch_chffrplus.sh | 3 ++- selfdrive/ui/qt/sidebar.cc | 3 +++ selfdrive/ui/qt/sidebar.h | 1 + system/manager/manager.py | 4 +++- system/manager/process_config.py | 3 ++- system/manager/sunnylink.py | 8 ++++---- 7 files changed, 17 insertions(+), 8 deletions(-) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index e2761cc29f..fe8627ceab 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -23,7 +23,8 @@ class SunnylinkApi(BaseApi): self.params = Params() def api_get(self, endpoint, method='GET', timeout=10, access_token=None, **kwargs): - if not self.params.get_bool("SunnylinkEnabled"): + sunnylink_dongle_id, comma_dongle_id = self._resolve_dongle_ids() + if not self.params.get_bool("SunnylinkEnabled") or sunnylink_dongle_id in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID): return None return super().api_get(endpoint, method, timeout, access_token, **kwargs) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index c5fedf68a8..ed8d5e37fc 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -87,7 +87,8 @@ function launch { ./build.py fi - ./sunnylink.py; ./mapd_installer.py; ./manager.py + ./sunnylink.py & + ./mapd_installer.py; ./manager.py # if broken, keep on screen error while true; do sleep 1; done diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 5d8245acc5..9463843ab2 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -155,6 +155,7 @@ void Sidebar::updateState(const UIState &s) { setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); ItemStatus sunnylinkStatus; + auto sl_dongle_id = getSunnylinkDongleId(); auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime"); auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str); auto current_nanos = nanos_since_boot(); @@ -162,6 +163,8 @@ void Sidebar::updateState(const UIState &s) { auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); if (!sunnylink_enabled) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("DISABLED")}, disabled_color}; + } else if(!sl_dongle_id.has_value()) { + sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("REGIST...")}, progress_color}; } else if (last_sunnylink_ping == 0) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("OFFLINE")}, warning_color}; } else { diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index be2c34cae0..9012f25135 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -52,6 +52,7 @@ protected: const QRect home_btn = QRect(60, 860, 180, 180); const QRect settings_btn = QRect(50, 35, 200, 117); const QColor good_color = QColor(255, 255, 255); + const QColor progress_color = QColor(3, 132, 252); const QColor warning_color = QColor(218, 202, 37); const QColor danger_color = QColor(201, 34, 49); const QColor disabled_color = QColor(128, 128, 128); diff --git a/system/manager/manager.py b/system/manager/manager.py index 07ebe0d38f..33ebcd73e6 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -8,6 +8,7 @@ import traceback from cereal import custom import cereal.messaging as messaging import openpilot.system.sentry as sentry +from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.params import Params, ParamKeyType from openpilot.common.text_window import TextWindow from openpilot.system.hardware import HARDWARE, PC @@ -105,7 +106,8 @@ def manager_init() -> None: ("OsmDownloadedDate", "0"), ("OSMDownloadProgress", "{}"), ("SidebarTemperatureOptions", "0"), - ("SunnylinkEnabled", "0"), + ("SunnylinkEnabled", "1"), + {"SunnylinkDongleId", UNREGISTERED_SUNNYLINK_DONGLE_ID}, ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 3ac3e1648b..89618eac1b 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -1,6 +1,7 @@ import os from cereal import car +from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.params import Params from openpilot.system.hardware import PC, TICI from openpilot.selfdrive.sunnypilot import get_model_generation @@ -104,7 +105,7 @@ procs = [ PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), ] -if Params().get_bool("SunnylinkEnabled"): +if Params().get_bool("SunnylinkEnabled") and Params().get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID): if os.path.exists("../athena/manage_sunnylinkd.py"): procs += [ DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), diff --git a/system/manager/sunnylink.py b/system/manager/sunnylink.py index b329132bc2..47017094b7 100755 --- a/system/manager/sunnylink.py +++ b/system/manager/sunnylink.py @@ -7,12 +7,12 @@ from openpilot.system.version import is_prebuilt if __name__ == "__main__": - spinner = Spinner() + # spinner = Spinner() extra_args = {} if not Params().get_bool("SunnylinkEnabled"): print("Sunnylink is not enabled. Exiting.") - spinner.close() + # spinner.close() exit(0) if not is_prebuilt(): @@ -21,6 +21,6 @@ if __name__ == "__main__": "timeout": 60 } - sunnylink_id = SunnylinkApi(None).register_device(spinner, **extra_args) + sunnylink_id = SunnylinkApi(None).register_device(None, **extra_args) print(f"SunnyLinkId: {sunnylink_id}") - spinner.close() + # spinner.close() From 65edbe4f47c0a00fa6b79b904dbcecdcb2acf8b1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 16 Jun 2024 20:51:06 +0200 Subject: [PATCH 280/575] Update Sunnylink dongle ID handling The Sunnylink dongle ID is now being formatted directly in the manager.py file, reducing redundancy. Also, the check for sunnylink_dongle_id in sunnylink.py was removed to simplify the SunnlinkEnabled conditional. --- common/api/sunnylink.py | 3 +-- system/manager/manager.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index fe8627ceab..e2761cc29f 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -23,8 +23,7 @@ class SunnylinkApi(BaseApi): self.params = Params() def api_get(self, endpoint, method='GET', timeout=10, access_token=None, **kwargs): - sunnylink_dongle_id, comma_dongle_id = self._resolve_dongle_ids() - if not self.params.get_bool("SunnylinkEnabled") or sunnylink_dongle_id in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID): + if not self.params.get_bool("SunnylinkEnabled"): return None return super().api_get(endpoint, method, timeout, access_token, **kwargs) diff --git a/system/manager/manager.py b/system/manager/manager.py index 33ebcd73e6..3155d28da3 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -107,7 +107,7 @@ def manager_init() -> None: ("OSMDownloadProgress", "{}"), ("SidebarTemperatureOptions", "0"), ("SunnylinkEnabled", "1"), - {"SunnylinkDongleId", UNREGISTERED_SUNNYLINK_DONGLE_ID}, + {"SunnylinkDongleId", f"{UNREGISTERED_SUNNYLINK_DONGLE_ID}"}, ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), From 7fcdcf64d82471c1029a14d66fe01d2a1ddfe5ba Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 17 Jun 2024 17:16:17 +0200 Subject: [PATCH 281/575] Fix format error in SunnylinkDongleId variable The format of the SunnylinkDongleId variable was incorrect in system manager code. It used a dictionary format instead of a tuple. This commit replaces it back with a tuple appropriately to avoid inconsistencies. --- system/manager/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/manager/manager.py b/system/manager/manager.py index 3155d28da3..1944a05ba6 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -107,7 +107,7 @@ def manager_init() -> None: ("OSMDownloadProgress", "{}"), ("SidebarTemperatureOptions", "0"), ("SunnylinkEnabled", "1"), - {"SunnylinkDongleId", f"{UNREGISTERED_SUNNYLINK_DONGLE_ID}"}, + ("SunnylinkDongleId", f"{UNREGISTERED_SUNNYLINK_DONGLE_ID}"), ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), From 00fb20e28c05a3f72d229022840c6138ff837fca Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 20:34:14 +0200 Subject: [PATCH 282/575] Refactor sunnylink process configuration logic The code for managing sunnylink processes has been refactored and improved. A function, use_sunnylink, has been added to check if sunnylink is both enabled and registered appropriately. Additionally, the launching of process logic for 'sunnylink_uploader' and 'manage_sunnylinkd' has been adjusted to use this new function. The main function of 'sunnylinkd.py' now also checks if the sunnylink is enabled before running. --- system/athena/sunnylinkd.py | 18 +++++++++++++++++- system/manager/process_config.py | 23 ++++++++++++++--------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 67ae8a95af..cf67f3c82b 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -8,6 +8,7 @@ import os import threading import time +from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \ recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler from jsonrpc import dispatcher @@ -52,6 +53,11 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: thread.start() try: while not end_event.wait(0.1): + if not params.get_bool("SunnylinkEnabled"): + cloudlog.warning("Exiting sunnylinkd.handle_long_poll as SunnylinkEnabled is False") + exit_event = exit_event or threading.Event() + exit_event.set() + sm.update(0) if exit_event is not None and exit_event.is_set(): end_event.set() @@ -223,4 +229,14 @@ def main(exit_event: threading.Event = None): if __name__ == "__main__": - main() + is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") + is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) + while is_sunnylink_enabled and not is_registered: + cloudlog.info("Waiting for sunnylink registration to complete") + time.sleep(60) + + if is_sunnylink_enabled: + main() + else: + cloudlog.info("Exiting sunnylinkd as SunnylinkEnabled is False") + exit(0) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 89618eac1b..85fed770a0 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -48,6 +48,12 @@ 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 not in (0, 4) + +def use_sunnylink(started, params, CP: car.CarParams) -> bool: + is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") + is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) + return is_sunnylink_enabled and is_registered + procs = [ DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), @@ -105,15 +111,14 @@ procs = [ PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), ] -if Params().get_bool("SunnylinkEnabled") and Params().get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID): - if os.path.exists("../athena/manage_sunnylinkd.py"): - procs += [ - DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), - ] - if os.path.exists("../loggerd/sunnylink_uploader.py"): - procs += [ - PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", always_run), - ] +if os.path.exists("../athena/manage_sunnylinkd.py"): + procs += [ + DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), + ] +if os.path.exists("../loggerd/sunnylink_uploader.py"): + procs += [ + PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", use_sunnylink), + ] if os.path.exists("./gitlab_runner.sh") and not PC: # Only devs! From b18987a5f7ef5743febc5105a38cdc627d61a51d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 20:42:30 +0200 Subject: [PATCH 283/575] Remove setting exit_event in sunnylinkd.py The code has been updated to remove the conditional that sets exit_event in the sunnylinkd.py file. This change simplifies control flow by immediately returning to the top of the loop when Sunnylink is not enabled, instead of managing an additional event. --- system/athena/sunnylinkd.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index cf67f3c82b..d269a3596a 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -55,8 +55,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: while not end_event.wait(0.1): if not params.get_bool("SunnylinkEnabled"): cloudlog.warning("Exiting sunnylinkd.handle_long_poll as SunnylinkEnabled is False") - exit_event = exit_event or threading.Event() - exit_event.set() + continue sm.update(0) if exit_event is not None and exit_event.is_set(): From 1f63d633986b2abc93f275ad6c1a0f4a7fb2a392 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 20:43:30 +0200 Subject: [PATCH 284/575] Change loop control from 'continue' to 'break' The loop control in sunnylinkd.py was changed from 'continue' to 'break' when Sunnylink is disabled. This prevents unnecessary iterations and makes the code more efficient when the Sunnylink functionality is not in use. --- system/athena/sunnylinkd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index d269a3596a..35eb828d51 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -55,7 +55,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: while not end_event.wait(0.1): if not params.get_bool("SunnylinkEnabled"): cloudlog.warning("Exiting sunnylinkd.handle_long_poll as SunnylinkEnabled is False") - continue + break sm.update(0) if exit_event is not None and exit_event.is_set(): From c7464fc615d1e7614deccbec5faf39dba3e47c1d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 20:50:23 +0200 Subject: [PATCH 285/575] Add Sunnylink registration check in main function The code has been adjusted to perform a check for Sunnylink registration in the main function of the sunnylinkd script. Previously, the script was checking for the SunnylinkEnabled state and if the dongle is registered at the beginning of the script. Now, these checks are executed within the main function, allowing the script to exit with a warning log if Sunnylink is not enabled. --- system/athena/sunnylinkd.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 35eb828d51..b61da2833c 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -190,6 +190,13 @@ def main(exit_event: threading.Event = None): except Exception: cloudlog.exception("failed to set core affinity") + is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") + is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) + + while is_sunnylink_enabled and not is_registered: + cloudlog.info("Waiting for sunnylink registration to complete") + time.sleep(60) + UploadQueueCache.initialize(upload_queue) ws_uri = SUNNYLINK_ATHENA_HOST @@ -197,6 +204,10 @@ def main(exit_event: threading.Event = None): conn_retries = 0 while exit_event is None or not exit_event.is_set(): try: + if not is_sunnylink_enabled: + cloudlog.warning("Exiting sunnylinkd.main as SunnylinkEnabled is False") + break + if conn_start is None: conn_start = time.monotonic() @@ -228,14 +239,4 @@ def main(exit_event: threading.Event = None): if __name__ == "__main__": - is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") - is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) - while is_sunnylink_enabled and not is_registered: - cloudlog.info("Waiting for sunnylink registration to complete") - time.sleep(60) - - if is_sunnylink_enabled: - main() - else: - cloudlog.info("Exiting sunnylinkd as SunnylinkEnabled is False") - exit(0) + main() From 62b0c9f8be2d1620b01101ca8d0def8b4de28382 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 20:52:15 +0200 Subject: [PATCH 286/575] Add delay before retrying sunnylinkd main The update introduces a 60-second delay before retrying the execution of sunnylinkd main when SunnylinkEnabled is false. This aims to prevent the immediate termination of the process when SunnylinkEnabled is turned off, allowing for a smoother transition. --- system/athena/sunnylinkd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index b61da2833c..4715994e48 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -205,7 +205,8 @@ def main(exit_event: threading.Event = None): while exit_event is None or not exit_event.is_set(): try: if not is_sunnylink_enabled: - cloudlog.warning("Exiting sunnylinkd.main as SunnylinkEnabled is False") + cloudlog.debug("Exiting sunnylinkd.main as SunnylinkEnabled is False but will retry in 60 seconds") + time.sleep(60) break if conn_start is None: From 524b97f8d4d7174ae18e8badef118bc93cd961e5 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 21:00:02 +0200 Subject: [PATCH 287/575] Refactor sunnylinkd.main exit conditions The checks for the sunnylink_enabled flag have been restructured to eliminate unnecessary loop iterations. The check has been moved out of the loop to do the evaluation only once, when exiting the loop. This significantly increases the efficiency of the code. --- system/athena/sunnylinkd.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 4715994e48..0bb4c28e16 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -202,13 +202,8 @@ def main(exit_event: threading.Event = None): ws_uri = SUNNYLINK_ATHENA_HOST conn_start = None conn_retries = 0 - while exit_event is None or not exit_event.is_set(): + while exit_event is None or not exit_event.is_set() and is_sunnylink_enabled: try: - if not is_sunnylink_enabled: - cloudlog.debug("Exiting sunnylinkd.main as SunnylinkEnabled is False but will retry in 60 seconds") - time.sleep(60) - break - if conn_start is None: conn_start = time.monotonic() @@ -238,6 +233,10 @@ def main(exit_event: threading.Event = None): time.sleep(backoff(conn_retries)) + if not is_sunnylink_enabled: + cloudlog.debug("Reached end of sunnylinkd.main while SunnylinkEnabled is False so will wait for 60 seconds before exiting") + time.sleep(60) + if __name__ == "__main__": main() From 583699509712a0cb5b1768eeeb141e268cb41486 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 21:07:03 +0200 Subject: [PATCH 288/575] Refactor sunnylinkd.py for dynamic Sunnylink check Removed the static check for Sunnylink's enabled status, allowing the feature to be checked dynamically within while loops. This change ensures that if the Sunnylink feature status changes, it can immediately affect the decision-making within the process. This is particularly useful for smooth enabling or disabling of the Sunnylink feature. --- system/athena/sunnylinkd.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 0bb4c28e16..f999f1eb2f 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -190,10 +190,8 @@ def main(exit_event: threading.Event = None): except Exception: cloudlog.exception("failed to set core affinity") - is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) - - while is_sunnylink_enabled and not is_registered: + while params.get_bool("SunnylinkEnabled") and not is_registered: cloudlog.info("Waiting for sunnylink registration to complete") time.sleep(60) @@ -202,7 +200,7 @@ def main(exit_event: threading.Event = None): ws_uri = SUNNYLINK_ATHENA_HOST conn_start = None conn_retries = 0 - while exit_event is None or not exit_event.is_set() and is_sunnylink_enabled: + while (exit_event is None or not exit_event.is_set()) and params.get_bool("SunnylinkEnabled"): try: if conn_start is None: conn_start = time.monotonic() @@ -233,7 +231,7 @@ def main(exit_event: threading.Event = None): time.sleep(backoff(conn_retries)) - if not is_sunnylink_enabled: + if not params.get_bool("SunnylinkEnabled"): cloudlog.debug("Reached end of sunnylinkd.main while SunnylinkEnabled is False so will wait for 60 seconds before exiting") time.sleep(60) From 7dc412fcf1932e7f6d53ec83609a95afcbd65643 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 21:24:01 +0200 Subject: [PATCH 289/575] Refactor encoding method and reduce sleep time The decoding method in the manage_athenad file has been refactored for parameter retrieval. Instead of decoding after receiving the parameter, decoding is now integrated in the parameter retrieval process. Also, the waiting time in the sunnylinkd.py has been reduced from 60 seconds to 10 to enhance performance and reduce lag. --- system/athena/manage_athenad.py | 2 +- system/athena/sunnylinkd.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/system/athena/manage_athenad.py b/system/athena/manage_athenad.py index fb514bc1ca..7158cd9220 100755 --- a/system/athena/manage_athenad.py +++ b/system/athena/manage_athenad.py @@ -18,7 +18,7 @@ def main(): def manage_athenad(dongle_id_param, pid_param, process_name, target): params = Params() - dongle_id = params.get(dongle_id_param).decode('utf-8') + dongle_id = params.get(dongle_id_param, encoding='utf-8') build_metadata = get_build_metadata() cloudlog.bind_global(dongle_id=dongle_id, diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index f999f1eb2f..2f19983198 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -193,7 +193,7 @@ def main(exit_event: threading.Event = None): is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) while params.get_bool("SunnylinkEnabled") and not is_registered: cloudlog.info("Waiting for sunnylink registration to complete") - time.sleep(60) + time.sleep(10) UploadQueueCache.initialize(upload_queue) From 61843ee71c1661095da80ab5f44a236d488c8981 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 21:28:31 +0200 Subject: [PATCH 290/575] Refactor Sunnylink registration condition The condition for Sunnylink registration has been simplified and refactored within the main loop. Previously, the "is_registered" variable was being set outside the registration loop. Now, the checking process of the dongle ID in regard to the registration status is directly integrated into the while condition. This makes the code more concise and the process flow clearer. --- system/athena/sunnylinkd.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 2f19983198..529d1b2059 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -190,8 +190,7 @@ def main(exit_event: threading.Event = None): except Exception: cloudlog.exception("failed to set core affinity") - is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) - while params.get_bool("SunnylinkEnabled") and not is_registered: + while params.get_bool("SunnylinkEnabled") and not params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID): cloudlog.info("Waiting for sunnylink registration to complete") time.sleep(10) From cd8d07dddd2f40c1ce2a108db0b9f5d9e1042f8a Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 21:38:39 +0200 Subject: [PATCH 291/575] Add Sunnylink registration check and process The commit introduces a new function, sunnylink_need_register, to check if the Sunnylink feature is registered. It also updates the process list to always include the manage_sunnylinkd process and introduces a new process for Sunnylink registration if it's enabled but not registered. --- system/manager/process_config.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 85fed770a0..5e709eae52 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -54,6 +54,11 @@ def use_sunnylink(started, params, CP: car.CarParams) -> bool: is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) return is_sunnylink_enabled and is_registered +def sunnylink_need_register(started, params, CP: car.CarParams) -> bool: + is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") + is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) + return is_sunnylink_enabled and not is_registered + procs = [ DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), @@ -109,12 +114,12 @@ procs = [ NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar), PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), + + # Sunnylink <3 + DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), + PythonProcess("sunnylink_registration", "system.manager.sunnylink", sunnylink_need_register), ] -if os.path.exists("../athena/manage_sunnylinkd.py"): - procs += [ - DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), - ] if os.path.exists("../loggerd/sunnylink_uploader.py"): procs += [ PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", use_sunnylink), From b80f6442bd85243f766a0fd6f285528838d7d52c Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 21:39:11 +0200 Subject: [PATCH 292/575] Remove unnecessary sunnylink.py launch command The sunnylink.py file was unnecessarily initiated in the launch_chffrplus.sh script. This initiation was removed to streamline the script and potentially increase overall runtime efficiency. --- launch_chffrplus.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index ed8d5e37fc..8b213c0972 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -87,7 +87,6 @@ function launch { ./build.py fi - ./sunnylink.py & ./mapd_installer.py; ./manager.py # if broken, keep on screen error From 7e7d98f0c0b9b042e3b6b515c82a361f443a8475 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 21:41:46 +0200 Subject: [PATCH 293/575] addming main --- system/manager/sunnylink.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/manager/sunnylink.py b/system/manager/sunnylink.py index 47017094b7..a530ddab10 100755 --- a/system/manager/sunnylink.py +++ b/system/manager/sunnylink.py @@ -6,7 +6,7 @@ from openpilot.common.spinner import Spinner from openpilot.system.version import is_prebuilt -if __name__ == "__main__": +def main(): # spinner = Spinner() extra_args = {} @@ -24,3 +24,7 @@ if __name__ == "__main__": sunnylink_id = SunnylinkApi(None).register_device(None, **extra_args) print(f"SunnyLinkId: {sunnylink_id}") # spinner.close() + + +if __name__ == "__main__": + main() From c4037ece24c79430e6c4f12efe59122230a8537b Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 19 Jun 2024 21:52:57 +0200 Subject: [PATCH 294/575] Update sunnylink registration processes Removed spinner from sunnylink.py and stopped reboot prompt in sunnylink_settings.cc. The spinner was unnecessary and the reboot, instead of being prompted, will be handled differently. Additionally, timestamp of the last successful ping is now recorded upon registration in the Sunnylink service. --- selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc | 8 ++++---- system/manager/sunnylink.py | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc index 118aa42159..3b1f1b64b9 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc @@ -45,10 +45,10 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { sunnylinkEnabledBtn->setDescription(shame_description); } - auto dialog_text = tr("A reboot is required to") + " " + (enabled ? tr("start") : tr("stop")) +" "+ tr("all connections and processes from sunnylink.") + "
"+ tr("If that's not a problem for you, you can ignore this.")+ ""; - if (ConfirmationDialog::confirm(dialog_text, tr("Reboot Now!"), this)) { - Hardware::reboot(); - } + //auto dialog_text = tr("A reboot is required to") + " " + (enabled ? tr("start") : tr("stop")) +" "+ tr("all connections and processes from sunnylink.") + "
"+ tr("If that's not a problem for you, you can ignore this.")+ ""; + //if (ConfirmationDialog::confirm(dialog_text, tr("Reboot Now!"), this)) { + // Hardware::reboot(); + //} updateLabels(); }); diff --git a/system/manager/sunnylink.py b/system/manager/sunnylink.py index a530ddab10..17384f14b8 100755 --- a/system/manager/sunnylink.py +++ b/system/manager/sunnylink.py @@ -2,12 +2,11 @@ from openpilot.common.api.sunnylink import SunnylinkApi from openpilot.common.params import Params -from openpilot.common.spinner import Spinner from openpilot.system.version import is_prebuilt +import time def main(): - # spinner = Spinner() extra_args = {} if not Params().get_bool("SunnylinkEnabled"): @@ -23,7 +22,10 @@ def main(): sunnylink_id = SunnylinkApi(None).register_device(None, **extra_args) print(f"SunnyLinkId: {sunnylink_id}") - # spinner.close() + + # Set the last ping time to the current time since we just registered + last_ping = int(time.monotonic() * 1e9) + Params().put("LastSunnylinkPingTime", str(last_ping)) if __name__ == "__main__": From b1f4d050514e31c1e7c64a5f437447bdb42b190b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 19 Jun 2024 19:05:50 -0400 Subject: [PATCH 295/575] Try this --- selfdrive/car/hyundai/carstate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 5ba83f08eb..77b13d6930 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -120,8 +120,8 @@ class CarState(CarStateBase): cruise_available_msg = "E_CRUISE_CONTROL" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "EMS16" cruise_enabled_msg = "E_CRUISE_CONTROL" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "LVR12" cruise_speed_msg = "ELECT_GEAR" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "LVR12" - ret.cruiseState.available = cp.vl[cruise_available_msg]['CRUISE_LAMP_M'] != 0 - ret.cruiseState.enabled = cp.vl[cruise_enabled_msg]['CF_Lvr_CruiseSet'] != 0 + ret.cruiseState.available = cp.vl[cruise_available_msg]["CRUISE_LAMP_M"] != 0 + ret.cruiseState.enabled = cp.vl[cruise_enabled_msg]["CF_Lvr_CruiseSet"] != 0 ret.cruiseState.speed = cp.vl[cruise_speed_msg]["CF_Lvr_CruiseSet"] * speed_conv ret.cruiseState.standstill = False ret.cruiseState.nonAdaptive = False @@ -367,7 +367,7 @@ class CarState(CarStateBase): if CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): messages.append(("ELECT_GEAR", 20)) - if CP.spFlags & HyundaiFlagsSP.SP_NON_SCC: + if CP.carFingerprint in NON_SCC_CAR: messages.append(("E_CRUISE_CONTROL", 10)) elif CP.carFingerprint in CAN_GEARS["use_cluster_gears"]: pass From 7f3a965aa2a174623befd7d3d78cb05fa89ea85a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 19 Jun 2024 19:31:25 -0400 Subject: [PATCH 296/575] Wrong key --- selfdrive/car/hyundai/carstate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 77b13d6930..f99499e749 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -120,9 +120,10 @@ class CarState(CarStateBase): cruise_available_msg = "E_CRUISE_CONTROL" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "EMS16" cruise_enabled_msg = "E_CRUISE_CONTROL" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "LVR12" cruise_speed_msg = "ELECT_GEAR" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "LVR12" + cruise_speed_sig = "VSetDis" if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) else "CF_Lvr_CruiseSet" ret.cruiseState.available = cp.vl[cruise_available_msg]["CRUISE_LAMP_M"] != 0 ret.cruiseState.enabled = cp.vl[cruise_enabled_msg]["CF_Lvr_CruiseSet"] != 0 - ret.cruiseState.speed = cp.vl[cruise_speed_msg]["CF_Lvr_CruiseSet"] * speed_conv + ret.cruiseState.speed = cp.vl[cruise_speed_msg][cruise_speed_sig] * speed_conv ret.cruiseState.standstill = False ret.cruiseState.nonAdaptive = False else: From 1bd2a2f007c7cb5dba4fb9ffb35193de48cdbe4b Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 16 Jun 2024 20:36:05 +0200 Subject: [PATCH 297/575] add back the dirinvg model even if dupped. it's not responisibility of this mr add back the dirinvg model even if dupped. it's not responisibility of this mr Refactor Sunnylink's operation and default state handling This update refines the operation and handling of the Sunnylink feature, focusing on its management within the launch script and the logic for its default state. Starting with the default state, the 'SunnylinkEnabled' parameter is no longer hard-coded to 0. Instead, it now depends on the presence of a release_channel or release_sp_channel. Furthermore, Sunnylink is set to be active in all non-release builds by default. Regarding the launch script, obsolete invocations of Sunnylink are avoided. This procedure actively checks for Sunnylink registration status before initiating the related processes, effectively conserving system resources. Also, this modifies the launching order of the mapd installer and manager. As for the functionality of Sunnylink, crucial improvements are brought into effect: - The status display post Sunnylink registration is made more accurate and informative, with the inclusion of a progress color. - The handling of Sunnylink capability is overhauled, ensuring that if Sunnylink feature is disabled, a clear warning is logged and the daemon process shuts down correctly. - System exit conditions now accommodate the Sunnylink registration status to ensure clean termination. - The start of Sunnylink Uploader is now strictly dependent on the validation of Sunnylink registration. - This optimization is also incorporated into the 'manage_athenad' method which now gracefully handles any potential null values for Sunnylink Dongle ID. This comprehensive update aims to enhance the transparency, functionality, and efficient operation of the Sunnylink feature. --- launch_chffrplus.sh | 2 +- .../offroad/sunnypilot/sunnylink_settings.cc | 4 --- selfdrive/ui/qt/sidebar.cc | 3 ++ selfdrive/ui/qt/sidebar.h | 1 + system/athena/manage_athenad.py | 2 +- system/athena/sunnylinkd.py | 15 +++++++++- system/manager/manager.py | 4 ++- system/manager/process_config.py | 29 +++++++++++++------ system/manager/sunnylink.py | 17 +++++++---- 9 files changed, 54 insertions(+), 23 deletions(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index c5fedf68a8..8b213c0972 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -87,7 +87,7 @@ function launch { ./build.py fi - ./sunnylink.py; ./mapd_installer.py; ./manager.py + ./mapd_installer.py; ./manager.py # if broken, keep on screen error while true; do sleep 1; done diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc index 118aa42159..1ebaf4f14e 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc @@ -45,10 +45,6 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { sunnylinkEnabledBtn->setDescription(shame_description); } - auto dialog_text = tr("A reboot is required to") + " " + (enabled ? tr("start") : tr("stop")) +" "+ tr("all connections and processes from sunnylink.") + "
"+ tr("If that's not a problem for you, you can ignore this.")+ ""; - if (ConfirmationDialog::confirm(dialog_text, tr("Reboot Now!"), this)) { - Hardware::reboot(); - } updateLabels(); }); diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 5d8245acc5..9463843ab2 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -155,6 +155,7 @@ void Sidebar::updateState(const UIState &s) { setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); ItemStatus sunnylinkStatus; + auto sl_dongle_id = getSunnylinkDongleId(); auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime"); auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str); auto current_nanos = nanos_since_boot(); @@ -162,6 +163,8 @@ void Sidebar::updateState(const UIState &s) { auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); if (!sunnylink_enabled) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("DISABLED")}, disabled_color}; + } else if(!sl_dongle_id.has_value()) { + sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("REGIST...")}, progress_color}; } else if (last_sunnylink_ping == 0) { sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("OFFLINE")}, warning_color}; } else { diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index be2c34cae0..9012f25135 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -52,6 +52,7 @@ protected: const QRect home_btn = QRect(60, 860, 180, 180); const QRect settings_btn = QRect(50, 35, 200, 117); const QColor good_color = QColor(255, 255, 255); + const QColor progress_color = QColor(3, 132, 252); const QColor warning_color = QColor(218, 202, 37); const QColor danger_color = QColor(201, 34, 49); const QColor disabled_color = QColor(128, 128, 128); diff --git a/system/athena/manage_athenad.py b/system/athena/manage_athenad.py index fb514bc1ca..7158cd9220 100755 --- a/system/athena/manage_athenad.py +++ b/system/athena/manage_athenad.py @@ -18,7 +18,7 @@ def main(): def manage_athenad(dongle_id_param, pid_param, process_name, target): params = Params() - dongle_id = params.get(dongle_id_param).decode('utf-8') + dongle_id = params.get(dongle_id_param, encoding='utf-8') build_metadata = get_build_metadata() cloudlog.bind_global(dongle_id=dongle_id, diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 67ae8a95af..529d1b2059 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -8,6 +8,7 @@ import os import threading import time +from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \ recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler from jsonrpc import dispatcher @@ -52,6 +53,10 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: thread.start() try: while not end_event.wait(0.1): + if not params.get_bool("SunnylinkEnabled"): + cloudlog.warning("Exiting sunnylinkd.handle_long_poll as SunnylinkEnabled is False") + break + sm.update(0) if exit_event is not None and exit_event.is_set(): end_event.set() @@ -185,12 +190,16 @@ def main(exit_event: threading.Event = None): except Exception: cloudlog.exception("failed to set core affinity") + while params.get_bool("SunnylinkEnabled") and not params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID): + cloudlog.info("Waiting for sunnylink registration to complete") + time.sleep(10) + UploadQueueCache.initialize(upload_queue) ws_uri = SUNNYLINK_ATHENA_HOST conn_start = None conn_retries = 0 - while exit_event is None or not exit_event.is_set(): + while (exit_event is None or not exit_event.is_set()) and params.get_bool("SunnylinkEnabled"): try: if conn_start is None: conn_start = time.monotonic() @@ -221,6 +230,10 @@ def main(exit_event: threading.Event = None): time.sleep(backoff(conn_retries)) + if not params.get_bool("SunnylinkEnabled"): + cloudlog.debug("Reached end of sunnylinkd.main while SunnylinkEnabled is False so will wait for 60 seconds before exiting") + time.sleep(60) + if __name__ == "__main__": main() diff --git a/system/manager/manager.py b/system/manager/manager.py index 07ebe0d38f..f3269ee4c3 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -8,6 +8,7 @@ import traceback from cereal import custom import cereal.messaging as messaging import openpilot.system.sentry as sentry +from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.params import Params, ParamKeyType from openpilot.common.text_window import TextWindow from openpilot.system.hardware import HARDWARE, PC @@ -105,7 +106,8 @@ def manager_init() -> None: ("OsmDownloadedDate", "0"), ("OSMDownloadProgress", "{}"), ("SidebarTemperatureOptions", "0"), - ("SunnylinkEnabled", "0"), + ("SunnylinkEnabled", "0" if (build_metadata.release_channel or build_metadata.release_sp_channel) else "1"), + ("SunnylinkDongleId", f"{UNREGISTERED_SUNNYLINK_DONGLE_ID}"), ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 3ac3e1648b..5e709eae52 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -1,6 +1,7 @@ import os from cereal import car +from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.params import Params from openpilot.system.hardware import PC, TICI from openpilot.selfdrive.sunnypilot import get_model_generation @@ -47,6 +48,17 @@ 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 not in (0, 4) + +def use_sunnylink(started, params, CP: car.CarParams) -> bool: + is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") + is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) + return is_sunnylink_enabled and is_registered + +def sunnylink_need_register(started, params, CP: car.CarParams) -> bool: + is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") + is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) + return is_sunnylink_enabled and not is_registered + procs = [ DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), @@ -102,17 +114,16 @@ procs = [ NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar), PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), + + # Sunnylink <3 + DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), + PythonProcess("sunnylink_registration", "system.manager.sunnylink", sunnylink_need_register), ] -if Params().get_bool("SunnylinkEnabled"): - if os.path.exists("../athena/manage_sunnylinkd.py"): - procs += [ - DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), - ] - if os.path.exists("../loggerd/sunnylink_uploader.py"): - procs += [ - PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", always_run), - ] +if os.path.exists("../loggerd/sunnylink_uploader.py"): + procs += [ + PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", use_sunnylink), + ] if os.path.exists("./gitlab_runner.sh") and not PC: # Only devs! diff --git a/system/manager/sunnylink.py b/system/manager/sunnylink.py index b329132bc2..9c396d25ea 100755 --- a/system/manager/sunnylink.py +++ b/system/manager/sunnylink.py @@ -2,17 +2,15 @@ from openpilot.common.api.sunnylink import SunnylinkApi from openpilot.common.params import Params -from openpilot.common.spinner import Spinner from openpilot.system.version import is_prebuilt +import time -if __name__ == "__main__": - spinner = Spinner() +def main(): extra_args = {} if not Params().get_bool("SunnylinkEnabled"): print("Sunnylink is not enabled. Exiting.") - spinner.close() exit(0) if not is_prebuilt(): @@ -21,6 +19,13 @@ if __name__ == "__main__": "timeout": 60 } - sunnylink_id = SunnylinkApi(None).register_device(spinner, **extra_args) + sunnylink_id = SunnylinkApi(None).register_device(None, **extra_args) print(f"SunnyLinkId: {sunnylink_id}") - spinner.close() + + # Set the last ping time to the current time since we just registered + last_ping = int(time.monotonic() * 1e9) + Params().put("LastSunnylinkPingTime", str(last_ping)) + + +if __name__ == "__main__": + main() From 4c6fec9ddff2cf2accc6deaea34506e5b7451311 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 21 Jun 2024 09:14:00 +0200 Subject: [PATCH 298/575] Refactor Sunnylink status checks and improve logic in multiple modules This update streamlines how the system checks the status of Sunnylink. New methods were added to `sunnylink.py` to encapsify status checking logic, such as whether the device is connected to a network, if Sunnylink is enabled, if the device is registered and if registration is needed. This resulted in simplified code in `sunnylinkd.py` and `process_config.py` as these status checks are now standardized. Furthermore, removed repetitive code in `sunnylinkd.py` by replacing it with these new methods and modified the while loop conditions according to the new status methods. --- system/athena/sunnylinkd.py | 12 +++++----- system/manager/process_config.py | 21 ++++++++--------- system/manager/sunnylink.py | 39 +++++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 529d1b2059..4719fffcee 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -8,7 +8,6 @@ import os import threading import time -from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \ recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler from jsonrpc import dispatcher @@ -19,6 +18,7 @@ from openpilot.common.api import SunnylinkApi from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.common.swaglog import cloudlog +from openpilot.system.manager.sunnylink import sunnylink_need_register, sunnylink_ready import cereal.messaging as messaging SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sunnypilot.ai') @@ -53,7 +53,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: thread.start() try: while not end_event.wait(0.1): - if not params.get_bool("SunnylinkEnabled"): + if not sunnylink_ready(params): cloudlog.warning("Exiting sunnylinkd.handle_long_poll as SunnylinkEnabled is False") break @@ -190,7 +190,7 @@ def main(exit_event: threading.Event = None): except Exception: cloudlog.exception("failed to set core affinity") - while params.get_bool("SunnylinkEnabled") and not params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID): + while sunnylink_need_register(params): cloudlog.info("Waiting for sunnylink registration to complete") time.sleep(10) @@ -199,7 +199,7 @@ def main(exit_event: threading.Event = None): ws_uri = SUNNYLINK_ATHENA_HOST conn_start = None conn_retries = 0 - while (exit_event is None or not exit_event.is_set()) and params.get_bool("SunnylinkEnabled"): + while (exit_event is None or not exit_event.is_set()) and sunnylink_ready(params): try: if conn_start is None: conn_start = time.monotonic() @@ -230,8 +230,8 @@ def main(exit_event: threading.Event = None): time.sleep(backoff(conn_retries)) - if not params.get_bool("SunnylinkEnabled"): - cloudlog.debug("Reached end of sunnylinkd.main while SunnylinkEnabled is False so will wait for 60 seconds before exiting") + if not sunnylink_ready(params): + cloudlog.debug("Reached end of sunnylinkd.main while sunnylink is not ready. Waiting 60s before retrying") time.sleep(60) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 5e709eae52..8a8cce69e0 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -1,12 +1,12 @@ import os from cereal import car -from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.params import Params from openpilot.system.hardware import PC, TICI from openpilot.selfdrive.sunnypilot import get_model_generation 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 WEBCAM = os.getenv("USE_WEBCAM") is not None @@ -48,16 +48,13 @@ 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 not in (0, 4) +def sunnylink_ready_shim(started, params, CP: car.CarParams) -> bool: + """Shim for sunnylink_ready to match the process manager signature.""" + return sunnylink_ready(params) -def use_sunnylink(started, params, CP: car.CarParams) -> bool: - is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") - is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) - return is_sunnylink_enabled and is_registered - -def sunnylink_need_register(started, params, CP: car.CarParams) -> bool: - is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") - is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) - return is_sunnylink_enabled and not is_registered +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) procs = [ DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), @@ -117,12 +114,12 @@ procs = [ # Sunnylink <3 DaemonProcess("manage_sunnylinkd", "system.athena.manage_sunnylinkd", "SunnylinkdPid"), - PythonProcess("sunnylink_registration", "system.manager.sunnylink", sunnylink_need_register), + PythonProcess("sunnylink_registration", "system.manager.sunnylink", sunnylink_need_register_shim), ] if os.path.exists("../loggerd/sunnylink_uploader.py"): procs += [ - PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", use_sunnylink), + PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", sunnylink_ready_shim), ] if os.path.exists("./gitlab_runner.sh") and not PC: diff --git a/system/manager/sunnylink.py b/system/manager/sunnylink.py index 9c396d25ea..1a70532e82 100755 --- a/system/manager/sunnylink.py +++ b/system/manager/sunnylink.py @@ -1,12 +1,40 @@ #!/usr/bin/env python3 - -from openpilot.common.api.sunnylink import SunnylinkApi +from cereal import log +from openpilot.common.api.sunnylink import SunnylinkApi, UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.params import Params +from openpilot.system.hardware import HARDWARE from openpilot.system.version import is_prebuilt import time +NetworkType = log.DeviceState.NetworkType -def main(): + +def is_network_connected() -> bool: + """Check if the device is connected to a network.""" + return HARDWARE.get_network_type() != NetworkType.none + + +def get_sunnylink_status(params=Params()) -> tuple[bool, bool]: + """Get the status of Sunnylink on the device. Returns a tuple of (is_sunnylink_enabled, is_registered).""" + is_sunnylink_enabled = params.get_bool("SunnylinkEnabled") + is_registered = params.get("SunnylinkDongleId", encoding='utf-8') not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID) + return is_sunnylink_enabled, is_registered + + +def sunnylink_ready(params=Params()) -> bool: + """Check if the device is ready to communicate with Sunnylink. That means it is enabled and registered.""" + is_sunnylink_enabled, is_registered = get_sunnylink_status(params) + return is_sunnylink_enabled and is_registered + + +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) + return is_sunnylink_enabled and not is_registered and is_network_connected() + + +def register_sunnylink(): + """Register the device with Sunnylink if it is enabled.""" extra_args = {} if not Params().get_bool("SunnylinkEnabled"): @@ -27,5 +55,10 @@ def main(): Params().put("LastSunnylinkPingTime", str(last_ping)) +def main(): + """The main method is expected to be called by the manager when the device boots up.""" + register_sunnylink() + + if __name__ == "__main__": main() From b9bec4853ce68d29755edf67c79a68875c874931 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 21 Jun 2024 09:27:45 +0200 Subject: [PATCH 299/575] Add toggle for log upload functionality A new functionality has been added to the sunnylinkd script which allows for the toggling of log uploads. This includes the establishment of "DISALLOW_LOG_UPLOAD" as a threading event and a new method, "toggleLogUpload", that sets or clears this event based on whether uploads are enabled or not. --- system/athena/sunnylinkd.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 4719fffcee..90b2379ce4 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -26,6 +26,7 @@ HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) LOCAL_PORT_WHITELIST = {8022} SUNNYLINK_LOG_ATTR_NAME = "user.sunny.upload" SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require a change on sidebar.cc +DISALLOW_LOG_UPLOAD = threading.Event() params = Params() sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8')) @@ -65,7 +66,10 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: prime_type = params.get("PrimeType", encoding='utf-8') or 0 metered = sm['deviceState'].networkMetered - if metered and int(prime_type) > 2: + if DISALLOW_LOG_UPLOAD.is_set() and not comma_prime_cellular_end_event.is_set(): + cloudlog.debug(f"sunnylinkd.handle_long_poll: DISALLOW_LOG_UPLOAD, setting comma_prime_cellular_end_event") + comma_prime_cellular_end_event.set() + elif metered and int(prime_type) > 2: cloudlog.debug(f"sunnylinkd.handle_long_poll: PrimeType({prime_type}) > 2 and networkMetered({metered})") comma_prime_cellular_end_event.set() elif comma_prime_cellular_end_event.is_set(): @@ -147,6 +151,10 @@ def sunny_log_handler(end_event: threading.Event, comma_prime_cellular_end_event comma_prime_cellular_end_event.set() +@dispatcher.add_method +def toggleLogUpload(enabled: bool): + DISALLOW_LOG_UPLOAD.clear() if enabled and DISALLOW_LOG_UPLOAD.is_set() else DISALLOW_LOG_UPLOAD.set() + @dispatcher.add_method def getParamsAllKeys() -> list[str]: keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] From 1a09978811cbed14a7c2a1e8c064dcf24ab06db7 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 21 Jun 2024 10:19:45 +0200 Subject: [PATCH 300/575] Fixing a little the conditions --- system/athena/sunnylinkd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 90b2379ce4..ca24f24090 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -72,7 +72,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: elif metered and int(prime_type) > 2: cloudlog.debug(f"sunnylinkd.handle_long_poll: PrimeType({prime_type}) > 2 and networkMetered({metered})") comma_prime_cellular_end_event.set() - elif comma_prime_cellular_end_event.is_set(): + elif comma_prime_cellular_end_event.is_set() and not DISALLOW_LOG_UPLOAD.is_set(): cloudlog.debug(f"sunnylinkd.handle_long_poll: comma_prime_cellular_end_event is set and not PrimeType({prime_type}) > 2 or not networkMetered({metered})") comma_prime_cellular_end_event.clear() finally: From 93286505e119bca071ed5281e815ec2612de2480 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 22 Jun 2024 14:31:51 +0200 Subject: [PATCH 301/575] Improve Sunnylink registration and exception handling This change enhances the Sunnylink registration process by including better error checks and exception handling for cases when the public key is in use or known to be not unique. The commit also adjusts the setting of the ping time right after a successful registration. Additionally, cleanup occurred in the UI to streamline some code relating to the display of Sunnylink status. The handling of the last Sunnylink ping time has been removed from the system manager. --- common/api/sunnylink.py | 32 ++++++++++++++++++++++++-------- selfdrive/ui/qt/sidebar.cc | 29 ++++++++++++++--------------- system/manager/sunnylink.py | 4 ---- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index e2761cc29f..76cc6f21d7 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -82,6 +82,8 @@ class SunnylinkApi(BaseApi): privkey_path = Path(Paths.persist_root()+"/comma/id_rsa") pubkey_path = Path(Paths.persist_root()+"/comma/id_rsa.pub") + start_time = time.monotonic() + successful_registration = False if not pubkey_path.is_file(): sunnylink_dongle_id = UNREGISTERED_SUNNYLINK_DONGLE_ID self._status_update("Public key not found, setting dongle ID to unregistered.") @@ -90,7 +92,6 @@ class SunnylinkApi(BaseApi): public_key = f1.read() private_key = f2.read() - start_time = time.monotonic() backoff = 1 while True: register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm='RS256') @@ -102,17 +103,29 @@ class SunnylinkApi(BaseApi): resp = self.api_get("v2/pilotauth/", method='POST', timeout=15, imei=imei1, imei2=imei2, serial=serial, comma_dongle_id=comma_dongle_id, public_key=public_key, register_token=register_token) + if resp.status_code in (409, 412): + timeout = time.monotonic() - start_time # Don't retry if the public key is already in use + key_in_use = "Public key is already in use, is your key unique? Contact your vendor for a new key." + unsafe_key = "Public key is known to not be unique and it's unsafe. Contact your vendor for a new key." + error_message = key_in_use if resp.status_code == 409 else unsafe_key + raise Exception(error_message) + if resp.status_code != 200: - raise Exception(f"Failed to register with sunnylink. Status code: {resp.status_code}") - else: - dongleauth = json.loads(resp.text) - sunnylink_dongle_id = dongleauth["device_id"] - if sunnylink_dongle_id: - self._status_update("Device registered successfully.") - break + raise Exception(f"Failed to register with sunnylink. Status code: {resp.status_code}\nData\n:{resp.text}") + + dongleauth = json.loads(resp.text) + sunnylink_dongle_id = dongleauth["device_id"] + if sunnylink_dongle_id: + self._status_update("Device registered successfully.") + successful_registration = True + break except Exception as e: if verbose: self._status_update(f"Waiting {backoff}s before retry, Exception occurred during registration: [{str(e)}]") + + with open('/data/community/crashes/error.txt', 'a') as f: + f.write(f"{datetime.now()}: {str(e)}\n") + backoff = min(backoff * 2, 60) time.sleep(backoff) @@ -123,5 +136,8 @@ class SunnylinkApi(BaseApi): self.params.put("SunnylinkDongleId", sunnylink_dongle_id or UNREGISTERED_SUNNYLINK_DONGLE_ID) + # Set the last ping time to the current time since we were just talking to the API + last_ping = int(time.monotonic() * 1e9) if successful_registration else start_time + Params().put("LastSunnylinkPingTime", str(last_ping)) self.spinner = None return sunnylink_dongle_id diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 9463843ab2..0e6409bbb5 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -158,23 +158,22 @@ void Sidebar::updateState(const UIState &s) { auto sl_dongle_id = getSunnylinkDongleId(); auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime"); auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str); - auto current_nanos = nanos_since_boot(); - auto elapsed_sunnylink_ping = current_nanos - last_sunnylink_ping; + auto elapsed_sunnylink_ping = nanos_since_boot() - last_sunnylink_ping; auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); - if (!sunnylink_enabled) { - sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("DISABLED")}, disabled_color}; - } else if(!sl_dongle_id.has_value()) { - sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("REGIST...")}, progress_color}; - } else if (last_sunnylink_ping == 0) { - sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("OFFLINE")}, warning_color}; - } else { - if (elapsed_sunnylink_ping < 80000000000ULL) { - sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ONLINE")}, good_color}; - } - else { - sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), tr("ERROR")}, danger_color}; - } + + QString status = tr("DISABLED"); + QColor color = disabled_color; + + if (sunnylink_enabled && last_sunnylink_ping == 0) { + // If sunnylink is enabled, but we don't have a dongle id, and we haven't received a ping yet, we are registering + status = sl_dongle_id.has_value() ? tr("OFFLINE") : tr("REGISTERING"); + color = sl_dongle_id.has_value() ? warning_color : progress_color; + } else if (sunnylink_enabled) { + // If sunnylink is enabled, we are considered online if we have received a ping in the last 80 seconds, else error. + status = elapsed_sunnylink_ping < 80000000000ULL ? tr("ONLINE") : tr("ERROR"); + color = elapsed_sunnylink_ping < 80000000000ULL ? good_color : danger_color; } + sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), status}, color }; setProperty("sunnylinkStatus", QVariant::fromValue(sunnylinkStatus)); } diff --git a/system/manager/sunnylink.py b/system/manager/sunnylink.py index 1a70532e82..84161fb817 100755 --- a/system/manager/sunnylink.py +++ b/system/manager/sunnylink.py @@ -50,10 +50,6 @@ def register_sunnylink(): sunnylink_id = SunnylinkApi(None).register_device(None, **extra_args) print(f"SunnyLinkId: {sunnylink_id}") - # Set the last ping time to the current time since we just registered - last_ping = int(time.monotonic() * 1e9) - Params().put("LastSunnylinkPingTime", str(last_ping)) - def main(): """The main method is expected to be called by the manager when the device boots up.""" From 696497c6e4f145abfd57cb0be2e979fd23805cfb Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 22 Jun 2024 14:45:04 +0200 Subject: [PATCH 302/575] Update sunnylink error logging and status display Updated the error logging in sunnylink.py to include the module name for clarity. In addition, adjusted the status display in sidebar.cc to shorten the "REGISTERING" status text for a cleaner user interface. --- common/api/sunnylink.py | 2 +- selfdrive/ui/qt/sidebar.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index 76cc6f21d7..d7a83c1530 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -124,7 +124,7 @@ class SunnylinkApi(BaseApi): self._status_update(f"Waiting {backoff}s before retry, Exception occurred during registration: [{str(e)}]") with open('/data/community/crashes/error.txt', 'a') as f: - f.write(f"{datetime.now()}: {str(e)}\n") + f.write(f"[{datetime.now()}] sunnylink: {str(e)}\n") backoff = min(backoff * 2, 60) time.sleep(backoff) diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 0e6409bbb5..8d45e62ce4 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -166,7 +166,7 @@ void Sidebar::updateState(const UIState &s) { if (sunnylink_enabled && last_sunnylink_ping == 0) { // If sunnylink is enabled, but we don't have a dongle id, and we haven't received a ping yet, we are registering - status = sl_dongle_id.has_value() ? tr("OFFLINE") : tr("REGISTERING"); + status = sl_dongle_id.has_value() ? tr("OFFLINE") : tr("REGIST..."); color = sl_dongle_id.has_value() ? warning_color : progress_color; } else if (sunnylink_enabled) { // If sunnylink is enabled, we are considered online if we have received a ping in the last 80 seconds, else error. From 5c351a994f0e1e482b3da97ebf3653f77a0da851 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 22 Jun 2024 14:47:32 +0200 Subject: [PATCH 303/575] Disable Sunnylink if registration is unsuccessful This code update sets Sunnylink to be disabled in the event that the registration does not go through successfully. This is to ensure that resources aren't wasted trying to access unregistered Sunnylink hardware. --- common/api/sunnylink.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index d7a83c1530..335965995f 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -139,5 +139,10 @@ class SunnylinkApi(BaseApi): # Set the last ping time to the current time since we were just talking to the API last_ping = int(time.monotonic() * 1e9) if successful_registration else start_time Params().put("LastSunnylinkPingTime", str(last_ping)) + + # Disable sunnylink if registration was not successful + if not successful_registration: + Params().put_bool("SunnylinkEnabled", False) + self.spinner = None return sunnylink_dongle_id From fcc61ac46e4490d818582bdd1083c811c5b6a655 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 22 Jun 2024 20:00:46 +0200 Subject: [PATCH 304/575] Reset Sunnylink ping time during registration In the Sunnylink API, a line was added to reset the last ping time to zero whenever we are trying to register. The purpose of this change is to ensure that registration attempts start with a clean slate, potentially improving the reliability of the registration process. --- common/api/sunnylink.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index 335965995f..4318a27c9a 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -88,6 +88,7 @@ class SunnylinkApi(BaseApi): sunnylink_dongle_id = UNREGISTERED_SUNNYLINK_DONGLE_ID self._status_update("Public key not found, setting dongle ID to unregistered.") else: + Params().put("LastSunnylinkPingTime", "0") # Reset the last ping time to 0 if we are trying to register with pubkey_path.open() as f1, privkey_path.open() as f2: public_key = f1.read() private_key = f2.read() From 3e0edf6faa0d0e76fc4869231d5500eca2534695 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 24 Jun 2024 17:56:55 -0400 Subject: [PATCH 305/575] MADS: Fix Disengage on Accelerator Pedal not disengaging properly --- selfdrive/car/chrysler/interface.py | 2 +- selfdrive/car/ford/interface.py | 2 +- selfdrive/car/gm/interface.py | 2 +- selfdrive/car/honda/interface.py | 2 +- selfdrive/car/hyundai/interface.py | 2 +- selfdrive/car/mazda/interface.py | 2 +- selfdrive/car/nissan/interface.py | 2 +- selfdrive/car/subaru/interface.py | 2 +- selfdrive/car/volkswagen/interface.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 7b221595c9..d09fa7ac56 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -129,7 +129,7 @@ class CarInterface(CarInterfaceBase): 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) - ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed: if ret.gasPressed and not ret.cruiseState.enabled: diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index cfdd86ecf0..0c3449dc14 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -105,7 +105,7 @@ class CarInterface(CarInterfaceBase): 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) - ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed: if ret.gasPressed and not ret.cruiseState.enabled: diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index b6ce9d53c0..7467517b78 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -238,7 +238,7 @@ class CarInterface(CarInterfaceBase): 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) - ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed: if ret.gasPressed and not ret.cruiseState.enabled: diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 47ae41c234..1aba4d3119 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -293,7 +293,7 @@ class CarInterface(CarInterfaceBase): 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) - ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed: if ret.gasPressed and not ret.cruiseState.enabled: diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index c10b358f44..e801b47771 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -238,7 +238,7 @@ class CarInterface(CarInterfaceBase): 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) - ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=(self.CS.cruise_buttons[-1] == 3)) diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 8d49218b02..bb9a3f93a0 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -70,7 +70,7 @@ class CarInterface(CarInterfaceBase): 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) - ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed: if ret.gasPressed and not ret.cruiseState.enabled: diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 878d8f14c0..3d744a41a0 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -49,7 +49,7 @@ class CarInterface(CarInterfaceBase): if (not ret.cruiseState.enabled and self.CS.out.cruiseState.enabled) or \ self.get_sp_pedal_disengage(ret): 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.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index cedcfdf7b5..744c017a96 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -141,7 +141,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = False if self.get_sp_pedal_disengage(ret): 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.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled ret, self.CS = self.get_sp_common_state(ret, self.CS) diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index b9fcc5c54f..31dea25cbb 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -148,7 +148,7 @@ class CarInterface(CarInterfaceBase): 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) - ret.cruiseState.enabled = False if self.CP.pcmCruise else self.CS.accEnabled + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed: if ret.gasPressed and not ret.cruiseState.enabled: From cb238431625a9a0b67bce4c7006cdce9372f5dd1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 25 Jun 2024 13:42:38 -0400 Subject: [PATCH 306/575] no more deadzone --- selfdrive/car/ford/interface.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 2e90b9bfbb..cbaefd3cb6 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -55,8 +55,6 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kpBP = [0.] ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.] - ret.longitudinalTuning.deadzoneBP = [0., 9.] - ret.longitudinalTuning.deadzoneV = [.0, .20] # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1 found_ecus = [fw.ecu for fw in car_fw] From ecdf0bdecc30b2f0a5ea254637186c18fe814987 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 27 Jun 2024 00:19:41 -0400 Subject: [PATCH 307/575] confirmed to work --- selfdrive/ui/qt/onroad/annotated_camera.cc | 28 +++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 5eafe74aa6..2e0bc73469 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -84,13 +84,23 @@ void AnnotatedCameraWidget::updateButtonsLayout(bool is_rhd) { buttons_layout->setContentsMargins(0, 0, 10, rn_offset != 0 ? rn_offset + 10 : 20); - buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); - buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | (is_rhd ? Qt::AlignRight : Qt::AlignLeft)); + if (is_rhd) { + buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); + buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); - buttons_layout->addStretch(1); + buttons_layout->addStretch(1); - buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); - buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | (is_rhd ? Qt::AlignLeft : Qt::AlignRight)); + buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); + buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); + } else { + buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); + buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); + + buttons_layout->addStretch(1); + + buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); + buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); // Add spacing to the right + } } void AnnotatedCameraWidget::updateState(const UIState &s) { @@ -205,13 +215,13 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { // hide map settings button for alerts and flip for right hand DM if (map_settings_btn->isEnabled()) { map_settings_btn->setVisible(!hideBottomIcons); - main_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); + buttons_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); } // hide onroad settings button for alerts and flip for right hand DM if (onroad_settings_btn->isEnabled()) { onroad_settings_btn->setVisible(!hideBottomIcons); - main_layout->setAlignment(onroad_settings_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignBottom); + buttons_layout->setAlignment(onroad_settings_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignBottom); } const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); @@ -543,7 +553,9 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { } if (!hideBottomIcons && featureStatusToggle) { - drawFeatureStatusText(p, UI_BORDER_SIZE * 2 + 370, rect().bottom() - 160 - rn_offset); + int x = UI_BORDER_SIZE * 2 + (rightHandDM ? 600 : 370); + int feature_status_text_x = rightHandDM ? rect().right() - x : x; + drawFeatureStatusText(p, feature_status_text_x, rect().bottom() - 160 - rn_offset); } p.restore(); From 0668513d13cd2d71f42995aae411538ba5efcb30 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 29 Jun 2024 01:04:27 +0200 Subject: [PATCH 308/575] Remove unused SunnylinkApi and improve error handling Removed the SunnylinkApi call from the registration process as it was no longer in use. Improved error logging by adding directory checks and more descriptive exception handling in the sunnylink API. --- common/api/sunnylink.py | 10 ++++++++-- system/athena/registration.py | 3 --- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/common/api/sunnylink.py b/common/api/sunnylink.py index 4318a27c9a..80e6cf9b6c 100644 --- a/common/api/sunnylink.py +++ b/common/api/sunnylink.py @@ -13,7 +13,7 @@ from openpilot.common.api.base import BaseApi API_HOST = os.getenv('SUNNYLINK_API_HOST', 'https://stg.api.sunnypilot.ai') UNREGISTERED_SUNNYLINK_DONGLE_ID = "UnregisteredDevice" MAX_RETRIES = 6 - +CRASH_LOG_DIR = '/data/community/crashes' class SunnylinkApi(BaseApi): def __init__(self, dongle_id): @@ -104,6 +104,9 @@ class SunnylinkApi(BaseApi): resp = self.api_get("v2/pilotauth/", method='POST', timeout=15, imei=imei1, imei2=imei2, serial=serial, comma_dongle_id=comma_dongle_id, public_key=public_key, register_token=register_token) + if resp is None: + raise Exception("Unable to register device, request was None") + if resp.status_code in (409, 412): timeout = time.monotonic() - start_time # Don't retry if the public key is already in use key_in_use = "Public key is already in use, is your key unique? Contact your vendor for a new key." @@ -124,7 +127,10 @@ class SunnylinkApi(BaseApi): if verbose: self._status_update(f"Waiting {backoff}s before retry, Exception occurred during registration: [{str(e)}]") - with open('/data/community/crashes/error.txt', 'a') as f: + if not os.path.exists(CRASH_LOG_DIR): + os.makedirs(CRASH_LOG_DIR) + + with open(f'{CRASH_LOG_DIR}/error.txt', 'a') as f: f.write(f"[{datetime.now()}] sunnylink: {str(e)}\n") backoff = min(backoff * 2, 60) diff --git a/system/athena/registration.py b/system/athena/registration.py index b28873a68e..97289e4199 100755 --- a/system/athena/registration.py +++ b/system/athena/registration.py @@ -6,7 +6,6 @@ from pathlib import Path from datetime import datetime, timedelta, UTC from openpilot.common.api import api_get -from openpilot.common.api.sunnylink import SunnylinkApi from openpilot.common.params import Params from openpilot.common.spinner import Spinner from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert @@ -87,8 +86,6 @@ def register(show_spinner=False) -> str | None: if time.monotonic() - start_time > 60 and show_spinner: spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1}, {imei2})") - SunnylinkApi(dongle_id).register_device(spinner if show_spinner else None) - if show_spinner: spinner.close() From 33de3dfae364f05bc788c5eb09b71a71d67128c2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 29 Jun 2024 10:44:33 -0400 Subject: [PATCH 309/575] OP long on keep alive --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index b55b59bb76..e469e3d663 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -91,7 +91,7 @@ class CarInterface(CarInterfaceBase): # *** longitudinal control *** if candidate in CANFD_CAR: - ret.experimentalLongitudinalAvailable = candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR) + ret.experimentalLongitudinalAvailable = candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | NON_SCC_CAR) if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC and not hda2: ret.spFlags |= HyundaiFlagsSP.SP_CAMERA_SCC_LEAD.value else: From 91b39629fec152da47600dbf272b004b12275d3b Mon Sep 17 00:00:00 2001 From: Brian Brown Date: Sat, 29 Jun 2024 14:14:44 +0000 Subject: [PATCH 310/575] add dongle id for taco testing --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index e469e3d663..0732058329 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -117,7 +117,7 @@ class CarInterface(CarInterfaceBase): if 0x1fa in fingerprint[CAN.ECAN]: ret.spFlags |= HyundaiFlagsSP.SP_NAV_MSG.value - if Params().get("DongleId", encoding='utf8') in ("012c95f06918eca4", "68d6a96e703c00c9"): + if Params().get("DongleId", encoding='utf8') in ("012c95f06918eca4", "68d6a96e703c00c9", "11c1f1909ca37bca"): ret.spFlags |= HyundaiFlagsSP.SP_UPSTREAM_TACO.value else: ret.enableBsm = 0x58b in fingerprint[0] From 342effe5dd918770a25ef0e6a1d7c89d2daa4fc6 Mon Sep 17 00:00:00 2001 From: rav4kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sat, 15 Jun 2024 00:17:52 -0700 Subject: [PATCH 311/575] toyota low speed override --- common/params.cc | 2 ++ selfdrive/controls/controlsd.py | 6 ++++- selfdrive/controls/lib/drive_helpers.py | 30 ++++++++++++++++++++++--- system/manager/manager.py | 2 ++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/common/params.cc b/common/params.cc index 73906d35f4..4a450f2fad 100644 --- a/common/params.cc +++ b/common/params.cc @@ -317,6 +317,8 @@ std::unordered_map keys = { {"TorqueFriction", PERSISTENT | BACKUP}, {"TorqueMaxLatAccel", PERSISTENT | BACKUP}, {"TorquedOverride", PERSISTENT | BACKUP}, + {"ToyotaCruiseOverride", PERSISTENT | BACKUP}, + {"ToyotaCruiseOverrideSpeed", PERSISTENT | BACKUP}, {"ToyotaSnG", PERSISTENT | BACKUP}, {"ToyotaTSS2Long", PERSISTENT | BACKUP}, {"TrueVEgoUi", PERSISTENT | BACKUP}, diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 21bf73cf9a..5a98bcf50f 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -181,6 +181,8 @@ class Controls: self.enable_mads = self.params.get_bool("EnableMads") self.mads_disengage_lateral_on_brake = self.params.get_bool("DisengageLateralOnBrake") self.mads_ndlob = self.enable_mads and not self.mads_disengage_lateral_on_brake + self.toyota_cruise_override = self.params.get_bool("ToyotaCruiseOverride") + self.toyota_cruise_override_speed = int(self.params.get("ToyotaCruiseOverrideSpeed", encoding="utf-8")) self.process_not_running = False self.custom_model, self.model_gen = get_model_generation(self.params) @@ -488,7 +490,9 @@ class Controls: def state_transition(self, CS): """Compute conditional state transitions and execute actions on state transitions""" - self.v_cruise_helper.update_v_cruise(CS, self.enabled_long, self.is_metric, self.reverse_acc_change, self.sm['longitudinalPlanSP']) + #sp - toyota low speed override + sp_override_speed = self.toyota_cruise_override_speed if self.toyota_cruise_override else False + self.v_cruise_helper.update_v_cruise(CS, self.enabled_long, self.is_metric, self.reverse_acc_change, sp_override_speed, self.sm['longitudinalPlanSP']) # decrement the soft disable timer at every step, as it's reset on # entrance in SOFT_DISABLING state diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 7ea0b17834..05e6777eb2 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -84,11 +84,16 @@ class VCruiseHelper: self.slc_state_prev = SpeedLimitControlState.inactive self.slc_speed_limit_offsetted = 0 + #sp: toyota low speed override + self.sp_override_v_cruise_kph = V_CRUISE_UNSET + self.sp_override_cruise_speed_last = V_CRUISE_UNSET + self.sp_override_enabled_last = False + @property def v_cruise_initialized(self): return self.v_cruise_kph != V_CRUISE_UNSET - def update_v_cruise(self, CS, enabled, is_metric, reverse_acc, long_plan_sp): + def update_v_cruise(self, CS, enabled, is_metric, reverse_acc, sp_override_speed, long_plan_sp): self.v_cruise_kph_last = self.v_cruise_kph self.slc_state = long_plan_sp.speedLimitControlState @@ -103,9 +108,28 @@ class VCruiseHelper: self.v_cruise_cluster_kph = self.v_cruise_kph self.update_button_timers(CS, enabled) else: - self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH - self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH + if enabled and sp_override_speed and CS.cruiseState.speed * CV.MS_TO_KPH < sp_override_speed: + if self.sp_override_v_cruise_kph == V_CRUISE_UNSET: + self.sp_override_v_cruise_kph = max(CS.vEgo * CV.MS_TO_KPH, V_CRUISE_MIN) + else: + self.sp_override_v_cruise_kph = V_CRUISE_UNSET + + # when we have an override_speed, use it + if self.sp_override_v_cruise_kph != V_CRUISE_UNSET: + self.v_cruise_kph = self.sp_override_v_cruise_kph + self.v_cruise_cluster_kph = self.sp_override_v_cruise_kph + else: + self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH + self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH + + #print("sp_override_v_cruise_kph:", self.sp_override_v_cruise_kph) + #print("v_cruise_kph:", self.v_cruise_kph) + #print("v_cruise_cluster_kph:", self.v_cruise_cluster_kph) + + self.sp_override_cruise_speed_last = CS.cruiseState.speed + self.sp_override_enabled_last = enabled else: + self.sp_override_v_cruise_kph = V_CRUISE_UNSET self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET diff --git a/system/manager/manager.py b/system/manager/manager.py index f3a5b1b366..6ebf6d183a 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -111,6 +111,8 @@ def manager_init() -> None: ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), + ("ToyotaCruiseOverride", "0"), + ("ToyotaCruiseOverrideSpeed", "30"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat().encode('utf8'))) From 972951de3efc6caaab152f77f2a058fb04b38c90 Mon Sep 17 00:00:00 2001 From: rav4kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:52:15 -0700 Subject: [PATCH 312/575] accel controller --- common/params.cc | 2 +- .../controls/lib/longitudinal_planner.py | 21 ++++- .../lib/sunnypilot/accel_controller.py | 76 +++++++++++++++++++ .../dynamic_experimental_controller.py | 0 selfdrive/ui/qt/offroad/settings.cc | 8 ++ system/manager/manager.py | 1 + 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 selfdrive/controls/lib/sunnypilot/accel_controller.py rename selfdrive/controls/lib/{ => sunnypilot}/dynamic_experimental_controller.py (100%) diff --git a/common/params.cc b/common/params.cc index 73906d35f4..473ea89a77 100644 --- a/common/params.cc +++ b/common/params.cc @@ -209,7 +209,7 @@ std::unordered_map keys = { {"UpdaterTargetBranch", CLEAR_ON_MANAGER_START}, {"UpdaterLastFetchTime", PERSISTENT}, {"Version", PERSISTENT}, - + {"AccelProfile", PERSISTENT | BACKUP}, {"AccMadsCombo", PERSISTENT | BACKUP}, {"AmapKey1", PERSISTENT | BACKUP}, {"AmapKey2", PERSISTENT | BACKUP}, diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 9f508fb76b..6b7c56d82e 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -19,7 +19,8 @@ from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDX from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N, get_speed_error from openpilot.selfdrive.controls.lib.vision_turn_controller import VisionTurnController from openpilot.selfdrive.controls.lib.turn_speed_controller import TurnSpeedController -from openpilot.selfdrive.controls.lib.dynamic_experimental_controller import DynamicExperimentalController +from openpilot.selfdrive.controls.lib.sunnypilot.dynamic_experimental_controller import DynamicExperimentalController +from openpilot.selfdrive.controls.lib.sunnypilot.accel_controller import AccelController from openpilot.selfdrive.controls.lib.events import Events from openpilot.common.swaglog import cloudlog @@ -100,6 +101,7 @@ class LongitudinalPlanner: self.events = Events() self.turn_speed_controller = TurnSpeedController() self.dynamic_experimental_controller = DynamicExperimentalController() + self.accel_controller = AccelController() def read_param(self): try: @@ -127,6 +129,7 @@ class LongitudinalPlanner: if self.param_read_counter % 50 == 0: self.read_param() self.param_read_counter += 1 + self.accel_controller.set_profile(self.params.get("AccelProfile", encoding='utf-8')) if self.dynamic_experimental_controller.is_enabled() and sm['controlsState'].experimentalMode: self.mpc.mode = self.dynamic_experimental_controller.get_mpc_mode(self.CP.radarUnavailable, sm['carState'], sm['radarState'].leadOne, sm['modelV2'], sm['controlsState'], sm['navInstruction'].maneuverDistance) else: @@ -152,6 +155,22 @@ class LongitudinalPlanner: accel_limits = [ACCEL_MIN, ACCEL_MAX] accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] + # override accel using Accel controller + if self.accel_controller.is_enabled(): + # get min, max from accel controller + min_limit, max_limit = self.accel_controller.get_accel_limits(v_ego, accel_limits) + if self.mpc.mode == 'acc': + # voacc car, just give it max min (-1.2) so I can brake harder + if self.CP.radarUnavailable: + accel_limits = [A_CRUISE_MIN, max_limit] + else: + accel_limits = [min_limit, max_limit] + # recalculate limit turn according to the new min, max + accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP) + else: + # blended, just give it max min (-3.5) and max from accel controller + accel_limits = accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] + if reset_state: self.v_desired_filter.x = v_ego # Clip aEgo to cruise limits to prevent large accelerations when becoming active diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py new file mode 100644 index 0000000000..e6911b4a6c --- /dev/null +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# The MIT License +# +# Copyright (c) 2019-, Rick Lan, dragonpilot community, and a number of other of contributors. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Last update: June 5, 2024 + +from openpilot.common.numpy_fast import interp + +DP_ACCEL_STOCK = 0 +DP_ACCEL_ECO = 1 +DP_ACCEL_NORMAL = 2 +DP_ACCEL_SPORT = 3 + +# accel profile by @arne182 modified by cgw +_DP_CRUISE_MIN_V = [-1.00, -1.00, -0.99, -0.90, -0.90, -0.88, -0.88, -0.82] +_DP_CRUISE_MIN_V_ECO = [-1.00, -1.00, -0.98, -0.88, -0.88, -0.86, -0.86, -0.80] +_DP_CRUISE_MIN_V_SPORT = [-1.01, -1.01, -1.00, -0.92, -0.92, -0.90, -0.90, -0.84] +_DP_CRUISE_MIN_BP = [0., 0.05, 0.4, 0.5, 8.33, 16., 30., 40.] + +_DP_CRUISE_MAX_V = [2.4, 2.4, 2.4, 1.60, 1.05, .81, .625, .42, .348, .12] +_DP_CRUISE_MAX_V_ECO = [1.6, 1.6, 1.6, 1.0, .60, .50, .40, .25, .15, .05] +_DP_CRUISE_MAX_V_SPORT = [3.5, 3.5, 2.8, 2.4, 1.4, 1.0, .89, .75, .50, .2] +_DP_CRUISE_MAX_BP = [0., 1., 6., 8., 11., 15., 20., 25., 30., 55.] + + +class AccelController: + + def __init__(self): + # self._params = Params() + self._profile = DP_ACCEL_STOCK + + def set_profile(self, profile): + try: + self._profile = int(profile) if int(profile) in [DP_ACCEL_STOCK, DP_ACCEL_ECO, DP_ACCEL_NORMAL, DP_ACCEL_SPORT] else DP_ACCEL_STOCK + except: + self._profile = DP_ACCEL_STOCK + + def _dp_calc_cruise_accel_limits(self, v_ego): + if self._profile == DP_ACCEL_ECO: + min_v = _DP_CRUISE_MIN_V_ECO + max_v = _DP_CRUISE_MAX_V_ECO + elif self._profile == DP_ACCEL_SPORT: + min_v = _DP_CRUISE_MIN_V_SPORT + max_v = _DP_CRUISE_MAX_V_SPORT + else: + min_v = _DP_CRUISE_MIN_V + max_v = _DP_CRUISE_MAX_V + + a_cruise_min = interp(v_ego, _DP_CRUISE_MIN_BP, min_v) + a_cruise_max = interp(v_ego, _DP_CRUISE_MAX_BP, max_v) + return a_cruise_min, a_cruise_max + + def get_accel_limits(self, v_ego, accel_limits): + return accel_limits if self._profile == DP_ACCEL_STOCK else self._dp_calc_cruise_accel_limits(v_ego) + + def is_enabled(self): + return self._profile != DP_ACCEL_STOCK diff --git a/selfdrive/controls/lib/dynamic_experimental_controller.py b/selfdrive/controls/lib/sunnypilot/dynamic_experimental_controller.py similarity index 100% rename from selfdrive/controls/lib/dynamic_experimental_controller.py rename to selfdrive/controls/lib/sunnypilot/dynamic_experimental_controller.py diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 4c9a2e9085..4fd4b77aa1 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -118,6 +118,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { 380); long_personality_setting->showDescription(); + // accel controller + std::vector accel_profile_texts{tr("OP"), tr("ECO"), tr("NOR"), tr("SPT")}; + ButtonParamControl* accel_profile_setting = new ButtonParamControl("AccelProfile", tr("Acceleration Profile"), + tr("OP - Stock tune.\nECO - Eco tune.\nNOR - Normal tune.\nSPT - Sport tune."), + "", + accel_profile_texts); + // set up uiState update for personality setting QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState); @@ -133,6 +140,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // insert longitudinal personality after NDOG toggle if (param == "DisengageOnAccelerator") { addItem(long_personality_setting); + addItem(accel_profile_setting); } } diff --git a/system/manager/manager.py b/system/manager/manager.py index f3a5b1b366..d431170b8d 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -111,6 +111,7 @@ def manager_init() -> None: ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), + ("AccelProfile", "0"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat().encode('utf8'))) From f809fcf0aa0c73f0ab5907c261a9934dbc42e412 Mon Sep 17 00:00:00 2001 From: rav4kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:47:22 -0700 Subject: [PATCH 313/575] dynamic personality --- common/params.cc | 1 + .../lib/longitudinal_mpc_lib/long_mpc.py | 26 ++++++++++++++++--- .../controls/lib/longitudinal_planner.py | 5 +++- selfdrive/ui/qt/offroad/settings.cc | 8 +++++- selfdrive/ui/ui.h | 2 +- system/manager/manager.py | 1 + 6 files changed, 36 insertions(+), 7 deletions(-) diff --git a/common/params.cc b/common/params.cc index 73906d35f4..552153fae1 100644 --- a/common/params.cc +++ b/common/params.cc @@ -243,6 +243,7 @@ std::unordered_map keys = { {"DrivingModelUrl", PERSISTENT}, {"DynamicExperimentalControl", PERSISTENT | BACKUP}, {"DynamicLaneProfile", PERSISTENT | BACKUP}, + {"DynamicPersonality", PERSISTENT | BACKUP}, {"EnableAmap", PERSISTENT | BACKUP}, {"EnableGmap", PERSISTENT | BACKUP}, {"EnableMads", PERSISTENT | BACKUP}, diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index cba398bcca..97a69b2a2a 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -63,9 +63,9 @@ def get_jerk_factor(personality=custom.LongitudinalPersonalitySP.standard): elif personality==custom.LongitudinalPersonalitySP.standard: return 1.0 elif personality==custom.LongitudinalPersonalitySP.moderate: - return 0.5 + return 0.75 elif personality==custom.LongitudinalPersonalitySP.aggressive: - return 0.222 + return 0.5 else: raise NotImplementedError("Longitudinal personality not supported") @@ -82,6 +82,24 @@ def get_T_FOLLOW(personality=custom.LongitudinalPersonalitySP.standard): else: raise NotImplementedError("Longitudinal personality not supported") +def get_dynamic_personality(v_ego, personality=custom.LongitudinalPersonalitySP.standard): + if personality==custom.LongitudinalPersonalitySP.relaxed: + x_vel = [0, 11, 14.5, 15, 20, 20.01, 25, 25.01, 36, 36.01] + y_dist = [1.5, 1.5, 1.5, 1.6, 1.76, 1.76, 1.78, 1.78, 1.8, 1.8] + elif personality==custom.LongitudinalPersonalitySP.standard: + x_vel = [0, 11, 14.5, 15, 20, 20.01, 25, 25.01, 36, 36.01] + y_dist = [1.40, 1.40, 1.40, 1.50, 1.60, 1.76, 1.76, 1.78, 1.8, 1.8] + elif personality==custom.LongitudinalPersonalitySP.moderate: + x_vel = [0, 11, 14.5, 15, 20, 20.01, 25, 25.01, 36, 36.01] + y_dist = [1.3, 1.3, 1.3, 1.35, 1.35, 1.385, 1.385, 1.4, 1.4, 1.45] + elif personality==custom.LongitudinalPersonalitySP.aggressive: + x_vel = [0, 5, 5.01, 11, 14.5, 15, 20, 20.01, 25, 25.01, 36, 36.01] + y_dist = [1.25, 1.25, 1.12, 1.12, 1.12, 1.105, 1.105, 1.15, 1.15, 1.18, 1.20, 1.23] + else: + raise NotImplementedError("Dynamic Follow personality not supported") + return np.interp(v_ego, x_vel, y_dist) + + def get_stopped_equivalence_factor(v_lead): return (v_lead**2) / (2 * COMFORT_BRAKE) @@ -338,9 +356,9 @@ class LongitudinalMpc: self.cruise_min_a = min_a self.max_a = max_a - def update(self, radarstate, v_cruise, x, v, a, j, personality=custom.LongitudinalPersonalitySP.standard): - t_follow = get_T_FOLLOW(personality) + def update(self, radarstate, v_cruise, x, v, a, j, personality=custom.LongitudinalPersonalitySP.standard, dynamic_personality=False): v_ego = self.x0[1] + t_follow = get_T_FOLLOW(personality) if not dynamic_personality else get_dynamic_personality(v_ego, personality) self.status = radarstate.leadOne.status or radarstate.leadTwo.status lead_xv_0 = self.process_lead(radarstate.leadOne) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 9f508fb76b..f91e71797c 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -101,11 +101,14 @@ class LongitudinalPlanner: self.turn_speed_controller = TurnSpeedController() self.dynamic_experimental_controller = DynamicExperimentalController() + self.dynamic_personality = False + def read_param(self): try: self.dynamic_experimental_controller.set_enabled(self.params.get_bool("DynamicExperimentalControl")) except AttributeError: self.dynamic_experimental_controller = DynamicExperimentalController() + self.dynamic_personality = self.params.get_bool("DynamicPersonality") @staticmethod def parse_model(model_msg, model_error): @@ -178,7 +181,7 @@ class LongitudinalPlanner: self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsState'].personality) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsState'].personality, dynamic_personality=self.dynamic_personality) self.v_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.a_solution) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 4c9a2e9085..bdf4d9aa13 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -55,6 +55,12 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal."), "../assets/offroad/icon_blank.png", }, + { + "DynamicPersonality", + tr("Enable Dynamic Personality"), + tr("Enable toggle to allow sunnypilot will dynamically adjust following distance based on your \"Driving Personality\" setting."), + "../assets/offroad/icon_blank.png", + }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), @@ -108,7 +114,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }; - std::vector longi_button_texts{tr("Maniac"), tr("Aggressive"), tr("Stock"), tr("Relaxed")}; + std::vector longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")}; long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), tr("Stock is recommended. In aggressive/maniac mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index ed9996f54d..a43887a100 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -27,7 +27,7 @@ const int UI_ROAD_NAME_MARGIN_X = 14; struct FeatureStatusText { const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto" }; - const QStringList gac_list_text = { "Maniac", "Aggressive", "Standard", "Relaxed" }; + const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed" }; const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active" }; }; diff --git a/system/manager/manager.py b/system/manager/manager.py index f3a5b1b366..1b8812a619 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -64,6 +64,7 @@ def manager_init() -> None: ("DisengageLateralOnBrake", "0"), ("DrivingModelGeneration", "0"), ("DynamicLaneProfile", "1"), + ("DynamicFollow", "0"), ("EnableMads", "1"), ("EnhancedScc", "0"), ("FeatureStatus", "1"), From c27efd01d630dce94a5e675d60a5e70dfa93905d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 09:37:29 -0400 Subject: [PATCH 314/575] Hyundai: Car Port for Kona Electric Non-SCC 2019 --- CHANGELOGS.md | 1 + selfdrive/car/hyundai/fingerprints.py | 11 +++++++++++ selfdrive/car/hyundai/interface.py | 3 ++- selfdrive/car/hyundai/values.py | 6 ++++++ selfdrive/car/sunnypilot_carname.json | 1 + selfdrive/car/torque_data/substitute.toml | 1 + 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 005afc9e8d..cf577cf592 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -4,6 +4,7 @@ sunnypilot - 0.9.8.0 (2024-xx-xx) ************************ * UPDATED: Synced with commaai's openpilot * master commit b45caf4 (June 14, 2024) +* Hyundai Kona Electric Non-SCC 2019 support thanks to NikitaNekrasov! * Kia Ceed Plug-in Hybrid Non-SCC 2022 support thanks to TerminatorNL! sunnypilot - 0.9.7.1 (2024-06-13) diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index b426ec9fb0..ceaf04f0e7 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -1195,6 +1195,17 @@ FW_VERSIONS = { b'\xf1\x006T6J0_C2\x00\x006T6K1051\x00\x00TOS4N20NS2\x00\x00\x00\x00', ], }, + CAR.HYUNDAI_KONA_EV_NON_SCC: { + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00OS MDPS C 1.00 1.04 56310K4000\x00 4OEDC104', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00OSE LKAS AT USA LHD 1.00 1.00 95740-K4100 W40', + ], + }, CAR.KIA_CEED_PHEV_2022_NON_SCC: { (Ecu.eps, 0x7D4, None): [ b'\xf1\x00CD MDPS C 1.00 1.01 56310-XX000 4CPHC101', diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index e801b47771..2b3b2dd68f 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -167,7 +167,8 @@ class CarInterface(CarInterfaceBase): elif ret.flags & HyundaiFlags.EV: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS - if candidate in (CAR.HYUNDAI_KONA, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_KONA_HEV, CAR.HYUNDAI_KONA_EV_2022, CAR.HYUNDAI_KONA_NON_SCC): + if candidate in (CAR.HYUNDAI_KONA, CAR.HYUNDAI_KONA_EV, CAR.HYUNDAI_KONA_HEV, CAR.HYUNDAI_KONA_EV_2022, + CAR.HYUNDAI_KONA_NON_SCC, CAR.HYUNDAI_KONA_EV_NON_SCC): ret.flags |= HyundaiFlags.ALT_LIMITS.value ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_ALT_LIMITS diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 075085ee8d..fb18c110ca 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -573,6 +573,12 @@ class CAR(Platforms): HYUNDAI_KONA.specs, spFlags=HyundaiFlagsSP.SP_NON_SCC | HyundaiFlagsSP.SP_NON_SCC_FCA, ) + HYUNDAI_KONA_EV_NON_SCC = HyundaiPlatformConfig( + [HyundaiCarDocs("Hyundai Kona Electric Non-SCC 2019", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_g]))], + HYUNDAI_KONA.specs, + flags=HyundaiFlags.EV, + spFlags=HyundaiFlagsSP.SP_NON_SCC | HyundaiFlagsSP.SP_NON_SCC_FCA, + ) KIA_CEED_PHEV_2022_NON_SCC = HyundaiPlatformConfig( [HyundaiCarDocs("Kia Ceed PHEV Non-SCC 2022", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_i]))], CarSpecs(mass=1650, wheelbase=2.65, steerRatio=13.75, tireStiffnessFactor=0.5), diff --git a/selfdrive/car/sunnypilot_carname.json b/selfdrive/car/sunnypilot_carname.json index 8f6a9660b1..5b8f8cafdb 100644 --- a/selfdrive/car/sunnypilot_carname.json +++ b/selfdrive/car/sunnypilot_carname.json @@ -101,6 +101,7 @@ "Hyundai Kona Electric 2018-21": "HYUNDAI_KONA_EV", "Hyundai Kona Electric 2022-23": "HYUNDAI_KONA_EV_2022", "Hyundai Kona Electric (with HDA II, Korea only) 2023": "HYUNDAI_KONA_EV_2ND_GEN", + "Hyundai Kona Electric Non-SCC 2019": "HYUNDAI_KONA_EV_NON_SCC", "Hyundai Kona Hybrid 2020": "HYUNDAI_KONA_HEV", "Hyundai Kona Non-SCC 2019": "HYUNDAI_KONA_NON_SCC", "Hyundai Palisade 2020-22": "HYUNDAI_PALISADE", diff --git a/selfdrive/car/torque_data/substitute.toml b/selfdrive/car/torque_data/substitute.toml index 921846667b..7fb8ec7f3f 100644 --- a/selfdrive/car/torque_data/substitute.toml +++ b/selfdrive/car/torque_data/substitute.toml @@ -86,6 +86,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "KIA_FORTE_2021_NON_SCC" = "HYUNDAI_SONATA" "KIA_SELTOS_2023_NON_SCC" = "HYUNDAI_SONATA" "HYUNDAI_KONA_NON_SCC" = "HYUNDAI_KONA_EV" +"HYUNDAI_KONA_EV_NON_SCC" = "HYUNDAI_KONA_EV" "HYUNDAI_ELANTRA_2022_NON_SCC" = "HYUNDAI_ELANTRA_2021" "GENESIS_G70_2021_NON_SCC" = "HYUNDAI_SONATA" "KIA_CEED_PHEV_2022_NON_SCC" = "HYUNDAI_SONATA" From 6098dd300a3ef4b3cc797eefd8d6fce38e7f277c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 11:18:47 -0400 Subject: [PATCH 315/575] formatting --- common/params.cc | 1 + .../controls/lib/longitudinal_planner.py | 2 +- .../lib/sunnypilot/accel_controller.py | 53 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/common/params.cc b/common/params.cc index 473ea89a77..49a8cba9b9 100644 --- a/common/params.cc +++ b/common/params.cc @@ -209,6 +209,7 @@ std::unordered_map keys = { {"UpdaterTargetBranch", CLEAR_ON_MANAGER_START}, {"UpdaterLastFetchTime", PERSISTENT}, {"Version", PERSISTENT}, + {"AccelProfile", PERSISTENT | BACKUP}, {"AccMadsCombo", PERSISTENT | BACKUP}, {"AmapKey1", PERSISTENT | BACKUP}, diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 6b7c56d82e..11bf18c363 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -19,8 +19,8 @@ from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDX from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N, get_speed_error from openpilot.selfdrive.controls.lib.vision_turn_controller import VisionTurnController from openpilot.selfdrive.controls.lib.turn_speed_controller import TurnSpeedController -from openpilot.selfdrive.controls.lib.sunnypilot.dynamic_experimental_controller import DynamicExperimentalController from openpilot.selfdrive.controls.lib.sunnypilot.accel_controller import AccelController +from openpilot.selfdrive.controls.lib.sunnypilot.dynamic_experimental_controller import DynamicExperimentalController from openpilot.selfdrive.controls.lib.events import Events from openpilot.common.swaglog import cloudlog diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py index e6911b4a6c..26ad7f6692 100644 --- a/selfdrive/controls/lib/sunnypilot/accel_controller.py +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# Last update: June 5, 2024 +# Last updated: June 5, 2024 from openpilot.common.numpy_fast import interp @@ -43,34 +43,33 @@ _DP_CRUISE_MAX_BP = [0., 1., 6., 8., 11., 15., 20., 25., 30., 55. class AccelController: + def __init__(self): + # self._params = Params() + self._profile = DP_ACCEL_STOCK - def __init__(self): - # self._params = Params() - self._profile = DP_ACCEL_STOCK + def set_profile(self, profile): + try: + self._profile = int(profile) if int(profile) in [DP_ACCEL_STOCK, DP_ACCEL_ECO, DP_ACCEL_NORMAL, DP_ACCEL_SPORT] else DP_ACCEL_STOCK + except: + self._profile = DP_ACCEL_STOCK - def set_profile(self, profile): - try: - self._profile = int(profile) if int(profile) in [DP_ACCEL_STOCK, DP_ACCEL_ECO, DP_ACCEL_NORMAL, DP_ACCEL_SPORT] else DP_ACCEL_STOCK - except: - self._profile = DP_ACCEL_STOCK + def _dp_calc_cruise_accel_limits(self, v_ego): + if self._profile == DP_ACCEL_ECO: + min_v = _DP_CRUISE_MIN_V_ECO + max_v = _DP_CRUISE_MAX_V_ECO + elif self._profile == DP_ACCEL_SPORT: + min_v = _DP_CRUISE_MIN_V_SPORT + max_v = _DP_CRUISE_MAX_V_SPORT + else: + min_v = _DP_CRUISE_MIN_V + max_v = _DP_CRUISE_MAX_V - def _dp_calc_cruise_accel_limits(self, v_ego): - if self._profile == DP_ACCEL_ECO: - min_v = _DP_CRUISE_MIN_V_ECO - max_v = _DP_CRUISE_MAX_V_ECO - elif self._profile == DP_ACCEL_SPORT: - min_v = _DP_CRUISE_MIN_V_SPORT - max_v = _DP_CRUISE_MAX_V_SPORT - else: - min_v = _DP_CRUISE_MIN_V - max_v = _DP_CRUISE_MAX_V + a_cruise_min = interp(v_ego, _DP_CRUISE_MIN_BP, min_v) + a_cruise_max = interp(v_ego, _DP_CRUISE_MAX_BP, max_v) + return a_cruise_min, a_cruise_max - a_cruise_min = interp(v_ego, _DP_CRUISE_MIN_BP, min_v) - a_cruise_max = interp(v_ego, _DP_CRUISE_MAX_BP, max_v) - return a_cruise_min, a_cruise_max + def get_accel_limits(self, v_ego, accel_limits): + return accel_limits if self._profile == DP_ACCEL_STOCK else self._dp_calc_cruise_accel_limits(v_ego) - def get_accel_limits(self, v_ego, accel_limits): - return accel_limits if self._profile == DP_ACCEL_STOCK else self._dp_calc_cruise_accel_limits(v_ego) - - def is_enabled(self): - return self._profile != DP_ACCEL_STOCK + def is_enabled(self): + return self._profile != DP_ACCEL_STOCK From 6e6de6742e5c7071507101ec4589d1243ff8ebda Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 11:51:01 -0400 Subject: [PATCH 316/575] some refactors --- .../controls/lib/longitudinal_planner.py | 6 ++-- .../lib/sunnypilot/accel_controller.py | 33 +++++++++++-------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 11bf18c363..382af48397 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -106,8 +106,10 @@ class LongitudinalPlanner: def read_param(self): try: self.dynamic_experimental_controller.set_enabled(self.params.get_bool("DynamicExperimentalControl")) + self.accel_controller.set_profile(int(self.params.get("AccelProfile"))) except AttributeError: self.dynamic_experimental_controller = DynamicExperimentalController() + self.accel_controller = AccelController() @staticmethod def parse_model(model_msg, model_error): @@ -129,7 +131,6 @@ class LongitudinalPlanner: if self.param_read_counter % 50 == 0: self.read_param() self.param_read_counter += 1 - self.accel_controller.set_profile(self.params.get("AccelProfile", encoding='utf-8')) if self.dynamic_experimental_controller.is_enabled() and sm['controlsState'].experimentalMode: self.mpc.mode = self.dynamic_experimental_controller.get_mpc_mode(self.CP.radarUnavailable, sm['carState'], sm['radarState'].leadOne, sm['modelV2'], sm['controlsState'], sm['navInstruction'].maneuverDistance) else: @@ -169,7 +170,8 @@ class LongitudinalPlanner: accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP) else: # blended, just give it max min (-3.5) and max from accel controller - accel_limits = accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] + accel_limits = [ACCEL_MIN, ACCEL_MAX] + accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] if reset_state: self.v_desired_filter.x = v_ego diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py index 26ad7f6692..7c40529dec 100644 --- a/selfdrive/controls/lib/sunnypilot/accel_controller.py +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -25,11 +25,6 @@ from openpilot.common.numpy_fast import interp -DP_ACCEL_STOCK = 0 -DP_ACCEL_ECO = 1 -DP_ACCEL_NORMAL = 2 -DP_ACCEL_SPORT = 3 - # accel profile by @arne182 modified by cgw _DP_CRUISE_MIN_V = [-1.00, -1.00, -0.99, -0.90, -0.90, -0.88, -0.88, -0.82] _DP_CRUISE_MIN_V_ECO = [-1.00, -1.00, -0.98, -0.88, -0.88, -0.86, -0.86, -0.80] @@ -42,22 +37,32 @@ _DP_CRUISE_MAX_V_SPORT = [3.5, 3.5, 2.8, 2.4, 1.4, 1.0, .89, .75, .50, .2] _DP_CRUISE_MAX_BP = [0., 1., 6., 8., 11., 15., 20., 25., 30., 55.] +class DPAccel: + STOCK = 0 + ECO = 1 + NORMAL = 2 + SPORT = 3 + + @classmethod + def accel_val(cls): + return cls.STOCK, cls.ECO, cls.NORMAL, cls.SPORT + + class AccelController: def __init__(self): - # self._params = Params() - self._profile = DP_ACCEL_STOCK + self._profile = DPAccel.STOCK def set_profile(self, profile): try: - self._profile = int(profile) if int(profile) in [DP_ACCEL_STOCK, DP_ACCEL_ECO, DP_ACCEL_NORMAL, DP_ACCEL_SPORT] else DP_ACCEL_STOCK - except: - self._profile = DP_ACCEL_STOCK + self._profile = profile if profile in DPAccel.accel_val() else DPAccel.STOCK + except (ValueError, TypeError): + self._profile = DPAccel.STOCK def _dp_calc_cruise_accel_limits(self, v_ego): - if self._profile == DP_ACCEL_ECO: + if self._profile == DPAccel.ECO: min_v = _DP_CRUISE_MIN_V_ECO max_v = _DP_CRUISE_MAX_V_ECO - elif self._profile == DP_ACCEL_SPORT: + elif self._profile == DPAccel.SPORT: min_v = _DP_CRUISE_MIN_V_SPORT max_v = _DP_CRUISE_MAX_V_SPORT else: @@ -69,7 +74,7 @@ class AccelController: return a_cruise_min, a_cruise_max def get_accel_limits(self, v_ego, accel_limits): - return accel_limits if self._profile == DP_ACCEL_STOCK else self._dp_calc_cruise_accel_limits(v_ego) + return accel_limits if self._profile == DPAccel.STOCK else self._dp_calc_cruise_accel_limits(v_ego) def is_enabled(self): - return self._profile != DP_ACCEL_STOCK + return self._profile != DPAccel.STOCK From 7d751da1b836e64e795fb59b4bf79e1116ec8e7b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 11:58:11 -0400 Subject: [PATCH 317/575] enforce type hint --- selfdrive/controls/lib/sunnypilot/accel_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py index 7c40529dec..95e5174660 100644 --- a/selfdrive/controls/lib/sunnypilot/accel_controller.py +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -52,13 +52,13 @@ class AccelController: def __init__(self): self._profile = DPAccel.STOCK - def set_profile(self, profile): + def set_profile(self, profile: int): try: self._profile = profile if profile in DPAccel.accel_val() else DPAccel.STOCK except (ValueError, TypeError): self._profile = DPAccel.STOCK - def _dp_calc_cruise_accel_limits(self, v_ego): + def _dp_calc_cruise_accel_limits(self, v_ego: float): if self._profile == DPAccel.ECO: min_v = _DP_CRUISE_MIN_V_ECO max_v = _DP_CRUISE_MAX_V_ECO @@ -71,9 +71,10 @@ class AccelController: a_cruise_min = interp(v_ego, _DP_CRUISE_MIN_BP, min_v) a_cruise_max = interp(v_ego, _DP_CRUISE_MAX_BP, max_v) + return a_cruise_min, a_cruise_max - def get_accel_limits(self, v_ego, accel_limits): + def get_accel_limits(self, v_ego: float, accel_limits: list[float]): return accel_limits if self._profile == DPAccel.STOCK else self._dp_calc_cruise_accel_limits(v_ego) def is_enabled(self): From 13475140ec17d7f2ffa493fe940a3fe462e6af21 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 12:01:06 -0400 Subject: [PATCH 318/575] inline --- selfdrive/controls/lib/longitudinal_planner.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 382af48397..29e21378ce 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -156,16 +156,13 @@ class LongitudinalPlanner: accel_limits = [ACCEL_MIN, ACCEL_MAX] accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] - # override accel using Accel controller + # override accel using Accel Controller if self.accel_controller.is_enabled(): # get min, max from accel controller min_limit, max_limit = self.accel_controller.get_accel_limits(v_ego, accel_limits) if self.mpc.mode == 'acc': - # voacc car, just give it max min (-1.2) so I can brake harder - if self.CP.radarUnavailable: - accel_limits = [A_CRUISE_MIN, max_limit] - else: - accel_limits = [min_limit, max_limit] + # VOACC car, just give it max min (-1.2) so I can brake harder + accel_limits = [A_CRUISE_MIN, max_limit] if self.CP.radarUnavailable else [min_limit, max_limit] # recalculate limit turn according to the new min, max accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP) else: From 7a1533eae05823d5c5bc7d134f4645bde066759c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 12:06:15 -0400 Subject: [PATCH 319/575] ui change --- selfdrive/ui/qt/offroad/settings.cc | 9 +++++---- selfdrive/ui/qt/offroad/settings.h | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 4fd4b77aa1..b39b4fc6d1 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -119,11 +119,12 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { long_personality_setting->showDescription(); // accel controller - std::vector accel_profile_texts{tr("OP"), tr("ECO"), tr("NOR"), tr("SPT")}; - ButtonParamControl* accel_profile_setting = new ButtonParamControl("AccelProfile", tr("Acceleration Profile"), - tr("OP - Stock tune.\nECO - Eco tune.\nNOR - Normal tune.\nSPT - Sport tune."), - "", + std::vector accel_profile_texts{tr("Stock"), tr("Eco"), tr("Normal"), tr("Sport")}; + accel_profile_setting = new ButtonParamControl("AccelProfile", tr("Acceleration Profile"), + tr("Stock - Stock tune.\nEco - Eco tune.\nNormal - Normal tune.\nSport - Sport tune."), + "../assets/offroad/icon_blank.png", accel_profile_texts); + accel_profile_setting->showDescription(); // set up uiState update for personality setting QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 0e550850e8..0a0a7a9431 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -96,6 +96,7 @@ private: Params params; std::map toggles; ButtonParamControl *long_personality_setting; + ButtonParamControl *accel_profile_setting; ParamWatcher *param_watcher; }; From 6afa49be9182c10a32592c6014109a005307c2fe Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 12:31:48 -0400 Subject: [PATCH 320/575] use custom cereal --- cereal/custom.capnp | 8 +++++ selfdrive/controls/controlsd.py | 10 ++++++ .../controls/lib/longitudinal_planner.py | 3 +- .../lib/sunnypilot/accel_controller.py | 33 ++++++------------- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 399679d03c..b557ef659b 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -18,9 +18,17 @@ enum LongitudinalPersonalitySP { relaxed @3; } +enum AccelerationProfile { + stock @0; + eco @1; + normal @2; + sport @3; +} + struct ControlsStateSP @0x81c2f05a394cf4af { lateralState @0 :Text; personality @8 :LongitudinalPersonalitySP; + accelProfile @9 :AccelerationProfile; lateralControlState :union { indiState @1 :LateralINDIState; diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 21bf73cf9a..c4ed4bd71b 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -187,6 +187,8 @@ class Controls: model_capabilities = ModelCapabilities.get_by_gen(self.model_gen) self.model_use_lateral_planner = self.custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution + self.accel_profile = self.read_accel_profile_param() + self.can_log_mono_time = 0 self.startup_event = get_startup_event(car_recognized, not self.CP.passive, len(self.CP.carFw) > 0) @@ -858,6 +860,7 @@ class Controls: controlsStateSP.lateralState = lat_tuning controlsStateSP.personality = self.personality + controlsStateSP.accelProfile = self.accel_profile if self.enable_nnff and lat_tuning == 'torque': controlsStateSP.lateralControlState.torqueState = self.LaC.pid_long_sp @@ -906,11 +909,18 @@ class Controls: except (ValueError, TypeError): return custom.LongitudinalPersonalitySP.standard + def read_accel_profile_param(self): + try: + return int(self.params.get("AccelProfile")) + except (ValueError, TypeError): + return custom.AccelerationProfile.stock + def params_thread(self, evt): while not evt.is_set(): self.is_metric = self.params.get_bool("IsMetric") self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl self.personality = self.read_personality_param() + self.accel_profile = self.read_accel_profile_param() if self.CP.notCar: self.joystick_mode = self.params.get_bool("JoystickDebugMode") diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 29e21378ce..c74f93c006 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -106,7 +106,6 @@ class LongitudinalPlanner: def read_param(self): try: self.dynamic_experimental_controller.set_enabled(self.params.get_bool("DynamicExperimentalControl")) - self.accel_controller.set_profile(int(self.params.get("AccelProfile"))) except AttributeError: self.dynamic_experimental_controller = DynamicExperimentalController() self.accel_controller = AccelController() @@ -157,7 +156,7 @@ class LongitudinalPlanner: accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] # override accel using Accel Controller - if self.accel_controller.is_enabled(): + if self.accel_controller.is_enabled(accel_profile=sm['controlsStateSP'].accelProfile): # get min, max from accel controller min_limit, max_limit = self.accel_controller.get_accel_limits(v_ego, accel_limits) if self.mpc.mode == 'acc': diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py index 95e5174660..ec006d3310 100644 --- a/selfdrive/controls/lib/sunnypilot/accel_controller.py +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -23,8 +23,11 @@ # Last updated: June 5, 2024 +from cereal import custom from openpilot.common.numpy_fast import interp +AccelProfile = custom.AccelerationProfile + # accel profile by @arne182 modified by cgw _DP_CRUISE_MIN_V = [-1.00, -1.00, -0.99, -0.90, -0.90, -0.88, -0.88, -0.82] _DP_CRUISE_MIN_V_ECO = [-1.00, -1.00, -0.98, -0.88, -0.88, -0.86, -0.86, -0.80] @@ -37,32 +40,15 @@ _DP_CRUISE_MAX_V_SPORT = [3.5, 3.5, 2.8, 2.4, 1.4, 1.0, .89, .75, .50, .2] _DP_CRUISE_MAX_BP = [0., 1., 6., 8., 11., 15., 20., 25., 30., 55.] -class DPAccel: - STOCK = 0 - ECO = 1 - NORMAL = 2 - SPORT = 3 - - @classmethod - def accel_val(cls): - return cls.STOCK, cls.ECO, cls.NORMAL, cls.SPORT - - class AccelController: def __init__(self): - self._profile = DPAccel.STOCK - - def set_profile(self, profile: int): - try: - self._profile = profile if profile in DPAccel.accel_val() else DPAccel.STOCK - except (ValueError, TypeError): - self._profile = DPAccel.STOCK + self._profile = AccelProfile.stock def _dp_calc_cruise_accel_limits(self, v_ego: float): - if self._profile == DPAccel.ECO: + if self._profile == AccelProfile.eco: min_v = _DP_CRUISE_MIN_V_ECO max_v = _DP_CRUISE_MAX_V_ECO - elif self._profile == DPAccel.SPORT: + elif self._profile == AccelProfile.sport: min_v = _DP_CRUISE_MIN_V_SPORT max_v = _DP_CRUISE_MAX_V_SPORT else: @@ -75,7 +61,8 @@ class AccelController: return a_cruise_min, a_cruise_max def get_accel_limits(self, v_ego: float, accel_limits: list[float]): - return accel_limits if self._profile == DPAccel.STOCK else self._dp_calc_cruise_accel_limits(v_ego) + return accel_limits if self._profile == AccelProfile.stock else self._dp_calc_cruise_accel_limits(v_ego) - def is_enabled(self): - return self._profile != DPAccel.STOCK + def is_enabled(self, accel_profile: int = AccelProfile.stock): + self._profile = accel_profile + return self._profile != AccelProfile.stock From c9ac0993b7d3c9e73619b69838a34f8a1b2ab265 Mon Sep 17 00:00:00 2001 From: Brian Brown Date: Mon, 1 Jul 2024 17:59:11 +0000 Subject: [PATCH 321/575] Update CHANGELOGS.md --- CHANGELOGS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 005afc9e8d..686d87178e 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,6 +1,10 @@ sunnypilot - 0.9.8.0 (2024-xx-xx) ======================== * Always on driver monitoring toggle +*NEW❗: Acceleration Profile thanks to kegman, rav4kumar, and arne1282 + * 3 new modes acceleration profiles for you pick Eco, Stock and Support + * Acceleration button some vehicle including most TSS1/2/2.5 and HKG vehicle + * These coded right into the model acceleration martix can be actived in real time! ************************ * UPDATED: Synced with commaai's openpilot * master commit b45caf4 (June 14, 2024) From d7545ddeb746c876094d9895f9ddcd8fca2119a5 Mon Sep 17 00:00:00 2001 From: Brian Brown Date: Mon, 1 Jul 2024 18:00:23 +0000 Subject: [PATCH 322/575] fix space --- CHANGELOGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 686d87178e..dc50a9257a 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,7 +1,7 @@ sunnypilot - 0.9.8.0 (2024-xx-xx) ======================== * Always on driver monitoring toggle -*NEW❗: Acceleration Profile thanks to kegman, rav4kumar, and arne1282 +* NEW❗: Acceleration Profile thanks to kegman, rav4kumar, and arne1282 * 3 new modes acceleration profiles for you pick Eco, Stock and Support * Acceleration button some vehicle including most TSS1/2/2.5 and HKG vehicle * These coded right into the model acceleration martix can be actived in real time! From 4bf4f627bd3b12598a861d5e29d5ec279a4747d7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 15:05:50 -0400 Subject: [PATCH 323/575] use cereal --- cereal/custom.capnp | 1 + selfdrive/controls/controlsd.py | 4 ++++ selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 6 ++++-- selfdrive/controls/lib/longitudinal_planner.py | 5 +---- selfdrive/ui/qt/offroad/settings.cc | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 399679d03c..1e74981450 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -21,6 +21,7 @@ enum LongitudinalPersonalitySP { struct ControlsStateSP @0x81c2f05a394cf4af { lateralState @0 :Text; personality @8 :LongitudinalPersonalitySP; + dynamicPersonality @9 :Bool; lateralControlState :union { indiState @1 :LateralINDIState; diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 21bf73cf9a..bf1875b000 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -187,6 +187,8 @@ class Controls: model_capabilities = ModelCapabilities.get_by_gen(self.model_gen) self.model_use_lateral_planner = self.custom_model and model_capabilities & ModelCapabilities.LateralPlannerSolution + self.dynamic_personality = self.params.get_bool("DynamicPersonality") + self.can_log_mono_time = 0 self.startup_event = get_startup_event(car_recognized, not self.CP.passive, len(self.CP.carFw) > 0) @@ -858,6 +860,7 @@ class Controls: controlsStateSP.lateralState = lat_tuning controlsStateSP.personality = self.personality + controlsStateSP.dynamicPersonality = self.dynamic_personality if self.enable_nnff and lat_tuning == 'torque': controlsStateSP.lateralControlState.torqueState = self.LaC.pid_long_sp @@ -911,6 +914,7 @@ class Controls: self.is_metric = self.params.get_bool("IsMetric") self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl self.personality = self.read_personality_param() + self.dynamic_personality = self.params.get_bool("DynamicPersonality") if self.CP.notCar: self.joystick_mode = self.params.get_bool("JoystickDebugMode") diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 97a69b2a2a..3a76350ccf 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -82,6 +82,7 @@ def get_T_FOLLOW(personality=custom.LongitudinalPersonalitySP.standard): else: raise NotImplementedError("Longitudinal personality not supported") + def get_dynamic_personality(v_ego, personality=custom.LongitudinalPersonalitySP.standard): if personality==custom.LongitudinalPersonalitySP.relaxed: x_vel = [0, 11, 14.5, 15, 20, 20.01, 25, 25.01, 36, 36.01] @@ -96,7 +97,8 @@ def get_dynamic_personality(v_ego, personality=custom.LongitudinalPersonalitySP. x_vel = [0, 5, 5.01, 11, 14.5, 15, 20, 20.01, 25, 25.01, 36, 36.01] y_dist = [1.25, 1.25, 1.12, 1.12, 1.12, 1.105, 1.105, 1.15, 1.15, 1.18, 1.20, 1.23] else: - raise NotImplementedError("Dynamic Follow personality not supported") + raise NotImplementedError("Dynamic personality not supported") + return np.interp(v_ego, x_vel, y_dist) @@ -358,7 +360,7 @@ class LongitudinalMpc: def update(self, radarstate, v_cruise, x, v, a, j, personality=custom.LongitudinalPersonalitySP.standard, dynamic_personality=False): v_ego = self.x0[1] - t_follow = get_T_FOLLOW(personality) if not dynamic_personality else get_dynamic_personality(v_ego, personality) + t_follow = get_dynamic_personality(v_ego, personality) if dynamic_personality else get_T_FOLLOW(personality) self.status = radarstate.leadOne.status or radarstate.leadTwo.status lead_xv_0 = self.process_lead(radarstate.leadOne) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index f91e71797c..8902b29d85 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -101,14 +101,11 @@ class LongitudinalPlanner: self.turn_speed_controller = TurnSpeedController() self.dynamic_experimental_controller = DynamicExperimentalController() - self.dynamic_personality = False - def read_param(self): try: self.dynamic_experimental_controller.set_enabled(self.params.get_bool("DynamicExperimentalControl")) except AttributeError: self.dynamic_experimental_controller = DynamicExperimentalController() - self.dynamic_personality = self.params.get_bool("DynamicPersonality") @staticmethod def parse_model(model_msg, model_error): @@ -181,7 +178,7 @@ class LongitudinalPlanner: self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsState'].personality, dynamic_personality=self.dynamic_personality) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsState'].personality, dynamic_personality=sm['controlsState'].dynamicPersonality) self.v_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.a_solution) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index bdf4d9aa13..211646de47 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -58,7 +58,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { { "DynamicPersonality", tr("Enable Dynamic Personality"), - tr("Enable toggle to allow sunnypilot will dynamically adjust following distance based on your \"Driving Personality\" setting."), + tr("Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your \"Driving Personality\" setting."), "../assets/offroad/icon_blank.png", }, { @@ -116,7 +116,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { std::vector longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")}; long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), - tr("Stock is recommended. In aggressive/maniac mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " + tr("Standard is recommended. In aggressive/moderate mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " "your steering wheel distance button."), "../assets/offroad/icon_blank.png", From 0ae9eb274cfd588a97628cf883377f9eafbea37d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 15:34:35 -0400 Subject: [PATCH 324/575] in plannerd --- selfdrive/controls/plannerd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 11192b8791..640251f163 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -35,7 +35,7 @@ def plannerd_thread(): pm = messaging.PubMaster(['longitudinalPlan', 'longitudinalPlanSP'] + lateral_planner_svs) sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2', 'longitudinalPlan', 'navInstruction', 'longitudinalPlanSP', - 'liveMapDataSP', 'e2eLongStateSP'] + lateral_planner_svs, + 'liveMapDataSP', 'e2eLongStateSP', 'controlsStateSP'] + lateral_planner_svs, poll='modelV2', ignore_avg_freq=['radarState']) while True: From 4de6fac0d462fc0598b560b399758f06a89bfff6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 15:35:27 -0400 Subject: [PATCH 325/575] in plannerd --- selfdrive/controls/lib/longitudinal_planner.py | 2 +- selfdrive/controls/plannerd.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 8902b29d85..4f948f195c 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -178,7 +178,7 @@ class LongitudinalPlanner: self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsState'].personality, dynamic_personality=sm['controlsState'].dynamicPersonality) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsState'].personality, dynamic_personality=sm['controlsStateSP'].dynamicPersonality) self.v_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.a_solution) diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 11192b8791..640251f163 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -35,7 +35,7 @@ def plannerd_thread(): pm = messaging.PubMaster(['longitudinalPlan', 'longitudinalPlanSP'] + lateral_planner_svs) sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2', 'longitudinalPlan', 'navInstruction', 'longitudinalPlanSP', - 'liveMapDataSP', 'e2eLongStateSP'] + lateral_planner_svs, + 'liveMapDataSP', 'e2eLongStateSP', 'controlsStateSP'] + lateral_planner_svs, poll='modelV2', ignore_avg_freq=['radarState']) while True: From 36c89ce66698cc9d74ee95fdfa3bd8c18885d488 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 15:47:18 -0400 Subject: [PATCH 326/575] Fix --- system/manager/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/manager/manager.py b/system/manager/manager.py index 2b0920fd8e..6fb415fe5c 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -64,7 +64,7 @@ def manager_init() -> None: ("DisengageLateralOnBrake", "0"), ("DrivingModelGeneration", "0"), ("DynamicLaneProfile", "1"), - ("DynamicFollow", "0"), + ("DynamicPersonality", "0"), ("EnableMads", "1"), ("EnhancedScc", "0"), ("FeatureStatus", "1"), From 69ae0445b7291e17c1a507222d3a6721456a93b2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 16:20:37 -0400 Subject: [PATCH 327/575] update tuning --- .../controls/lib/sunnypilot/accel_controller.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py index ec006d3310..8ee1cb6746 100644 --- a/selfdrive/controls/lib/sunnypilot/accel_controller.py +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -29,13 +29,13 @@ from openpilot.common.numpy_fast import interp AccelProfile = custom.AccelerationProfile # accel profile by @arne182 modified by cgw -_DP_CRUISE_MIN_V = [-1.00, -1.00, -0.99, -0.90, -0.90, -0.88, -0.88, -0.82] -_DP_CRUISE_MIN_V_ECO = [-1.00, -1.00, -0.98, -0.88, -0.88, -0.86, -0.86, -0.80] -_DP_CRUISE_MIN_V_SPORT = [-1.01, -1.01, -1.00, -0.92, -0.92, -0.90, -0.90, -0.84] -_DP_CRUISE_MIN_BP = [0., 0.05, 0.4, 0.5, 8.33, 16., 30., 40.] +_DP_CRUISE_MIN_V = [-1.03, -0.79, -0.77, -0.77, -0.75, -0.75, -0.88, -0.82] +_DP_CRUISE_MIN_V_ECO = [-1.02, -0.78, -0.75, -0.75, -0.73, -0.73, -0.80, -0.80] +_DP_CRUISE_MIN_V_SPORT = [-1.04, -0.81, -0.79, -0.79, -0.77, -0.77, -0.90, -0.84] +_DP_CRUISE_MIN_BP = [0., 0.05, 0.1, 0.5, 8.33, 16., 30., 40.] -_DP_CRUISE_MAX_V = [2.4, 2.4, 2.4, 1.60, 1.05, .81, .625, .42, .348, .12] -_DP_CRUISE_MAX_V_ECO = [1.6, 1.6, 1.6, 1.0, .60, .50, .40, .25, .15, .05] +_DP_CRUISE_MAX_V = [2.5, 2.5, 2.5, 1.70, 1.05, .81, .625, .42, .348, .12] +_DP_CRUISE_MAX_V_ECO = [2.0, 2.0, 2.0, 1.4, .80, .68, .53, .32, .20, .085] _DP_CRUISE_MAX_V_SPORT = [3.5, 3.5, 2.8, 2.4, 1.4, 1.0, .89, .75, .50, .2] _DP_CRUISE_MAX_BP = [0., 1., 6., 8., 11., 15., 20., 25., 30., 55.] From c97fa56bd328d4b367991dd9688290952f6d17c8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 16:22:12 -0400 Subject: [PATCH 328/575] Toyota Longitudinal: New TSS2 Tuning --- selfdrive/car/toyota/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 451265f9cd..dca2cfd12a 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -164,7 +164,7 @@ class CarInterface(CarInterfaceBase): def custom_tss2_longitudinal_tuning(): ret.vEgoStopping = 0.01 ret.vEgoStarting = 0.01 - ret.stoppingDecelRate = 0.35 + ret.stoppingDecelRate = 0.40 def default_tss2_longitudinal_tuning(): ret.vEgoStopping = 0.25 @@ -178,8 +178,8 @@ class CarInterface(CarInterfaceBase): tune = ret.longitudinalTuning if candidate in TSS2_CAR or ret.enableGasInterceptorDEPRECATED: if sp_tss2_long_tune: - tune.kiBP = [0., 5., 12., 20., 27., 36., 50] - tune.kiV = [0.35, 0.23, 0.20, 0.17, 0.10, 0.07, 0.01] + tune.kiBP = [0., 12., 16, 20., 27., 36., 50] + tune.kiV = [0.33, 0.22, 0.19, 0.17, 0.10, 0.07, 0.01] custom_tss2_longitudinal_tuning() else: tune.kpV = [0.0] From 9ee63438ef310e2f1ddbbf9aaf86b3a390e2c532 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Jul 2024 16:23:06 -0400 Subject: [PATCH 329/575] update tuning --- selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 3a76350ccf..a2df527a55 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -63,9 +63,9 @@ def get_jerk_factor(personality=custom.LongitudinalPersonalitySP.standard): elif personality==custom.LongitudinalPersonalitySP.standard: return 1.0 elif personality==custom.LongitudinalPersonalitySP.moderate: - return 0.75 + return 0.8 elif personality==custom.LongitudinalPersonalitySP.aggressive: - return 0.5 + return 0.6 else: raise NotImplementedError("Longitudinal personality not supported") @@ -95,7 +95,7 @@ def get_dynamic_personality(v_ego, personality=custom.LongitudinalPersonalitySP. y_dist = [1.3, 1.3, 1.3, 1.35, 1.35, 1.385, 1.385, 1.4, 1.4, 1.45] elif personality==custom.LongitudinalPersonalitySP.aggressive: x_vel = [0, 5, 5.01, 11, 14.5, 15, 20, 20.01, 25, 25.01, 36, 36.01] - y_dist = [1.25, 1.25, 1.12, 1.12, 1.12, 1.105, 1.105, 1.15, 1.15, 1.18, 1.20, 1.23] + y_dist = [1.12, 1.12, 1.12, 1.12, 1.12, 1.105, 1.105, 1.15, 1.15, 1.18, 1.20, 1.23] else: raise NotImplementedError("Dynamic personality not supported") From f23982d74875e2970c273253ecf9e4cfa30bacef Mon Sep 17 00:00:00 2001 From: Brian Brown Date: Tue, 2 Jul 2024 00:17:05 +0000 Subject: [PATCH 330/575] more english --- CHANGELOGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 362734b7a3..87bc4d5602 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -3,7 +3,7 @@ sunnypilot - 0.9.8.0 (2024-xx-xx) * Always on driver monitoring toggle * NEW❗: Acceleration Profile thanks to kegman, rav4kumar, and arne1282 * 3 new modes acceleration profiles for you pick Eco, Stock and Support - * Acceleration button some vehicle including most TSS1/2/2.5 and HKG vehicle + * The ability to use the physical acceleration button on some vehicle including most TSS1/2/2.5 and HKG vehicle * These coded right into the model acceleration martix can be actived in real time! ************************ * UPDATED: Synced with commaai's openpilot From a5d1e13b48c9273d6977ea2e71f97cb2ec33f6b6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Jul 2024 00:53:27 -0400 Subject: [PATCH 331/575] Update CHANGELOGS.md --- CHANGELOGS.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 86f6544554..0dcb945d0c 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,10 +1,6 @@ sunnypilot - 0.9.8.0 (2024-xx-xx) ======================== * Always on driver monitoring toggle -* NEW❗: Acceleration Profile thanks to kegman, rav4kumar, and arne1282 - * 3 new modes acceleration profiles for you pick Eco, Stock and Support - * The ability to use the physical acceleration button on some vehicle including most TSS1/2/2.5 and HKG vehicle - * These coded right into the model acceleration martix can be actived in real time! ************************ * UPDATED: Synced with commaai's openpilot * master commit b45caf4 (June 14, 2024) From 191fe69d0c38b9a95d0772c9b9d05c7919271be7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Jul 2024 01:09:39 -0400 Subject: [PATCH 332/575] extra --- system/manager/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/system/manager/manager.py b/system/manager/manager.py index 4355bfb92b..388198736c 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -117,7 +117,6 @@ def manager_init() -> None: ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), - ("AccelProfile", "0"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat().encode('utf8'))) From 59d960addf2370a93d56f2c099c650142697ffde Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Jul 2024 10:46:32 -0400 Subject: [PATCH 333/575] housekeeping --- selfdrive/controls/controlsd.py | 2 +- selfdrive/controls/lib/drive_helpers.py | 2 +- system/manager/manager.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 5eb690b098..8cb3c9ea9d 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -494,7 +494,7 @@ class Controls: def state_transition(self, CS): """Compute conditional state transitions and execute actions on state transitions""" - #sp - toyota low speed override + # sp - toyota low speed override sp_override_speed = self.toyota_cruise_override_speed if self.toyota_cruise_override else False self.v_cruise_helper.update_v_cruise(CS, self.enabled_long, self.is_metric, self.reverse_acc_change, sp_override_speed, self.sm['longitudinalPlanSP']) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 05e6777eb2..7c77f3013a 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -84,7 +84,7 @@ class VCruiseHelper: self.slc_state_prev = SpeedLimitControlState.inactive self.slc_speed_limit_offsetted = 0 - #sp: toyota low speed override + # sp: toyota low speed override self.sp_override_v_cruise_kph = V_CRUISE_UNSET self.sp_override_cruise_speed_last = V_CRUISE_UNSET self.sp_override_enabled_last = False diff --git a/system/manager/manager.py b/system/manager/manager.py index 434f4cb851..cfabbfd583 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -102,6 +102,8 @@ def manager_init() -> None: ("ToyotaAutoHold", "0"), ("ToyotaAutoLockBySpeed", "0"), ("ToyotaAutoUnlockByShifter", "0"), + ("ToyotaCruiseOverride", "0"), + ("ToyotaCruiseOverrideSpeed", "30"), ("ToyotaEnhancedBsm", "0"), ("TrueVEgoUi", "0"), ("TurnSpeedControl", "0"), @@ -117,8 +119,6 @@ def manager_init() -> None: ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), - ("ToyotaCruiseOverride", "0"), - ("ToyotaCruiseOverrideSpeed", "30"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat().encode('utf8'))) From 5d097fa5deffc6b954f9a6666dd1fa533dc14546 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Jul 2024 10:57:40 -0400 Subject: [PATCH 334/575] rename to PCM Speed Override --- common/params.cc | 4 ++-- selfdrive/controls/controlsd.py | 8 ++++---- selfdrive/controls/lib/drive_helpers.py | 2 +- system/manager/manager.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/common/params.cc b/common/params.cc index 66acd7c044..61e2ae8225 100644 --- a/common/params.cc +++ b/common/params.cc @@ -293,6 +293,8 @@ std::unordered_map keys = { {"OsmDownloadedDate", PERSISTENT}, {"PathOffset", PERSISTENT | BACKUP}, {"PauseLateralSpeed", PERSISTENT | BACKUP}, + {"PCMVCruiseOverride", PERSISTENT | BACKUP}, + {"PCMVCruiseOverrideSpeed", PERSISTENT | BACKUP}, {"QuietDrive", PERSISTENT | BACKUP}, {"RoadEdge", PERSISTENT | BACKUP}, {"ReverseAccChange", PERSISTENT | BACKUP}, @@ -322,8 +324,6 @@ std::unordered_map keys = { {"ToyotaAutoHold", PERSISTENT | BACKUP}, {"ToyotaAutoLockBySpeed", PERSISTENT | BACKUP}, {"ToyotaAutoUnlockByShifter", PERSISTENT | BACKUP}, - {"ToyotaCruiseOverride", PERSISTENT | BACKUP}, - {"ToyotaCruiseOverrideSpeed", PERSISTENT | BACKUP}, {"ToyotaEnhancedBsm", PERSISTENT | BACKUP}, {"ToyotaSnG", PERSISTENT | BACKUP}, {"ToyotaTSS2Long", PERSISTENT | BACKUP}, diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 8cb3c9ea9d..ebb44660ed 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -181,8 +181,8 @@ class Controls: self.enable_mads = self.params.get_bool("EnableMads") self.mads_disengage_lateral_on_brake = self.params.get_bool("DisengageLateralOnBrake") self.mads_ndlob = self.enable_mads and not self.mads_disengage_lateral_on_brake - self.toyota_cruise_override = self.params.get_bool("ToyotaCruiseOverride") - self.toyota_cruise_override_speed = int(self.params.get("ToyotaCruiseOverrideSpeed", encoding="utf-8")) + self.pcm_v_cruise_override = self.params.get_bool("PCMVCruiseOverride") + self.pcm_v_cruise_override_speed = int(self.params.get("PCMVCruiseOverrideSpeed", encoding="utf-8")) self.process_not_running = False self.custom_model, self.model_gen = get_model_generation(self.params) @@ -494,8 +494,8 @@ class Controls: def state_transition(self, CS): """Compute conditional state transitions and execute actions on state transitions""" - # sp - toyota low speed override - sp_override_speed = self.toyota_cruise_override_speed if self.toyota_cruise_override else False + # sp - PCM speed override + sp_override_speed = self.pcm_v_cruise_override_speed if self.pcm_v_cruise_override else False self.v_cruise_helper.update_v_cruise(CS, self.enabled_long, self.is_metric, self.reverse_acc_change, sp_override_speed, self.sm['longitudinalPlanSP']) # decrement the soft disable timer at every step, as it's reset on diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 7c77f3013a..e9f78ef969 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -84,7 +84,7 @@ class VCruiseHelper: self.slc_state_prev = SpeedLimitControlState.inactive self.slc_speed_limit_offsetted = 0 - # sp: toyota low speed override + # sp: PCM speed override self.sp_override_v_cruise_kph = V_CRUISE_UNSET self.sp_override_cruise_speed_last = V_CRUISE_UNSET self.sp_override_enabled_last = False diff --git a/system/manager/manager.py b/system/manager/manager.py index cfabbfd583..eb6e956a1c 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -84,6 +84,8 @@ def manager_init() -> None: ("OnroadSettings", "1"), ("PathOffset", "0"), ("PauseLateralSpeed", "0"), + ("PCMVCruiseOverride", "0"), + ("PCMVCruiseOverrideSpeed", "30"), ("ReverseAccChange", "0"), ("ScreenRecorder", "1"), ("ShowDebugUI", "1"), @@ -102,8 +104,6 @@ def manager_init() -> None: ("ToyotaAutoHold", "0"), ("ToyotaAutoLockBySpeed", "0"), ("ToyotaAutoUnlockByShifter", "0"), - ("ToyotaCruiseOverride", "0"), - ("ToyotaCruiseOverrideSpeed", "30"), ("ToyotaEnhancedBsm", "0"), ("TrueVEgoUi", "0"), ("TurnSpeedControl", "0"), From 9af616605b9e50db26edc0280a02e05768d370dd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Jul 2024 12:01:58 -0400 Subject: [PATCH 335/575] ui: Torque Lateral Control: Update every 0.005 --- selfdrive/car/interfaces.py | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 2 +- selfdrive/locationd/torqued.py | 2 +- selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc | 5 +++-- system/manager/manager.py | 4 +++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 35858557f4..ad56ee78b4 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -410,7 +410,7 @@ class CarInterfaceBase(ABC): @staticmethod def sp_configure_custom_torque_tune(ret, params): - ret.lateralTuning.torque.friction = float(params.get("TorqueFriction", encoding="utf8")) * 0.01 + ret.lateralTuning.torque.friction = float(params.get("TorqueFriction", encoding="utf8")) * 0.001 ret.lateralTuning.torque.latAccelFactor = float(params.get("TorqueMaxLatAccel", encoding="utf8")) * 0.01 return ret diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index ac4450a1de..58a87022c0 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -144,7 +144,7 @@ class LatControlTorque(LatControl): return self.torque_params.latAccelFactor = float(self.param_s.get("TorqueMaxLatAccel", encoding="utf8")) * 0.01 - self.torque_params.friction = float(self.param_s.get("TorqueFriction", encoding="utf8")) * 0.01 + self.torque_params.friction = float(self.param_s.get("TorqueFriction", encoding="utf8")) * 0.001 @property def pid_long_sp(self): diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 73a2efeec8..439f0c3d4a 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -79,7 +79,7 @@ class TorqueEstimator(ParameterEstimator): params = Params() if params.get_bool("EnforceTorqueLateral"): if params.get_bool("CustomTorqueLateral"): - self.offline_friction = float(params.get("TorqueFriction", encoding="utf8")) * 0.01 + self.offline_friction = float(params.get("TorqueFriction", encoding="utf8")) * 0.001 self.offline_latAccelFactor = float(params.get("TorqueMaxLatAccel", encoding="utf8")) * 0.01 if params.get_bool("LiveTorqueRelaxed"): self.min_bucket_points = np.array([0, 200, 300, 500, 500, 300, 200, 0]) / (10 if decimated else 1) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc index 0413102bb5..3eac53a4c1 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc @@ -586,13 +586,14 @@ TorqueFriction::TorqueFriction() : SPOptionControl ( tr("FRICTION"), tr("Adjust Friction for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), "../assets/offroad/icon_blank.png", - {0, 50}) { + {0, 50}, + 5) { refresh(); } void TorqueFriction::refresh() { - float torqueFrictionVal = QString::fromStdString(params.get("TorqueFriction")).toInt() * 0.01; + float torqueFrictionVal = QString::fromStdString(params.get("TorqueFriction")).toInt() * 0.001; setTitle("FRICTION - " + (params.getBool("TorquedOverride") ? tr("Real-time and Offline") : tr("Offline Only"))); setLabel(QString::number(torqueFrictionVal)); } diff --git a/system/manager/manager.py b/system/manager/manager.py index 388198736c..be931353e0 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -97,7 +97,7 @@ def manager_init() -> None: ("StandStillTimer", "0"), ("StockLongToyota", "0"), ("TorqueDeadzoneDeg", "0"), - ("TorqueFriction", "1"), + ("TorqueFriction", "10"), ("TorqueMaxLatAccel", "250"), ("ToyotaAutoHold", "0"), ("ToyotaAutoLockBySpeed", "0"), @@ -117,6 +117,8 @@ def manager_init() -> None: ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), + ("ToyotaCruiseOverride", "0"), + ("ToyotaCruiseOverrideSpeed", "30"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat().encode('utf8'))) From 090c97b2fcb9f69ac9746de1c907a703051a9e33 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Jul 2024 12:05:13 -0400 Subject: [PATCH 336/575] nope --- system/manager/manager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/system/manager/manager.py b/system/manager/manager.py index 3dd8d75c47..094f3c188e 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -119,8 +119,6 @@ def manager_init() -> None: ("CustomDrivingModel", "0"), ("DrivingModelGeneration", "4"), ("LastSunnylinkPingTime", "0"), - ("ToyotaCruiseOverride", "0"), - ("ToyotaCruiseOverrideSpeed", "30"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat().encode('utf8'))) From a86ce23f4c9ce585c709b09a3d0259d47baac981 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Jul 2024 12:05:43 -0400 Subject: [PATCH 337/575] higher --- selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc index 3eac53a4c1..c4b3d1b6e0 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc @@ -586,7 +586,7 @@ TorqueFriction::TorqueFriction() : SPOptionControl ( tr("FRICTION"), tr("Adjust Friction for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), "../assets/offroad/icon_blank.png", - {0, 50}, + {0, 500}, 5) { refresh(); From 8525cf6333816f067a042629e828dbee36d02173 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Jul 2024 17:23:15 -0400 Subject: [PATCH 338/575] Revert "MADS: Honda Nidec: Fix enforce cruise cancel with stock PCM" This reverts commit c313af5c --- selfdrive/car/honda/carstate.py | 2 +- selfdrive/car/honda/interface.py | 2 +- selfdrive/car/interfaces.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index d412f81142..ae8746d958 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -242,7 +242,7 @@ class CarState(CarStateBase): ret.brakePressed = (cp.vl["POWERTRAIN_DATA"]["BRAKE_PRESSED"] != 0) or self.brake_switch_active ret.brake = cp.vl["VSA_STATUS"]["USER_BRAKE"] - ret.cruiseState.enabled = self.pcm_cruise_enabled = cp.vl["POWERTRAIN_DATA"]["ACC_STATUS"] != 0 + ret.cruiseState.enabled = cp.vl["POWERTRAIN_DATA"]["ACC_STATUS"] != 0 ret.cruiseState.available = bool(cp.vl[self.main_on_sig_msg]["MAIN_ON"]) # Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 1aba4d3119..457b095e6a 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -298,7 +298,7 @@ class CarInterface(CarInterfaceBase): if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed: if ret.gasPressed and not ret.cruiseState.enabled: self.CS.accEnabled = False - self.CS.accEnabled = self.CS.pcm_cruise_enabled + self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled ret, self.CS = self.get_sp_common_state(ret, self.CS, min_enable_speed_pcm=(self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed), diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 35858557f4..00f48f0e07 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -775,7 +775,6 @@ class CarStateBase(ABC): self.mads_enabled = False self.prev_mads_enabled = False self.control_initialized = False - self.pcm_cruise_enabled = False Q = [[0.0, 0.0], [0.0, 100.0]] R = 0.3 From 303d244966ecacae115bef624bb0507baea1f5f2 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 5 Jul 2024 10:50:32 +0200 Subject: [PATCH 339/575] Refactor setup command sequence by removing redundant kill operation. The `kill_op` command to stop the `comma` service was removed as it is redundant. This change simplifies the `cmd` string construction and ensures the setup process starts with deleting `continue.sh` and restarting the `comma` service. don't know what diff it makes MinimumExpanding vs Expanding so I am leaving it as I found it and some formatting on the code Improved user interface of application The user interface in selfdrive/ui/qt/text.cc was significantly enhanced. This was accomplished by streamlining the QVBoxLayout for displaying text and the QGridLayout for displaying buttons. The QPushButton were updated to handle actions for updating and reinstalling. Additional formatting was also added to improve the visibility of the buttons. Furthermore, unnecessary QWidget import was removed and replaced with QLabel. The QLabel was set to clear before setting newText, creating a smoother update of application status messages. Lastly, QApplication::exec() was used in place of a.exec() to cater for the button states on different platforms and handle the application's exit. --- selfdrive/ui/qt/text.cc | 126 +++++++++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 40 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 4b5f8ee21e..7fdf6f6559 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -1,9 +1,7 @@ -#include #include #include #include #include -#include #include #include #include @@ -29,50 +27,51 @@ std::string executeCommand(const char* cmd) { return result.str(); } -// The format is intentional! +QLabel *label; + +// The format is intentional, even if on PC doesn't look aligned. Font differences are likely the cause. On device it looks fine. QString text = QString("%1\n%2\n%3\n%4\n%5\n%6\n%7") - .arg(" ________________________________________") - .arg("| |") - .arg("| " + QObject::tr("Update downloaded. Ready to reboot.") + " |") - .arg("| |") - .arg("| " + QObject::tr("Update: Check and Download Update") + " |") - .arg("| " + QObject::tr("Reboot: Reboot Device") + " |") - .arg("|________________________________________|"); + .arg(" ________________________________________________________") + .arg("|\t\t\t\t\t\t|") + .arg("|\t" + QObject::tr("Update downloaded. Ready to reboot.") + "\t|") + .arg("|\t\t\t\t\t\t|") + .arg("|\t" + QObject::tr("Update: Check and Download Update") + "\t\t|") + .arg("|\t" + QObject::tr("Reboot: Reboot Device") + "\t\t\t|") + .arg("| _______________________________________________________\t|"); -int main(int argc, char *argv[]) { - initApp(argc, argv); - QApplication a(argc, argv); - QWidget window; - setMainWindow(&window); - - QGridLayout *main_layout = new QGridLayout(&window); - main_layout->setMargin(50); - - QLabel *label = new QLabel(argv[1]); +void setupErrorMessageLabel(QVBoxLayout *main_layout) { label->setWordWrap(true); label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); label->setAlignment(Qt::AlignTop | Qt::AlignLeft); - ScrollView *scroll = new ScrollView(label); + auto *scroll = new ScrollView(label); scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - main_layout->addWidget(scroll, 0, 0, Qt::AlignTop); + main_layout->addWidget(scroll); // Scroll to the bottom QObject::connect(scroll->verticalScrollBar(), &QAbstractSlider::rangeChanged, [=]() { scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum()); }); +} - QPushButton *btn = new QPushButton(); - QPushButton *update_btn = new QPushButton(); +void setupActionButton(QGridLayout *button_grid) { + auto *left_buttons = new QHBoxLayout(); + auto *right_buttons = new QHBoxLayout(); + + auto *btn = new QPushButton(); + auto *update_btn = new QPushButton(); update_btn->setText(QObject::tr("Update")); + auto *reinstall_btn = new QPushButton(); + reinstall_btn->setText(QObject::tr("Launch Setup")); #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); - QFutureWatcher watcher; + auto *watcher = new QFutureWatcher(); QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() { + QObject::connect(update_btn, &QPushButton::clicked, [=]() { btn->setEnabled(false); update_btn->setEnabled(false); + reinstall_btn->setEnabled(false); update_btn->setText(QObject::tr("Updating...")); const std::string git_branch = Params().get("GitBranch"); const std::string git_remote = Params().get("GitRemote"); @@ -90,26 +89,70 @@ int main(int argc, char *argv[]) { const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; QFuture future = QtConcurrent::run([=]() { - label->clear(); std::string output = executeCommand(cmd.c_str()); - //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); - QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(output) + text)); + auto formatted = QString::fromStdString(output) + text; + QMetaObject::invokeMethod(label, "clear", Qt::QueuedConnection); + QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, formatted)); }); - QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { - btn->setEnabled(true); - update_btn->setEnabled(true); - update_btn->setText(QObject::tr("Update")); - }); - watcher.setFuture(future); + watcher->setFuture(future); }); + QObject::connect(watcher, &QFutureWatcher::finished, [=]() { + btn->setEnabled(true); + update_btn->setEnabled(true); + reinstall_btn->setEnabled(true); + update_btn->setText(QObject::tr("Update")); + }); + + QObject::connect(reinstall_btn, &QPushButton::clicked, [=]() { + btn->setEnabled(false); + update_btn->setEnabled(false); + reinstall_btn->setEnabled(false); + reinstall_btn->setText(QObject::tr("Starting setup...")); + + const std::string delete_continue = "sudo rm /data/continue.sh"; + const std::string restart_op = "sudo systemctl restart comma"; + + const std::string cmd = delete_continue + "; " + restart_op + "; "; + + // Start setup command asynchronously + QtConcurrent::run([=]() { + executeCommand(cmd.c_str()); + }); + QApplication::quit(); + }); + #else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); - QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); + QObject::connect(btn, &QPushButton::clicked, &app, &QApplication::quit); #endif - main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); - main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); + left_buttons->addWidget(update_btn); + left_buttons->addWidget(reinstall_btn); + right_buttons->addWidget(btn); + + button_grid->addLayout(left_buttons, 0, 0, Qt::AlignLeft | Qt::AlignBottom); + button_grid->addLayout(right_buttons, 0, 1, Qt::AlignRight | Qt::AlignBottom); +} + +int main(int argc, char *argv[]) { + initApp(argc, argv); + QApplication app(argc, argv); + QWidget window; + setMainWindow(&window); + + auto *main_layout = new QVBoxLayout(&window); + main_layout->setMargin(50); + + // Setup the text area layout + auto *text_layout = new QVBoxLayout(); + label = new QLabel(argv[1]); + setupErrorMessageLabel(text_layout); + main_layout->addLayout(text_layout); + + // Setup the button area layout + auto *button_layout = new QGridLayout(); + setupActionButton(button_layout); + main_layout->addLayout(button_layout); window.setStyleSheet(R"( * { @@ -126,11 +169,14 @@ int main(int argc, char *argv[]) { border-radius: 20px; margin-right: 40px; } + QPushButton:pressed { + background-color: #4a4a4a; + } QPushButton:disabled { color: #33FFFFFF; border: 2px solid #33FFFFFF; } )"); - return a.exec(); + return QApplication::exec(); } From d6600c32aa77d9a9a17a3b9d61d41fd72d222ef6 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 6 Jul 2024 20:04:09 +0200 Subject: [PATCH 340/575] disable log upload handler --- system/athena/sunnylinkd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index ca24f24090..3b4c81b680 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -43,7 +43,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: threading.Thread(target=ws_ping, args=(ws, end_event), name='ws_ping'), threading.Thread(target=ws_queue, args=(end_event,), name='ws_queue'), # threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'), - threading.Thread(target=sunny_log_handler, args=(end_event, comma_prime_cellular_end_event), name='log_handler'), + # threading.Thread(target=sunny_log_handler, args=(end_event, comma_prime_cellular_end_event), name='log_handler'), # threading.Thread(target=stat_handler, args=(end_event,), name='stat_handler'), ] + [ threading.Thread(target=jsonrpc_handler, args=(end_event,), name=f'worker_{x}') From 127ba8f859ec5fe6bf36800396ad2311e0972b1d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 7 Jul 2024 22:17:39 -0400 Subject: [PATCH 341/575] ui: Lead car chevron: Time to Lead Car --- CHANGELOGS.md | 5 +++++ .../qt/offroad/sunnypilot/visuals_settings.cc | 4 ++-- selfdrive/ui/qt/onroad/annotated_camera.cc | 18 ++++++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) 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/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 7e5629c4ef..59624c4f46 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1524,12 +1524,18 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState painter.drawPolygon(chevron, std::size(chevron)); if (num == 0) { // Display metrics to the 0th lead car - QStringList chevron_text[2]; - if (chevron_data == 1 || chevron_data == 3) { - chevron_text[0].append(QString::number(radar_d_rel,'f', 0) + " " + "m"); + QStringList chevron_text[3]; + int position = 0; + if (chevron_data == 1 || chevron_data == 4) { + chevron_text[position].append(QString::number(radar_d_rel,'f', 0) + " " + "m"); } - if (chevron_data == 2 || chevron_data == 3) { - chevron_text[chevron_data - 2].append(QString::number((radar_v_rel + v_ego) * (isMetric ? MS_TO_KPH : MS_TO_MPH),'f', 0) + " " + (isMetric ? "km/h" : "mph")); + if (chevron_data == 2 || chevron_data == 4) { + position = (chevron_data == 2) ? 0 : 1; + chevron_text[position].append(QString::number((radar_v_rel + v_ego) * (isMetric ? MS_TO_KPH : MS_TO_MPH),'f', 0) + " " + (isMetric ? "km/h" : "mph")); + } + if (chevron_data == 3 || chevron_data == 4) { + position = (chevron_data == 3) ? 0 : 2; + chevron_text[position].append(QString::number(d_rel / v_ego, 'f', 1) + " " + "s"); } int str_w = 200; // Width of the text box, might need adjustment @@ -1539,7 +1545,7 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState float text_y = y + sz + 12; // Position the text at the bottom of the chevron QRect textRect(x - str_w / 2, text_y, str_w, str_h); // Adjust the rectangle to center the text horizontally at the chevron's bottom QPoint shadow_offset(2, 2); - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < 3; ++i) { if (!chevron_text[i].isEmpty()) { painter.setPen(QColor(0x0, 0x0, 0x0, 200)); // Draw shadow painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y() + i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); From bdb3c0a09c355016403ac3365c3e8d28b59b8887 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 7 Jul 2024 22:23:46 -0400 Subject: [PATCH 342/575] Update CHANGELOGS.md --- CHANGELOGS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 2748876a6b..9d66bf8c58 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -28,6 +28,8 @@ sunnypilot - 0.9.8.0 (2024-xx-xx) * UPDATED: Driving Model Selector v5 * NEW❗: Driving Model additions * Notre Dame (July 01, 2024) - NDv3 +* UPDATED: Neural Network Lateral Control (NNLC) + * FIXED: Hotfix for "lazy" steering performance in tighter curves thanks to twilsonco! * UPDATED: Toyota: Continued support for Smart DSU (SDSU) and Radar CAN Filter * In response to the official deprecation of support for Smart DSU (SDSU) and Radar CAN Filter in the upstream ([commaai/openpilot#32777](https://github.com/commaai/openpilot/pull/32777)), sunnypilot will continue maintaining software support for Smart DSU (SDSU) and Radar CAN Filter * UPDATED: Continued support for Mapbox navigation @@ -105,7 +107,6 @@ sunnypilot - 0.9.7.1 (2024-06-13) * Reset self-service access tokens for Mapbox, Amap, and Google Maps * UPDATED: Upstream native support for Gap Adjust Cruise * 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 @twilsonco 😉) * UI Updates From 8e256ee6c50d9034501a20f63e40436a9a859740 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 7 Jul 2024 23:11:18 -0400 Subject: [PATCH 343/575] Add toggles and update CHANGELOGS.md --- CHANGELOGS.md | 2 + .../offroad/sunnypilot/sunnypilot_settings.cc | 43 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 9d66bf8c58..f8d25f602c 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -29,6 +29,8 @@ sunnypilot - 0.9.8.0 (2024-xx-xx) * NEW❗: Driving Model additions * Notre Dame (July 01, 2024) - NDv3 * UPDATED: Neural Network Lateral Control (NNLC) + * NEW❗: Remove Lateral Jerk Response (Alpha) + * * FIXED: Hotfix for "lazy" steering performance in tighter curves thanks to twilsonco! * UPDATED: Toyota: Continued support for Smart DSU (SDSU) and Radar CAN Filter * In response to the official deprecation of support for Smart DSU (SDSU) and Radar CAN Filter in the upstream ([commaai/openpilot#32777](https://github.com/commaai/openpilot/pull/32777)), sunnypilot will continue maintaining software support for Smart DSU (SDSU) and Radar CAN Filter diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc index 8db23500b4..fdbad474a5 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc @@ -59,12 +59,24 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { "", "../assets/offroad/icon_blank.png", }, + { + "NNFFNoLateralJerk", + tr("NNLC: Remove Lateral Jerk Response (Alpha)"), + tr("When NNLC is active, enable this to disables the use of lateral jerk in steering torque calculations, focusing solely on lateral acceleration for a simplified control response."), + "../assets/offroad/icon_blank.png", + }, { "EnforceTorqueLateral", tr("Enforce Torque Lateral Control"), tr("Enable this to enforce sunnypilot to steer with Torque lateral control."), "../assets/offroad/icon_blank.png", }, + { + "TorqueLateralJerk", + tr("Lateral Jerk with Torque Lateral Control (Alpha)"), + tr("Utilizes limited lateral jerk control for improved steering response, leveraging stock torque lateral controller capabilities. Designed to mimic NNLC behavior without training models or data collection."), + "../assets/offroad/icon_blank.png", + }, { "LiveTorque", tr("Enable Self-Tune"), @@ -312,6 +324,17 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { } }); + connect(toggles["EnforceTorqueLateral"], &ToggleControl::toggleFlipped, [=](bool state) { + if (state) { + toggles["NNFF"]->setEnabled(false); + params.putBool("NNFF", false); + toggles["NNFF"]->refresh(); + } else { + toggles["NNFF"]->setEnabled(true); + toggles["NNFF"]->refresh(); + } + }); + // trigger updateToggles() when toggleFlipped for (const auto& updateToggleName : updateTogglesNames) { if (toggles.find(updateToggleName) != toggles.end()) { @@ -541,7 +564,7 @@ void SunnypilotPanel::updateToggles() { } // toggle names to update when EnforceTorqueLateral is flipped - std::vector torqueLateralGroup{"CustomTorqueLateral", "LiveTorque", "LiveTorqueRelaxed", "TorquedOverride"}; + std::vector torqueLateralGroup{"CustomTorqueLateral", "TorqueLateralJerk", "LiveTorque", "LiveTorqueRelaxed", "TorquedOverride"}; for (const auto& torqueLateralToggle : torqueLateralGroup) { if (toggles.find(torqueLateralToggle) != toggles.end()) { if (nnff_toggle->isToggled()) { @@ -558,6 +581,24 @@ void SunnypilotPanel::updateToggles() { } } + // toggle names to update when EnforceTorqueLateral is flipped + std::vector nnffGroup{"NNFFNoLateralJerk"}; + for (const auto& nnffToggle : nnffGroup) { + if (toggles.find(nnffToggle) != toggles.end()) { + if (enforce_torque_lateral->isToggled()) { + toggles[nnffToggle]->setVisible(false); + toggles[nnffToggle]->setEnabled(false); + } + } + } + + for (const auto& nnffToggle : nnffGroup) { + if (toggles.find(nnffToggle) != toggles.end()) { + toggles[nnffToggle]->setVisible(nnff_toggle->isToggled()); + toggles[nnffToggle]->setEnabled(nnff_toggle->isToggled()); + } + } + if (enforce_torque_lateral->isToggled()) { live_torque_relaxed->setVisible(live_torque->isToggled()); torqued_override->setVisible(custom_torque_lateral->isToggled()); From fc1a294d5e72b476defcec70d7557e5102e186b6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 7 Jul 2024 23:33:05 -0400 Subject: [PATCH 344/575] revert !729 for now till it's fixed and compiled --- selfdrive/ui/qt/text.cc | 124 +++++++++++++--------------------------- 1 file changed, 39 insertions(+), 85 deletions(-) diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 7fdf6f6559..4b5f8ee21e 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -1,7 +1,9 @@ +#include #include #include #include #include +#include #include #include #include @@ -27,51 +29,50 @@ std::string executeCommand(const char* cmd) { return result.str(); } -QLabel *label; - -// The format is intentional, even if on PC doesn't look aligned. Font differences are likely the cause. On device it looks fine. +// The format is intentional! QString text = QString("%1\n%2\n%3\n%4\n%5\n%6\n%7") - .arg(" ________________________________________________________") - .arg("|\t\t\t\t\t\t|") - .arg("|\t" + QObject::tr("Update downloaded. Ready to reboot.") + "\t|") - .arg("|\t\t\t\t\t\t|") - .arg("|\t" + QObject::tr("Update: Check and Download Update") + "\t\t|") - .arg("|\t" + QObject::tr("Reboot: Reboot Device") + "\t\t\t|") - .arg("| _______________________________________________________\t|"); + .arg(" ________________________________________") + .arg("| |") + .arg("| " + QObject::tr("Update downloaded. Ready to reboot.") + " |") + .arg("| |") + .arg("| " + QObject::tr("Update: Check and Download Update") + " |") + .arg("| " + QObject::tr("Reboot: Reboot Device") + " |") + .arg("|________________________________________|"); -void setupErrorMessageLabel(QVBoxLayout *main_layout) { +int main(int argc, char *argv[]) { + initApp(argc, argv); + QApplication a(argc, argv); + QWidget window; + setMainWindow(&window); + + QGridLayout *main_layout = new QGridLayout(&window); + main_layout->setMargin(50); + + QLabel *label = new QLabel(argv[1]); label->setWordWrap(true); label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); label->setAlignment(Qt::AlignTop | Qt::AlignLeft); - auto *scroll = new ScrollView(label); + ScrollView *scroll = new ScrollView(label); scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - main_layout->addWidget(scroll); + main_layout->addWidget(scroll, 0, 0, Qt::AlignTop); // Scroll to the bottom QObject::connect(scroll->verticalScrollBar(), &QAbstractSlider::rangeChanged, [=]() { scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum()); }); -} -void setupActionButton(QGridLayout *button_grid) { - auto *left_buttons = new QHBoxLayout(); - auto *right_buttons = new QHBoxLayout(); - - auto *btn = new QPushButton(); - auto *update_btn = new QPushButton(); + QPushButton *btn = new QPushButton(); + QPushButton *update_btn = new QPushButton(); update_btn->setText(QObject::tr("Update")); - auto *reinstall_btn = new QPushButton(); - reinstall_btn->setText(QObject::tr("Launch Setup")); #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); - auto *watcher = new QFutureWatcher(); + QFutureWatcher watcher; QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - QObject::connect(update_btn, &QPushButton::clicked, [=]() { + QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() { btn->setEnabled(false); update_btn->setEnabled(false); - reinstall_btn->setEnabled(false); update_btn->setText(QObject::tr("Updating...")); const std::string git_branch = Params().get("GitBranch"); const std::string git_remote = Params().get("GitRemote"); @@ -89,70 +90,26 @@ void setupActionButton(QGridLayout *button_grid) { const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; QFuture future = QtConcurrent::run([=]() { + label->clear(); std::string output = executeCommand(cmd.c_str()); - auto formatted = QString::fromStdString(output) + text; - QMetaObject::invokeMethod(label, "clear", Qt::QueuedConnection); - QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, Q_ARG(QString, formatted)); + //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); + QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(output) + text)); }); - watcher->setFuture(future); - }); - QObject::connect(watcher, &QFutureWatcher::finished, [=]() { - btn->setEnabled(true); - update_btn->setEnabled(true); - reinstall_btn->setEnabled(true); - update_btn->setText(QObject::tr("Update")); - }); - - QObject::connect(reinstall_btn, &QPushButton::clicked, [=]() { - btn->setEnabled(false); - update_btn->setEnabled(false); - reinstall_btn->setEnabled(false); - reinstall_btn->setText(QObject::tr("Starting setup...")); - - const std::string delete_continue = "sudo rm /data/continue.sh"; - const std::string restart_op = "sudo systemctl restart comma"; - - const std::string cmd = delete_continue + "; " + restart_op + "; "; - - // Start setup command asynchronously - QtConcurrent::run([=]() { - executeCommand(cmd.c_str()); + QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { + btn->setEnabled(true); + update_btn->setEnabled(true); + update_btn->setText(QObject::tr("Update")); }); - QApplication::quit(); + watcher.setFuture(future); }); - #else update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); - QObject::connect(btn, &QPushButton::clicked, &app, &QApplication::quit); + QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); #endif - left_buttons->addWidget(update_btn); - left_buttons->addWidget(reinstall_btn); - right_buttons->addWidget(btn); - - button_grid->addLayout(left_buttons, 0, 0, Qt::AlignLeft | Qt::AlignBottom); - button_grid->addLayout(right_buttons, 0, 1, Qt::AlignRight | Qt::AlignBottom); -} - -int main(int argc, char *argv[]) { - initApp(argc, argv); - QApplication app(argc, argv); - QWidget window; - setMainWindow(&window); - - auto *main_layout = new QVBoxLayout(&window); - main_layout->setMargin(50); - - // Setup the text area layout - auto *text_layout = new QVBoxLayout(); - label = new QLabel(argv[1]); - setupErrorMessageLabel(text_layout); - main_layout->addLayout(text_layout); - - // Setup the button area layout - auto *button_layout = new QGridLayout(); - setupActionButton(button_layout); - main_layout->addLayout(button_layout); + main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); + main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); window.setStyleSheet(R"( * { @@ -169,14 +126,11 @@ int main(int argc, char *argv[]) { border-radius: 20px; margin-right: 40px; } - QPushButton:pressed { - background-color: #4a4a4a; - } QPushButton:disabled { color: #33FFFFFF; border: 2px solid #33FFFFFF; } )"); - return QApplication::exec(); + return a.exec(); } From a64b17b680679ccbc4013f068e6d35fcb8afdf62 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 9 Jul 2024 22:16:30 -0400 Subject: [PATCH 345/575] fix division by 0 and negative values --- selfdrive/ui/qt/onroad/annotated_camera.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 59624c4f46..a99517d33d 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1535,7 +1535,8 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState } if (chevron_data == 3 || chevron_data == 4) { position = (chevron_data == 3) ? 0 : 2; - chevron_text[position].append(QString::number(d_rel / v_ego, 'f', 1) + " " + "s"); + float val = (v_ego != 0.0f) ? std::max(0.0f, d_rel / v_ego) : 0.0f; + chevron_text[position].append(QString::number(val, 'f', 1) + " " + "s"); } int str_w = 200; // Width of the text box, might need adjustment From ebce2d15cb9d05f7e4c4c27fe565e24b97760c30 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 9 Jul 2024 23:49:48 -0400 Subject: [PATCH 346/575] sync --- selfdrive/ui/qt/onroad/annotated_camera.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index c9faed9ba2..fdea2a30dd 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1540,9 +1540,11 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState chevron_text[position].append(QString::number(val,'f', 0) + " " + (is_metric ? "km/h" : "mph")); } if (chevron_data == 3 || chevron_data == 4) { + float STOP_DISTANCE = 6.0; position = (chevron_data == 3) ? 0 : 2; - val = (v_ego != 0.0f) ? std::max(0.0f, d_rel / v_ego) : 0.0f; - chevron_text[position].append(QString::number(val, 'f', 1) + " " + "s"); + val = (v_ego > 0) ? std::min(std::max(0.0f, (d_rel + STOP_DISTANCE) / v_ego), 200.0f) : 0.0f; + bool invalid_val = val == 200.0f || val == 0.0f; + chevron_text[position].append(invalid_val ? "---" : QString::number(val, 'f', 1) + " " + "s"); } float str_w = 200; // Width of the text box, might need adjustment From 6c481e5a0759c6684fe7ebf01ddc1278a414efd1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 9 Jul 2024 23:54:47 -0400 Subject: [PATCH 347/575] more --- selfdrive/ui/qt/onroad/annotated_camera.cc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index fdea2a30dd..5e3ee832e6 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1525,25 +1525,26 @@ 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 = 3; + 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 == 4) { + 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 == 4) { + 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 == 4) { - float STOP_DISTANCE = 6.0; + if (chevron_data == 3 || chevron_data == chevron_all) { + const float STOP_DISTANCE = 6.0; position = (chevron_data == 3) ? 0 : 2; val = (v_ego > 0) ? std::min(std::max(0.0f, (d_rel + STOP_DISTANCE) / v_ego), 200.0f) : 0.0f; - bool invalid_val = val == 200.0f || val == 0.0f; + const bool invalid_val = val == 200.0f || val == 0.0f; chevron_text[position].append(invalid_val ? "---" : QString::number(val, 'f', 1) + " " + "s"); } From f20973d4cf23f5984fd4b687fcaf0d40dc4ead17 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 10 Jul 2024 11:27:07 -0400 Subject: [PATCH 348/575] cleaner --- selfdrive/ui/qt/onroad/annotated_camera.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 5e3ee832e6..bb19798e92 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1541,11 +1541,11 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState chevron_text[position].append(QString::number(val,'f', 0) + " " + (is_metric ? "km/h" : "mph")); } if (chevron_data == 3 || chevron_data == chevron_all) { - const float STOP_DISTANCE = 6.0; position = (chevron_data == 3) ? 0 : 2; - val = (v_ego > 0) ? std::min(std::max(0.0f, (d_rel + STOP_DISTANCE) / v_ego), 200.0f) : 0.0f; - const bool invalid_val = val == 200.0f || val == 0.0f; - chevron_text[position].append(invalid_val ? "---" : QString::number(val, 'f', 1) + " " + "s"); + 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 From 4e6fd4ee81d7851fb3ff5ded032cbca6937bd9e1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 10 Jul 2024 20:37:58 -0400 Subject: [PATCH 349/575] try lowering it for to 20 due to exceeded message traversal limit --- msgq_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgq_repo b/msgq_repo index da77480a96..7622841e5d 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit da77480a969b3a48ee8c03839e7b6a66f6b5ed97 +Subproject commit 7622841e5d95a39c7010400dbf3f44d771eea4b4 From f9159301b392e34d829b46b4da62b2c1c45ed675 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 10 Jul 2024 21:11:02 -0400 Subject: [PATCH 350/575] add logging --- selfdrive/car/gm/interface.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index f1e7161396..f39d150c2f 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -287,6 +287,10 @@ class CarInterface(CarInterfaceBase): if ret.vEgo < self.CP.minSteerSpeed and self.CS.madsEnabled: events.add(EventName.belowSteerSpeed) + ret.customStockLong = self.CS.update_custom_stock_long(self.CC.cruise_button, self.CC.final_speed_kph, + self.CC.target_speed, self.CC.v_set_dis, + self.CC.speed_diff, self.CC.button_type) + ret.events = events.to_msg() return ret From a0f96588eaf6e966ac56aaf60b41adff81ff4d98 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 10 Jul 2024 21:24:08 -0400 Subject: [PATCH 351/575] need unpressed --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 518337d559..3682e37dca 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 518337d559731f17a57bc24898c8cfced0d44132 +Subproject commit 3682e37dcaf2ce52b06b9c1309b50d9c130186aa From bc23432dd5a5fac6577e129abb871866a08d121d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 10 Jul 2024 21:24:45 -0400 Subject: [PATCH 352/575] need unpressed --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 16ede3d7ae..4978ccfee9 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 16ede3d7ae8e597c6d2a16b967ae1d7e0c5c8cb5 +Subproject commit 4978ccfee9c179b9aac785791aee9610836da2b5 From f947397d841a09a3a7a3134235a8c8002ee1f80c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 10 Jul 2024 22:19:53 -0400 Subject: [PATCH 353/575] try this --- selfdrive/car/gm/carcontroller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index d8b06e026f..426168d215 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -221,7 +221,7 @@ class CarController(CarControllerBase): if not (self.v_tsc_state != 0 or self.m_tsc_state > 1) and abs(self.target_speed - self.v_set_dis) <= 2: send_freq = 3 if self.frame % 12 < 6: # thanks to mochi86420 for the magic numbers - can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, (CS.button_counter + 2) % 4, self.cruise_button)) + can_sends.extend([gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, (CS.button_counter + 2) % 4, self.cruise_button)] * 3) if self.CP.networkLocation == NetworkLocation.fwdCamera: # Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1 @@ -301,7 +301,7 @@ class CarController(CarControllerBase): return cruise_button def type_3(self): - cruise_button = None + cruise_button = CruiseButtons.UNPRESS self.button_count += 1 if self.button_count > self.t_interval: self.button_type = 0 From 4a67489e3337d87e65b846fbf7fcd3bc30bcae47 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 21:47:14 -0400 Subject: [PATCH 354/575] this might have been blocking engagement --- selfdrive/car/gm/carcontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 426168d215..ce572ce86c 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -239,7 +239,7 @@ class CarController(CarControllerBase): # multikyd methods, sunnyhaibin logic def get_cruise_buttons_status(self, CS): - if not CS.out.cruiseState.enabled or CS.cruise_buttons != CruiseButtons.UNPRESS or CS.cruise_buttons != CruiseButtons.INIT: + if not CS.out.cruiseState.enabled or CS.cruise_buttons != CruiseButtons.UNPRESS: self.timer = 40 elif self.timer: self.timer -= 1 From 692e6783e48df1cc3e6b8659936a96cff2470a84 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 22:37:04 -0400 Subject: [PATCH 355/575] typo --- selfdrive/car/gm/carcontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index ce572ce86c..bd7e33fa14 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -221,7 +221,7 @@ class CarController(CarControllerBase): if not (self.v_tsc_state != 0 or self.m_tsc_state > 1) and abs(self.target_speed - self.v_set_dis) <= 2: send_freq = 3 if self.frame % 12 < 6: # thanks to mochi86420 for the magic numbers - can_sends.extend([gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, (CS.button_counter + 2) % 4, self.cruise_button)] * 3) + can_sends.extend([gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, (CS.buttons_counter + 2) % 4, self.cruise_button)] * 3) if self.CP.networkLocation == NetworkLocation.fwdCamera: # Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1 From 120699df971abfcd69114526f2fb0f98b4d254b8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 22:36:00 -0400 Subject: [PATCH 356/575] Chrysler: Use modern button events --- selfdrive/car/chrysler/carcontroller.py | 12 +++++++---- selfdrive/car/chrysler/carstate.py | 23 ++++++++++++--------- selfdrive/car/chrysler/interface.py | 27 +++++++++---------------- selfdrive/car/chrysler/values.py | 14 +++++++------ selfdrive/car/interfaces.py | 3 +++ 5 files changed, 43 insertions(+), 36 deletions(-) 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..6d949e9f9a 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,18 @@ class CarState(CarStateBase): unit=1, ) + # Buttons + button_events = [] + 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 + button_events.append(event) + self.button_states[button.event_type] = state + self.button_events = button_events + # 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 +126,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..bc80530011 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -2,7 +2,7 @@ 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.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,15 @@ 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 = [ + *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, 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 +120,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) @@ -146,14 +141,14 @@ class CarInterface(CarInterfaceBase): # 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.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events # events events = self.create_common_events(ret, c, extra_gears=[car.CarState.GearShifter.low], pcm_enable=False) @@ -180,6 +175,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/interfaces.py b/selfdrive/car/interfaces.py index 5fe64832c8..f2d921e108 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 @@ -780,6 +781,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]] From e4175e636ae66350d117e4fa60734c35507387cd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 22:02:00 -0400 Subject: [PATCH 357/575] Volkswagen: Use modern button events --- selfdrive/car/volkswagen/carstate.py | 26 +++------------------- selfdrive/car/volkswagen/interface.py | 32 +++++++-------------------- selfdrive/car/volkswagen/values.py | 10 --------- 3 files changed, 11 insertions(+), 57 deletions(-) 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..fe042984e2 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -4,7 +4,7 @@ from openpilot.common.params import Params from openpilot.selfdrive.car import get_safety_config, create_mads_event 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,20 @@ 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.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events events = self.create_common_events(ret, c, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], pcm_enable=False, @@ -199,8 +187,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)) From dbc086744b33f06a4363b1443bf3404bb44637a2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:06:18 -0400 Subject: [PATCH 358/575] Ford: Use modern button events --- selfdrive/car/ford/carstate.py | 26 +++++++++++++++----------- selfdrive/car/ford/interface.py | 29 ++++++++++------------------- selfdrive/car/ford/values.py | 21 +++++++++++---------- 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 21e2f12def..fb06bf5f5f 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,18 @@ class CarState(CarStateBase): else: ret.gearShifter = GearShifter.drive + # Buttons + button_events = [] + 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 + button_events.append(event) + self.button_states[button.event_type] = state + self.button_events = button_events + # 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 +123,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..68db0f096d 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -3,7 +3,7 @@ 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.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,15 @@ 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 = [ + *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: @@ -101,7 +95,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) @@ -116,14 +110,14 @@ class CarInterface(CarInterfaceBase): if self.CS.out.madsEnabled != self.CS.madsEnabled: if self.mads_event_lock: - buttonEvents.append(create_mads_event(self.mads_event_lock)) + self.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events events = self.create_common_events(ret, c, extra_gears=[GearShifter.manumatic], pcm_enable=False) @@ -134,7 +128,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 From 29bc69be5b46da559550953dec3ad268c258e993 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:15:34 -0400 Subject: [PATCH 359/575] Mazda: Use modern button events --- selfdrive/car/mazda/carcontroller.py | 9 +++++---- selfdrive/car/mazda/carstate.py | 23 ++++++++++++++--------- selfdrive/car/mazda/interface.py | 27 ++++++++++----------------- selfdrive/car/mazda/values.py | 18 ++++++++++-------- 4 files changed, 39 insertions(+), 38 deletions(-) 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..6fd287fa7c 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,18 @@ class CarState(CarStateBase): can_gear = int(cp.vl["GEAR"]["GEAR"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) + # Buttons + button_events = [] + 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 + button_events.append(event) + self.button_states[button.event_type] = state + self.button_events = button_events + 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 +124,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..f8919d13e7 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -1,7 +1,7 @@ #!/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.mazda.values import CAR, LKAS_LIMITS from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase @@ -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) @@ -82,14 +77,14 @@ class CarInterface(CarInterfaceBase): # 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.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events # events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], @@ -108,6 +103,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 From a3e2661ca528c2f8b5abc47faa30c206675e75a7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:20:19 -0400 Subject: [PATCH 360/575] Nissan: Use modern button events --- selfdrive/car/nissan/interface.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 3d744a41a0..707eab06c0 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -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) @@ -58,19 +58,19 @@ class CarInterface(CarInterfaceBase): be = car.CarState.ButtonEvent.new_message() be.pressed = True be.type = ButtonType.cancel - buttonEvents.append(be) + self.CS.button_events.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.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], pcm_enable=False) From 207b4473c75b0e3aafc87da88a6a5a02ae039c9a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:22:25 -0400 Subject: [PATCH 361/575] GM: Use modern button events --- selfdrive/car/gm/interface.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 22a8d619a6..2e2f63fd44 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -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) @@ -255,14 +254,14 @@ class CarInterface(CarInterfaceBase): # 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.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events # 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, From 4e9ab2abafb1c1234c2f0652bfd8e85086812f3f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:22:50 -0400 Subject: [PATCH 362/575] Honda: Use modern button events --- selfdrive/car/honda/interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 457b095e6a..52984035c4 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,7 @@ 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 # events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low], pcm_enable=False) From 25fdd735c2ed6fb5a3be8d932afce0333cbd111d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:23:21 -0400 Subject: [PATCH 363/575] Hyundai: Use modern button events --- selfdrive/car/hyundai/interface.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index e801b47771..60411abc39 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -205,10 +205,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 @@ -231,7 +231,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: @@ -245,14 +245,14 @@ class CarInterface(CarInterfaceBase): # 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.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events # 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 From 56119eb66ef4518f41b74404a581c1dcd60f7bac Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:23:43 -0400 Subject: [PATCH 364/575] Subaru: Use modern button events --- selfdrive/car/subaru/interface.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 744c017a96..967d426fe4 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -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: @@ -150,19 +148,19 @@ class CarInterface(CarInterfaceBase): be = car.CarState.ButtonEvent.new_message() be.pressed = True be.type = ButtonType.cancel - buttonEvents.append(be) + self.CS.button_events.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.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low], pcm_enable=False) From a44677bde3008fda5f59660111fe6b01e08fc472 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:24:08 -0400 Subject: [PATCH 365/575] Toyota: Use modern button events --- selfdrive/car/toyota/interface.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 451265f9cd..7b808f5d61 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -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) @@ -250,19 +249,19 @@ class CarInterface(CarInterfaceBase): be = car.CarState.ButtonEvent.new_message() be.pressed = True be.type = ButtonType.cancel - buttonEvents.append(be) + self.CS.button_events.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.CS.button_events.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.CS.button_events.append(create_mads_event(self.mads_event_lock)) self.mads_event_lock = True - ret.buttonEvents = buttonEvents + ret.buttonEvents = self.CS.button_events # events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], From 11567ed7dc858400f5fda996487e5d778d0b8b00 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 11 Jul 2024 23:29:26 -0400 Subject: [PATCH 366/575] Empty button events at start of loop --- selfdrive/car/gm/carstate.py | 1 + selfdrive/car/honda/carstate.py | 1 + selfdrive/car/hyundai/carstate.py | 1 + selfdrive/car/nissan/carstate.py | 1 + selfdrive/car/subaru/carstate.py | 1 + selfdrive/car/toyota/carstate.py | 1 + 6 files changed, 6 insertions(+) diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 2fc1ac024b..78092c23bd 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -33,6 +33,7 @@ class CarState(CarStateBase): self.prev_lkas_enabled = False def update(self, pt_cp, cam_cp, loopback_cp): + self.button_events = [] ret = car.CarState.new_message() self.prev_cruise_buttons = self.cruise_buttons diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index ae8746d958..c50c6d08fb 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -111,6 +111,7 @@ class CarState(CarStateBase): self.dash_speed_seen = False def update(self, cp, cp_cam, cp_body): + self.button_events = [] ret = car.CarState.new_message() # car params diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index d8329c5ba7..4066a988ec 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -63,6 +63,7 @@ class CarState(CarStateBase): self._speed_limit_clu = 0 def update(self, cp, cp_cam): + self.button_events = [] if self.CP.carFingerprint in CANFD_CAR: return self.update_canfd(cp, cp_cam) diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index 17c6abf681..8dba6dcdf6 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -24,6 +24,7 @@ class CarState(CarStateBase): self.distance_button = 0 def update(self, cp, cp_adas, cp_cam): + self.button_events = [] ret = car.CarState.new_message() self.prev_distance_button = self.distance_button diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 5cff425efe..1a48901dde 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -20,6 +20,7 @@ class CarState(CarStateBase): self.prev_lkas_enabled = None def update(self, cp, cp_cam, cp_body): + self.button_events = [] ret = car.CarState.new_message() self.prev_mads_enabled = self.mads_enabled diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 456282982b..53bf7da4c2 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -85,6 +85,7 @@ class CarState(CarStateBase): self.frame = 0 def update(self, cp, cp_cam): + self.button_events = [] ret = car.CarState.new_message() self.prev_mads_enabled = self.mads_enabled From e7ea608c4520d577ca9e0d183b9ea726a76797d1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Jul 2024 22:09:45 -0400 Subject: [PATCH 367/575] Split all SP files out --- selfdrive/ui/SConscript | 23 +++++-------------- selfdrive/ui/qt/offroad/settings.cc | 2 +- selfdrive/ui/qt/offroad/sunnypilot_main.h | 11 --------- selfdrive/ui/qt/onroad/onroad_home.cc | 2 +- selfdrive/ui/sunnypilot/SConscript | 19 +++++++++++++++ .../qt/network/sunnylink/models/role_model.h | 0 .../sunnylink/models/sponsor_role_model.h | 0 .../qt/network/sunnylink/models/user_model.h | 0 .../sunnylink/services/base_device_service.cc | 2 +- .../sunnylink/services/base_device_service.h | 0 .../sunnylink/services/role_service.cc | 2 +- .../network/sunnylink/services/role_service.h | 4 ++-- .../sunnylink/services/user_service.cc | 2 +- .../network/sunnylink/services/user_service.h | 4 ++-- .../qt/network/sunnylink/sunnylink_client.cc | 4 ++-- .../qt/network/sunnylink/sunnylink_client.h | 4 ++-- .../qt/offroad}/custom_offsets_settings.cc | 2 +- .../qt/offroad}/custom_offsets_settings.h | 0 .../qt/offroad}/display_settings.cc | 2 +- .../qt/offroad}/display_settings.h | 0 .../qt/offroad}/json_fetcher.h | 0 .../qt/offroad}/lane_change_settings.cc | 2 +- .../qt/offroad}/lane_change_settings.h | 0 .../qt/offroad}/locations_fetcher.h | 2 +- .../qt/offroad}/mads_settings.cc | 2 +- .../qt/offroad}/mads_settings.h | 0 .../qt/offroad}/models_fetcher.cc | 2 +- .../qt/offroad}/models_fetcher.h | 2 +- .../qt/offroad}/monitoring_settings.cc | 2 +- .../qt/offroad}/monitoring_settings.h | 0 .../qt/offroad}/osm_settings.cc | 2 +- .../qt/offroad}/osm_settings.h | 2 +- .../qt/offroad}/software_settings_sp.cc | 2 +- .../qt/offroad}/software_settings_sp.h | 2 +- .../offroad}/speed_limit_control_settings.cc | 2 +- .../offroad}/speed_limit_control_settings.h | 0 .../offroad}/speed_limit_policy_settings.cc | 2 +- .../qt/offroad}/speed_limit_policy_settings.h | 0 .../offroad}/speed_limit_warning_settings.cc | 2 +- .../offroad}/speed_limit_warning_settings.h | 0 .../qt/offroad}/sunnylink_settings.cc | 2 +- .../qt/offroad}/sunnylink_settings.h | 2 +- .../qt/offroad}/sunnypilot_settings.cc | 2 +- .../qt/offroad}/sunnypilot_settings.h | 12 +++++----- .../qt/offroad}/trips_settings.cc | 2 +- .../qt/offroad}/trips_settings.h | 2 +- .../qt/offroad}/vehicle_settings.cc | 2 +- .../qt/offroad}/vehicle_settings.h | 0 .../qt/offroad}/visuals_settings.cc | 2 +- .../qt/offroad}/visuals_settings.h | 0 .../qt/onroad}/onroad_settings.cc | 4 ++-- .../qt/onroad}/onroad_settings.h | 0 .../qt/onroad}/onroad_settings_panel.cc | 8 +++---- .../qt/onroad}/onroad_settings_panel.h | 0 .../qt/widgets}/drive_stats.cc | 2 +- .../qt/widgets}/drive_stats.h | 0 selfdrive/ui/sunnypilot/sunnypilot_main.h | 11 +++++++++ selfdrive/ui/ui.cc | 2 +- selfdrive/ui/ui.h | 6 ++--- 59 files changed, 88 insertions(+), 80 deletions(-) delete mode 100644 selfdrive/ui/qt/offroad/sunnypilot_main.h create mode 100644 selfdrive/ui/sunnypilot/SConscript rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/models/role_model.h (100%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/models/sponsor_role_model.h (100%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/models/user_model.h (100%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/services/base_device_service.cc (96%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/services/base_device_service.h (100%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/services/role_service.cc (88%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/services/role_service.h (74%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/services/user_service.cc (89%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/services/user_service.h (74%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/sunnylink_client.cc (50%) rename selfdrive/ui/{ => sunnypilot}/qt/network/sunnylink/sunnylink_client.h (61%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/custom_offsets_settings.cc (97%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/custom_offsets_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/display_settings.cc (98%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/display_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/json_fetcher.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/lane_change_settings.cc (98%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/lane_change_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/locations_fetcher.h (97%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/mads_settings.cc (97%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/mads_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/models_fetcher.cc (98%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/models_fetcher.h (98%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/monitoring_settings.cc (93%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/monitoring_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/osm_settings.cc (99%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/osm_settings.h (99%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/software_settings_sp.cc (99%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/software_settings_sp.h (98%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/speed_limit_control_settings.cc (98%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/speed_limit_control_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/speed_limit_policy_settings.cc (96%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/speed_limit_policy_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/speed_limit_warning_settings.cc (98%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/speed_limit_warning_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/sunnylink_settings.cc (99%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/sunnylink_settings.h (97%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/sunnypilot_settings.cc (99%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/sunnypilot_settings.h (87%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/trips_settings.cc (92%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/trips_settings.h (81%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/vehicle_settings.cc (99%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/vehicle_settings.h (100%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/visuals_settings.cc (98%) rename selfdrive/ui/{qt/offroad/sunnypilot => sunnypilot/qt/offroad}/visuals_settings.h (100%) rename selfdrive/ui/{qt => sunnypilot/qt/onroad}/onroad_settings.cc (99%) rename selfdrive/ui/{qt => sunnypilot/qt/onroad}/onroad_settings.h (100%) rename selfdrive/ui/{qt => sunnypilot/qt/onroad}/onroad_settings_panel.cc (75%) rename selfdrive/ui/{qt => sunnypilot/qt/onroad}/onroad_settings_panel.h (100%) rename selfdrive/ui/{qt/widgets/sunnypilot => sunnypilot/qt/widgets}/drive_stats.cc (98%) rename selfdrive/ui/{qt/widgets/sunnypilot => sunnypilot/qt/widgets}/drive_stats.h (100%) create mode 100644 selfdrive/ui/sunnypilot/sunnypilot_main.h diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index e1233b5cbc..7060a6e9ee 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -2,6 +2,9 @@ import os import json Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations', 'UBUNTU_FOCAL') +SConscript(['sunnypilot/SConscript']) +Import('sp_widgets_src', 'sp_qt_src') + base_libs = [common, messaging, visionipc, transformations, 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] @@ -22,20 +25,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", - "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] - -widgets_src += ["qt/offroad/sunnypilot/display_settings.cc", "qt/offroad/sunnypilot/sunnypilot_settings.cc", - "qt/offroad/sunnypilot/vehicle_settings.cc", "qt/offroad/sunnypilot/visuals_settings.cc", - "qt/offroad/sunnypilot/trips_settings.cc", "qt/offroad/sunnypilot/mads_settings.cc", - "qt/offroad/sunnypilot/lane_change_settings.cc", "qt/offroad/sunnypilot/speed_limit_control_settings.cc", - "qt/offroad/sunnypilot/monitoring_settings.cc", "qt/offroad/sunnypilot/osm_settings.cc", - "qt/offroad/sunnypilot/custom_offsets_settings.cc", "qt/widgets/sunnypilot/drive_stats.cc", - "qt/offroad/sunnypilot/software_settings_sp.cc", "qt/offroad/sunnypilot/models_fetcher.cc", - "qt/offroad/sunnypilot/speed_limit_warning_settings.cc", "qt/offroad/sunnypilot/speed_limit_policy_settings.cc", - "qt/offroad/sunnypilot/sunnylink_settings.cc"] - -widgets_src += ["qt/network/sunnylink/sunnylink_client.cc", "qt/network/sunnylink/services/base_device_service.cc", - "qt/network/sunnylink/services/role_service.cc", "qt/network/sunnylink/services/user_service.cc"] + "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src qt_env['CPPDEFINES'] = [] if maps: @@ -53,8 +43,7 @@ qt_src = ["main.cc", "qt/sidebar.cc", "qt/body.cc", "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc", "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", "qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", - "qt/onroad/buttons.cc", "qt/onroad/alerts.cc", - "qt/onroad_settings.cc", "qt/onroad_settings_panel.cc"] + "qt/onroad/buttons.cc", "qt/onroad/alerts.cc"] + sp_qt_src # build translation files with open(File("translations/languages.json").abspath) as f: @@ -94,7 +83,7 @@ qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs) if GetOption('extras'): qt_src.remove("main.cc") # replaced by test_runner - #qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs) + qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs) qt_env.Program('tests/ui_snapshot', [asset_obj, "tests/ui_snapshot.cc"] + qt_src, LIBS=qt_libs) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index f9f852a3b4..22448e895b 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -13,11 +13,11 @@ #include "common/util.h" #include "selfdrive/ui/qt/network/networking.h" #include "selfdrive/ui/qt/offroad/settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot_main.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/widgets/prime.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/widgets/ssh_keys.h" +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon diff --git a/selfdrive/ui/qt/offroad/sunnypilot_main.h b/selfdrive/ui/qt/offroad/sunnypilot_main.h deleted file mode 100644 index fe4526db08..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot_main.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "selfdrive/ui/qt/offroad/sunnypilot/display_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h" diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc index 7413cdff7e..2425c31aa9 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/qt/onroad/onroad_home.cc @@ -7,7 +7,7 @@ #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/maps/map_panel.h" #endif -#include "selfdrive/ui/qt/onroad_settings_panel.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript new file mode 100644 index 0000000000..46b6e7c649 --- /dev/null +++ b/selfdrive/ui/sunnypilot/SConscript @@ -0,0 +1,19 @@ +widgets_src = ["sunnypilot/qt/offroad/display_settings.cc", "sunnypilot/qt/offroad/sunnypilot_settings.cc", + "sunnypilot/qt/offroad/vehicle_settings.cc", "sunnypilot/qt/offroad/visuals_settings.cc", + "sunnypilot/qt/offroad/trips_settings.cc", "sunnypilot/qt/offroad/mads_settings.cc", + "sunnypilot/qt/offroad/lane_change_settings.cc", "sunnypilot/qt/offroad/speed_limit_control_settings.cc", + "sunnypilot/qt/offroad/monitoring_settings.cc", "sunnypilot/qt/offroad/osm_settings.cc", + "sunnypilot/qt/offroad/custom_offsets_settings.cc", "sunnypilot/qt/widgets/drive_stats.cc", + "sunnypilot/qt/offroad/software_settings_sp.cc", "sunnypilot/qt/offroad/models_fetcher.cc", + "sunnypilot/qt/offroad/speed_limit_warning_settings.cc", "sunnypilot/qt/offroad/speed_limit_policy_settings.cc", + "sunnypilot/qt/offroad/sunnylink_settings.cc"] + +network_src = ["sunnypilot/qt/network/sunnylink/sunnylink_client.cc", "sunnypilot/qt/network/sunnylink/services/base_device_service.cc", + "sunnypilot/qt/network/sunnylink/services/role_service.cc", "sunnypilot/qt/network/sunnylink/services/user_service.cc"] + +qt_src = ["sunnypilot/qt/onroad/onroad_settings.cc", "sunnypilot/qt/onroad/onroad_settings_panel.cc"] + +sp_widgets_src = widgets_src + network_src +sp_qt_src = qt_src + +Export('sp_widgets_src', 'sp_qt_src') diff --git a/selfdrive/ui/qt/network/sunnylink/models/role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h similarity index 100% rename from selfdrive/ui/qt/network/sunnylink/models/role_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h diff --git a/selfdrive/ui/qt/network/sunnylink/models/sponsor_role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h similarity index 100% rename from selfdrive/ui/qt/network/sunnylink/models/sponsor_role_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h diff --git a/selfdrive/ui/qt/network/sunnylink/models/user_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h similarity index 100% rename from selfdrive/ui/qt/network/sunnylink/models/user_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h diff --git a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc similarity index 96% rename from selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc index 2f2561a49a..46752b38a4 100644 --- a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc @@ -2,7 +2,7 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h" BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent), initial_request(nullptr), repeater(nullptr) { param_watcher = new ParamWatcher(this); diff --git a/selfdrive/ui/qt/network/sunnylink/services/base_device_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h similarity index 100% rename from selfdrive/ui/qt/network/sunnylink/services/base_device_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h diff --git a/selfdrive/ui/qt/network/sunnylink/services/role_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc similarity index 88% rename from selfdrive/ui/qt/network/sunnylink/services/role_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc index 8bd0904421..e31d2af5fe 100644 --- a/selfdrive/ui/qt/network/sunnylink/services/role_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/network/sunnylink/services/role_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h" #include #include diff --git a/selfdrive/ui/qt/network/sunnylink/services/role_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h similarity index 74% rename from selfdrive/ui/qt/network/sunnylink/services/role_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h index bc40342982..13b5d0505b 100644 --- a/selfdrive/ui/qt/network/sunnylink/services/role_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h @@ -1,8 +1,8 @@ #ifndef ROLESERVICE_H #define ROLESERVICE_H -#include "selfdrive/ui/qt/network/sunnylink/services/base_device_service.h" -#include "selfdrive/ui/qt/network/sunnylink/models/role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" class RoleService : public BaseDeviceService { Q_OBJECT diff --git a/selfdrive/ui/qt/network/sunnylink/services/user_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc similarity index 89% rename from selfdrive/ui/qt/network/sunnylink/services/user_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc index 2d52421d69..9076dc6a5c 100644 --- a/selfdrive/ui/qt/network/sunnylink/services/user_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" #include #include diff --git a/selfdrive/ui/qt/network/sunnylink/services/user_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h similarity index 74% rename from selfdrive/ui/qt/network/sunnylink/services/user_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h index e4af93fa29..53f490722c 100644 --- a/selfdrive/ui/qt/network/sunnylink/services/user_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h @@ -1,8 +1,8 @@ #ifndef USERSERVICE_H #define USERSERVICE_H -#include "selfdrive/ui/qt/network/sunnylink/services/base_device_service.h" -#include "selfdrive/ui/qt/network/sunnylink/models/user_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h" class UserService : public BaseDeviceService { Q_OBJECT diff --git a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc similarity index 50% rename from selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc index 2d1613c0eb..b56d9bd582 100644 --- a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc @@ -1,5 +1,5 @@ -#include "selfdrive/ui/qt/network/sunnylink/sunnylink_client.h" -#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" SunnylinkClient::SunnylinkClient(QObject* parent) : QObject(parent) { role_service = new RoleService(parent); diff --git a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h similarity index 61% rename from selfdrive/ui/qt/network/sunnylink/sunnylink_client.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h index 872d80ccf7..cd802b590e 100644 --- a/selfdrive/ui/qt/network/sunnylink/sunnylink_client.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h @@ -3,8 +3,8 @@ #include -#include "selfdrive/ui/qt/network/sunnylink/services/role_service.h" -#include "selfdrive/ui/qt/network/sunnylink/services/user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" class SunnylinkClient : public QObject { Q_OBJECT diff --git a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc similarity index 97% rename from selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc index 405eca50ce..689f42bb11 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h" CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc similarity index 98% rename from selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc index 5d11c8526c..02d723c363 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/display_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/display_settings.h" DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { // param, title, desc, icon diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/display_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/display_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc similarity index 98% rename from selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc index 8f16091460..e950ab71c3 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h" LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/locations_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h similarity index 97% rename from selfdrive/ui/qt/offroad/sunnypilot/locations_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h index 1e5b20139f..e79c6f2e94 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/locations_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h @@ -5,7 +5,7 @@ #include #include -#include "selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h" static const std::tuple defaultLocation = std::make_tuple("== None ==", ""); // New class LocationsFetcher that handles web requests and JSON parsing diff --git a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc similarity index 97% rename from selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc index e213cc19a7..0a3ced7c69 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h" MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.cc similarity index 98% rename from selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc rename to selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.cc index 3df9c09fe3..a46e75706e 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h" #include ModelsFetcher::ModelsFetcher(QObject* parent) : QObject(parent) { diff --git a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h similarity index 98% rename from selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h index 22ab81e541..8154065e6b 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h @@ -11,7 +11,7 @@ #include "common/util.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/json_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "system/hardware/hw.h" diff --git a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc similarity index 93% rename from selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc index e2a410f9ff..7d740a3586 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h" MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc similarity index 99% rename from selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc index 236e4406cf..be3837262a 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h" OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h similarity index 99% rename from selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h index 2f9cf8455f..cc2556b150 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h @@ -10,7 +10,7 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/network/wifi_manager.h" #include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/locations_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/ui.h" diff --git a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc similarity index 99% rename from selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc rename to selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc index bbe8572797..ce6a6712e6 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h" SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { // Get current model name and create new ButtonControl diff --git a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h similarity index 98% rename from selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h rename to selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h index 384a592de4..4f8fc0d22a 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h @@ -3,7 +3,7 @@ #include "common/model.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/offroad/settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h" class SoftwarePanelSP final : public SoftwarePanel { Q_OBJECT diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc similarity index 98% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc index 7686f0a873..13b1b715d4 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h" SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc similarity index 96% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc index 2de70e9b30..f93b14d62d 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc similarity index 98% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc index 6b355e2740..16557c1228 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h" SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc similarity index 99% rename from selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc index 1ebaf4f14e..c363e5655f 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h" #include #include diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h similarity index 97% rename from selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h index fdca62d1c5..5a7ead697a 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/ui/qt/network/sunnylink/sunnylink_client.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc similarity index 99% rename from selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc index 8db23500b4..055bb648a4 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h" SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h similarity index 87% rename from selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index 8026cf7f1b..379a9a2875 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -2,12 +2,12 @@ #include "common/model.h" #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" diff --git a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.cc similarity index 92% rename from selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/trips_settings.cc index e21dc675ae..0f080926b4 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h" TripsPanel::TripsPanel(QWidget* parent) : QFrame(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h similarity index 81% rename from selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h index dc77acac75..539bdb2677 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" class TripsPanel : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc similarity index 99% rename from selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc index ef0954c1ed..42222f0b2b 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h" VehiclePanel::VehiclePanel(QWidget *parent) : QWidget(parent) { main_layout = new QStackedLayout(this); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc similarity index 98% rename from selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc index 46bd84b6d9..364c1e2f6d 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h" VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { // param, title, desc, icon diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h similarity index 100% rename from selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h diff --git a/selfdrive/ui/qt/onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc similarity index 99% rename from selfdrive/ui/qt/onroad_settings.cc rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc index 00810294ba..4ef4afc025 100644 --- a/selfdrive/ui/qt/onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/onroad_settings.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" #include @@ -7,7 +7,7 @@ #include "common/util.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) { setContentsMargins(0, 0, 0, 0); diff --git a/selfdrive/ui/qt/onroad_settings.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h similarity index 100% rename from selfdrive/ui/qt/onroad_settings.h rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h diff --git a/selfdrive/ui/qt/onroad_settings_panel.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc similarity index 75% rename from selfdrive/ui/qt/onroad_settings_panel.cc rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc index 163f0fb31f..8a8283fad3 100644 --- a/selfdrive/ui/qt/onroad_settings_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc @@ -1,11 +1,11 @@ -#include "selfdrive/ui/qt/onroad_settings_panel.h" +#include "../../../sunnypilot/qt/onroad/onroad_settings_panel.h" #include #include -#include "selfdrive/ui/qt/onroad_settings.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" +#include "../../../qt/util.h" +#include "../../../ui.h" OnroadSettingsPanel::OnroadSettingsPanel(QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); diff --git a/selfdrive/ui/qt/onroad_settings_panel.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h similarity index 100% rename from selfdrive/ui/qt/onroad_settings_panel.h rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h diff --git a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc similarity index 98% rename from selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc rename to selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc index e4c127e6b5..2ac3f59893 100644 --- a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" #include #include diff --git a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h similarity index 100% rename from selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h rename to selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h diff --git a/selfdrive/ui/sunnypilot/sunnypilot_main.h b/selfdrive/ui/sunnypilot/sunnypilot_main.h new file mode 100644 index 0000000000..c94e13050d --- /dev/null +++ b/selfdrive/ui/sunnypilot/sunnypilot_main.h @@ -0,0 +1,11 @@ +#pragma once + +#include "selfdrive/ui/sunnypilot/qt/offroad/display_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h" diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 126be0d6b1..72571c4b85 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -11,7 +11,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" -#include "qt/network/sunnylink/models/role_model.h" +#include "sunnypilot/qt/network/sunnylink/models/role_model.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index a9fb6f0671..eb02b8d2d8 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -15,9 +15,9 @@ #include "common/mat.h" #include "common/params.h" #include "common/timing.h" -#include "qt/network/sunnylink/models/role_model.h" -#include "qt/network/sunnylink/models/sponsor_role_model.h" -#include "qt/network/sunnylink/models/user_model.h" +#include "sunnypilot/qt/network/sunnylink/models/role_model.h" +#include "sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h" +#include "sunnypilot/qt/network/sunnylink/models/user_model.h" #include "system/hardware/hw.h" const int UI_BORDER_SIZE = 30; From 43a38ed0394c4803f68556cd92a0119e8538cdbb Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Jul 2024 22:28:30 -0400 Subject: [PATCH 368/575] set constant with added gpg key id --- SConstruct | 34 ++++++++++++++++++++++++++++++++++ selfdrive/ui/SConscript | 11 +++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/SConstruct b/SConstruct index da70e4e587..59858c8e65 100644 --- a/SConstruct +++ b/SConstruct @@ -7,6 +7,8 @@ import numpy as np import SCons.Errors +from openpilot.common.basedir import BASEDIR + SCons.Warnings.warningAsException(True) # pending upstream fix - https://github.com/SCons/scons/issues/4461 @@ -17,6 +19,34 @@ AGNOS = TICI UBUNTU_FOCAL = int(subprocess.check_output('[ -f /etc/os-release ] && . /etc/os-release && [ "$ID" = "ubuntu" ] && [ "$VERSION_ID" = "20.04" ] && echo 1 || echo 0', shell=True, encoding='utf-8').rstrip()) Export('UBUNTU_FOCAL') +gpg_id_path = os.path.join(BASEDIR, "../gpg_id.txt") + +# Read the required GPG key ID from gpg_id.txt +try: + with open(gpg_id_path, 'r') as file: + REQUIRED_GPG_KEY_ID = file.read().strip() + print(f"Required GPG key ID: {REQUIRED_GPG_KEY_ID}") +except IOError as e: + print(f"Failed to read GPG key ID from {gpg_id_path}. Error: {e}") + REQUIRED_GPG_KEY_ID = None + +# Check for the specific GPG key +if REQUIRED_GPG_KEY_ID: + try: + result = subprocess.check_output(['gpg', '--list-keys', REQUIRED_GPG_KEY_ID], stderr=subprocess.STDOUT) + SUNNYPILOT = REQUIRED_GPG_KEY_ID in result.decode() + if SUNNYPILOT: + print(f"GPG key {REQUIRED_GPG_KEY_ID} is available.") + else: + print(f"GPG key {REQUIRED_GPG_KEY_ID} is not available.") + except subprocess.CalledProcessError as e: + SUNNYPILOT = False + print(f"Failed to list GPG key {REQUIRED_GPG_KEY_ID}. Error:", e.output.decode().strip()) +else: + SUNNYPILOT = False +print("SUNNYPILOT: ", SUNNYPILOT) +Export('SUNNYPILOT') + Decider('MD5-timestamp') SetOption('num_jobs', int(os.cpu_count()/2)) @@ -175,6 +205,10 @@ if arch != "Darwin": cflags += ['-DSWAGLOG="\\"common/swaglog.h\\""'] cxxflags += ['-DSWAGLOG="\\"common/swaglog.h\\""'] +if SUNNYPILOT: + cflags += ['-DSUNNYPILOT'] + cxxflags += ['-DSUNNYPILOT'] + ccflags_option = GetOption('ccflags') if ccflags_option: ccflags += ccflags_option.split(' ') diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 7060a6e9ee..0696548e22 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,9 +1,6 @@ import os import json -Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations', 'UBUNTU_FOCAL') - -SConscript(['sunnypilot/SConscript']) -Import('sp_widgets_src', 'sp_qt_src') +Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations', 'UBUNTU_FOCAL', 'SUNNYPILOT') base_libs = [common, messaging, visionipc, transformations, 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] @@ -20,6 +17,12 @@ if arch == "Darwin": # FIXME: remove this once we're on 5.15 (24.04) qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] +sp_widgets_src = [] +sp_qt_src = [] +if SUNNYPILOT: + SConscript(['sunnypilot/SConscript']) + Import('sp_widgets_src', 'sp_qt_src') + qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", From 2ac8bb6c1dfda33c635cbc42fadca75f65135d63 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Jul 2024 23:12:34 -0400 Subject: [PATCH 369/575] test --- selfdrive/ui/qt/onroad/onroad_home.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc index 2425c31aa9..850218ac34 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/qt/onroad/onroad_home.cc @@ -7,7 +7,9 @@ #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/maps/map_panel.h" #endif +#ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" +#endif #include "selfdrive/ui/qt/util.h" @@ -18,7 +20,11 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { stacked_layout->setStackingMode(QStackedLayout::StackAll); main_layout->addLayout(stacked_layout); +#ifdef SUNNYPILOT nvg = new AnnotatedCameraWidget(VISION_STREAM_ROAD, this); +#else + nvg = new AnnotatedCameraSPWidget(VISION_STREAM_ROAD, this); +#endif QWidget * split_wrapper = new QWidget; split = new QHBoxLayout(split_wrapper); @@ -88,6 +94,7 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) { } } #endif +#ifdef SUNNYPILOT if (onroad_settings != nullptr && !isMapVisible()) { if (wakeScreenTimeout()) { onroad_settings->setVisible(false); @@ -95,6 +102,7 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) { } // propagation event to parent(HomeWindow) QWidget::mousePressEvent(e); +#endif } void OnroadWindow::createMapWidget() { @@ -117,6 +125,7 @@ void OnroadWindow::createMapWidget() { } void OnroadWindow::createOnroadSettingsWidget() { +#ifdef SUNNYPILOT auto os = new OnroadSettingsPanel(this); onroad_settings = os; @@ -130,6 +139,7 @@ void OnroadWindow::createOnroadSettingsWidget() { // hidden by default os->setVisible(false); +#endif } void OnroadWindow::offroadTransition(bool offroad) { @@ -139,9 +149,11 @@ void OnroadWindow::offroadTransition(bool offroad) { createMapWidget(); } #endif +#ifdef SUNNYPILOT if (onroad_settings == nullptr) { createOnroadSettingsWidget(); } +#endif } alerts->clear(); From 54ffb8b1feb4ee7e93e8c32e64120d121009425e Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 13 Jul 2024 07:34:40 +0200 Subject: [PATCH 370/575] Enable conditional compilation for SUNNYPILOT Add conditional definition for SUNNYPILOT in SConscript. This allows code to be compiled differently based on the presence of the SUNNYPILOT environment variable. --- selfdrive/ui/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 0696548e22..41d4d1b4b9 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -30,7 +30,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src -qt_env['CPPDEFINES'] = [] +qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if SUNNYPILOT else [] if maps: base_libs += ['QMapLibre'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc", From ec6f86efbf3aa19b6e78d09285fe62153f5b0367 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 02:21:30 -0400 Subject: [PATCH 371/575] new way to detect added gpg keys --- SConstruct | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/SConstruct b/SConstruct index 59858c8e65..8f2263e167 100644 --- a/SConstruct +++ b/SConstruct @@ -19,31 +19,33 @@ AGNOS = TICI UBUNTU_FOCAL = int(subprocess.check_output('[ -f /etc/os-release ] && . /etc/os-release && [ "$ID" = "ubuntu" ] && [ "$VERSION_ID" = "20.04" ] && echo 1 || echo 0', shell=True, encoding='utf-8').rstrip()) Export('UBUNTU_FOCAL') -gpg_id_path = os.path.join(BASEDIR, "../gpg_id.txt") +keys_dir = os.path.join(BASEDIR, ".git-crypt/keys/default/0") -# Read the required GPG key ID from gpg_id.txt +# Collect the required GPG key IDs from the filenames in the keys directory +required_gpg_key_ids = [] try: - with open(gpg_id_path, 'r') as file: - REQUIRED_GPG_KEY_ID = file.read().strip() - print(f"Required GPG key ID: {REQUIRED_GPG_KEY_ID}") -except IOError as e: - print(f"Failed to read GPG key ID from {gpg_id_path}. Error: {e}") - REQUIRED_GPG_KEY_ID = None + for filename in os.listdir(keys_dir): + if filename.endswith(".gpg"): + required_gpg_key_ids.append(filename.split('.')[0]) + print(f"Required GPG key IDs: {required_gpg_key_ids}") +except OSError as e: + print(f"Failed to read GPG key IDs from {keys_dir}. Error: {e}") + required_gpg_key_ids = [] -# Check for the specific GPG key -if REQUIRED_GPG_KEY_ID: +# Check for the specific GPG keys in the local keyring +SUNNYPILOT = False +for key_id in required_gpg_key_ids: try: - result = subprocess.check_output(['gpg', '--list-keys', REQUIRED_GPG_KEY_ID], stderr=subprocess.STDOUT) - SUNNYPILOT = REQUIRED_GPG_KEY_ID in result.decode() - if SUNNYPILOT: - print(f"GPG key {REQUIRED_GPG_KEY_ID} is available.") - else: - print(f"GPG key {REQUIRED_GPG_KEY_ID} is not available.") + result = subprocess.check_output(['gpg', '--list-keys', key_id], stderr=subprocess.STDOUT) + if key_id in result.decode(): + SUNNYPILOT = True + print(f"GPG key {key_id} is available.") + break except subprocess.CalledProcessError as e: - SUNNYPILOT = False - print(f"Failed to list GPG key {REQUIRED_GPG_KEY_ID}. Error:", e.output.decode().strip()) -else: - SUNNYPILOT = False + print(f"Failed to list GPG key {key_id}. Error:", e.output.decode().strip()) + +if not SUNNYPILOT: + print("None of the required GPG keys are available.") print("SUNNYPILOT: ", SUNNYPILOT) Export('SUNNYPILOT') From c7932f8b73007fa0f053efe8fdc3897735834e66 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 13 Jul 2024 08:24:32 +0200 Subject: [PATCH 372/575] Refactor GPG key check and update SCons build tools. Converted GPG key check logic into a more modular function `is_sunnypilot_developer` for better readability and maintainability. Updated `External Tools.xml` to include `--sunnypilot` flag in SCons build commands. --- .idea/tools/External Tools.xml | 4 +-- SConstruct | 65 ++++++++++++++++++---------------- selfdrive/ui/SConscript | 6 ++-- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/.idea/tools/External Tools.xml b/.idea/tools/External Tools.xml index 2d57ba4d76..b084b0c392 100644 --- a/.idea/tools/External Tools.xml +++ b/.idea/tools/External Tools.xml @@ -2,7 +2,7 @@ @@ -16,7 +16,7 @@ diff --git a/SConstruct b/SConstruct index 8f2263e167..b087785a06 100644 --- a/SConstruct +++ b/SConstruct @@ -19,35 +19,38 @@ AGNOS = TICI UBUNTU_FOCAL = int(subprocess.check_output('[ -f /etc/os-release ] && . /etc/os-release && [ "$ID" = "ubuntu" ] && [ "$VERSION_ID" = "20.04" ] && echo 1 || echo 0', shell=True, encoding='utf-8').rstrip()) Export('UBUNTU_FOCAL') -keys_dir = os.path.join(BASEDIR, ".git-crypt/keys/default/0") +def is_sunnypilot_developer(): + """Check if the current user is a SunnyPilot developer.""" + + def collect_required_gpg_key_ids(keys_dir): + try: + key_ids = [filename.split('.')[0] for filename in os.listdir(keys_dir) if filename.endswith(".gpg")] + print(f"Required GPG key IDs: {key_ids}") + return key_ids + except OSError as e: + print(f"Failed to read GPG key IDs from {keys_dir}. Error: {e}") + return [] + + def is_sunnypilot_key_available(required_gpg_key_ids): + for key_id in required_gpg_key_ids: + try: + result = subprocess.check_output(['gpg', '--list-keys', key_id], stderr=subprocess.STDOUT) + if key_id in result.decode(): + print(f"GPG key {key_id} is available.") + return True + except subprocess.CalledProcessError as e: + print(f"Failed to list GPG key {key_id}. Error:", e.output.decode().strip()) + return False -# Collect the required GPG key IDs from the filenames in the keys directory -required_gpg_key_ids = [] -try: - for filename in os.listdir(keys_dir): - if filename.endswith(".gpg"): - required_gpg_key_ids.append(filename.split('.')[0]) - print(f"Required GPG key IDs: {required_gpg_key_ids}") -except OSError as e: - print(f"Failed to read GPG key IDs from {keys_dir}. Error: {e}") - required_gpg_key_ids = [] + keys_dir = os.path.join(BASEDIR, ".git-crypt/keys/default/0") + required_gpg_key_ids = collect_required_gpg_key_ids(keys_dir) -# Check for the specific GPG keys in the local keyring -SUNNYPILOT = False -for key_id in required_gpg_key_ids: - try: - result = subprocess.check_output(['gpg', '--list-keys', key_id], stderr=subprocess.STDOUT) - if key_id in result.decode(): - SUNNYPILOT = True - print(f"GPG key {key_id} is available.") - break - except subprocess.CalledProcessError as e: - print(f"Failed to list GPG key {key_id}. Error:", e.output.decode().strip()) + sunnypilot = is_sunnypilot_key_available(required_gpg_key_ids) -if not SUNNYPILOT: - print("None of the required GPG keys are available.") -print("SUNNYPILOT: ", SUNNYPILOT) -Export('SUNNYPILOT') + if not sunnypilot: + print("None of the required GPG keys are available.") + + return sunnypilot Decider('MD5-timestamp') @@ -103,6 +106,12 @@ AddOption('--minimal', dest='extras', default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS) help='the minimum build to run openpilot. no tests, tools, etc.') + +AddOption('--sunnypilot', + action='store_true', + dest='sunnypilot', + default=is_sunnypilot_developer(), # check if the current user is a SunnyPilot developer + help='Will make sure it builds SP ui and other SP specific things that are not public (encrypted sources)') ## Architecture name breakdown (arch) ## - larch64: linux tici aarch64 @@ -207,10 +216,6 @@ if arch != "Darwin": cflags += ['-DSWAGLOG="\\"common/swaglog.h\\""'] cxxflags += ['-DSWAGLOG="\\"common/swaglog.h\\""'] -if SUNNYPILOT: - cflags += ['-DSUNNYPILOT'] - cxxflags += ['-DSUNNYPILOT'] - ccflags_option = GetOption('ccflags') if ccflags_option: ccflags += ccflags_option.split(' ') diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 41d4d1b4b9..76dc283f63 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,6 +1,6 @@ import os import json -Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations', 'UBUNTU_FOCAL', 'SUNNYPILOT') +Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations', 'UBUNTU_FOCAL') base_libs = [common, messaging, visionipc, transformations, 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] @@ -19,7 +19,7 @@ qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] sp_widgets_src = [] sp_qt_src = [] -if SUNNYPILOT: +if GetOption('sunnypilot'): SConscript(['sunnypilot/SConscript']) Import('sp_widgets_src', 'sp_qt_src') @@ -30,7 +30,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src -qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if SUNNYPILOT else [] +qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if GetOption('sunnypilot') else [] if maps: base_libs += ['QMapLibre'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc", From f99799701f744236e094ec6a73cbb24e7406c2b2 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 13 Jul 2024 08:54:49 +0200 Subject: [PATCH 373/575] Remove the sunnypilot on clion scripts because code will default properly --- .idea/tools/External Tools.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.idea/tools/External Tools.xml b/.idea/tools/External Tools.xml index b084b0c392..2d57ba4d76 100644 --- a/.idea/tools/External Tools.xml +++ b/.idea/tools/External Tools.xml @@ -2,7 +2,7 @@ @@ -16,7 +16,7 @@ From 19c1c99a28a963a06f77a6e45dba71c6a3969afc Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 02:56:00 -0400 Subject: [PATCH 374/575] nuke dev's temporarily --- .../E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg | Bin 735 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .git-crypt/keys/default/0/E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg diff --git a/.git-crypt/keys/default/0/E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg b/.git-crypt/keys/default/0/E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg deleted file mode 100644 index 787ec0578dbc386486a76729dc8e53dcbf37a39d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 735 zcmV<50wDc`0t^Er*f{PGyTCC45CEnDr6M6uImhG_&*Y6xnw~Ng4}%a&^{1h*wHsnm ze8Jzg1Xsl1{-SmUPp$DHI8=0YSOf7d+9zC-&&!|*r7{e9nU`y5Tl5#7KqW1+8L<|p zFBL9>gn?l6xnAgEx#Dza?w=UzyMmxvM+Qe+SWlz+UQ?Q5m53(MN8h>Y>D@RZ-j5M)P(Kq>$`lN{RFr{12*0F2 znlSADOAt%tK)Tu_aX3x5P=wumS{_Dv-_i20k{~-J6SYQl>~UN0JXnmf;WYQ+ zl=rpmLzyLjcWaVY!<;pOvWuLA(=M<#_tbnAXLS)ki|#8FaJ)whiG&coR=Z_ikWR>? zFBgrx0Tgi?(Q}^(EIoM>nkS(~sCSo~1hPsEAML=nFNbsqHIK9R^QNoeBBF7c`X>Pv z2tZj&5Q(v;8Tl^wn9uy`m|qfw_BRewH3nw`$fLi)1Zji~Pel2nzBJymh@fGl!s)#%;n5V(c*u;yCs?@dDe?5QF{V6*~;eqdI%ym|q}v^cIxfgJah zqPTravGaxq?yS&u?A~3E+J@@>Am<`ioGZhA`B?4FL%P5Ju*TZc+T%>mQZjHZ3PF!AeqNfQ7bGbH`6kQ_jEj zxk`Gih-)5$|KoWu-$MlpAwwb|lTjPXCnRW-KEjg+vFbb8bf`&zYCkC94P{ekg-Euy zRaURgwANGWf>8S~<}tx-0%z3@hQAg0*mp#WlH>^*hKrdC1X+)1(5^$P0q!#8j`fGP zk8f_lbc5xFGx{2PXsOrdN6YR9AJA=?S=57 RnAcT|%MwwGhU?B@M#$H9W Date: Sat, 13 Jul 2024 09:36:05 +0200 Subject: [PATCH 375/575] Revert "nuke dev's temporarily" This reverts commit 19c1c99a28a963a06f77a6e45dba71c6a3969afc. --- .../E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg | Bin 0 -> 735 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .git-crypt/keys/default/0/E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg diff --git a/.git-crypt/keys/default/0/E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg b/.git-crypt/keys/default/0/E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg new file mode 100644 index 0000000000000000000000000000000000000000..787ec0578dbc386486a76729dc8e53dcbf37a39d GIT binary patch literal 735 zcmV<50wDc`0t^Er*f{PGyTCC45CEnDr6M6uImhG_&*Y6xnw~Ng4}%a&^{1h*wHsnm ze8Jzg1Xsl1{-SmUPp$DHI8=0YSOf7d+9zC-&&!|*r7{e9nU`y5Tl5#7KqW1+8L<|p zFBL9>gn?l6xnAgEx#Dza?w=UzyMmxvM+Qe+SWlz+UQ?Q5m53(MN8h>Y>D@RZ-j5M)P(Kq>$`lN{RFr{12*0F2 znlSADOAt%tK)Tu_aX3x5P=wumS{_Dv-_i20k{~-J6SYQl>~UN0JXnmf;WYQ+ zl=rpmLzyLjcWaVY!<;pOvWuLA(=M<#_tbnAXLS)ki|#8FaJ)whiG&coR=Z_ikWR>? zFBgrx0Tgi?(Q}^(EIoM>nkS(~sCSo~1hPsEAML=nFNbsqHIK9R^QNoeBBF7c`X>Pv z2tZj&5Q(v;8Tl^wn9uy`m|qfw_BRewH3nw`$fLi)1Zji~Pel2nzBJymh@fGl!s)#%;n5V(c*u;yCs?@dDe?5QF{V6*~;eqdI%ym|q}v^cIxfgJah zqPTravGaxq?yS&u?A~3E+J@@>Am<`ioGZhA`B?4FL%P5Ju*TZc+T%>mQZjHZ3PF!AeqNfQ7bGbH`6kQ_jEj zxk`Gih-)5$|KoWu-$MlpAwwb|lTjPXCnRW-KEjg+vFbb8bf`&zYCkC94P{ekg-Euy zRaURgwANGWf>8S~<}tx-0%z3@hQAg0*mp#WlH>^*hKrdC1X+)1(5^$P0q!#8j`fGP zk8f_lbc5xFGx{2PXsOrdN6YR9AJA=?S=57 RnAcT|%MwwGhU?B@M#$H9W Date: Sat, 13 Jul 2024 11:38:48 -0400 Subject: [PATCH 376/575] Driving Model Selector v5: Bug fixes and revert to param checks --- cereal/custom.capnp | 12 ----------- selfdrive/modeld/custom_model_metadata.py | 24 ++++++++++++++-------- selfdrive/modeld/fill_model_msg.py | 8 +++++--- selfdrive/modeld/modeld.py | 3 --- selfdrive/ui/qt/onroad/annotated_camera.cc | 3 ++- selfdrive/ui/qt/onroad/annotated_camera.h | 3 ++- selfdrive/ui/qt/onroad/buttons.cc | 2 +- selfdrive/ui/qt/onroad_settings.cc | 4 ++-- selfdrive/ui/ui.cc | 10 +++------ selfdrive/ui/ui.h | 3 +-- 10 files changed, 31 insertions(+), 41 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 8825cf6f07..3b36532ac9 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -25,15 +25,6 @@ enum AccelerationPersonality { stock @3; } -enum ModelGeneration { - default @0; - one @1; - two @2; - three @3; - four @4; - five @5; -} - struct ControlsStateSP @0x81c2f05a394cf4af { lateralState @0 :Text; personality @8 :LongitudinalPersonalitySP; @@ -198,9 +189,6 @@ struct E2eLongStateSP @0xa5cd762cd951a455 { struct ModelDataV2SP @0xf98d843bfd7004a3 { laneChangePrev @0 :Bool; laneChangeEdgeBlock @1 :Bool; - customModel @2 :Bool; - modelGeneration @3 :ModelGeneration; - modelCapabilities @4 :UInt32; } struct CustomReserved7 @0xb86e6369214c01c8 { diff --git a/selfdrive/modeld/custom_model_metadata.py b/selfdrive/modeld/custom_model_metadata.py index 360f7046ad..685d9655e4 100644 --- a/selfdrive/modeld/custom_model_metadata.py +++ b/selfdrive/modeld/custom_model_metadata.py @@ -1,12 +1,18 @@ -from enum import IntFlag +from enum import IntFlag, IntEnum import os -from cereal import custom from openpilot.common.params import Params SIMULATION = "SIMULATION" in os.environ -ModelGeneration = custom.ModelGeneration + +class ModelGeneration(IntEnum): + DEFAULT = 0 + ONE = 1 + TWO = 2 + THREE = 3 + FOUR = 4 + FIVE = 5 class ModelCapabilities(IntFlag): @@ -47,19 +53,19 @@ class CustomModelMetadata: self.capabilities != ModelCapabilities.Default def read_model_generation_param(self) -> ModelGeneration: - return int(self.params.get('DrivingModelGeneration') or ModelGeneration.default) + return int(self.params.get('DrivingModelGeneration') or ModelGeneration.DEFAULT) def get_model_capabilities(self) -> int: """Returns the model capabilities for a given generation.""" - if self.generation == ModelGeneration.five: + if self.generation == ModelGeneration.FIVE: return ModelCapabilities.DesiredCurvatureV2 - elif self.generation == ModelGeneration.four: + elif self.generation == ModelGeneration.FOUR: return ModelCapabilities.DesiredCurvatureV2 - elif self.generation == ModelGeneration.three: + elif self.generation == ModelGeneration.THREE: return ModelCapabilities.DesiredCurvatureV2 | ModelCapabilities.NoO - elif self.generation == ModelGeneration.two: + elif self.generation == ModelGeneration.TWO: return ModelCapabilities.DesiredCurvatureV1 | ModelCapabilities.NoO - elif self.generation == ModelGeneration.one: + elif self.generation == ModelGeneration.ONE: return ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO else: # Default model is meant to represent the capabilities of the prebuilt model diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 39a161ed89..95099d27c3 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -55,7 +55,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float, timestamp_eof: int, timestamp_llk: int, model_execution_time: float, nav_enabled: bool, valid: bool, - custom_model_valid: bool, custom_model_capabilities: ModelCapabilities) -> None: + custom_model_valid: bool, custom_model_capabilities: int) -> None: frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0 frame_drop_perc = frame_drop * 100 extended_msg.valid = valid @@ -68,7 +68,9 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D driving_model_data.frameDropPerc = frame_drop_perc action = driving_model_data.action - action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) + model_use_lateral_planner = custom_model_valid and custom_model_capabilities & ModelCapabilities.LateralPlannerSolution + if not model_use_lateral_planner: + action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) modelV2 = extended_msg.modelV2 modelV2.frameId = vipc_frame_id @@ -100,7 +102,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D fill_xyz_poly(poly_path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T) # lateral planning - if custom_model_valid and custom_model_capabilities & ModelCapabilities.LateralPlannerSolution: + if model_use_lateral_planner: solution = modelV2.lateralPlannerSolutionDEPRECATED solution.x, solution.y, solution.yaw, solution.yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)] solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = [net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)] diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index ff3d9ec633..b6880d4d8d 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -402,9 +402,6 @@ def main(demo=False): if not (custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.LateralPlannerSolution): modelV2SP.laneChangePrev = DH.prev_lane_change modelV2SP.laneChangeEdgeBlock = lat_plan_sp.laneChangeEdgeBlockDEPRECATED - modelV2SP.customModel = custom_model_metadata.valid - modelV2SP.modelGeneration = custom_model_metadata.generation - modelV2SP.modelCapabilities = int(custom_model_metadata.capabilities) pm.send('modelV2SP', modelv2_sp_send) last_vipc_frame_id = meta_main.frame_id diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 009ff76138..7e27449dc5 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -375,6 +375,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { featureStatusToggle = s.scene.feature_status_toggle; experimental_btn->setVisible(!(showDebugUI && showVTC)); + customModelValid = s.scene.custom_driving_model_valid; drivingModelGen = s.scene.driving_model_generation; } @@ -1209,7 +1210,7 @@ void AnnotatedCameraWidget::drawFeatureStatusText(QPainter &p, int x, int y) { } // Dynamic Lane Profile - if (drivingModelGen == cereal::ModelGeneration::ONE) { + if (customModelValid && drivingModelGen == 1) { // ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO drawFeatureStatusElement(dynamicLaneProfile, feature_text.dlp_list_text, feature_color.dlp_list_color, true, "OFF", "DLP"); } diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h index 29b9c4833b..5c3697aaf3 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -203,7 +203,8 @@ private: bool featureStatusToggle; - cereal::ModelGeneration drivingModelGen; + bool customModelValid; + int drivingModelGen; protected: void paintGL() override; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index fbca42da7b..57de6c12ee 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -99,7 +99,7 @@ void OnroadSettingsButton::paintEvent(QPaintEvent *event) { void OnroadSettingsButton::updateState(const UIState &s) { const auto cp = (*s.sm)["carParams"].getCarParams(); - auto dlp_enabled = s.scene.driving_model_generation == cereal::ModelGeneration::ONE; + auto dlp_enabled = s.scene.custom_driving_model_valid && s.scene.driving_model_generation == 1; // ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO bool allow_btn = s.scene.onroad_settings_toggle && (dlp_enabled || hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed()); setVisible(allow_btn); diff --git a/selfdrive/ui/qt/onroad_settings.cc b/selfdrive/ui/qt/onroad_settings.cc index 00810294ba..d39d908662 100644 --- a/selfdrive/ui/qt/onroad_settings.cc +++ b/selfdrive/ui/qt/onroad_settings.cc @@ -119,7 +119,7 @@ OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) void OnroadSettings::changeDynamicLaneProfile() { UIScene &scene = uiState()->scene; - bool can_change = scene.driving_model_generation == cereal::ModelGeneration::ONE; + bool can_change = scene.custom_driving_model_valid && scene.driving_model_generation == 1; // ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO if (can_change) { scene.dynamic_lane_profile++; scene.dynamic_lane_profile = scene.dynamic_lane_profile > 2 ? 0 : scene.dynamic_lane_profile; @@ -229,7 +229,7 @@ void OnroadSettings::refresh() { // Dynamic Lane Profile dlp_widget->updateDynamicLaneProfile("DynamicLaneProfile"); - dlp_widget->setVisible(scene.driving_model_generation == cereal::ModelGeneration::ONE); + dlp_widget->setVisible(scene.custom_driving_model_valid && scene.driving_model_generation == 1); // ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO // Gap Adjust Cruise gac_widget->updateGapAdjustCruise("LongitudinalPersonality"); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 126be0d6b1..b0ef6d4431 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -230,12 +230,6 @@ static void update_state(UIState *s) { scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; } } - if (sm.updated("modelV2SP")) { - auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); - scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); - scene.driving_model_generation = model_v2_sp.getModelGeneration(); - scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); - } } void ui_update_params(UIState *s) { @@ -269,6 +263,8 @@ void ui_update_params(UIState *s) { s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); s->scene.feature_status_toggle = params.getBool("FeatureStatus"); s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); + s->scene.custom_driving_model_valid = params.getBool("CustomDrivingModel"); + s->scene.driving_model_generation = std::atoi(params.get("DrivingModelGeneration").c_str()); // Handle Onroad Screen Off params if (s->scene.onroadScreenOff > 0) { @@ -393,7 +389,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", - "controlsStateSP", "modelV2SP" + "controlsStateSP" }); Params params; diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index a9fb6f0671..49a38a3e14 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -202,8 +202,7 @@ typedef struct UIScene { int speed_limit_warning_value_offset; bool custom_driving_model_valid; - cereal::ModelGeneration driving_model_generation; - uint32_t driving_model_capabilities; + int driving_model_generation; bool feature_status_toggle; bool onroad_settings_toggle; From 3cdbde6dbe5871796bade3e67031495267c860f8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 11:45:36 -0400 Subject: [PATCH 377/575] fix types --- selfdrive/modeld/custom_model_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/custom_model_metadata.py b/selfdrive/modeld/custom_model_metadata.py index 685d9655e4..783d11be22 100644 --- a/selfdrive/modeld/custom_model_metadata.py +++ b/selfdrive/modeld/custom_model_metadata.py @@ -48,14 +48,14 @@ class CustomModelMetadata: self.params: Params = params self.generation: ModelGeneration = self.read_model_generation_param() - self.capabilities: int = self.get_model_capabilities() + self.capabilities: ModelCapabilities = self.get_model_capabilities() self.valid: bool = self.params.get_bool("CustomDrivingModel") and not SIMULATION and \ self.capabilities != ModelCapabilities.Default def read_model_generation_param(self) -> ModelGeneration: - return int(self.params.get('DrivingModelGeneration') or ModelGeneration.DEFAULT) + return ModelGeneration(int(self.params.get("DrivingModelGeneration", encoding='utf8')) or ModelGeneration.DEFAULT) - def get_model_capabilities(self) -> int: + def get_model_capabilities(self) -> ModelCapabilities: """Returns the model capabilities for a given generation.""" if self.generation == ModelGeneration.FIVE: return ModelCapabilities.DesiredCurvatureV2 From 3c3fdb842817417cb07957f882508e41f441059f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 9 Jul 2024 04:50:31 +0800 Subject: [PATCH 378/575] CarParams: set no traversal limit (#32929) * Increase traversal_limit_in_words to Fix Traversal Limit Errors * just use log_from_bytes * come on pycharm * no free lunch --------- Co-authored-by: Shane Smiskol (cherry picked from commit 12d729a0a0c3c1d9bbf988c09ea3a5b68433e83f) --- cereal/messaging/__init__.py | 4 ++-- selfdrive/controls/controlsd.py | 3 +-- selfdrive/controls/plannerd.py | 3 +-- selfdrive/controls/radard.py | 3 +-- selfdrive/locationd/paramsd.py | 6 ++---- selfdrive/locationd/torqued.py | 3 +-- selfdrive/modeld/modeld.py | 4 ++-- 7 files changed, 10 insertions(+), 16 deletions(-) diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index 4ba55cf7b9..8dfa42056d 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -17,8 +17,8 @@ from cereal.services import SERVICE_LIST NO_TRAVERSAL_LIMIT = 2**64-1 -def log_from_bytes(dat: bytes) -> capnp.lib.capnp._DynamicStructReader: - with log.Event.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as msg: +def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event) -> capnp.lib.capnp._DynamicStructReader: + with struct.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as msg: return msg diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index dfa17232b0..3770dd9622 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -69,8 +69,7 @@ class Controls: if CI is None: cloudlog.info("controlsd is waiting for CarParams") - with car.CarParams.from_bytes(self.params.get("CarParams", block=True)) as msg: - self.CP = msg + self.CP = messaging.log_from_bytes(self.params.get("CarParams", block=True), car.CarParams) cloudlog.info("controlsd got CarParams") # Uses car interface helper functions, altering state won't be considered by card for actuation diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 0d1eaeb075..6c51b38b13 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -17,8 +17,7 @@ def plannerd_thread(): cloudlog.info("plannerd is waiting for CarParams") params = Params() - with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg: - CP = msg + CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) cloudlog.info("plannerd got CarParams: %s", CP.carName) debug_mode = bool(int(os.getenv("DEBUG", "0"))) diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index c53c50cd7e..68ac350e94 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -292,8 +292,7 @@ def main(): # wait for stats about the car to come in from controls cloudlog.info("radard is waiting for CarParams") - with car.CarParams.from_bytes(Params().get("CarParams", block=True)) as msg: - CP = msg + CP = messaging.log_from_bytes(Params().get("CarParams", block=True), car.CarParams) cloudlog.info("radard got CarParams") # import the radar from the fingerprint diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index a6f51773a1..6020b3efe0 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -5,8 +5,7 @@ import json import numpy as np import cereal.messaging as messaging -from cereal import car -from cereal import log +from cereal import car, log from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.numpy_fast import clip @@ -130,8 +129,7 @@ def main(): params_reader = Params() # wait for stats about the car to come in from controls cloudlog.info("paramsd is waiting for CarParams") - with car.CarParams.from_bytes(params_reader.get("CarParams", block=True)) as msg: - CP = msg + CP = messaging.log_from_bytes(params_reader.get("CarParams", block=True), car.CarParams) cloudlog.info("paramsd got CarParams") min_sr, max_sr = 0.5 * CP.steerRatio, 2.0 * CP.steerRatio diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 439f0c3d4a..506da2652a 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -232,8 +232,7 @@ def main(demo=False): sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveLocationKalman'], poll='liveLocationKalman') params = Params() - with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP: - estimator = TorqueEstimator(CP) + estimator = TorqueEstimator(messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)) while True: sm.update() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index ff3d9ec633..bb883c32cb 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -236,8 +236,8 @@ def main(demo=False): if demo: CP = get_demo_car_params() else: - with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg: - CP = msg + CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) + cloudlog.info("modeld got CarParams: %s", CP.carName) # TODO this needs more thought, use .2s extra for now to estimate other delays From 1b419974b04fdb25e15b45cea38cf5cf7e090c72 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 11:52:44 -0400 Subject: [PATCH 379/575] Revert "fix types" This reverts commit 3cdbde6dbe5871796bade3e67031495267c860f8. --- selfdrive/modeld/custom_model_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/custom_model_metadata.py b/selfdrive/modeld/custom_model_metadata.py index 783d11be22..685d9655e4 100644 --- a/selfdrive/modeld/custom_model_metadata.py +++ b/selfdrive/modeld/custom_model_metadata.py @@ -48,14 +48,14 @@ class CustomModelMetadata: self.params: Params = params self.generation: ModelGeneration = self.read_model_generation_param() - self.capabilities: ModelCapabilities = self.get_model_capabilities() + self.capabilities: int = self.get_model_capabilities() self.valid: bool = self.params.get_bool("CustomDrivingModel") and not SIMULATION and \ self.capabilities != ModelCapabilities.Default def read_model_generation_param(self) -> ModelGeneration: - return ModelGeneration(int(self.params.get("DrivingModelGeneration", encoding='utf8')) or ModelGeneration.DEFAULT) + return int(self.params.get('DrivingModelGeneration') or ModelGeneration.DEFAULT) - def get_model_capabilities(self) -> ModelCapabilities: + def get_model_capabilities(self) -> int: """Returns the model capabilities for a given generation.""" if self.generation == ModelGeneration.FIVE: return ModelCapabilities.DesiredCurvatureV2 From 73994d4bb17dbb8e61c6feb1827192bae9ea293d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 11:52:44 -0400 Subject: [PATCH 380/575] Revert "Driving Model Selector v5: Bug fixes and revert to param checks" This reverts commit 03fd6050f752fff00b3f7ce7ca31f7b8b919dc53. --- cereal/custom.capnp | 12 +++++++++++ selfdrive/modeld/custom_model_metadata.py | 24 ++++++++-------------- selfdrive/modeld/fill_model_msg.py | 8 +++----- selfdrive/modeld/modeld.py | 3 +++ selfdrive/ui/qt/onroad/annotated_camera.cc | 3 +-- selfdrive/ui/qt/onroad/annotated_camera.h | 3 +-- selfdrive/ui/qt/onroad/buttons.cc | 2 +- selfdrive/ui/qt/onroad_settings.cc | 4 ++-- selfdrive/ui/ui.cc | 10 ++++++--- selfdrive/ui/ui.h | 3 ++- 10 files changed, 41 insertions(+), 31 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 3b36532ac9..8825cf6f07 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -25,6 +25,15 @@ enum AccelerationPersonality { stock @3; } +enum ModelGeneration { + default @0; + one @1; + two @2; + three @3; + four @4; + five @5; +} + struct ControlsStateSP @0x81c2f05a394cf4af { lateralState @0 :Text; personality @8 :LongitudinalPersonalitySP; @@ -189,6 +198,9 @@ struct E2eLongStateSP @0xa5cd762cd951a455 { struct ModelDataV2SP @0xf98d843bfd7004a3 { laneChangePrev @0 :Bool; laneChangeEdgeBlock @1 :Bool; + customModel @2 :Bool; + modelGeneration @3 :ModelGeneration; + modelCapabilities @4 :UInt32; } struct CustomReserved7 @0xb86e6369214c01c8 { diff --git a/selfdrive/modeld/custom_model_metadata.py b/selfdrive/modeld/custom_model_metadata.py index 685d9655e4..360f7046ad 100644 --- a/selfdrive/modeld/custom_model_metadata.py +++ b/selfdrive/modeld/custom_model_metadata.py @@ -1,18 +1,12 @@ -from enum import IntFlag, IntEnum +from enum import IntFlag import os +from cereal import custom from openpilot.common.params import Params SIMULATION = "SIMULATION" in os.environ - -class ModelGeneration(IntEnum): - DEFAULT = 0 - ONE = 1 - TWO = 2 - THREE = 3 - FOUR = 4 - FIVE = 5 +ModelGeneration = custom.ModelGeneration class ModelCapabilities(IntFlag): @@ -53,19 +47,19 @@ class CustomModelMetadata: self.capabilities != ModelCapabilities.Default def read_model_generation_param(self) -> ModelGeneration: - return int(self.params.get('DrivingModelGeneration') or ModelGeneration.DEFAULT) + return int(self.params.get('DrivingModelGeneration') or ModelGeneration.default) def get_model_capabilities(self) -> int: """Returns the model capabilities for a given generation.""" - if self.generation == ModelGeneration.FIVE: + if self.generation == ModelGeneration.five: return ModelCapabilities.DesiredCurvatureV2 - elif self.generation == ModelGeneration.FOUR: + elif self.generation == ModelGeneration.four: return ModelCapabilities.DesiredCurvatureV2 - elif self.generation == ModelGeneration.THREE: + elif self.generation == ModelGeneration.three: return ModelCapabilities.DesiredCurvatureV2 | ModelCapabilities.NoO - elif self.generation == ModelGeneration.TWO: + elif self.generation == ModelGeneration.two: return ModelCapabilities.DesiredCurvatureV1 | ModelCapabilities.NoO - elif self.generation == ModelGeneration.ONE: + elif self.generation == ModelGeneration.one: return ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO else: # Default model is meant to represent the capabilities of the prebuilt model diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 95099d27c3..39a161ed89 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -55,7 +55,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float, timestamp_eof: int, timestamp_llk: int, model_execution_time: float, nav_enabled: bool, valid: bool, - custom_model_valid: bool, custom_model_capabilities: int) -> None: + custom_model_valid: bool, custom_model_capabilities: ModelCapabilities) -> None: frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0 frame_drop_perc = frame_drop * 100 extended_msg.valid = valid @@ -68,9 +68,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D driving_model_data.frameDropPerc = frame_drop_perc action = driving_model_data.action - model_use_lateral_planner = custom_model_valid and custom_model_capabilities & ModelCapabilities.LateralPlannerSolution - if not model_use_lateral_planner: - action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) + action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) modelV2 = extended_msg.modelV2 modelV2.frameId = vipc_frame_id @@ -102,7 +100,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D fill_xyz_poly(poly_path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T) # lateral planning - if model_use_lateral_planner: + if custom_model_valid and custom_model_capabilities & ModelCapabilities.LateralPlannerSolution: solution = modelV2.lateralPlannerSolutionDEPRECATED solution.x, solution.y, solution.yaw, solution.yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)] solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = [net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)] diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index b6880d4d8d..ff3d9ec633 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -402,6 +402,9 @@ def main(demo=False): if not (custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.LateralPlannerSolution): modelV2SP.laneChangePrev = DH.prev_lane_change modelV2SP.laneChangeEdgeBlock = lat_plan_sp.laneChangeEdgeBlockDEPRECATED + modelV2SP.customModel = custom_model_metadata.valid + modelV2SP.modelGeneration = custom_model_metadata.generation + modelV2SP.modelCapabilities = int(custom_model_metadata.capabilities) pm.send('modelV2SP', modelv2_sp_send) last_vipc_frame_id = meta_main.frame_id diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 7e27449dc5..009ff76138 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -375,7 +375,6 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { featureStatusToggle = s.scene.feature_status_toggle; experimental_btn->setVisible(!(showDebugUI && showVTC)); - customModelValid = s.scene.custom_driving_model_valid; drivingModelGen = s.scene.driving_model_generation; } @@ -1210,7 +1209,7 @@ void AnnotatedCameraWidget::drawFeatureStatusText(QPainter &p, int x, int y) { } // Dynamic Lane Profile - if (customModelValid && drivingModelGen == 1) { // ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO + if (drivingModelGen == cereal::ModelGeneration::ONE) { drawFeatureStatusElement(dynamicLaneProfile, feature_text.dlp_list_text, feature_color.dlp_list_color, true, "OFF", "DLP"); } diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h index 5c3697aaf3..29b9c4833b 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -203,8 +203,7 @@ private: bool featureStatusToggle; - bool customModelValid; - int drivingModelGen; + cereal::ModelGeneration drivingModelGen; protected: void paintGL() override; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index 57de6c12ee..fbca42da7b 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -99,7 +99,7 @@ void OnroadSettingsButton::paintEvent(QPaintEvent *event) { void OnroadSettingsButton::updateState(const UIState &s) { const auto cp = (*s.sm)["carParams"].getCarParams(); - auto dlp_enabled = s.scene.custom_driving_model_valid && s.scene.driving_model_generation == 1; // ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO + auto dlp_enabled = s.scene.driving_model_generation == cereal::ModelGeneration::ONE; bool allow_btn = s.scene.onroad_settings_toggle && (dlp_enabled || hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed()); setVisible(allow_btn); diff --git a/selfdrive/ui/qt/onroad_settings.cc b/selfdrive/ui/qt/onroad_settings.cc index d39d908662..00810294ba 100644 --- a/selfdrive/ui/qt/onroad_settings.cc +++ b/selfdrive/ui/qt/onroad_settings.cc @@ -119,7 +119,7 @@ OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) void OnroadSettings::changeDynamicLaneProfile() { UIScene &scene = uiState()->scene; - bool can_change = scene.custom_driving_model_valid && scene.driving_model_generation == 1; // ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO + bool can_change = scene.driving_model_generation == cereal::ModelGeneration::ONE; if (can_change) { scene.dynamic_lane_profile++; scene.dynamic_lane_profile = scene.dynamic_lane_profile > 2 ? 0 : scene.dynamic_lane_profile; @@ -229,7 +229,7 @@ void OnroadSettings::refresh() { // Dynamic Lane Profile dlp_widget->updateDynamicLaneProfile("DynamicLaneProfile"); - dlp_widget->setVisible(scene.custom_driving_model_valid && scene.driving_model_generation == 1); // ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO + dlp_widget->setVisible(scene.driving_model_generation == cereal::ModelGeneration::ONE); // Gap Adjust Cruise gac_widget->updateGapAdjustCruise("LongitudinalPersonality"); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index b0ef6d4431..126be0d6b1 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -230,6 +230,12 @@ static void update_state(UIState *s) { scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; } } + if (sm.updated("modelV2SP")) { + auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); + scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); + scene.driving_model_generation = model_v2_sp.getModelGeneration(); + scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); + } } void ui_update_params(UIState *s) { @@ -263,8 +269,6 @@ void ui_update_params(UIState *s) { s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); s->scene.feature_status_toggle = params.getBool("FeatureStatus"); s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); - s->scene.custom_driving_model_valid = params.getBool("CustomDrivingModel"); - s->scene.driving_model_generation = std::atoi(params.get("DrivingModelGeneration").c_str()); // Handle Onroad Screen Off params if (s->scene.onroadScreenOff > 0) { @@ -389,7 +393,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", - "controlsStateSP" + "controlsStateSP", "modelV2SP" }); Params params; diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 49a38a3e14..a9fb6f0671 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -202,7 +202,8 @@ typedef struct UIScene { int speed_limit_warning_value_offset; bool custom_driving_model_valid; - int driving_model_generation; + cereal::ModelGeneration driving_model_generation; + uint32_t driving_model_capabilities; bool feature_status_toggle; bool onroad_settings_toggle; From 5c90b31f721617608697b697942c354258a82f73 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 11:57:06 -0400 Subject: [PATCH 381/575] Driving Model Selector v5: Bug fixes --- selfdrive/modeld/custom_model_metadata.py | 4 ++-- selfdrive/modeld/fill_model_msg.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/selfdrive/modeld/custom_model_metadata.py b/selfdrive/modeld/custom_model_metadata.py index 360f7046ad..0469bd189c 100644 --- a/selfdrive/modeld/custom_model_metadata.py +++ b/selfdrive/modeld/custom_model_metadata.py @@ -42,14 +42,14 @@ class CustomModelMetadata: self.params: Params = params self.generation: ModelGeneration = self.read_model_generation_param() - self.capabilities: int = self.get_model_capabilities() + self.capabilities: ModelCapabilities = self.get_model_capabilities() self.valid: bool = self.params.get_bool("CustomDrivingModel") and not SIMULATION and \ self.capabilities != ModelCapabilities.Default def read_model_generation_param(self) -> ModelGeneration: return int(self.params.get('DrivingModelGeneration') or ModelGeneration.default) - def get_model_capabilities(self) -> int: + def get_model_capabilities(self) -> ModelCapabilities: """Returns the model capabilities for a given generation.""" if self.generation == ModelGeneration.five: return ModelCapabilities.DesiredCurvatureV2 diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 39a161ed89..8d2bcc0ef4 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -68,7 +68,9 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D driving_model_data.frameDropPerc = frame_drop_perc action = driving_model_data.action - action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) + model_use_lateral_planner = custom_model_valid and custom_model_capabilities & ModelCapabilities.LateralPlannerSolution + if not model_use_lateral_planner: + action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) modelV2 = extended_msg.modelV2 modelV2.frameId = vipc_frame_id @@ -100,7 +102,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D fill_xyz_poly(poly_path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T) # lateral planning - if custom_model_valid and custom_model_capabilities & ModelCapabilities.LateralPlannerSolution: + if model_use_lateral_planner: solution = modelV2.lateralPlannerSolutionDEPRECATED solution.x, solution.y, solution.yaw, solution.yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)] solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = [net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)] From 3c5070ce4a6d78b6f8c6ac8dc389105904b040a6 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 13 Jul 2024 20:48:03 +0000 Subject: [PATCH 382/575] Moving some of elements out first and then I will restructure them to not duplicate code --- selfdrive/ui/SConscript | 2 +- selfdrive/ui/qt/home.cc | 177 +- selfdrive/ui/qt/home.h | 45 +- selfdrive/ui/qt/offroad/settings.cc | 10 + selfdrive/ui/qt/offroad_home.cc | 153 ++ selfdrive/ui/qt/offroad_home.h | 57 + selfdrive/ui/qt/onroad/annotated_camera.cc | 1397 +------------- selfdrive/ui/qt/onroad/annotated_camera.h | 178 +- selfdrive/ui/qt/onroad/onroad_home.cc | 121 +- selfdrive/ui/qt/onroad/onroad_home.h | 47 +- selfdrive/ui/qt/window.cc | 3 + selfdrive/ui/qt/window.h | 8 +- selfdrive/ui/sunnypilot/SConscript | 44 +- .../qt/onroad/sp_priv_annotated_camera.cc | 1697 +++++++++++++++++ .../qt/onroad/sp_priv_annotated_camera.h | 227 +++ .../qt/onroad/sp_priv_onroad_home.cc | 134 ++ .../qt/onroad/sp_priv_onroad_home.h | 50 + selfdrive/ui/sunnypilot/qt/sp_priv_home.cc | 167 ++ selfdrive/ui/sunnypilot/qt/sp_priv_home.h | 39 + .../ui/sunnypilot/qt/sp_priv_offroad_home.cc | 35 + .../ui/sunnypilot/qt/sp_priv_offroad_home.h | 16 + 21 files changed, 2704 insertions(+), 1903 deletions(-) create mode 100644 selfdrive/ui/qt/offroad_home.cc create mode 100644 selfdrive/ui/qt/offroad_home.h create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_home.cc create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_home.h create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 76dc283f63..f62e204136 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -41,7 +41,7 @@ widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) Export('widgets') qt_libs = [widgets, qt_util] + base_libs -qt_src = ["main.cc", "qt/sidebar.cc", "qt/body.cc", +qt_src = ["main.cc", "qt/sidebar.cc", "qt/body.cc", "qt/offroad_home.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc", "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 48de10a79c..56d9e176c3 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -9,10 +9,6 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/prime.h" -#ifdef ENABLE_MAPS -#include "selfdrive/ui/qt/maps/map_settings.h" -#endif - // HomeWindow: the container for the offroad and onroad UIs HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { @@ -32,8 +28,6 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { slayout->addWidget(home); onroad = new OnroadWindow(this); - QObject::connect(onroad, &OnroadWindow::mapPanelRequested, this, [=] { sidebar->hide(); }); - QObject::connect(onroad, &OnroadWindow::onroadSettingsPanelRequested, this, [=] { sidebar->hide(); }); slayout->addWidget(onroad); body = new BodyWindow(this); @@ -54,10 +48,6 @@ void HomeWindow::showSidebar(bool show) { sidebar->setVisible(show); } -void HomeWindow::showMapPanel(bool show) { - onroad->showMapPanel(show); -} - void HomeWindow::updateState(const UIState &s) { const SubMaster &sm = *(s.sm); @@ -66,9 +56,6 @@ void HomeWindow::updateState(const UIState &s) { body->setEnabled(true); slayout->setCurrentWidget(body); } - - uiState()->scene.map_visible = onroad->isMapVisible(); - uiState()->scene.onroad_settings_visible = onroad->isOnroadSettingsVisible(); } void HomeWindow::offroadTransition(bool offroad) { @@ -92,28 +79,15 @@ void HomeWindow::showDriverView(bool show) { } void HomeWindow::mousePressEvent(QMouseEvent* e) { - if (uiState()->scene.started) { - if (uiState()->scene.onroadScreenOff != -2) { - uiState()->scene.touched2 = true; - QTimer::singleShot(500, []() { uiState()->scene.touched2 = false; }); - } - if (uiState()->scene.button_auto_hide) { - uiState()->scene.touch_to_wake = true; - uiState()->scene.sleep_btn_fading_in = true; - QTimer::singleShot(500, []() { uiState()->scene.touch_to_wake = false; }); - } - } - // Handle sidebar collapsing if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) { - if (onroad->wakeScreenTimeout()) { - sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible()); - } + sidebar->setVisible(!sidebar->isVisible()); } } void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) { - HomeWindow::mousePressEvent(e); + // By removing the static call to HomeWindow::mousePressEvent, we can now rely on child classes to handle the event + mousePressEvent(e); const SubMaster &sm = *(uiState()->sm); if (sm["carParams"].getCarParams().getNotCar()) { if (onroad->isVisible()) { @@ -123,147 +97,4 @@ void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) { } showSidebar(false); } -} - -// OffroadHome: the offroad home page - -OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { - QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(40, 40, 40, 40); - - // top header - QHBoxLayout* header_layout = new QHBoxLayout(); - header_layout->setContentsMargins(0, 0, 0, 0); - header_layout->setSpacing(16); - - update_notif = new QPushButton(tr("UPDATE")); - update_notif->setVisible(false); - update_notif->setStyleSheet("background-color: #364DEF;"); - QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); - header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); - - alert_notif = new QPushButton(); - alert_notif->setVisible(false); - alert_notif->setStyleSheet("background-color: #E22C2C;"); - QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); }); - header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); - - version = new ElidedLabel(); - header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight); - - main_layout->addLayout(header_layout); - - // main content - main_layout->addSpacing(25); - center_layout = new QStackedLayout(); - - QWidget *home_widget = new QWidget(this); - { - QHBoxLayout *home_layout = new QHBoxLayout(home_widget); - home_layout->setContentsMargins(0, 0, 0, 0); - home_layout->setSpacing(30); - - // left: MapSettings/PrimeAdWidget - QStackedWidget *left_widget = new QStackedWidget(this); -#ifdef ENABLE_MAPS - left_widget->addWidget(new MapSettings); -#else - left_widget->addWidget(new QWidget); -#endif - custom_mapbox = QString::fromStdString(params.get("CustomMapboxTokenSk")) != ""; - if (!custom_mapbox) { - left_widget->addWidget(new PrimeAdWidget); - } - left_widget->setStyleSheet("border-radius: 10px;"); - - left_widget->setCurrentIndex((uiState()->hasPrime() || custom_mapbox) ? 0 : 1); - connect(uiState(), &UIState::primeChanged, [=](bool prime) { - left_widget->setCurrentIndex((prime || custom_mapbox) ? 0 : 1); - }); - - home_layout->addWidget(left_widget, 1); - - // right: ExperimentalModeButton, SetupWidget - QWidget* right_widget = new QWidget(this); - QVBoxLayout* right_column = new QVBoxLayout(right_widget); - right_column->setContentsMargins(0, 0, 0, 0); - right_widget->setFixedWidth(750); - right_column->setSpacing(30); - - ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); - QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); - right_column->addWidget(experimental_mode, 1); - - SetupWidget *setup_widget = new SetupWidget; - QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings); - right_column->addWidget(setup_widget, 1); - - home_layout->addWidget(right_widget, 1); - } - center_layout->addWidget(home_widget); - - // add update & alerts widgets - update_widget = new UpdateAlert(); - QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); - center_layout->addWidget(update_widget); - alerts_widget = new OffroadAlert(); - QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); - center_layout->addWidget(alerts_widget); - - main_layout->addLayout(center_layout, 1); - - // set up refresh timer - timer = new QTimer(this); - timer->callOnTimeout(this, &OffroadHome::refresh); - - setStyleSheet(R"( - * { - color: white; - } - OffroadHome { - background-color: black; - } - OffroadHome > QPushButton { - padding: 15px 30px; - border-radius: 5px; - font-size: 40px; - font-weight: 500; - } - OffroadHome > QLabel { - font-size: 55px; - } - )"); -} - -void OffroadHome::showEvent(QShowEvent *event) { - refresh(); - timer->start(10 * 1000); -} - -void OffroadHome::hideEvent(QHideEvent *event) { - timer->stop(); -} - -void OffroadHome::refresh() { - version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription"))); - - bool updateAvailable = update_widget->refresh(); - int alerts = alerts_widget->refresh(); - - // pop-up new notification - int idx = center_layout->currentIndex(); - if (!updateAvailable && !alerts) { - idx = 0; - } else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) { - idx = 1; - } else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) { - idx = 2; - } - center_layout->setCurrentIndex(idx); - - update_notif->setVisible(updateAvailable); - alert_notif->setVisible(alerts); - if (alerts) { - alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT"))); - } -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 7e0267a8a9..02cb384823 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -16,31 +16,15 @@ #include "selfdrive/ui/qt/widgets/offroad_alerts.h" #include "selfdrive/ui/ui.h" -class OffroadHome : public QFrame { - Q_OBJECT - -public: - explicit OffroadHome(QWidget* parent = 0); - -signals: - void openSettings(int index = 0, const QString ¶m = ""); - -private: - void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; - void refresh(); - - Params params; - - QTimer* timer; - ElidedLabel* version; - QStackedLayout* center_layout; - UpdateAlert *update_widget; - OffroadAlert* alerts_widget; - QPushButton* alert_notif; - QPushButton* update_notif; - bool custom_mapbox; -}; +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" +#define OnroadWindow OnroadWindowSP +#define OffroadHome OffroadHomeSP +#else +#include "selfdrive/ui/qt/onroad/onroad_home.h" +#include "selfdrive/ui/qt/offroad_home.h" +#endif class HomeWindow : public QWidget { Q_OBJECT @@ -56,20 +40,19 @@ public slots: void offroadTransition(bool offroad); void showDriverView(bool show); void showSidebar(bool show); - void showMapPanel(bool show); protected: void mousePressEvent(QMouseEvent* e) override; void mouseDoubleClickEvent(QMouseEvent* e) override; -private: +protected: Sidebar *sidebar; - OffroadHome *home; + OffroadHome* home; OnroadWindow *onroad; BodyWindow *body; DriverViewWindow *driver_view; QStackedLayout *slayout; -private slots: - void updateState(const UIState &s); -}; +protected slots: + virtual void updateState(const UIState &s); +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 22448e895b..13b486d8a7 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -17,7 +17,9 @@ #include "selfdrive/ui/qt/widgets/prime.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/widgets/ssh_keys.h" +#ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/sunnypilot_main.h" +#endif TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon @@ -652,9 +654,16 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QList panels = { PanelInfo(" " + tr("Device"), device, "../assets/navigation/icon_home.svg"), PanelInfo(" " + tr("Network"), new Networking(this), "../assets/offroad/icon_network.png"), +#ifdef SUNNYPILOT PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/offroad/icon_wifi_strength_full.svg"), +#endif PanelInfo(" " + tr("Toggles"), toggles, "../assets/offroad/icon_toggle.png"), +#ifdef SUNNYPILOT PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../assets/offroad/icon_software.png"), +#else + PanelInfo(" " + tr("Software"), new SoftwarePanel(this), "../assets/offroad/icon_software.png"), +#endif +#ifdef SUNNYPILOT PanelInfo(" " + tr("sunnypilot"), new SunnypilotPanel(this), "../assets/offroad/icon_openpilot.png"), PanelInfo(" " + tr("OSM"), new OsmPanel(this), "../assets/offroad/icon_map.png"), PanelInfo(" " + tr("Monitoring"), new MonitoringPanel(this), "../assets/offroad/icon_monitoring.png"), @@ -662,6 +671,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { PanelInfo(" " + tr("Display"), new DisplayPanel(this), "../assets/offroad/icon_display.png"), PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../assets/offroad/icon_trips.png"), PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../assets/offroad/icon_vehicle.png"), +#endif }; nav_btns = new QButtonGroup(this); diff --git a/selfdrive/ui/qt/offroad_home.cc b/selfdrive/ui/qt/offroad_home.cc new file mode 100644 index 0000000000..260cc49cac --- /dev/null +++ b/selfdrive/ui/qt/offroad_home.cc @@ -0,0 +1,153 @@ +#include "selfdrive/ui/qt/offroad_home.h" + +#include +#include +#include + +#include "selfdrive/ui/qt/offroad/experimental_mode.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/prime.h" + +// OffroadHome: the offroad home page + +OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(40, 40, 40, 40); + + // top header + QHBoxLayout* header_layout = new QHBoxLayout(); + header_layout->setContentsMargins(0, 0, 0, 0); + header_layout->setSpacing(16); + + update_notif = new QPushButton(tr("UPDATE")); + update_notif->setVisible(false); + update_notif->setStyleSheet("background-color: #364DEF;"); + QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); + header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); + + alert_notif = new QPushButton(); + alert_notif->setVisible(false); + alert_notif->setStyleSheet("background-color: #E22C2C;"); + QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); }); + header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); + + version = new ElidedLabel(); + header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight); + + main_layout->addLayout(header_layout); + + // main content + main_layout->addSpacing(25); + center_layout = new QStackedLayout(); + + home_widget = new QWidget(this); + { + home_layout = new QHBoxLayout(home_widget); + home_layout->setContentsMargins(0, 0, 0, 0); + home_layout->setSpacing(30); + + // left: PrimeAdWidget + left_widget = new QStackedWidget(this); + QVBoxLayout *left_prime_layout = new QVBoxLayout(); + QWidget *prime_user = new PrimeUserWidget(); + prime_user->setStyleSheet(R"( + border-radius: 10px; + background-color: #333333; + )"); + left_prime_layout->addWidget(prime_user); + left_prime_layout->addStretch(); + left_widget->addWidget(new LayoutWidget(left_prime_layout)); + left_widget->addWidget(new PrimeAdWidget); + left_widget->setStyleSheet("border-radius: 10px;"); + + left_widget->setCurrentIndex(uiState()->hasPrime() ? 0 : 1); + connect(uiState(), &UIState::primeChanged, [=](bool prime) { + left_widget->setCurrentIndex(prime ? 0 : 1); + }); + + home_layout->addWidget(left_widget, 1); + + // right: ExperimentalModeButton, SetupWidget + QWidget* right_widget = new QWidget(this); + QVBoxLayout* right_column = new QVBoxLayout(right_widget); + right_column->setContentsMargins(0, 0, 0, 0); + right_widget->setFixedWidth(750); + right_column->setSpacing(30); + + ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); + QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); + right_column->addWidget(experimental_mode, 1); + + SetupWidget *setup_widget = new SetupWidget; + QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings); + right_column->addWidget(setup_widget, 1); + + home_layout->addWidget(right_widget, 1); + } + center_layout->addWidget(home_widget); + + // add update & alerts widgets + update_widget = new UpdateAlert(); + QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); + center_layout->addWidget(update_widget); + alerts_widget = new OffroadAlert(); + QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); + center_layout->addWidget(alerts_widget); + + main_layout->addLayout(center_layout, 1); + + // set up refresh timer + timer = new QTimer(this); + timer->callOnTimeout(this, &OffroadHome::refresh); + + setStyleSheet(R"( + * { + color: white; + } + OffroadHome { + background-color: black; + } + OffroadHome > QPushButton { + padding: 15px 30px; + border-radius: 5px; + font-size: 40px; + font-weight: 500; + } + OffroadHome > QLabel { + font-size: 55px; + } + )"); +} + +void OffroadHome::showEvent(QShowEvent *event) { + refresh(); + timer->start(10 * 1000); +} + +void OffroadHome::hideEvent(QHideEvent *event) { + timer->stop(); +} + +void OffroadHome::refresh() { + version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription"))); + + bool updateAvailable = update_widget->refresh(); + int alerts = alerts_widget->refresh(); + + // pop-up new notification + int idx = center_layout->currentIndex(); + if (!updateAvailable && !alerts) { + idx = 0; + } else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) { + idx = 1; + } else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) { + idx = 2; + } + center_layout->setCurrentIndex(idx); + + update_notif->setVisible(updateAvailable); + alert_notif->setVisible(alerts); + if (alerts) { + alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT"))); + } +} \ No newline at end of file diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h new file mode 100644 index 0000000000..edf2bc6cb7 --- /dev/null +++ b/selfdrive/ui/qt/offroad_home.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "common/params.h" +#include "selfdrive/ui/qt/offroad/driverview.h" +#include "selfdrive/ui/qt/body.h" +#include "selfdrive/ui/qt/onroad/onroad_home.h" +#include "selfdrive/ui/qt/sidebar.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/offroad_alerts.h" +#include "selfdrive/ui/ui.h" + +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" +#define OnroadWindow OnroadWindowSP +#define OffroadHomeImp OffroadHomeSP +#else +#include "selfdrive/ui/qt/onroad/onroad_home.h" +#endif + +class OffroadHome : public QFrame { + Q_OBJECT + +public: + void do_work(QWidget*& home_widget); + explicit OffroadHome(QWidget* parent = 0); + +signals: + void openSettings(int index = 0, const QString ¶m = ""); + +protected: + QStackedLayout* center_layout; + QWidget* home_widget; + QHBoxLayout *home_layout; + QStackedWidget *left_widget; + +private: + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; + void refresh(); + + Params params; + + QTimer* timer; + ElidedLabel* version; + UpdateAlert *update_widget; + OffroadAlert* alerts_widget; + QPushButton* alert_notif; + QPushButton* update_notif; +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index 009ff76138..db268bc74b 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1,36 +1,17 @@ + #include "selfdrive/ui/qt/onroad/annotated_camera.h" +#include #include #include -#include -#include #include "common/swaglog.h" #include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/util.h" -static std::pair getFeatureStatus(int value, QStringList text_list, QStringList color_list, - bool condition, QString off_text) { - - QString text("Error"); - QColor color("#ffffff"); - - for (int i = 0; i < text_list.size() && i < color_list.size(); ++i) { - if (value == i) { - text = condition ? text_list[i] : off_text; - color = condition ? QColor(color_list[i]) : QColor("#ffffff"); - break; // Exit the loop once a match is found - } - } - - return {text, color}; -} - - // Window that shows camera view and variety of info drawn on top AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { pm = std::make_unique>({"uiDebug"}); - e2e_state = std::make_unique>({"e2eLongStateSP"}); main_layout = new QVBoxLayout(this); main_layout->setMargin(UI_BORDER_SIZE); @@ -39,68 +20,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par experimental_btn = new ExperimentalButton(this); main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); - onroad_settings_btn = new OnroadSettingsButton(this); - - map_settings_btn = new MapSettingsButton(this); - dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); - map_img = loadPixmap("../assets/img_world_icon.png", {subsign_img_size, subsign_img_size}); - left_img = loadPixmap("../assets/img_turn_left_icon.png", {subsign_img_size, subsign_img_size}); - right_img = loadPixmap("../assets/img_turn_right_icon.png", {subsign_img_size, subsign_img_size}); - - buttons_layout = new QHBoxLayout(); - buttons_layout->setContentsMargins(0, 0, 10, 20); - main_layout->addLayout(buttons_layout); - updateButtonsLayout(false); -} - -void AnnotatedCameraWidget::mousePressEvent(QMouseEvent* e) { - bool propagate_event = true; - - UIState *s = uiState(); - UIScene &scene = s->scene; - const SubMaster &sm = *(s->sm); - const auto longitudinal_plan_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); - - if (longitudinal_plan_sp.getSpeedLimit() > 0.0 && sl_sign_rect.contains(e->x(), e->y())) { - // If touching the speed limit sign area when visible - scene.last_speed_limit_sign_tap = seconds_since_boot(); - params.putBool("LastSpeedLimitSignTap", true); - scene.speed_limit_control_enabled = !scene.speed_limit_control_enabled; - params.putBool("EnableSlc", scene.speed_limit_control_enabled); - propagate_event = false; - } - - if (propagate_event) { - QWidget::mousePressEvent(e); - } -} - -void AnnotatedCameraWidget::updateButtonsLayout(bool is_rhd) { - QLayoutItem *item; - while ((item = buttons_layout->takeAt(0)) != nullptr) { - delete item; - } - - buttons_layout->setContentsMargins(0, 0, 10, rn_offset != 0 ? rn_offset + 10 : 20); - - if (is_rhd) { - buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); - buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); - - buttons_layout->addStretch(1); - - buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); - buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); - } else { - buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); - buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); - - buttons_layout->addStretch(1); - - buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); - buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); // Add spacing to the right - } } void AnnotatedCameraWidget::updateState(const UIState &s) { @@ -108,18 +28,8 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { const SubMaster &sm = *(s.sm); const bool cs_alive = sm.alive("controlsState"); - const bool nav_alive = sm.alive("navInstruction") && sm["navInstruction"].getValid(); const auto cs = sm["controlsState"].getControlsState(); - const auto cs_sp = sm["controlsStateSP"].getControlsStateSP(); const auto car_state = sm["carState"].getCarState(); - const auto nav_instruction = sm["navInstruction"].getNavInstruction(); - const auto car_control = sm["carControl"].getCarControl(); - const auto radar_state = sm["radarState"].getRadarState(); - const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1; - const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); - const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); - const auto lateral_plan_sp = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED(); - car_params = sm["carParams"].getCarParams(); // Handle older routes where vCruiseCluster is not set float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); @@ -132,250 +42,23 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { // Handle older routes where vEgoCluster is not set v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0; float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo(); - v_ego = s.scene.true_vego_ui ? car_state.getVEgo() : v_ego; speed = cs_alive ? std::max(0.0, v_ego) : 0.0; speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; - auto speed_limit_sign = nav_instruction.getSpeedLimitSign(); - speedLimit = nav_alive ? nav_instruction.getSpeedLimit() : 0.0; - speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - - has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD); - has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA); is_metric = s.scene.is_metric; speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); status = s.status; - // TODO: Add minimum speed? - left_blindspot = cs_alive && car_state.getLeftBlindspot(); - right_blindspot = cs_alive && car_state.getRightBlindspot(); - - steerOverride = car_state.getSteeringPressed(); - gasOverride = car_state.getGasPressed(); - latActive = car_control.getLatActive(); - madsEnabled = car_state.getMadsEnabled(); - - brakeLights = car_state.getBrakeLightsDEPRECATED() && s.scene.visual_brake_lights; - - standStillTimer = s.scene.stand_still_timer; - standStill = car_state.getStandstill(); - standstillElapsedTime = lateral_plan_sp.getStandstillElapsed(); - - hideVEgoUi = s.scene.hide_vego_ui; - - splitPanelVisible = s.scene.map_visible || s.scene.onroad_settings_visible; - - // ############################## DEV UI START ############################## - lead_d_rel = radar_state.getLeadOne().getDRel(); - lead_v_rel = radar_state.getLeadOne().getVRel(); - lead_status = radar_state.getLeadOne().getStatus(); - lateralState = QString::fromStdString(cs_sp.getLateralState()); - angleSteers = car_state.getSteeringAngleDeg(); - steerAngleDesired = cs.getLateralControlState().getPidState().getSteeringAngleDesiredDeg(); - curvature = cs.getCurvature(); - roll = sm["liveParameters"].getLiveParameters().getRoll(); - memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); - devUiInfo = s.scene.dev_ui_info; - gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; //External reports accuracy, internal does not. - altitude = gpsLocation.getAltitude(); - vEgo = car_state.getVEgo(); - aEgo = car_state.getAEgo(); - steeringTorqueEps = car_state.getSteeringTorqueEps(); - bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); - bearingDeg = gpsLocation.getBearingDeg(); - torquedUseParams = (ltp.getUseParams() || s.scene.live_torque_toggle) && !s.scene.torqued_override; - latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); - frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); - liveValid = ltp.getLiveValid(); - // ############################## DEV UI END ############################## - - btnPerc = s.scene.sleep_btn_opacity * 0.05; - - left_blinker = car_state.getLeftBlinker(); - right_blinker = car_state.getRightBlinker(); - lane_change_edge_block = lateral_plan_sp.getLaneChangeEdgeBlockDEPRECATED(); - // update engageability/experimental mode button experimental_btn->updateState(s); - // update onroad settings button state - onroad_settings_btn->updateState(s); - // update DM icon auto dm_state = sm["driverMonitoringState"].getDriverMonitoringState(); dmActive = dm_state.getIsActiveMode(); rightHandDM = dm_state.getIsRHD(); // DM icon transition dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0); - - // update buttons layout - updateButtonsLayout(rightHandDM); - - // hide map settings button for alerts and flip for right hand DM - if (map_settings_btn->isEnabled()) { - map_settings_btn->setVisible(!hideBottomIcons); - buttons_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); - } - - // hide onroad settings button for alerts and flip for right hand DM - if (onroad_settings_btn->isEnabled()) { - onroad_settings_btn->setVisible(!hideBottomIcons); - buttons_layout->setAlignment(onroad_settings_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignBottom); - } - - const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); - slcState = lp_sp.getSpeedLimitControlState(); - - speedLimitControlToggle = s.scene.speed_limit_control_enabled; - - const auto vtcState = lp_sp.getVisionTurnControllerState(); - const float vtc_speed = lp_sp.getVisionTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - const auto lpSoruce = lp_sp.getLongitudinalPlanSource(); - QColor vtc_color = tcs_colors[int(vtcState)]; - vtc_color.setAlpha(lpSoruce == cereal::LongitudinalPlanSP::LongitudinalPlanSource::TURN ? 255 : 100); - - showVTC = vtcState > cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED; - vtcSpeed = QString::number(std::nearbyint(vtc_speed)); - vtcColor = vtc_color; - showDebugUI = s.scene.show_debug_ui; - - const auto lmd_sp = sm["liveMapDataSP"].getLiveMapDataSP(); - - const auto data_type = int(lmd_sp.getDataType()); - const QString data_type_draw(data_type == 2 ? "🌐 " : ""); - roadName = QString::fromStdString(lmd_sp.getCurrentRoadName()); - roadName = !roadName.isEmpty() ? data_type_draw + roadName : ""; - - float speed_limit_slc = lp_sp.getSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - const float speed_limit_offset = lp_sp.getSpeedLimitOffset() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - const bool sl_force_active = speedLimitControlToggle && - seconds_since_boot() < s.scene.last_speed_limit_sign_tap + 2.0; - const bool sl_inactive = !sl_force_active && (!speedLimitControlToggle || - slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::INACTIVE); - const bool sl_temp_inactive = !sl_force_active && (speedLimitControlToggle && - slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE); - const bool sl_pre_active = !sl_force_active && (speedLimitControlToggle && - slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE); - const int sl_distance = int(lp_sp.getDistToSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; - const QString sl_distance_str(QString::number(sl_distance) + (s.scene.is_metric ? "m" : "f")); - const QString sl_offset_str(speed_limit_offset > 0.0 ? speed_limit_offset < 0.0 ? - "-" + QString::number(std::nearbyint(std::abs(speed_limit_offset))) : - "+" + QString::number(std::nearbyint(speed_limit_offset)) : ""); - const QString sl_inactive_str(sl_temp_inactive && s.scene.speed_limit_control_engage_type == 0 ? "TEMP" : ""); - const QString sl_substring(sl_inactive || sl_temp_inactive || sl_pre_active ? sl_inactive_str : - sl_distance > 0 ? sl_distance_str : sl_offset_str); - - showSpeedLimit = speed_limit_slc > 0.0; - speedLimitSLC = speed_limit_slc; - speedLimitSLCOffset = speed_limit_offset; - slcSubText = sl_substring; - slcSubTextSize = sl_inactive || sl_temp_inactive || sl_distance > 0 ? 25.0 : 27.0; - mapSourcedSpeedLimit = lp_sp.getIsMapSpeedLimit(); - slcActive = !sl_inactive && !sl_temp_inactive; - overSpeedLimit = showSpeedLimit && s.scene.speed_limit_warning_type != 0 && - (std::nearbyint(speed_limit_slc + s.scene.speed_limit_warning_value_offset) < std::nearbyint(speed)); - plus_arrow_up_img = loadPixmap("../assets/img_plus_arrow_up", {105, 105}); - minus_arrow_down_img = loadPixmap("../assets/img_minus_arrow_down", {105, 105}); - - const float tsc_speed = lp_sp.getTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); - const auto tscState = lp_sp.getTurnSpeedControlState(); - const int t_distance = int(lp_sp.getDistToTurn() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; - const QString t_distance_str(QString::number(t_distance) + (s.scene.is_metric ? "m" : "f")); - - showTurnSpeedLimit = tsc_speed > 0.0 && std::round(tsc_speed) < 224 && (tsc_speed < speed || s.scene.show_debug_ui); - turnSpeedLimit = QString::number(std::nearbyint(tsc_speed)); - tscSubText = t_distance > 0 ? t_distance_str : QString(""); - tscActive = tscState > cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE; - curveSign = lp_sp.getTurnSign(); - - // TODO: Add toggle variables to cereal, and parse from cereal - longitudinalPersonality = s.scene.longitudinal_personality; - dynamicLaneProfile = s.scene.dynamic_lane_profile; - mpcMode = QString::fromStdString(lp_sp.getE2eBlended()); - mpcMode = (mpcMode == "blended") ? mpcMode.replace(0, 1, mpcMode[0].toUpper()) : mpcMode.toUpper(); - - static int reverse_delay = 0; - bool reverse_allowed = false; - if (int(car_state.getGearShifter()) != 4) { - reverse_delay = 0; - reverse_allowed = false; - } else { - reverse_delay += 50; - if (reverse_delay >= 1000) { - reverse_allowed = true; - } - } - - reversing = reverse_allowed; - - cruiseStateEnabled = car_state.getCruiseState().getEnabled(); - - int e2eLStatus = 0; - static bool chime_sent = false; - static int chime_count = 0; - int chime_prompt = 0; - static float last_lead_distance = -1; - const float lead_distance = radar_state.getLeadOne().getDRel(); - - if (s.scene.e2eX[12] > 30 && car_state.getVEgo() < 1.0) { - e2eLStatus = 2; - } else if ((s.scene.e2eX[12] > 0 && s.scene.e2eX[12] < 80) || s.scene.e2eX[12] < 0) { - e2eLStatus = 1; - } else { - e2eLStatus = 0; - } - - if (!car_state.getStandstill()) { - chime_prompt = 0; - chime_sent = false; - chime_count = 0; - - if (last_lead_distance != -1) { - last_lead_distance = -1; - } - } - - if ((cruiseStateEnabled || car_state.getBrakeLightsDEPRECATED()) && !car_state.getGasPressed() && car_state.getStandstill()) { - if (e2eLStatus == 2 && !radar_state.getLeadOne().getStatus()) { - if (chime_sent) { - chime_count = 0; - } else { - chime_count += 1; - } - if (s.scene.e2e_long_alert_light && chime_count >= 2 && !chime_sent) { - chime_prompt = 1; - chime_sent = true; - } else { - chime_prompt = 0; - } - } else if (radar_state.getLeadOne().getStatus()) { - if ((last_lead_distance == -1) || (lead_distance < last_lead_distance)) { - last_lead_distance = lead_distance; - } - if (s.scene.e2e_long_alert_lead && (lead_distance - last_lead_distance > 1.0) && !chime_sent) { - chime_prompt = 2; - chime_sent = true; - } else { - chime_prompt = 0; - } - } else { - chime_prompt = 0; - } - } else { - } - - e2eStatus = chime_prompt; - e2eState = e2eLStatus; - e2eLongAlertUi = s.scene.e2e_long_alert_ui; - dynamicExperimentalControlToggle = s.scene.dynamic_experimental_control; - speedLimitWarningFlash = s.scene.speed_limit_warning_flash; - experimentalMode = cs.getExperimentalMode(); - - featureStatusToggle = s.scene.feature_status_toggle; - - experimental_btn->setVisible(!(showDebugUI && showVTC)); - drivingModelGen = s.scene.driving_model_generation; } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -387,33 +70,18 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0)); p.fillRect(0, 0, width(), UI_HEADER_HEIGHT, bg); - QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–"; - QString speedLimitStrSlc = showSpeedLimit ? QString::number(std::nearbyint(speedLimitSLC)) : "–"; QString speedStr = QString::number(std::nearbyint(speed)); QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–"; - const bool isNavSpeedLimit = has_us_speed_limit || has_eu_speed_limit; - - // Draw outer box + border to contain set speed and speed limit - const int sign_margin = 12; - const int us_sign_height = slcSubText == "" ? 186 : 216; - const int eu_sign_size = 176; + // Draw outer box + border to contain set speed const QSize default_size = {172, 204}; QSize set_speed_size = default_size; - if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200; - if ((mapSourcedSpeedLimit && !is_metric && speedLimitStrSlc.size() >= 3) || - (has_us_speed_limit && speedLimitStr.size() >= 3)) set_speed_size.rwidth() = 223; - - if ((mapSourcedSpeedLimit && !is_metric) || has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin; - else if ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin; - - int top_radius = 32; - int bottom_radius = ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) ? 100 : 32; + if (is_metric) set_speed_size.rwidth() = 200; QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); p.setPen(QPen(whiteColor(75), 6)); p.setBrush(blackColor(166)); - drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); + p.drawRoundedRect(set_speed_rect, 32, 32); // Draw MAX QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff); @@ -421,20 +89,8 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { if (is_cruise_set) { if (status == STATUS_DISENGAGED) { max_color = whiteColor(); - } else if (status == STATUS_OVERRIDE && gasOverride) { + } else if (status == STATUS_OVERRIDE) { max_color = QColor(0x91, 0x9b, 0x95, 0xff); - } else if (speedLimitSLC > 0) { - auto interp_color = [=](QColor c1, QColor c2, QColor c3) { - return speedLimitSLC > 0 ? interpColor(setSpeed, {speedLimitSLC + 5, speedLimitSLC + 15, speedLimitSLC + 25}, {c1, c2, c3}) : c1; - }; - max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); - set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); - } else if (speedLimit > 0) { - auto interp_color = [=](QColor c1, QColor c2, QColor c3) { - return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1; - }; - max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); - set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); } } else { max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); @@ -447,116 +103,11 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { p.setPen(set_speed_color); p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); - const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); - sl_sign_rect = sign_rect; - - speedLimitWarning(p, sign_rect, sign_margin); - - // US/Canada (MUTCD style) sign - if (((mapSourcedSpeedLimit && !is_metric && !isNavSpeedLimit) || has_us_speed_limit) && slcShowSign) { - p.setPen(Qt::NoPen); - p.setBrush(whiteColor()); - p.drawRoundedRect(sign_rect, 24, 24); - p.setPen(QPen(blackColor(), 6)); - p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); - - p.setFont(InterFont(28, QFont::DemiBold)); - p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED")); - p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); - p.setFont(InterFont(70, QFont::Bold)); - if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255)); - else p.setPen(blackColor()); - p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStrSlc); - - // Speed limit offset value - p.setFont(InterFont(32, QFont::Bold)); - p.setPen(blackColor()); - p.drawText(sign_rect.adjusted(0, 85 + 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText); - } - - // EU (Vienna style) sign - if (((mapSourcedSpeedLimit && is_metric && !isNavSpeedLimit) || has_eu_speed_limit) && slcShowSign) { - p.setPen(Qt::NoPen); - p.setBrush(whiteColor()); - p.drawEllipse(sign_rect); - p.setPen(QPen(Qt::red, 20)); - p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); - - p.setFont(InterFont((speedLimitStrSlc.size() >= 3) ? 60 : 70, QFont::Bold)); - if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255)); - else p.setPen(blackColor()); - p.drawText(sign_rect, Qt::AlignCenter, speedLimitStrSlc); - - // Speed limit offset value - p.setFont(InterFont(slcSubTextSize, QFont::Bold)); - p.setPen(blackColor()); - p.drawText(sign_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText); - } - // current speed - if (!hideVEgoUi) { - p.setFont(InterFont(176, QFont::Bold)); - drawColoredText(p, rect().center().x(), 210, speedStr, brakeLights ? QColor(0xff, 0, 0, 255) : QColor(0xff, 0xff, 0xff, 255)); - p.setFont(InterFont(66)); - drawText(p, rect().center().x(), 290, speedUnit, 200); - } - - if (!reversing) { - // ####### 1 ROW ####### - QRect bar_rect1(rect().left(), rect().bottom() - 60, rect().width(), 61); - if (!splitPanelVisible && devUiInfo == 2) { - p.setPen(Qt::NoPen); - p.setBrush(QColor(0, 0, 0, 100)); - p.drawRect(bar_rect1); - drawNewDevUi2(p, bar_rect1.left(), bar_rect1.center().y()); - } - - // ####### 1 COLUMN ######## - QRect rc2(rect().right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 152); - if (devUiInfo != 0) { - drawRightDevUi(p, rect().right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rc2.height()); - } - - int rn_btn = 0; - rn_btn = !splitPanelVisible && devUiInfo == 2 ? 35 : 0; - rn_offset = rn_btn; - - // Stand Still Timer - if (standStillTimer && standStill && !splitPanelVisible) { - drawStandstillTimer(p, rect().right() - 650, 30 + 160 + 250); - } - - // V-TSC - if (showDebugUI && showVTC) { - drawVisionTurnControllerUI(p, rect().right() - 184 - (UI_BORDER_SIZE * 1.5), int(UI_BORDER_SIZE * 1.5), 184, vtcColor, vtcSpeed, 100); - } - - // Bottom bar road name - if (showDebugUI && !roadName.isEmpty()) { - int font_size = splitPanelVisible ? 38 : 50; - int h = splitPanelVisible ? 18 : 26; - p.setFont(InterFont(font_size, QFont::Bold)); - drawRoadNameText(p, rect().center().x(), h, roadName, QColor(255, 255, 255, 255)); - } - - // Turn Speed Sign - if (showTurnSpeedLimit) { - QRect rc = sign_rect; - rc.moveTop(sign_rect.bottom() + UI_BORDER_SIZE); - drawTrunSpeedSign(p, rc, turnSpeedLimit, tscSubText, curveSign, tscActive); - } - } - - // E2E Status - if (e2eLongAlertUi && e2eState != 0) { - drawE2eStatus(p, UI_BORDER_SIZE * 2 + 190, 45, 150, 150, e2eState); - } - - if (!hideBottomIcons && featureStatusToggle) { - int x = UI_BORDER_SIZE * 2 + (rightHandDM ? 600 : 370); - int feature_status_text_x = rightHandDM ? rect().right() - x : x; - drawFeatureStatusText(p, feature_status_text_x, rect().bottom() - 160 - rn_offset); - } + p.setFont(InterFont(176, QFont::Bold)); + drawText(p, rect().center().x(), 210, speedStr); + p.setFont(InterFont(66)); + drawText(p, rect().center().x(), 290, speedUnit, 200); p.restore(); } @@ -569,711 +120,6 @@ void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &t p.drawText(real_rect.x(), real_rect.bottom(), text); } -void AnnotatedCameraWidget::drawColoredText(QPainter &p, int x, int y, const QString &text, QColor color) { - QRect real_rect = p.fontMetrics().boundingRect(text); - real_rect.moveCenter({x, y - real_rect.height() / 2}); - - p.setPen(color); - p.drawText(real_rect.x(), real_rect.bottom(), text); -} - -void AnnotatedCameraWidget::drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color) { - QRect real_rect = p.fontMetrics().boundingRect(text); - real_rect.moveCenter({x, y}); - - p.setPen(color); - p.drawText(real_rect, Qt::AlignCenter, text); -} - -void AnnotatedCameraWidget::drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color) { - QRect real_rect = p.fontMetrics().boundingRect(text); - real_rect.moveCenter({x, y}); - - QRect real_rect_adjusted(real_rect); - real_rect_adjusted.adjust(-UI_ROAD_NAME_MARGIN_X, 5, UI_ROAD_NAME_MARGIN_X, 0); - QPainterPath path; - path.addRoundedRect(real_rect_adjusted, 10, 10); - p.setPen(Qt::NoPen); - p.setBrush(QColor(0, 0, 0, 100)); - p.drawPath(path); - - p.setPen(color); - p.drawText(real_rect, Qt::AlignCenter, text); -} - -void AnnotatedCameraWidget::drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color, - const QString &vision_speed, int alpha) { - QRect rvtc(x, y, size, size); - p.setPen(QPen(color, 10)); - p.setBrush(QColor(0, 0, 0, alpha)); - p.drawRoundedRect(rvtc, 20, 20); - p.setPen(Qt::NoPen); - - p.setFont(InterFont(56, QFont::DemiBold)); - drawCenteredText(p, rvtc.center().x(), rvtc.center().y(), vision_speed, color); -} - -void AnnotatedCameraWidget::drawStandstillTimer(QPainter &p, int x, int y) { - char lab_str[16]; - char val_str[16]; - int minute = (int)(standstillElapsedTime / 60); - int second = (int)((standstillElapsedTime) - (minute * 60)); - - if (standStill) { - snprintf(lab_str, sizeof(lab_str), "STOP"); - snprintf(val_str, sizeof(val_str), "%01d:%02d", minute, second); - } - - p.setFont(InterFont(125, QFont::DemiBold)); - drawColoredText(p, x, y, QString(lab_str), QColor(255, 175, 3, 240)); - p.setFont(InterFont(150, QFont::DemiBold)); - drawColoredText(p, x, y + 150, QString(val_str), QColor(255, 255, 255, 240)); -} - -void AnnotatedCameraWidget::drawCircle(QPainter &p, int x, int y, int r, QBrush bg) { - p.setPen(Qt::NoPen); - p.setBrush(bg); - p.drawEllipse(x - r, y - r, 2 * r, 2 * r); -} - -void AnnotatedCameraWidget::drawSpeedSign(QPainter &p, QRect rc, const QString &speed_limit, const QString &sub_text, - int subtext_size, bool is_map_sourced, bool is_active) { - const QColor ring_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50); - const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85); - const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); - - const int x = rc.center().x(); - const int y = rc.center().y(); - const int r = rc.width() / 2.0f; - - drawCircle(p, x, y, r, ring_color); - drawCircle(p, x, y, int(r * 0.8f), inner_color); - - p.setFont(InterFont(89, QFont::Bold)); - drawCenteredText(p, x, y, speed_limit, text_color); - p.setFont(InterFont(subtext_size, QFont::Bold)); - drawCenteredText(p, x, y + 55, sub_text, text_color); - - if (is_map_sourced) { - p.setPen(Qt::NoPen); - p.setOpacity(is_active ? 1.0 : 0.3); - p.drawPixmap(x - subsign_img_size / 2, y - 55 - subsign_img_size / 2, map_img); - p.setOpacity(1.0); - } -} - -void AnnotatedCameraWidget::drawTrunSpeedSign(QPainter &p, QRect rc, const QString &turn_speed, const QString &sub_text, - int curv_sign, bool is_active) { - const QColor border_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50); - const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85); - const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); - - const int x = rc.center().x(); - const int y = 184 * 2 + UI_BORDER_SIZE + 202; - const int width = 184; - - const float stroke_w = 15.0; - const float cS = stroke_w / 2.0 + 4.5; // half width of the stroke on the corners of the triangle - const float R = width / 2.0 - stroke_w / 2.0; - const float A = 0.73205; - const float h2 = 2.0 * R / (1.0 + A); - const float h1 = A * h2; - const float L = 4.0 * R / sqrt(3.0); - - // Draw the internal triangle, compensate for stroke width. Needed to improve rendering when in inactive - // state due to stroke transparency being different from inner transparency. - QPainterPath path; - path.moveTo(x, y - R + cS); - path.lineTo(x - L / 2.0 + cS, y + h1 + h2 - R - stroke_w / 2.0); - path.lineTo(x + L / 2.0 - cS, y + h1 + h2 - R - stroke_w / 2.0); - path.lineTo(x, y - R + cS); - p.setPen(Qt::NoPen); - p.setBrush(inner_color); - p.drawPath(path); - - // Draw the stroke - QPainterPath stroke_path; - stroke_path.moveTo(x, y - R); - stroke_path.lineTo(x - L / 2.0, y + h1 + h2 - R); - stroke_path.lineTo(x + L / 2.0, y + h1 + h2 - R); - stroke_path.lineTo(x, y - R); - p.setPen(QPen(border_color, stroke_w, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - p.setBrush(Qt::NoBrush); - p.drawPath(stroke_path); - - // Draw the turn sign - if (curv_sign != 0) { - p.setPen(Qt::NoPen); - p.setOpacity(is_active ? 1.0 : 0.3); - p.drawPixmap(int(x - (subsign_img_size / 2)), int(y - R + stroke_w + 30), curv_sign > 0 ? left_img : right_img); - p.setOpacity(1.0); - } - - // Draw the texts. - p.setFont(InterFont(67, QFont::Bold)); - drawCenteredText(p, x, y + 25, turn_speed, text_color); - p.setFont(InterFont(22, QFont::Bold)); - drawCenteredText(p, x, y + 65, sub_text, text_color); -} - -// ############################## DEV UI START ############################## -void AnnotatedCameraWidget::drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2) { - QFontMetrics fm(p.font()); - QRect init_rect = fm.boundingRect(text1 + " "); - QRect real_rect = fm.boundingRect(init_rect, 0, text1 + " "); - real_rect.moveCenter({x, y}); - - QRect init_rect3 = fm.boundingRect(text3); - QRect real_rect3 = fm.boundingRect(init_rect3, 0, text3); - real_rect3.moveTop(real_rect.top()); - real_rect3.moveLeft(real_rect.right() + 135); - - QRect init_rect2 = fm.boundingRect(text2); - QRect real_rect2 = fm.boundingRect(init_rect2, 0, text2); - real_rect2.moveTop(real_rect.top()); - real_rect2.moveRight(real_rect.right() + 125); - - p.setPen(color1); - p.drawText(real_rect, Qt::AlignLeft | Qt::AlignVCenter, text1); - - p.setPen(color2); - p.drawText(real_rect2, Qt::AlignRight | Qt::AlignVCenter, text2); - p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, text3); -} - -int AnnotatedCameraWidget::drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { - p.setFont(InterFont(30 * 2, QFont::Bold)); - drawColoredText(p, x + 92, y + 80, value, color); - - p.setFont(InterFont(28, QFont::Bold)); - drawText(p, x + 92, y + 80 + 42, label, 255); - - if (units.length() > 0) { - p.save(); - p.translate(x + 54 + 30 - 3 + 92 + 30, y + 37 + 25); - p.rotate(-90); - drawText(p, 0, 0, units, 255); - p.restore(); - } - - return 110; -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getDRel() { - QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Orange if close, Red if very close - if (lead_d_rel < 5) { - color = QColor(255, 0, 0, 255); - } else if (lead_d_rel < 15) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "REL DIST", "m", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getVRel() { - QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Red if approaching faster than 10mph - // Orange if approaching (negative) - if (lead_v_rel < -4.4704) { - color = QColor(255, 0, 0, 255); - } else if (lead_v_rel < 0) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "REL SPEED", speedUnit, color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringAngleDeg() { - QString value = QString("%1%2%3").arg(QString::number(angleSteers, 'f', 1)).arg("°").arg(""); - QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - // Red if large steering angle - // Orange if moderate steering angle - if (std::fabs(angleSteers) > 180) { - color = QColor(255, 0, 0, 255); - } else if (std::fabs(angleSteers) > 90) { - color = QColor(255, 188, 0, 255); - } - - return UiElement(value, "REAL STEER", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getActualLateralAccel() { - float actualLateralAccel = (curvature * pow(vEgo, 2)) - (roll * 9.81); - - QString value = QString::number(actualLateralAccel, 'f', 2); - QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "ACTUAL LAT", "m/s²", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringAngleDesiredDeg() { - QString value = (madsEnabled && latActive) ? QString("%1%2%3").arg(QString::number(steerAngleDesired, 'f', 1)).arg("°").arg("") : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (madsEnabled && latActive) { - // Red if large steering angle - // Orange if moderate steering angle - if (std::fabs(angleSteers) > 180) { - color = QColor(255, 0, 0, 255); - } else if (std::fabs(angleSteers) > 90) { - color = QColor(255, 188, 0, 255); - } else { - color = QColor(0, 255, 0, 255); - } - } - - return UiElement(value, "DESIRED STEER", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getMemoryUsagePercent() { - QString value = QString("%1%2").arg(QString::number(memoryUsagePercent, 'd', 0)).arg("%"); - QColor color = (memoryUsagePercent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "RAM", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getAEgo() { - QString value = QString::number(aEgo, 'f', 1); - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "ACC.", "m/s²", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getVEgoLead() { - QString value = lead_status ? QString::number((lead_v_rel + vEgo) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Red if approaching faster than 10mph - // Orange if approaching (negative) - if (lead_v_rel < -4.4704) { - color = QColor(255, 0, 0, 255); - } else if (lead_v_rel < 0) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "L.S.", speedUnit, color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getFrictionCoefficientFiltered() { - QString value = QString::number(frictionCoefficientFiltered, 'f', 3); - QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "FRIC.", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getLatAccelFactorFiltered() { - QString value = QString::number(latAccelFactorFiltered, 'f', 3); - QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "L.A.", "m/s²", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getSteeringTorqueEps() { - QString value = QString::number(std::fabs(steeringTorqueEps), 'f', 1); - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "E.T.", "N·dm", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getBearingDeg() { - QString value = (bearingAccuracyDeg != 180.00) ? QString("%1%2%3").arg(QString::number(bearingDeg, 'd', 0)).arg("°").arg("") : "-"; - QColor color = QColor(255, 255, 255, 255); - QString dir_value; - - if (bearingAccuracyDeg != 180.00) { - if (((bearingDeg >= 337.5) && (bearingDeg <= 360)) || ((bearingDeg >= 0) && (bearingDeg <= 22.5))) { - dir_value = "N"; - } else if ((bearingDeg > 22.5) && (bearingDeg < 67.5)) { - dir_value = "NE"; - } else if ((bearingDeg >= 67.5) && (bearingDeg <= 112.5)) { - dir_value = "E"; - } else if ((bearingDeg > 112.5) && (bearingDeg < 157.5)) { - dir_value = "SE"; - } else if ((bearingDeg >= 157.5) && (bearingDeg <= 202.5)) { - dir_value = "S"; - } else if ((bearingDeg > 202.5) && (bearingDeg < 247.5)) { - dir_value = "SW"; - } else if ((bearingDeg >= 247.5) && (bearingDeg <= 292.5)) { - dir_value = "W"; - } else if ((bearingDeg > 292.5) && (bearingDeg < 337.5)) { - dir_value = "NW"; - } - } else { - dir_value = "OFF"; - } - - return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color); -} - -AnnotatedCameraWidget::UiElement AnnotatedCameraWidget::getAltitude() { - QString value = (gpsAccuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-"; - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "ALT.", "m", color); -} - -void AnnotatedCameraWidget::drawRightDevUi(QPainter &p, int x, int y) { - int rh = 5; - int ry = y; - - // Add Relative Distance to Primary Lead Car - // Unit: Meters - UiElement dRelElement = getDRel(); - rh += drawDevUiElementRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color); - ry = y + rh; - - // Add Relative Velocity vs Primary Lead Car - // Unit: kph if metric, else mph - UiElement vRelElement = getVRel(); - rh += drawDevUiElementRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color); - ry = y + rh; - - // Add Real Steering Angle - // Unit: Degrees - UiElement steeringAngleDegElement = getSteeringAngleDeg(); - rh += drawDevUiElementRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color); - ry = y + rh; - - if (lateralState == "torque") { - // Add Actual Lateral Acceleration (roll compensated) when using Torque - // Unit: m/s² - UiElement actualLateralAccelElement = getActualLateralAccel(); - rh += drawDevUiElementRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color); - } else { - // Add Desired Steering Angle when using PID - // Unit: Degrees - UiElement steeringAngleDesiredDegElement = getSteeringAngleDesiredDeg(); - rh += drawDevUiElementRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color); - } - ry = y + rh; - - // Add Device Memory (RAM) Usage - // Unit: Percent - UiElement memoryUsagePercentElement = getMemoryUsagePercent(); - rh += drawDevUiElementRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color); - ry = y + rh; - - rh += 25; - p.setBrush(QColor(0, 0, 0, 0)); - QRect ldu(x, y, 184, rh); -} - -int AnnotatedCameraWidget::drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { - p.setFont(InterFont(38, QFont::Bold)); - drawCenteredLeftText(p, x, y, label, whiteColor(), value, units, color); - - return 430; -} - -void AnnotatedCameraWidget::drawNewDevUi2(QPainter &p, int x, int y) { - int rw = 90; - - // Add Acceleration from Car - // Unit: Meters per Second Squared - UiElement aEgoElement = getAEgo(); - rw += drawNewDevUiElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color); - - // Add Velocity of Primary Lead Car - // Unit: kph if metric, else mph - UiElement vEgoLeadElement = getVEgoLead(); - rw += drawNewDevUiElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color); - - if (torquedUseParams) { - // Add Friction Coefficient Raw from torqued - // Unit: None - UiElement frictionCoefficientFilteredElement = getFrictionCoefficientFiltered(); - rw += drawNewDevUiElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color); - - // Add Lateral Acceleration Factor Raw from torqued - // Unit: m/s² - UiElement latAccelFactorFilteredElement = getLatAccelFactorFiltered(); - rw += drawNewDevUiElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color); - } else { - // Add Steering Torque from Car EPS - // Unit: Newton Meters - UiElement steeringTorqueEpsElement = getSteeringTorqueEps(); - rw += drawNewDevUiElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color); - - // Add Bearing Degree and Direction from Car (Compass) - // Unit: Meters - UiElement bearingDegElement = getBearingDeg(); - rw += drawNewDevUiElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color); - } - - // Add Altitude of Current Location - // Unit: Meters - UiElement altitudeElement = getAltitude(); - rw += drawNewDevUiElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color); -} - -// ############################## DEV UI END ############################## - -void AnnotatedCameraWidget::drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status) { - QColor status_color; - QRect e2eStatusIcon(x, y, w, h); - p.setPen(Qt::NoPen); - p.setBrush(QBrush(blackColor(70))); - p.drawEllipse(e2eStatusIcon); - e2eStatusIcon -= QMargins(25, 25, 25, 25); - p.setPen(Qt::NoPen); - if (e2e_long_status == 2) { - status_color = QColor::fromRgbF(0.0, 1.0, 0.0, 0.9); - } else if (e2e_long_status == 1) { - status_color = QColor::fromRgbF(1.0, 0.0, 0.0, 0.9); - } - p.setBrush(QBrush(status_color)); - p.drawEllipse(e2eStatusIcon); -} - -void AnnotatedCameraWidget::drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) { - painter.setRenderHint(QPainter::Antialiasing, true); - - QColor circle_color, circle_color_0, circle_color_1; - QColor arrow_color, arrow_color_0, arrow_color_1; - if ((left_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) { - circle_color_0 = QColor(164, 0, 1); - circle_color_1 = QColor(204, 0, 1); - arrow_color_0 = QColor(72, 1, 1); - arrow_color_1 = QColor(255, 255, 255); - } else { - circle_color_0 = QColor(22, 156, 69); - circle_color_1 = QColor(30, 215, 96); - arrow_color_0 = QColor(9, 56, 27); - arrow_color_1 = QColor(255, 255, 255); - } - - if (state == 1) { - circle_color = circle_color_1; - arrow_color = arrow_color_1; - } else if (state == 0) { - circle_color = circle_color_0; - arrow_color = arrow_color_0; - } - - // Draw the circle - int circleX = x; - int circleY = y; - painter.setPen(Qt::NoPen); - painter.setBrush(circle_color); - painter.drawEllipse(circleX, circleY, circle_size, circle_size); - - // Draw the arrow - int arrowSize = 50; - int arrowX = circleX + (circle_size - arrowSize) / 4; - int arrowY = circleY + (circle_size - arrowSize) / 2; - painter.setPen(Qt::NoPen); - painter.setBrush(arrow_color); - - // Draw the arrow shape - QPolygon arrowPolygon; - arrowPolygon << QPoint(arrowX + 10, arrowY + arrowSize / 2) - << QPoint(arrowX + arrowSize - 3, arrowY) - << QPoint(arrowX + arrowSize, arrowY) - << QPoint(arrowX + arrowSize, arrowY + arrowSize) - << QPoint(arrowX + arrowSize - 3, arrowY + arrowSize) - << QPoint(arrowX + 10, arrowY + arrowSize / 2); - painter.drawPolygon(arrowPolygon); - - // Draw the tail rectangle - int tailWidth = arrowSize / 2.25; - int tailHeight = arrowSize / 2; - QRect tailRect(arrowX + arrowSize - 3, arrowY + arrowSize / 4, tailWidth, tailHeight); - painter.fillRect(tailRect, arrow_color); -} - -void AnnotatedCameraWidget::drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) { - painter.setRenderHint(QPainter::Antialiasing, true); - - QColor circle_color, circle_color_0, circle_color_1; - QColor arrow_color, arrow_color_0, arrow_color_1; - if ((right_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) { - circle_color_0 = QColor(164, 0, 1); - circle_color_1 = QColor(204, 0, 1); - arrow_color_0 = QColor(72, 1, 1); - arrow_color_1 = QColor(255, 255, 255); - } else { - circle_color_0 = QColor(22, 156, 69); - circle_color_1 = QColor(30, 215, 96); - arrow_color_0 = QColor(9, 56, 27); - arrow_color_1 = QColor(255, 255, 255); - } - - if (state == 1) { - circle_color = circle_color_1; - arrow_color = arrow_color_1; - } else if (state == 0) { - circle_color = circle_color_0; - arrow_color = arrow_color_0; - } - - - // Draw the circle - int circleX = x; - int circleY = y; - painter.setPen(Qt::NoPen); - painter.setBrush(circle_color); - painter.drawEllipse(circleX, circleY, circle_size, circle_size); - - // Draw the arrow - int arrowSize = 50; - int arrowX = circleX + (circle_size - arrowSize) / 2 + (arrowSize / 2.5) - 3; - int arrowY = circleY + (circle_size - arrowSize) / 2; - painter.setPen(Qt::NoPen); - painter.setBrush(arrow_color); - - // Draw the arrow shape - QPolygon arrowPolygon; - arrowPolygon << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2) - << QPoint(arrowX + 3, arrowY) - << QPoint(arrowX, arrowY) - << QPoint(arrowX, arrowY + arrowSize) - << QPoint(arrowX + 3, arrowY + arrowSize) - << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2); - painter.drawPolygon(arrowPolygon); - - // Draw the tail rectangle - int tailWidth = arrowSize / 2.25; - int tailHeight = arrowSize / 2; - QRect tailRect(arrowX - tailWidth + 3, arrowY + arrowSize / 4, tailWidth, tailHeight); - painter.fillRect(tailRect, arrow_color); -} - -int AnnotatedCameraWidget::blinkerPulse(int frame) { - if (frame % UI_FREQ < (UI_FREQ / 2)) { - blinker_state = 1; - } else { - blinker_state = 0; - } - - return blinker_state; -} - -void AnnotatedCameraWidget::speedLimitSignPulse(int frame) { - if (frame % UI_FREQ < (UI_FREQ / 2.5)) { - slcShowSign = false; - } else { - slcShowSign = true; - } -} - -void AnnotatedCameraWidget::drawFeatureStatusText(QPainter &p, int x, int y) { - const FeatureStatusText feature_text; - const FeatureStatusColor feature_color; - const QColor text_color = whiteColor(); - const QColor shadow_color = blackColor(38); - const int text_height = 34; - const int drop_shadow_size = 2; - const int eclipse_x_offset = 25; - const int eclipse_y_offset = 20; - const int w = 16; - const int h = 16; - - const bool longitudinal = hasLongitudinalControl(car_params); - - p.setFont(InterFont(32, QFont::Bold)); - - // Define a function to draw a feature status button - auto drawFeatureStatusElement = [&](int value, const QStringList& text_list, const QStringList& color_list, bool condition, const QString& off_text, const QString& label) { - std::pair feature_status = getFeatureStatus(value, text_list, color_list, condition, off_text); - QRect btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); - QRect btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); - p.setPen(Qt::NoPen); - p.setBrush(shadow_color); - p.drawEllipse(btn_shadow); - p.setBrush(feature_status.second); - p.drawEllipse(btn); - QString status_text; - status_text.sprintf("%s: %s", label.toStdString().c_str(), (feature_status.first).toStdString().c_str()); - p.setPen(QPen(shadow_color, 2)); - p.drawText(x + drop_shadow_size, y + drop_shadow_size, status_text); - p.setPen(QPen(text_color, 2)); - p.drawText(x, y, status_text); - y += text_height; - }; - - // Driving Personality / Gap Adjust Cruise - if (longitudinal) { - drawFeatureStatusElement(longitudinalPersonality, feature_text.gac_list_text, feature_color.gac_list_color, longitudinal, "N/A", "GAP"); - } - - // Dynamic Lane Profile - if (drivingModelGen == cereal::ModelGeneration::ONE) { - drawFeatureStatusElement(dynamicLaneProfile, feature_text.dlp_list_text, feature_color.dlp_list_color, true, "OFF", "DLP"); - } - - // TODO: Add toggle variables to cereal, and parse from cereal - if (longitudinal) { - QColor dec_color((cruiseStateEnabled && dynamicExperimentalControlToggle) ? "#4bff66" : "#ffffff"); - QRect dec_btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); - QRect dec_btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); - p.setPen(Qt::NoPen); - p.setBrush(shadow_color); - p.drawEllipse(dec_btn_shadow); - p.setBrush(dec_color); - p.drawEllipse(dec_btn); - QString dec_status_text; - dec_status_text.sprintf("DEC: %s\n", dynamicExperimentalControlToggle ? (experimentalMode ? QString(mpcMode).toStdString().c_str() : QString("Inactive").toStdString().c_str()) : "OFF"); - p.setPen(QPen(shadow_color, 2)); - p.drawText(x + drop_shadow_size, y + drop_shadow_size, dec_status_text); - p.setPen(QPen(text_color, 2)); - p.drawText(x, y, dec_status_text); - y += text_height; - } - - // TODO: Add toggle variables to cereal, and parse from cereal - // Speed Limit Control - if (longitudinal || !car_params.getPcmCruiseSpeed()) { - drawFeatureStatusElement(int(slcState), feature_text.slc_list_text, feature_color.slc_list_color, speedLimitControlToggle, "OFF", "SLC"); - } -} - -void AnnotatedCameraWidget::speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin) { - // PRE ACTIVE - if (slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE) { - int set_speed = std::nearbyint(setSpeed); - int speed_limit_offsetted = std::nearbyint(speedLimitSLC + speedLimitSLCOffset); - - // Calculate the vertical offset using a sinusoidal function for smooth bouncing - double bounce_frequency = 2.0 * M_PI / 20.0; // 20 frames for one full oscillation - int bounce_offset = 20 * sin(speed_limit_frame * bounce_frequency); // Adjust the amplitude (20 pixels) as needed - - if (set_speed < speed_limit_offsetted) { - QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - plus_arrow_up_img.height() / 2 + bounce_offset); - p.drawPixmap(iconPosition, plus_arrow_up_img); - } else if (set_speed > speed_limit_offsetted) { - QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - minus_arrow_down_img.height() / 2 - bounce_offset); - p.drawPixmap(iconPosition, minus_arrow_down_img); - } - - speed_limit_frame++; - speedLimitSignPulse(speed_limit_frame); - } - - // current speed over speed limit - else if (overSpeedLimit && speedLimitWarningFlash) { - speed_limit_frame++; - speedLimitSignPulse(speed_limit_frame); - } - - else { - speed_limit_frame = 0; - slcShowSign = true; - } -} - - void AnnotatedCameraWidget::initializeGL() { CameraWidget::initializeGL(); qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION)); @@ -1309,29 +155,12 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { const UIScene &scene = s->scene; SubMaster &sm = *(s->sm); - const auto car_state = sm["carState"].getCarState(); - - // Shane's colored lanelines + // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { - if (i == 1 || i == 2) { - // TODO: can we just use the projected vertices somehow? - const cereal::XYZTData::Reader &line = (*s->sm)["modelV2"].getModelV2().getLaneLines()[i]; - const float default_pos = 1.4; // when lane poly isn't available - const float lane_pos = line.getY().size() > 0 ? std::abs(line.getY()[5]) : default_pos; // get redder when line is closer to car - float hue = 332.5 * lane_pos - 332.5; // equivalent to {1.4, 1.0}: {133, 0} (green to red) - hue = std::fmin(133, fmax(0, hue)) / 360.; // clip and normalize - painter.setBrush(QColor::fromHslF(hue, 1.0, 0.50, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); - } else { - painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); - } + painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); painter.drawPolygon(scene.lane_line_vertices[i]); } - // TODO: Fix empty spaces when curiving back on itself - painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.2)); - if (left_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[0]); - if (right_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[1]); - // road edges for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0))); @@ -1340,70 +169,41 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // paint path QLinearGradient bg(0, height(), 0, 0); - if (madsEnabled || car_state.getCruiseState().getEnabled()) { - if (steerOverride && latActive) { - bg.setColorAt(0.0, QColor::fromHslF(20 / 360., 0.94, 0.51, 0.17)); - bg.setColorAt(0.5, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.17)); - bg.setColorAt(1.0, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.0)); - } else if (!(latActive || car_state.getCruiseState().getEnabled())) { - bg.setColorAt(0, whiteColor()); - bg.setColorAt(1, whiteColor(0)); - } else if (sm["controlsState"].getControlsState().getExperimentalMode()) { - // The first half of track_vertices are the points for the right side of the path - const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX(); - const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size()); + if (sm["controlsState"].getControlsState().getExperimentalMode()) { + // The first half of track_vertices are the points for the right side of the path + const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX(); + const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size()); - for (int i = 0; i < max_len; ++i) { - // Some points are out of frame - if (scene.track_vertices[i].y() < 0 || scene.track_vertices[i].y() > height()) continue; + for (int i = 0; i < max_len; ++i) { + // Some points are out of frame + if (scene.track_vertices[i].y() < 0 || scene.track_vertices[i].y() > height()) continue; - // Flip so 0 is bottom of frame - float lin_grad_point = (height() - scene.track_vertices[i].y()) / height(); + // Flip so 0 is bottom of frame + float lin_grad_point = (height() - scene.track_vertices[i].y()) / height(); - // speed up: 120, slow down: 0 - float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); - // FIXME: painter.drawPolygon can be slow if hue is not rounded - path_hue = int(path_hue * 100 + 0.5) / 100; + // speed up: 120, slow down: 0 + float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); + // FIXME: painter.drawPolygon can be slow if hue is not rounded + path_hue = int(path_hue * 100 + 0.5) / 100; - float saturation = fmin(fabs(acceleration[i] * 1.5), 1); - float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey - float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade - bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha)); + float saturation = fmin(fabs(acceleration[i] * 1.5), 1); + float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey + float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade + bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha)); - // Skip a point, unless next is last - i += (i + 2) < max_len ? 1 : 0; - } - - } else { - bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); - bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); - bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0)); + // Skip a point, unless next is last + i += (i + 2) < max_len ? 1 : 0; } + } else { - bg.setColorAt(0.0, whiteColor(102)); - bg.setColorAt(0.5, whiteColor(89)); - bg.setColorAt(1.0, whiteColor(0)); + bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); + bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); + bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0)); } painter.setBrush(bg); painter.drawPolygon(scene.track_vertices); - // create path combining track vertices and track edge vertices - QPainterPath path; - path.addPolygon(scene.track_vertices); - path.addPolygon(scene.track_edge_vertices); - - // paint path edges - QLinearGradient pe(0, height(), 0, height() / 4); - if (!scene.dynamic_lane_profile_status) { - pe.setColorAt(0.0, QColor::fromHslF(240 / 360., 0.94, 0.51, 1.0)); - pe.setColorAt(0.5, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.5)); - pe.setColorAt(1.0, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.0)); - - painter.setBrush(pe); - painter.drawPath(path); - } - painter.restore(); } @@ -1415,7 +215,7 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) // base icon int offset = UI_BORDER_SIZE + btn_size / 2; int x = rightHandDM ? width() - offset : offset; - int y = height() - offset - rn_offset; + int y = height() - offset; float opacity = dmActive ? 0.65 : 0.2; drawIcon(painter, QPoint(x, y), dm_img, blackColor(70), opacity); @@ -1448,56 +248,13 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) painter.restore(); } -void AnnotatedCameraWidget::rocketFuel(QPainter &p) { - - static const int ct_n = 1; - static float ct; - - int rect_w = rect().width(); - int rect_h = rect().height(); - - const int n = 15 + 1; //Add one off screen due to timing issues - static float t[n]; - int dim_n = (sin(ct / 5) + 1) * (n - 0.01); - t[dim_n] = 1.0; - t[(int)(ct/ct_n)] = 1.0; - - UIState *s = uiState(); - float vc_accel0 = (*s->sm)["carState"].getCarState().getAEgo(); - static float vc_accel; - vc_accel = vc_accel + (vc_accel0 - vc_accel) / 5; - float hha = 0; - if (vc_accel > 0) { - hha = 0.85 - 0.1 / vc_accel; // only extend up to 85% - p.setBrush(QColor(0, 245, 0, 200)); - } - if (vc_accel < 0) { - hha = 0.85 + 0.1 / vc_accel; // only extend up to 85% - p.setBrush(QColor(245, 0, 0, 200)); - } - if (hha < 0) { - hha = 0; - } - hha = hha * rect_h; - float wp = 28; - if (vc_accel > 0) { - QRect ra = QRect(rect_w - wp, rect_h / 2 - hha / 2, wp, hha / 2); - p.drawRect(ra); - } else { - QRect ra = QRect(rect_w - wp, rect_h / 2, wp, hha / 2); - p.drawRect(ra); - } -} - -void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, - int num, const cereal::CarState::Reader &car_data, int chevron_data) { +void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) { painter.save(); const float speedBuff = 10.; const float leadBuff = 40.; const float d_rel = lead_data.getDRel(); const float v_rel = lead_data.getVRel(); - const float v_ego = car_data.getVEgo(); float fillAlpha = 0; if (d_rel < leadBuff) { @@ -1524,54 +281,15 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState painter.setBrush(redColor(fillAlpha)); painter.drawPolygon(chevron, std::size(chevron)); - if (num == 0) { // Display metrics to the 0th lead car - int chevron_types = 2; - QStringList chevron_text[chevron_types]; - int position; - float val; - if (chevron_data == 1 || chevron_data == 3) { - 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) { - 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")); - } - - float str_w = 200; // Width of the text box, might need adjustment - float str_h = 50; // Height of the text box, adjust as necessary - painter.setFont(InterFont(45, QFont::Bold)); - // Calculate the center of the chevron and adjust the text box position - float text_y = y + sz + 12; // Position the text at the bottom of the chevron - QRect textRect(x - str_w / 2, text_y, str_w, str_h); // Adjust the rectangle to center the text horizontally at the chevron's bottom - QPoint shadow_offset(2, 2); - for (int i = 0; i < chevron_types; ++i) { - if (!chevron_text[i].isEmpty()) { - painter.setPen(QColor(0x0, 0x0, 0x0, 200)); // Draw shadow - painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y() + i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); - painter.setPen(QColor(0xff, 0xff, 0xff)); // Draw text - painter.drawText(textRect.translated(0, i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); - painter.setPen(Qt::NoPen); // Reset pen to default - } - } - } - painter.restore(); } void AnnotatedCameraWidget::paintGL() { -} - -void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { UIState *s = uiState(); SubMaster &sm = *(s->sm); const double start_draw_t = millis_since_boot(); const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); - QPainter painter(this); - // draw camera frame { std::lock_guard lk(frame_lock); @@ -1593,10 +311,9 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); if (has_wide_cam) { float v_ego = sm["carState"].getCarState().getVEgo(); - float steer_angle = sm["carState"].getCarState().getSteeringAngleDeg(); - if ((v_ego < 10) || available_streams.size() == 1 || (std::fabs(steer_angle) > 45)) { + if ((v_ego < 10) || available_streams.size() == 1) { wide_cam_requested = true; - } else if ((v_ego > 15) && (std::fabs(steer_angle) < 30)) { + } else if (v_ego > 15) { wide_cam_requested = false; } wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode(); @@ -1605,10 +322,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { } CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); - if (reversing && s->scene.reverse_dm_cam) { - CameraWidget::setStreamType(VISION_STREAM_DRIVER, s->scene.reverse_dm_cam); - } - s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD; if (s->scene.calibration_valid) { auto calib = s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib; @@ -1616,12 +329,11 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { } else { CameraWidget::updateCalibration(DEFAULT_CALIBRATION); } - painter.beginNativePainting(); CameraWidget::setFrameId(model.getFrameId()); CameraWidget::paintGL(); - painter.endNativePainting(); } + QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); @@ -1631,18 +343,15 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) { auto radar_state = sm["radarState"].getRadarState(); - auto car_state = sm["carState"].getCarState(); update_leads(s, radar_state, model.getPosition()); auto lead_one = radar_state.getLeadOne(); auto lead_two = radar_state.getLeadTwo(); if (lead_one.getStatus()) { - drawLead(painter, lead_one, s->scene.lead_vertices[0], 0, car_state, s->scene.chevron_data); + drawLead(painter, lead_one, s->scene.lead_vertices[0]); } if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { - drawLead(painter, lead_two, s->scene.lead_vertices[1], 1, car_state, s->scene.chevron_data); + drawLead(painter, lead_two, s->scene.lead_vertices[1]); } - - rocketFuel(painter); } } @@ -1654,21 +363,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { drawHud(painter); - if (left_blinker || right_blinker) { - blinker_frame++; - int state = blinkerPulse(blinker_frame); - int blinker_x = splitPanelVisible ? 150 : 180; - int blinker_y = splitPanelVisible ? 220 : 90; - if (left_blinker) { - drawLeftTurnSignal(painter, rect().center().x() - (blinker_x + blinker_size), blinker_y, blinker_size, state); - } - if (right_blinker) { - drawRightTurnSignal(painter, rect().center().x() + blinker_x, blinker_y, blinker_size, state); - } - } else { - blinker_frame = 0; - } - double cur_draw_t = millis_since_boot(); double dt = cur_draw_t - prev_draw_t; double fps = fps_filter.update(1. / dt * 1000); @@ -1682,11 +376,6 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { auto m = msg.initEvent().initUiDebug(); m.setDrawTimeMillis(cur_draw_t - start_draw_t); pm->send("uiDebug", msg); - - MessageBuilder e2e_long_msg; - auto e2eLongStatus = e2e_long_msg.initEvent().initE2eLongStateSP(); - e2eLongStatus.setStatus(e2eStatus); - e2e_state->send("e2eLongStateSP", e2e_long_msg); } void AnnotatedCameraWidget::showEvent(QShowEvent *event) { @@ -1694,4 +383,4 @@ void AnnotatedCameraWidget::showEvent(QShowEvent *event) { ui_update_params(uiState()); prev_draw_t = millis_since_boot(); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h index 29b9c4833b..c92c53af46 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -2,14 +2,10 @@ #include #include -#include #include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/widgets/cameraview.h" -const int subsign_img_size = 35; -const int blinker_size = 120; - class AnnotatedCameraWidget : public CameraWidget { Q_OBJECT @@ -17,68 +13,8 @@ public: explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0); void updateState(const UIState &s); - MapSettingsButton *map_settings_btn; - OnroadSettingsButton *onroad_settings_btn; - private: void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); - void drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color); - void drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color, const QString &speed, - int alpha); - void drawCircle(QPainter &p, int x, int y, int r, QBrush bg); - void drawSpeedSign(QPainter &p, QRect rc, const QString &speed, const QString &sub_text, int subtext_size, - bool is_map_sourced, bool is_active); - void drawTrunSpeedSign(QPainter &p, QRect rc, const QString &speed, const QString &sub_text, int curv_sign, - bool is_active); - - void drawColoredText(QPainter &p, int x, int y, const QString &text, QColor color); - void drawStandstillTimer(QPainter &p, int x, int y); - - // ############################## DEV UI START ############################## - void drawRightDevUi(QPainter &p, int x, int y); - int drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); - int drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); - void drawNewDevUi2(QPainter &p, int x, int y); - void drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2); - struct UiElement { - QString value; - QString label; - QString units; - QColor color; - - UiElement(const QString &value, const QString &label, const QString &units, const QColor &color = QColor(255, 255, 255, 255)) - : value(value), label(label), units(units), color(color) {} - }; - - UiElement getDRel(); - UiElement getVRel(); - UiElement getSteeringAngleDeg(); - UiElement getActualLateralAccel(); - UiElement getSteeringAngleDesiredDeg(); - UiElement getMemoryUsagePercent(); - - UiElement getAEgo(); - UiElement getVEgoLead(); - UiElement getFrictionCoefficientFiltered(); - UiElement getLatAccelFactorFiltered(); - UiElement getSteeringTorqueEps(); - UiElement getBearingDeg(); - UiElement getAltitude(); - - // ############################## DEV UI END ############################## - - void drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status); - - void drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state); - void drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state); - int blinkerPulse(int frame); - void updateButtonsLayout(bool is_rhd); - - void drawFeatureStatusText(QPainter &p, int x, int y); - void speedLimitSignPulse(int frame); - void speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin); - void mousePressEvent(QMouseEvent* e) override; - void drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color); QVBoxLayout *main_layout; ExperimentalButton *experimental_btn; @@ -86,15 +22,12 @@ private: float speed; QString speedUnit; float setSpeed; - float speedLimit; bool is_cruise_set = false; bool is_metric = false; bool dmActive = false; bool hideBottomIcons = false; bool rightHandDM = false; float dm_fade_state = 1.0; - bool has_us_speed_limit = false; - bool has_eu_speed_limit = false; bool v_ego_cluster_seen = false; int status = STATUS_DISENGAGED; std::unique_ptr pm; @@ -102,126 +35,19 @@ private: int skip_frame_count = 0; bool wide_cam_requested = false; - Params params; - QHBoxLayout *buttons_layout; - QPixmap map_img; - QPixmap left_img; - QPixmap right_img; - bool left_blindspot = false; - bool right_blindspot = false; - std::unique_ptr e2e_state; - - bool steerOverride = false; - bool gasOverride = false; - bool latActive = false; - bool madsEnabled = false; - - bool brakeLights; - - bool standStillTimer; - bool standStill; - float standstillElapsedTime; - - bool showVTC = false; - QString vtcSpeed; - QColor vtcColor; - - bool showDebugUI = false; - - QString roadName = ""; - - bool showSpeedLimit = false; - float speedLimitSLC; - float speedLimitSLCOffset; - float speedLimitWarningOffset; - QString slcSubText; - float slcSubTextSize = 0.0; - bool overSpeedLimit; - bool mapSourcedSpeedLimit = false; - bool slcActive = false; - - bool showTurnSpeedLimit = false; - QString turnSpeedLimit; - QString tscSubText; - bool tscActive = false; - int curveSign = 0; - - bool hideVEgoUi; - - bool splitPanelVisible; - - // ############################## DEV UI START ############################## - bool lead_status; - float lead_d_rel = 0; - float lead_v_rel = 0; - QString lateralState; - float angleSteers = 0; - float steerAngleDesired = 0; - float curvature; - float roll; - int memoryUsagePercent; - int devUiInfo; - float gpsAccuracy; - float altitude; - float vEgo; - float aEgo; - float steeringTorqueEps; - float bearingAccuracyDeg; - float bearingDeg; - bool torquedUseParams; - float latAccelFactorFiltered; - float frictionCoefficientFiltered; - bool liveValid; - // ############################## DEV UI END ############################## - - float btnPerc; - - bool reversing; - - int e2eState; - int e2eStatus; - - bool left_blinker, right_blinker, lane_change_edge_block; - int blinker_frame; - int blinker_state = 0; - - cereal::LongitudinalPlanSP::SpeedLimitControlState slcState; - int longitudinalPersonality; - int dynamicLaneProfile; - QString mpcMode; - - int speed_limit_frame; - bool slcShowSign = true; - QPixmap plus_arrow_up_img; - QPixmap minus_arrow_down_img; - QRect sl_sign_rect; - int rn_offset = 0; - bool e2eLongAlertUi, dynamicExperimentalControlToggle, speedLimitControlToggle, speedLimitWarningFlash; - cereal::CarParams::Reader car_params; - bool cruiseStateEnabled; - bool experimentalMode; - - bool featureStatusToggle; - - cereal::ModelGeneration drivingModelGen; - protected: void paintGL() override; void initializeGL() override; void showEvent(QShowEvent *event) override; void updateFrameMat() override; void drawLaneLines(QPainter &painter, const UIState *s); - void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, - int num, const cereal::CarState::Reader &car_data, int chevron_data); + void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd); void drawHud(QPainter &p); void drawDriverState(QPainter &painter, const UIState *s); inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } - void paintEvent(QPaintEvent *event) override; - void rocketFuel(QPainter &p); - double prev_draw_t = 0; FirstOrderFilter fps_filter; -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc index 850218ac34..acb9cad89d 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/qt/onroad/onroad_home.cc @@ -3,14 +3,6 @@ #include #include -#ifdef ENABLE_MAPS -#include "selfdrive/ui/qt/maps/map_helpers.h" -#include "selfdrive/ui/qt/maps/map_panel.h" -#endif -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" -#endif - #include "selfdrive/ui/qt/util.h" OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { @@ -20,11 +12,7 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { stacked_layout->setStackingMode(QStackedLayout::StackAll); main_layout->addLayout(stacked_layout); -#ifdef SUNNYPILOT nvg = new AnnotatedCameraWidget(VISION_STREAM_ROAD, this); -#else - nvg = new AnnotatedCameraSPWidget(VISION_STREAM_ROAD, this); -#endif QWidget * split_wrapper = new QWidget; split = new QHBoxLayout(split_wrapper); @@ -37,11 +25,6 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { split->insertWidget(0, arCam); } - if (getenv("MAP_RENDER_VIEW")) { - CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); - split->insertWidget(0, map_render); - } - stacked_layout->addWidget(split_wrapper); alerts = new OnroadAlerts(this); @@ -54,7 +37,6 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); - QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindow::primeChanged); } void OnroadWindow::updateState(const UIState &s) { @@ -62,12 +44,6 @@ void OnroadWindow::updateState(const UIState &s) { return; } - if (s.scene.map_on_left || s.scene.mapbox_fullscreen) { - split->setDirection(QBoxLayout::LeftToRight); - } else { - split->setDirection(QBoxLayout::RightToLeft); - } - alerts->updateState(s); nvg->updateState(s); @@ -79,106 +55,11 @@ void OnroadWindow::updateState(const UIState &s) { } } - -void OnroadWindow::mousePressEvent(QMouseEvent* e) { -#ifdef ENABLE_MAPS - UIState *s = uiState(); - UIScene &scene = s->scene; - if (map != nullptr && !isOnroadSettingsVisible()) { - if (wakeScreenTimeout()) { - // Switch between map and sidebar when using navigate on openpilot - bool sidebarVisible = geometry().x() > 0; - bool show_map = uiState()->scene.navigate_on_openpilot_deprecated ? sidebarVisible : !sidebarVisible; - updateMapSize(scene); - map->setVisible(show_map && !map->isVisible()); - } - } -#endif -#ifdef SUNNYPILOT - if (onroad_settings != nullptr && !isMapVisible()) { - if (wakeScreenTimeout()) { - onroad_settings->setVisible(false); - } - } - // propagation event to parent(HomeWindow) - QWidget::mousePressEvent(e); -#endif -} - -void OnroadWindow::createMapWidget() { -#ifdef ENABLE_MAPS - auto m = new MapPanel(get_mapbox_settings()); - map = m; - - QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::mapPanelRequested); - QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::onroadSettingsPanelNotRequested); - QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings); - nvg->map_settings_btn->setEnabled(true); - - m->setFixedWidth(uiState()->scene.mapbox_fullscreen ? topWidget(this)->width() : - topWidget(this)->width() / 2 - UI_BORDER_SIZE); - split->insertWidget(0, m); - - // hidden by default, made visible when navRoute is published - m->setVisible(false); -#endif -} - -void OnroadWindow::createOnroadSettingsWidget() { -#ifdef SUNNYPILOT - auto os = new OnroadSettingsPanel(this); - onroad_settings = os; - - QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindow::onroadSettingsPanelRequested); - QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindow::mapPanelNotRequested); - QObject::connect(nvg->onroad_settings_btn, &OnroadSettingsButton::clicked, os, &OnroadSettingsPanel::toggleOnroadSettings); - nvg->onroad_settings_btn->setEnabled(true); - - os->setFixedWidth(topWidget(this)->width() / 2.6 - UI_BORDER_SIZE); - split->insertWidget(0, os); - - // hidden by default - os->setVisible(false); -#endif -} - void OnroadWindow::offroadTransition(bool offroad) { - if (!offroad) { -#ifdef ENABLE_MAPS - if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { - createMapWidget(); - } -#endif -#ifdef SUNNYPILOT - if (onroad_settings == nullptr) { - createOnroadSettingsWidget(); - } -#endif - } - alerts->clear(); } -void OnroadWindow::updateMapSize(const UIScene &scene) { - map->setFixedWidth(scene.mapbox_fullscreen ? topWidget(this)->width() : - topWidget(this)->width() / 2 - UI_BORDER_SIZE); - split->insertWidget(0, map); -} - -void OnroadWindow::primeChanged(bool prime) { -#ifdef ENABLE_MAPS - if (map && (!prime && MAPBOX_TOKEN.isEmpty())) { - nvg->map_settings_btn->setEnabled(false); - nvg->map_settings_btn->setVisible(false); - map->deleteLater(); - map = nullptr; - } else if (!map && (prime || !MAPBOX_TOKEN.isEmpty())) { - createMapWidget(); - } -#endif -} - void OnroadWindow::paintEvent(QPaintEvent *event) { QPainter p(this); p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h index 1beadcd13b..40292dfa9a 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.h +++ b/selfdrive/ui/qt/onroad/onroad_home.h @@ -1,51 +1,28 @@ #pragma once -#include "common/params.h" #include "selfdrive/ui/qt/onroad/alerts.h" + +#if SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h" +#define AnnotatedCameraWidget AnnotatedCameraWidgetSP +#else #include "selfdrive/ui/qt/onroad/annotated_camera.h" +#endif class OnroadWindow : public QWidget { Q_OBJECT public: OnroadWindow(QWidget* parent = 0); - bool isMapVisible() const { return map && map->isVisible(); } - void showMapPanel(bool show) { if (map) map->setVisible(show); } - bool isOnroadSettingsVisible() const { return onroad_settings && onroad_settings->isVisible(); } - bool isMapAvailable() const { return map; } - void mapPanelNotRequested() { if (map) map->setVisible(false); } - void onroadSettingsPanelNotRequested() { if (onroad_settings) onroad_settings->setVisible(false); } - - bool wakeScreenTimeout() { - if ((uiState()->scene.sleep_btn != 0 && uiState()->scene.sleep_btn_opacity != 0) || - (uiState()->scene.sleep_time != 0 && uiState()->scene.onroadScreenOff != -2)) { - return true; - } - return false; - } - -signals: - void mapPanelRequested(); - void onroadSettingsPanelRequested(); - -private: - void createMapWidget(); - void createOnroadSettingsWidget(); - void paintEvent(QPaintEvent *event); - void mousePressEvent(QMouseEvent* e) override; +protected: + void paintEvent(QPaintEvent *event) override; OnroadAlerts *alerts; AnnotatedCameraWidget *nvg; QColor bg = bg_colors[STATUS_DISENGAGED]; - QWidget *map = nullptr; - QWidget *onroad_settings = nullptr; QHBoxLayout* split; - Params params; - -private slots: - void offroadTransition(bool offroad); - void primeChanged(bool prime); - void updateState(const UIState &s); - void updateMapSize(const UIScene &scene); -}; +protected slots: + virtual void offroadTransition(bool offroad); + virtual void updateState(const UIState &s); +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 6b6f939af4..00b4693822 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -75,10 +75,13 @@ void MainWindow::closeSettings() { if (uiState()->scene.started) { homeWindow->showSidebar(false); +//TODO: TBD if it's worth it to create our own sp_priv_window.cc to have it split from the OG code and not have preprocessor defs in the OG code, at least not the cc +#ifdef SUNNYPILOT // Map is always shown when using navigate on openpilot if (uiState()->scene.navigate_on_openpilot_deprecated) { homeWindow->showMapPanel(true); } +#endif } } diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 05b61e1f76..515e9f362a 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -3,10 +3,16 @@ #include #include -#include "selfdrive/ui/qt/home.h" #include "selfdrive/ui/qt/offroad/onboarding.h" #include "selfdrive/ui/qt/offroad/settings.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" +#define HomeWindow HomeWindowSP +#else +#include "selfdrive/ui/qt/home.h" +#endif + class MainWindow : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 46b6e7c649..eab8fe62d7 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -1,17 +1,37 @@ -widgets_src = ["sunnypilot/qt/offroad/display_settings.cc", "sunnypilot/qt/offroad/sunnypilot_settings.cc", - "sunnypilot/qt/offroad/vehicle_settings.cc", "sunnypilot/qt/offroad/visuals_settings.cc", - "sunnypilot/qt/offroad/trips_settings.cc", "sunnypilot/qt/offroad/mads_settings.cc", - "sunnypilot/qt/offroad/lane_change_settings.cc", "sunnypilot/qt/offroad/speed_limit_control_settings.cc", - "sunnypilot/qt/offroad/monitoring_settings.cc", "sunnypilot/qt/offroad/osm_settings.cc", - "sunnypilot/qt/offroad/custom_offsets_settings.cc", "sunnypilot/qt/widgets/drive_stats.cc", - "sunnypilot/qt/offroad/software_settings_sp.cc", "sunnypilot/qt/offroad/models_fetcher.cc", - "sunnypilot/qt/offroad/speed_limit_warning_settings.cc", "sunnypilot/qt/offroad/speed_limit_policy_settings.cc", - "sunnypilot/qt/offroad/sunnylink_settings.cc"] +widgets_src = [ + "sunnypilot/qt/offroad/display_settings.cc", + "sunnypilot/qt/offroad/sunnypilot_settings.cc", + "sunnypilot/qt/offroad/vehicle_settings.cc", + "sunnypilot/qt/offroad/visuals_settings.cc", + "sunnypilot/qt/offroad/trips_settings.cc", + "sunnypilot/qt/offroad/mads_settings.cc", + "sunnypilot/qt/offroad/lane_change_settings.cc", + "sunnypilot/qt/offroad/speed_limit_control_settings.cc", + "sunnypilot/qt/offroad/monitoring_settings.cc", + "sunnypilot/qt/offroad/osm_settings.cc", + "sunnypilot/qt/offroad/custom_offsets_settings.cc", + "sunnypilot/qt/widgets/drive_stats.cc", + "sunnypilot/qt/offroad/software_settings_sp.cc", + "sunnypilot/qt/offroad/models_fetcher.cc", + "sunnypilot/qt/offroad/speed_limit_warning_settings.cc", + "sunnypilot/qt/offroad/speed_limit_policy_settings.cc", + "sunnypilot/qt/offroad/sunnylink_settings.cc" +] -network_src = ["sunnypilot/qt/network/sunnylink/sunnylink_client.cc", "sunnypilot/qt/network/sunnylink/services/base_device_service.cc", - "sunnypilot/qt/network/sunnylink/services/role_service.cc", "sunnypilot/qt/network/sunnylink/services/user_service.cc"] +network_src = [ + "sunnypilot/qt/network/sunnylink/sunnylink_client.cc", + "sunnypilot/qt/network/sunnylink/services/base_device_service.cc", + "sunnypilot/qt/network/sunnylink/services/role_service.cc", + "sunnypilot/qt/network/sunnylink/services/user_service.cc"] -qt_src = ["sunnypilot/qt/onroad/onroad_settings.cc", "sunnypilot/qt/onroad/onroad_settings_panel.cc"] +qt_src = [ + "sunnypilot/qt/sp_priv_home.cc", + "sunnypilot/qt/sp_priv_offroad_home.cc", + "sunnypilot/qt/onroad/onroad_settings.cc", + "sunnypilot/qt/onroad/onroad_settings_panel.cc", + "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", + "sunnypilot/qt/onroad/sp_priv_annotated_camera.cc", +] sp_widgets_src = widgets_src + network_src sp_qt_src = qt_src diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc new file mode 100644 index 0000000000..70812ae9e3 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc @@ -0,0 +1,1697 @@ +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h" + +#include +#include +#include +#include + +#include "common/swaglog.h" +#include "selfdrive/ui/qt/onroad/buttons.h" +#include "selfdrive/ui/qt/util.h" + +static std::pair getFeatureStatus(int value, QStringList text_list, QStringList color_list, + bool condition, QString off_text) { + + QString text("Error"); + QColor color("#ffffff"); + + for (int i = 0; i < text_list.size() && i < color_list.size(); ++i) { + if (value == i) { + text = condition ? text_list[i] : off_text; + color = condition ? QColor(color_list[i]) : QColor("#ffffff"); + break; // Exit the loop once a match is found + } + } + + return {text, color}; +} + + +// Window that shows camera view and variety of info drawn on top +AnnotatedCameraWidgetSP::AnnotatedCameraWidgetSP(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { + pm = std::make_unique>({"uiDebug"}); + e2e_state = std::make_unique>({"e2eLongStateSP"}); + + main_layout = new QVBoxLayout(this); + main_layout->setMargin(UI_BORDER_SIZE); + main_layout->setSpacing(0); + + experimental_btn = new ExperimentalButton(this); + main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); + + onroad_settings_btn = new OnroadSettingsButton(this); + + map_settings_btn = new MapSettingsButton(this); + + dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); + map_img = loadPixmap("../assets/img_world_icon.png", {subsign_img_size, subsign_img_size}); + left_img = loadPixmap("../assets/img_turn_left_icon.png", {subsign_img_size, subsign_img_size}); + right_img = loadPixmap("../assets/img_turn_right_icon.png", {subsign_img_size, subsign_img_size}); + + buttons_layout = new QHBoxLayout(); + buttons_layout->setContentsMargins(0, 0, 10, 20); + main_layout->addLayout(buttons_layout); + updateButtonsLayout(false); +} + +void AnnotatedCameraWidgetSP::mousePressEvent(QMouseEvent* e) { + bool propagate_event = true; + + UIState *s = uiState(); + UIScene &scene = s->scene; + const SubMaster &sm = *(s->sm); + const auto longitudinal_plan_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); + + if (longitudinal_plan_sp.getSpeedLimit() > 0.0 && sl_sign_rect.contains(e->x(), e->y())) { + // If touching the speed limit sign area when visible + scene.last_speed_limit_sign_tap = seconds_since_boot(); + params.putBool("LastSpeedLimitSignTap", true); + scene.speed_limit_control_enabled = !scene.speed_limit_control_enabled; + params.putBool("EnableSlc", scene.speed_limit_control_enabled); + propagate_event = false; + } + + if (propagate_event) { + QWidget::mousePressEvent(e); + } +} + +void AnnotatedCameraWidgetSP::updateButtonsLayout(bool is_rhd) { + QLayoutItem *item; + while ((item = buttons_layout->takeAt(0)) != nullptr) { + delete item; + } + + buttons_layout->setContentsMargins(0, 0, 10, rn_offset != 0 ? rn_offset + 10 : 20); + + if (is_rhd) { + buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); + buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); + + buttons_layout->addStretch(1); + + buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); + buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); + } else { + buttons_layout->addSpacing(onroad_settings_btn->isVisible() ? 216 : 0); + buttons_layout->addWidget(onroad_settings_btn, 0, Qt::AlignBottom | Qt::AlignLeft); + + buttons_layout->addStretch(1); + + buttons_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight); + buttons_layout->addSpacing(map_settings_btn->isVisible() ? 30 : 0); // Add spacing to the right + } +} + +void AnnotatedCameraWidgetSP::updateState(const UIState &s) { + const int SET_SPEED_NA = 255; + const SubMaster &sm = *(s.sm); + + const bool cs_alive = sm.alive("controlsState"); + const bool nav_alive = sm.alive("navInstruction") && sm["navInstruction"].getValid(); + const auto cs = sm["controlsState"].getControlsState(); + const auto cs_sp = sm["controlsStateSP"].getControlsStateSP(); + const auto car_state = sm["carState"].getCarState(); + const auto nav_instruction = sm["navInstruction"].getNavInstruction(); + const auto car_control = sm["carControl"].getCarControl(); + const auto radar_state = sm["radarState"].getRadarState(); + const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1; + const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); + const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); + const auto lateral_plan_sp = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED(); + car_params = sm["carParams"].getCarParams(); + + // Handle older routes where vCruiseCluster is not set + float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); + setSpeed = cs_alive ? v_cruise : SET_SPEED_NA; + is_cruise_set = setSpeed > 0 && (int)setSpeed != SET_SPEED_NA; + if (is_cruise_set && !s.scene.is_metric) { + setSpeed *= KM_TO_MILE; + } + + // Handle older routes where vEgoCluster is not set + v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0; + float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo(); + v_ego = s.scene.true_vego_ui ? car_state.getVEgo() : v_ego; + speed = cs_alive ? std::max(0.0, v_ego) : 0.0; + speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; + + auto speed_limit_sign = nav_instruction.getSpeedLimitSign(); + speedLimit = nav_alive ? nav_instruction.getSpeedLimit() : 0.0; + speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + + has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD); + has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA); + is_metric = s.scene.is_metric; + speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); + hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); + status = s.status; + + // TODO: Add minimum speed? + left_blindspot = cs_alive && car_state.getLeftBlindspot(); + right_blindspot = cs_alive && car_state.getRightBlindspot(); + + steerOverride = car_state.getSteeringPressed(); + gasOverride = car_state.getGasPressed(); + latActive = car_control.getLatActive(); + madsEnabled = car_state.getMadsEnabled(); + + brakeLights = car_state.getBrakeLightsDEPRECATED() && s.scene.visual_brake_lights; + + standStillTimer = s.scene.stand_still_timer; + standStill = car_state.getStandstill(); + standstillElapsedTime = lateral_plan_sp.getStandstillElapsed(); + + hideVEgoUi = s.scene.hide_vego_ui; + + splitPanelVisible = s.scene.map_visible || s.scene.onroad_settings_visible; + + // ############################## DEV UI START ############################## + lead_d_rel = radar_state.getLeadOne().getDRel(); + lead_v_rel = radar_state.getLeadOne().getVRel(); + lead_status = radar_state.getLeadOne().getStatus(); + lateralState = QString::fromStdString(cs_sp.getLateralState()); + angleSteers = car_state.getSteeringAngleDeg(); + steerAngleDesired = cs.getLateralControlState().getPidState().getSteeringAngleDesiredDeg(); + curvature = cs.getCurvature(); + roll = sm["liveParameters"].getLiveParameters().getRoll(); + memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); + devUiInfo = s.scene.dev_ui_info; + gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; //External reports accuracy, internal does not. + altitude = gpsLocation.getAltitude(); + vEgo = car_state.getVEgo(); + aEgo = car_state.getAEgo(); + steeringTorqueEps = car_state.getSteeringTorqueEps(); + bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); + bearingDeg = gpsLocation.getBearingDeg(); + torquedUseParams = (ltp.getUseParams() || s.scene.live_torque_toggle) && !s.scene.torqued_override; + latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); + frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); + liveValid = ltp.getLiveValid(); + // ############################## DEV UI END ############################## + + btnPerc = s.scene.sleep_btn_opacity * 0.05; + + left_blinker = car_state.getLeftBlinker(); + right_blinker = car_state.getRightBlinker(); + lane_change_edge_block = lateral_plan_sp.getLaneChangeEdgeBlockDEPRECATED(); + + // update engageability/experimental mode button + experimental_btn->updateState(s); + + // update onroad settings button state + onroad_settings_btn->updateState(s); + + // update DM icon + auto dm_state = sm["driverMonitoringState"].getDriverMonitoringState(); + dmActive = dm_state.getIsActiveMode(); + rightHandDM = dm_state.getIsRHD(); + // DM icon transition + dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0); + + // update buttons layout + updateButtonsLayout(rightHandDM); + + // hide map settings button for alerts and flip for right hand DM + if (map_settings_btn->isEnabled()) { + map_settings_btn->setVisible(!hideBottomIcons); + buttons_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); + } + + // hide onroad settings button for alerts and flip for right hand DM + if (onroad_settings_btn->isEnabled()) { + onroad_settings_btn->setVisible(!hideBottomIcons); + buttons_layout->setAlignment(onroad_settings_btn, (rightHandDM ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignBottom); + } + + const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); + slcState = lp_sp.getSpeedLimitControlState(); + + speedLimitControlToggle = s.scene.speed_limit_control_enabled; + + const auto vtcState = lp_sp.getVisionTurnControllerState(); + const float vtc_speed = lp_sp.getVisionTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + const auto lpSoruce = lp_sp.getLongitudinalPlanSource(); + QColor vtc_color = tcs_colors[int(vtcState)]; + vtc_color.setAlpha(lpSoruce == cereal::LongitudinalPlanSP::LongitudinalPlanSource::TURN ? 255 : 100); + + showVTC = vtcState > cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED; + vtcSpeed = QString::number(std::nearbyint(vtc_speed)); + vtcColor = vtc_color; + showDebugUI = s.scene.show_debug_ui; + + const auto lmd_sp = sm["liveMapDataSP"].getLiveMapDataSP(); + + const auto data_type = int(lmd_sp.getDataType()); + const QString data_type_draw(data_type == 2 ? "🌐 " : ""); + roadName = QString::fromStdString(lmd_sp.getCurrentRoadName()); + roadName = !roadName.isEmpty() ? data_type_draw + roadName : ""; + + float speed_limit_slc = lp_sp.getSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + const float speed_limit_offset = lp_sp.getSpeedLimitOffset() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + const bool sl_force_active = speedLimitControlToggle && + seconds_since_boot() < s.scene.last_speed_limit_sign_tap + 2.0; + const bool sl_inactive = !sl_force_active && (!speedLimitControlToggle || + slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::INACTIVE); + const bool sl_temp_inactive = !sl_force_active && (speedLimitControlToggle && + slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE); + const bool sl_pre_active = !sl_force_active && (speedLimitControlToggle && + slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE); + const int sl_distance = int(lp_sp.getDistToSpeedLimit() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; + const QString sl_distance_str(QString::number(sl_distance) + (s.scene.is_metric ? "m" : "f")); + const QString sl_offset_str(speed_limit_offset > 0.0 ? speed_limit_offset < 0.0 ? + "-" + QString::number(std::nearbyint(std::abs(speed_limit_offset))) : + "+" + QString::number(std::nearbyint(speed_limit_offset)) : ""); + const QString sl_inactive_str(sl_temp_inactive && s.scene.speed_limit_control_engage_type == 0 ? "TEMP" : ""); + const QString sl_substring(sl_inactive || sl_temp_inactive || sl_pre_active ? sl_inactive_str : + sl_distance > 0 ? sl_distance_str : sl_offset_str); + + showSpeedLimit = speed_limit_slc > 0.0; + speedLimitSLC = speed_limit_slc; + speedLimitSLCOffset = speed_limit_offset; + slcSubText = sl_substring; + slcSubTextSize = sl_inactive || sl_temp_inactive || sl_distance > 0 ? 25.0 : 27.0; + mapSourcedSpeedLimit = lp_sp.getIsMapSpeedLimit(); + slcActive = !sl_inactive && !sl_temp_inactive; + overSpeedLimit = showSpeedLimit && s.scene.speed_limit_warning_type != 0 && + (std::nearbyint(speed_limit_slc + s.scene.speed_limit_warning_value_offset) < std::nearbyint(speed)); + plus_arrow_up_img = loadPixmap("../assets/img_plus_arrow_up", {105, 105}); + minus_arrow_down_img = loadPixmap("../assets/img_minus_arrow_down", {105, 105}); + + const float tsc_speed = lp_sp.getTurnSpeed() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH); + const auto tscState = lp_sp.getTurnSpeedControlState(); + const int t_distance = int(lp_sp.getDistToTurn() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; + const QString t_distance_str(QString::number(t_distance) + (s.scene.is_metric ? "m" : "f")); + + showTurnSpeedLimit = tsc_speed > 0.0 && std::round(tsc_speed) < 224 && (tsc_speed < speed || s.scene.show_debug_ui); + turnSpeedLimit = QString::number(std::nearbyint(tsc_speed)); + tscSubText = t_distance > 0 ? t_distance_str : QString(""); + tscActive = tscState > cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE; + curveSign = lp_sp.getTurnSign(); + + // TODO: Add toggle variables to cereal, and parse from cereal + longitudinalPersonality = s.scene.longitudinal_personality; + dynamicLaneProfile = s.scene.dynamic_lane_profile; + mpcMode = QString::fromStdString(lp_sp.getE2eBlended()); + mpcMode = (mpcMode == "blended") ? mpcMode.replace(0, 1, mpcMode[0].toUpper()) : mpcMode.toUpper(); + + static int reverse_delay = 0; + bool reverse_allowed = false; + if (int(car_state.getGearShifter()) != 4) { + reverse_delay = 0; + reverse_allowed = false; + } else { + reverse_delay += 50; + if (reverse_delay >= 1000) { + reverse_allowed = true; + } + } + + reversing = reverse_allowed; + + cruiseStateEnabled = car_state.getCruiseState().getEnabled(); + + int e2eLStatus = 0; + static bool chime_sent = false; + static int chime_count = 0; + int chime_prompt = 0; + static float last_lead_distance = -1; + const float lead_distance = radar_state.getLeadOne().getDRel(); + + if (s.scene.e2eX[12] > 30 && car_state.getVEgo() < 1.0) { + e2eLStatus = 2; + } else if ((s.scene.e2eX[12] > 0 && s.scene.e2eX[12] < 80) || s.scene.e2eX[12] < 0) { + e2eLStatus = 1; + } else { + e2eLStatus = 0; + } + + if (!car_state.getStandstill()) { + chime_prompt = 0; + chime_sent = false; + chime_count = 0; + + if (last_lead_distance != -1) { + last_lead_distance = -1; + } + } + + if ((cruiseStateEnabled || car_state.getBrakeLightsDEPRECATED()) && !car_state.getGasPressed() && car_state.getStandstill()) { + if (e2eLStatus == 2 && !radar_state.getLeadOne().getStatus()) { + if (chime_sent) { + chime_count = 0; + } else { + chime_count += 1; + } + if (s.scene.e2e_long_alert_light && chime_count >= 2 && !chime_sent) { + chime_prompt = 1; + chime_sent = true; + } else { + chime_prompt = 0; + } + } else if (radar_state.getLeadOne().getStatus()) { + if ((last_lead_distance == -1) || (lead_distance < last_lead_distance)) { + last_lead_distance = lead_distance; + } + if (s.scene.e2e_long_alert_lead && (lead_distance - last_lead_distance > 1.0) && !chime_sent) { + chime_prompt = 2; + chime_sent = true; + } else { + chime_prompt = 0; + } + } else { + chime_prompt = 0; + } + } else { + } + + e2eStatus = chime_prompt; + e2eState = e2eLStatus; + e2eLongAlertUi = s.scene.e2e_long_alert_ui; + dynamicExperimentalControlToggle = s.scene.dynamic_experimental_control; + speedLimitWarningFlash = s.scene.speed_limit_warning_flash; + experimentalMode = cs.getExperimentalMode(); + + featureStatusToggle = s.scene.feature_status_toggle; + + experimental_btn->setVisible(!(showDebugUI && showVTC)); + drivingModelGen = s.scene.driving_model_generation; +} + +void AnnotatedCameraWidgetSP::drawHud(QPainter &p) { + p.save(); + + // Header gradient + QLinearGradient bg(0, UI_HEADER_HEIGHT - (UI_HEADER_HEIGHT / 2.5), 0, UI_HEADER_HEIGHT); + bg.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.45)); + bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0)); + p.fillRect(0, 0, width(), UI_HEADER_HEIGHT, bg); + + QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–"; + QString speedLimitStrSlc = showSpeedLimit ? QString::number(std::nearbyint(speedLimitSLC)) : "–"; + QString speedStr = QString::number(std::nearbyint(speed)); + QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–"; + const bool isNavSpeedLimit = has_us_speed_limit || has_eu_speed_limit; + + // Draw outer box + border to contain set speed and speed limit + const int sign_margin = 12; + const int us_sign_height = slcSubText == "" ? 186 : 216; + const int eu_sign_size = 176; + + const QSize default_size = {172, 204}; + QSize set_speed_size = default_size; + if (is_metric || has_eu_speed_limit) set_speed_size.rwidth() = 200; + if ((mapSourcedSpeedLimit && !is_metric && speedLimitStrSlc.size() >= 3) || + (has_us_speed_limit && speedLimitStr.size() >= 3)) set_speed_size.rwidth() = 223; + + if ((mapSourcedSpeedLimit && !is_metric) || has_us_speed_limit) set_speed_size.rheight() += us_sign_height + sign_margin; + else if ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) set_speed_size.rheight() += eu_sign_size + sign_margin; + + int top_radius = 32; + int bottom_radius = ((mapSourcedSpeedLimit && is_metric) || has_eu_speed_limit) ? 100 : 32; + + QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); + p.setPen(QPen(whiteColor(75), 6)); + p.setBrush(blackColor(166)); + drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius); + + // Draw MAX + QColor max_color = QColor(0x80, 0xd8, 0xa6, 0xff); + QColor set_speed_color = whiteColor(); + if (is_cruise_set) { + if (status == STATUS_DISENGAGED) { + max_color = whiteColor(); + } else if (status == STATUS_OVERRIDE && gasOverride) { + max_color = QColor(0x91, 0x9b, 0x95, 0xff); + } else if (speedLimitSLC > 0) { + auto interp_color = [=](QColor c1, QColor c2, QColor c3) { + return speedLimitSLC > 0 ? interpColor(setSpeed, {speedLimitSLC + 5, speedLimitSLC + 15, speedLimitSLC + 25}, {c1, c2, c3}) : c1; + }; + max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); + set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); + } else if (speedLimit > 0) { + auto interp_color = [=](QColor c1, QColor c2, QColor c3) { + return speedLimit > 0 ? interpColor(setSpeed, {speedLimit + 5, speedLimit + 15, speedLimit + 25}, {c1, c2, c3}) : c1; + }; + max_color = interp_color(max_color, QColor(0xff, 0xe4, 0xbf), QColor(0xff, 0xbf, 0xbf)); + set_speed_color = interp_color(set_speed_color, QColor(0xff, 0x95, 0x00), QColor(0xff, 0x00, 0x00)); + } + } else { + max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); + set_speed_color = QColor(0x72, 0x72, 0x72, 0xff); + } + p.setFont(InterFont(40, QFont::DemiBold)); + p.setPen(max_color); + p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); + p.setFont(InterFont(90, QFont::Bold)); + p.setPen(set_speed_color); + p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); + + const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); + sl_sign_rect = sign_rect; + + speedLimitWarning(p, sign_rect, sign_margin); + + // US/Canada (MUTCD style) sign + if (((mapSourcedSpeedLimit && !is_metric && !isNavSpeedLimit) || has_us_speed_limit) && slcShowSign) { + p.setPen(Qt::NoPen); + p.setBrush(whiteColor()); + p.drawRoundedRect(sign_rect, 24, 24); + p.setPen(QPen(blackColor(), 6)); + p.drawRoundedRect(sign_rect.adjusted(9, 9, -9, -9), 16, 16); + + p.setFont(InterFont(28, QFont::DemiBold)); + p.drawText(sign_rect.adjusted(0, 22, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("SPEED")); + p.drawText(sign_rect.adjusted(0, 51, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("LIMIT")); + p.setFont(InterFont(70, QFont::Bold)); + if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255)); + else p.setPen(blackColor()); + p.drawText(sign_rect.adjusted(0, 85, 0, 0), Qt::AlignTop | Qt::AlignHCenter, speedLimitStrSlc); + + // Speed limit offset value + p.setFont(InterFont(32, QFont::Bold)); + p.setPen(blackColor()); + p.drawText(sign_rect.adjusted(0, 85 + 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText); + } + + // EU (Vienna style) sign + if (((mapSourcedSpeedLimit && is_metric && !isNavSpeedLimit) || has_eu_speed_limit) && slcShowSign) { + p.setPen(Qt::NoPen); + p.setBrush(whiteColor()); + p.drawEllipse(sign_rect); + p.setPen(QPen(Qt::red, 20)); + p.drawEllipse(sign_rect.adjusted(16, 16, -16, -16)); + + p.setFont(InterFont((speedLimitStrSlc.size() >= 3) ? 60 : 70, QFont::Bold)); + if (overSpeedLimit) p.setPen(QColor(255, 0, 0, 255)); + else p.setPen(blackColor()); + p.drawText(sign_rect, Qt::AlignCenter, speedLimitStrSlc); + + // Speed limit offset value + p.setFont(InterFont(slcSubTextSize, QFont::Bold)); + p.setPen(blackColor()); + p.drawText(sign_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, slcSubText); + } + + // current speed + if (!hideVEgoUi) { + p.setFont(InterFont(176, QFont::Bold)); + drawColoredText(p, rect().center().x(), 210, speedStr, brakeLights ? QColor(0xff, 0, 0, 255) : QColor(0xff, 0xff, 0xff, 255)); + p.setFont(InterFont(66)); + drawText(p, rect().center().x(), 290, speedUnit, 200); + } + + if (!reversing) { + // ####### 1 ROW ####### + QRect bar_rect1(rect().left(), rect().bottom() - 60, rect().width(), 61); + if (!splitPanelVisible && devUiInfo == 2) { + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 100)); + p.drawRect(bar_rect1); + drawNewDevUi2(p, bar_rect1.left(), bar_rect1.center().y()); + } + + // ####### 1 COLUMN ######## + QRect rc2(rect().right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 152); + if (devUiInfo != 0) { + drawRightDevUi(p, rect().right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rc2.height()); + } + + int rn_btn = 0; + rn_btn = !splitPanelVisible && devUiInfo == 2 ? 35 : 0; + rn_offset = rn_btn; + + // Stand Still Timer + if (standStillTimer && standStill && !splitPanelVisible) { + drawStandstillTimer(p, rect().right() - 650, 30 + 160 + 250); + } + + // V-TSC + if (showDebugUI && showVTC) { + drawVisionTurnControllerUI(p, rect().right() - 184 - (UI_BORDER_SIZE * 1.5), int(UI_BORDER_SIZE * 1.5), 184, vtcColor, vtcSpeed, 100); + } + + // Bottom bar road name + if (showDebugUI && !roadName.isEmpty()) { + int font_size = splitPanelVisible ? 38 : 50; + int h = splitPanelVisible ? 18 : 26; + p.setFont(InterFont(font_size, QFont::Bold)); + drawRoadNameText(p, rect().center().x(), h, roadName, QColor(255, 255, 255, 255)); + } + + // Turn Speed Sign + if (showTurnSpeedLimit) { + QRect rc = sign_rect; + rc.moveTop(sign_rect.bottom() + UI_BORDER_SIZE); + drawTrunSpeedSign(p, rc, turnSpeedLimit, tscSubText, curveSign, tscActive); + } + } + + // E2E Status + if (e2eLongAlertUi && e2eState != 0) { + drawE2eStatus(p, UI_BORDER_SIZE * 2 + 190, 45, 150, 150, e2eState); + } + + if (!hideBottomIcons && featureStatusToggle) { + int x = UI_BORDER_SIZE * 2 + (rightHandDM ? 600 : 370); + int feature_status_text_x = rightHandDM ? rect().right() - x : x; + drawFeatureStatusText(p, feature_status_text_x, rect().bottom() - 160 - rn_offset); + } + + p.restore(); +} + +void AnnotatedCameraWidgetSP::drawText(QPainter &p, int x, int y, const QString &text, int alpha) { + QRect real_rect = p.fontMetrics().boundingRect(text); + real_rect.moveCenter({x, y - real_rect.height() / 2}); + + p.setPen(QColor(0xff, 0xff, 0xff, alpha)); + p.drawText(real_rect.x(), real_rect.bottom(), text); +} + +void AnnotatedCameraWidgetSP::drawColoredText(QPainter &p, int x, int y, const QString &text, QColor color) { + QRect real_rect = p.fontMetrics().boundingRect(text); + real_rect.moveCenter({x, y - real_rect.height() / 2}); + + p.setPen(color); + p.drawText(real_rect.x(), real_rect.bottom(), text); +} + +void AnnotatedCameraWidgetSP::drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color) { + QRect real_rect = p.fontMetrics().boundingRect(text); + real_rect.moveCenter({x, y}); + + p.setPen(color); + p.drawText(real_rect, Qt::AlignCenter, text); +} + +void AnnotatedCameraWidgetSP::drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color) { + QRect real_rect = p.fontMetrics().boundingRect(text); + real_rect.moveCenter({x, y}); + + QRect real_rect_adjusted(real_rect); + real_rect_adjusted.adjust(-UI_ROAD_NAME_MARGIN_X, 5, UI_ROAD_NAME_MARGIN_X, 0); + QPainterPath path; + path.addRoundedRect(real_rect_adjusted, 10, 10); + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 100)); + p.drawPath(path); + + p.setPen(color); + p.drawText(real_rect, Qt::AlignCenter, text); +} + +void AnnotatedCameraWidgetSP::drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color, + const QString &vision_speed, int alpha) { + QRect rvtc(x, y, size, size); + p.setPen(QPen(color, 10)); + p.setBrush(QColor(0, 0, 0, alpha)); + p.drawRoundedRect(rvtc, 20, 20); + p.setPen(Qt::NoPen); + + p.setFont(InterFont(56, QFont::DemiBold)); + drawCenteredText(p, rvtc.center().x(), rvtc.center().y(), vision_speed, color); +} + +void AnnotatedCameraWidgetSP::drawStandstillTimer(QPainter &p, int x, int y) { + char lab_str[16]; + char val_str[16]; + int minute = (int)(standstillElapsedTime / 60); + int second = (int)((standstillElapsedTime) - (minute * 60)); + + if (standStill) { + snprintf(lab_str, sizeof(lab_str), "STOP"); + snprintf(val_str, sizeof(val_str), "%01d:%02d", minute, second); + } + + p.setFont(InterFont(125, QFont::DemiBold)); + drawColoredText(p, x, y, QString(lab_str), QColor(255, 175, 3, 240)); + p.setFont(InterFont(150, QFont::DemiBold)); + drawColoredText(p, x, y + 150, QString(val_str), QColor(255, 255, 255, 240)); +} + +void AnnotatedCameraWidgetSP::drawCircle(QPainter &p, int x, int y, int r, QBrush bg) { + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(x - r, y - r, 2 * r, 2 * r); +} + +void AnnotatedCameraWidgetSP::drawSpeedSign(QPainter &p, QRect rc, const QString &speed_limit, const QString &sub_text, + int subtext_size, bool is_map_sourced, bool is_active) { + const QColor ring_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50); + const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85); + const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); + + const int x = rc.center().x(); + const int y = rc.center().y(); + const int r = rc.width() / 2.0f; + + drawCircle(p, x, y, r, ring_color); + drawCircle(p, x, y, int(r * 0.8f), inner_color); + + p.setFont(InterFont(89, QFont::Bold)); + drawCenteredText(p, x, y, speed_limit, text_color); + p.setFont(InterFont(subtext_size, QFont::Bold)); + drawCenteredText(p, x, y + 55, sub_text, text_color); + + if (is_map_sourced) { + p.setPen(Qt::NoPen); + p.setOpacity(is_active ? 1.0 : 0.3); + p.drawPixmap(x - subsign_img_size / 2, y - 55 - subsign_img_size / 2, map_img); + p.setOpacity(1.0); + } +} + +void AnnotatedCameraWidgetSP::drawTrunSpeedSign(QPainter &p, QRect rc, const QString &turn_speed, const QString &sub_text, + int curv_sign, bool is_active) { + const QColor border_color = is_active ? QColor(255, 0, 0, 255) : QColor(0, 0, 0, 50); + const QColor inner_color = QColor(255, 255, 255, is_active ? 255 : 85); + const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); + + const int x = rc.center().x(); + const int y = 184 * 2 + UI_BORDER_SIZE + 202; + const int width = 184; + + const float stroke_w = 15.0; + const float cS = stroke_w / 2.0 + 4.5; // half width of the stroke on the corners of the triangle + const float R = width / 2.0 - stroke_w / 2.0; + const float A = 0.73205; + const float h2 = 2.0 * R / (1.0 + A); + const float h1 = A * h2; + const float L = 4.0 * R / sqrt(3.0); + + // Draw the internal triangle, compensate for stroke width. Needed to improve rendering when in inactive + // state due to stroke transparency being different from inner transparency. + QPainterPath path; + path.moveTo(x, y - R + cS); + path.lineTo(x - L / 2.0 + cS, y + h1 + h2 - R - stroke_w / 2.0); + path.lineTo(x + L / 2.0 - cS, y + h1 + h2 - R - stroke_w / 2.0); + path.lineTo(x, y - R + cS); + p.setPen(Qt::NoPen); + p.setBrush(inner_color); + p.drawPath(path); + + // Draw the stroke + QPainterPath stroke_path; + stroke_path.moveTo(x, y - R); + stroke_path.lineTo(x - L / 2.0, y + h1 + h2 - R); + stroke_path.lineTo(x + L / 2.0, y + h1 + h2 - R); + stroke_path.lineTo(x, y - R); + p.setPen(QPen(border_color, stroke_w, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + p.setBrush(Qt::NoBrush); + p.drawPath(stroke_path); + + // Draw the turn sign + if (curv_sign != 0) { + p.setPen(Qt::NoPen); + p.setOpacity(is_active ? 1.0 : 0.3); + p.drawPixmap(int(x - (subsign_img_size / 2)), int(y - R + stroke_w + 30), curv_sign > 0 ? left_img : right_img); + p.setOpacity(1.0); + } + + // Draw the texts. + p.setFont(InterFont(67, QFont::Bold)); + drawCenteredText(p, x, y + 25, turn_speed, text_color); + p.setFont(InterFont(22, QFont::Bold)); + drawCenteredText(p, x, y + 65, sub_text, text_color); +} + +// ############################## DEV UI START ############################## +void AnnotatedCameraWidgetSP::drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2) { + QFontMetrics fm(p.font()); + QRect init_rect = fm.boundingRect(text1 + " "); + QRect real_rect = fm.boundingRect(init_rect, 0, text1 + " "); + real_rect.moveCenter({x, y}); + + QRect init_rect3 = fm.boundingRect(text3); + QRect real_rect3 = fm.boundingRect(init_rect3, 0, text3); + real_rect3.moveTop(real_rect.top()); + real_rect3.moveLeft(real_rect.right() + 135); + + QRect init_rect2 = fm.boundingRect(text2); + QRect real_rect2 = fm.boundingRect(init_rect2, 0, text2); + real_rect2.moveTop(real_rect.top()); + real_rect2.moveRight(real_rect.right() + 125); + + p.setPen(color1); + p.drawText(real_rect, Qt::AlignLeft | Qt::AlignVCenter, text1); + + p.setPen(color2); + p.drawText(real_rect2, Qt::AlignRight | Qt::AlignVCenter, text2); + p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, text3); +} + +int AnnotatedCameraWidgetSP::drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { + p.setFont(InterFont(30 * 2, QFont::Bold)); + drawColoredText(p, x + 92, y + 80, value, color); + + p.setFont(InterFont(28, QFont::Bold)); + drawText(p, x + 92, y + 80 + 42, label, 255); + + if (units.length() > 0) { + p.save(); + p.translate(x + 54 + 30 - 3 + 92 + 30, y + 37 + 25); + p.rotate(-90); + drawText(p, 0, 0, units, 255); + p.restore(); + } + + return 110; +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getDRel() { + QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Orange if close, Red if very close + if (lead_d_rel < 5) { + color = QColor(255, 0, 0, 255); + } else if (lead_d_rel < 15) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "REL DIST", "m", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getVRel() { + QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Red if approaching faster than 10mph + // Orange if approaching (negative) + if (lead_v_rel < -4.4704) { + color = QColor(255, 0, 0, 255); + } else if (lead_v_rel < 0) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "REL SPEED", speedUnit, color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getSteeringAngleDeg() { + QString value = QString("%1%2%3").arg(QString::number(angleSteers, 'f', 1)).arg("°").arg(""); + QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + // Red if large steering angle + // Orange if moderate steering angle + if (std::fabs(angleSteers) > 180) { + color = QColor(255, 0, 0, 255); + } else if (std::fabs(angleSteers) > 90) { + color = QColor(255, 188, 0, 255); + } + + return UiElement(value, "REAL STEER", "", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getActualLateralAccel() { + float actualLateralAccel = (curvature * pow(vEgo, 2)) - (roll * 9.81); + + QString value = QString::number(actualLateralAccel, 'f', 2); + QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "ACTUAL LAT", "m/s²", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getSteeringAngleDesiredDeg() { + QString value = (madsEnabled && latActive) ? QString("%1%2%3").arg(QString::number(steerAngleDesired, 'f', 1)).arg("°").arg("") : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (madsEnabled && latActive) { + // Red if large steering angle + // Orange if moderate steering angle + if (std::fabs(angleSteers) > 180) { + color = QColor(255, 0, 0, 255); + } else if (std::fabs(angleSteers) > 90) { + color = QColor(255, 188, 0, 255); + } else { + color = QColor(0, 255, 0, 255); + } + } + + return UiElement(value, "DESIRED STEER", "", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getMemoryUsagePercent() { + QString value = QString("%1%2").arg(QString::number(memoryUsagePercent, 'd', 0)).arg("%"); + QColor color = (memoryUsagePercent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "RAM", "", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getAEgo() { + QString value = QString::number(aEgo, 'f', 1); + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "ACC.", "m/s²", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getVEgoLead() { + QString value = lead_status ? QString::number((lead_v_rel + vEgo) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Red if approaching faster than 10mph + // Orange if approaching (negative) + if (lead_v_rel < -4.4704) { + color = QColor(255, 0, 0, 255); + } else if (lead_v_rel < 0) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "L.S.", speedUnit, color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getFrictionCoefficientFiltered() { + QString value = QString::number(frictionCoefficientFiltered, 'f', 3); + QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "FRIC.", "", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getLatAccelFactorFiltered() { + QString value = QString::number(latAccelFactorFiltered, 'f', 3); + QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "L.A.", "m/s²", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getSteeringTorqueEps() { + QString value = QString::number(std::fabs(steeringTorqueEps), 'f', 1); + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "E.T.", "N·dm", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getBearingDeg() { + QString value = (bearingAccuracyDeg != 180.00) ? QString("%1%2%3").arg(QString::number(bearingDeg, 'd', 0)).arg("°").arg("") : "-"; + QColor color = QColor(255, 255, 255, 255); + QString dir_value; + + if (bearingAccuracyDeg != 180.00) { + if (((bearingDeg >= 337.5) && (bearingDeg <= 360)) || ((bearingDeg >= 0) && (bearingDeg <= 22.5))) { + dir_value = "N"; + } else if ((bearingDeg > 22.5) && (bearingDeg < 67.5)) { + dir_value = "NE"; + } else if ((bearingDeg >= 67.5) && (bearingDeg <= 112.5)) { + dir_value = "E"; + } else if ((bearingDeg > 112.5) && (bearingDeg < 157.5)) { + dir_value = "SE"; + } else if ((bearingDeg >= 157.5) && (bearingDeg <= 202.5)) { + dir_value = "S"; + } else if ((bearingDeg > 202.5) && (bearingDeg < 247.5)) { + dir_value = "SW"; + } else if ((bearingDeg >= 247.5) && (bearingDeg <= 292.5)) { + dir_value = "W"; + } else if ((bearingDeg > 292.5) && (bearingDeg < 337.5)) { + dir_value = "NW"; + } + } else { + dir_value = "OFF"; + } + + return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color); +} + +AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getAltitude() { + QString value = (gpsAccuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-"; + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "ALT.", "m", color); +} + +void AnnotatedCameraWidgetSP::drawRightDevUi(QPainter &p, int x, int y) { + int rh = 5; + int ry = y; + + // Add Relative Distance to Primary Lead Car + // Unit: Meters + UiElement dRelElement = getDRel(); + rh += drawDevUiElementRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color); + ry = y + rh; + + // Add Relative Velocity vs Primary Lead Car + // Unit: kph if metric, else mph + UiElement vRelElement = getVRel(); + rh += drawDevUiElementRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color); + ry = y + rh; + + // Add Real Steering Angle + // Unit: Degrees + UiElement steeringAngleDegElement = getSteeringAngleDeg(); + rh += drawDevUiElementRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color); + ry = y + rh; + + if (lateralState == "torque") { + // Add Actual Lateral Acceleration (roll compensated) when using Torque + // Unit: m/s² + UiElement actualLateralAccelElement = getActualLateralAccel(); + rh += drawDevUiElementRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color); + } else { + // Add Desired Steering Angle when using PID + // Unit: Degrees + UiElement steeringAngleDesiredDegElement = getSteeringAngleDesiredDeg(); + rh += drawDevUiElementRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color); + } + ry = y + rh; + + // Add Device Memory (RAM) Usage + // Unit: Percent + UiElement memoryUsagePercentElement = getMemoryUsagePercent(); + rh += drawDevUiElementRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color); + ry = y + rh; + + rh += 25; + p.setBrush(QColor(0, 0, 0, 0)); + QRect ldu(x, y, 184, rh); +} + +int AnnotatedCameraWidgetSP::drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { + p.setFont(InterFont(38, QFont::Bold)); + drawCenteredLeftText(p, x, y, label, whiteColor(), value, units, color); + + return 430; +} + +void AnnotatedCameraWidgetSP::drawNewDevUi2(QPainter &p, int x, int y) { + int rw = 90; + + // Add Acceleration from Car + // Unit: Meters per Second Squared + UiElement aEgoElement = getAEgo(); + rw += drawNewDevUiElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color); + + // Add Velocity of Primary Lead Car + // Unit: kph if metric, else mph + UiElement vEgoLeadElement = getVEgoLead(); + rw += drawNewDevUiElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color); + + if (torquedUseParams) { + // Add Friction Coefficient Raw from torqued + // Unit: None + UiElement frictionCoefficientFilteredElement = getFrictionCoefficientFiltered(); + rw += drawNewDevUiElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color); + + // Add Lateral Acceleration Factor Raw from torqued + // Unit: m/s² + UiElement latAccelFactorFilteredElement = getLatAccelFactorFiltered(); + rw += drawNewDevUiElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color); + } else { + // Add Steering Torque from Car EPS + // Unit: Newton Meters + UiElement steeringTorqueEpsElement = getSteeringTorqueEps(); + rw += drawNewDevUiElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color); + + // Add Bearing Degree and Direction from Car (Compass) + // Unit: Meters + UiElement bearingDegElement = getBearingDeg(); + rw += drawNewDevUiElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color); + } + + // Add Altitude of Current Location + // Unit: Meters + UiElement altitudeElement = getAltitude(); + rw += drawNewDevUiElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color); +} + +// ############################## DEV UI END ############################## + +void AnnotatedCameraWidgetSP::drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status) { + QColor status_color; + QRect e2eStatusIcon(x, y, w, h); + p.setPen(Qt::NoPen); + p.setBrush(QBrush(blackColor(70))); + p.drawEllipse(e2eStatusIcon); + e2eStatusIcon -= QMargins(25, 25, 25, 25); + p.setPen(Qt::NoPen); + if (e2e_long_status == 2) { + status_color = QColor::fromRgbF(0.0, 1.0, 0.0, 0.9); + } else if (e2e_long_status == 1) { + status_color = QColor::fromRgbF(1.0, 0.0, 0.0, 0.9); + } + p.setBrush(QBrush(status_color)); + p.drawEllipse(e2eStatusIcon); +} + +void AnnotatedCameraWidgetSP::drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) { + painter.setRenderHint(QPainter::Antialiasing, true); + + QColor circle_color, circle_color_0, circle_color_1; + QColor arrow_color, arrow_color_0, arrow_color_1; + if ((left_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) { + circle_color_0 = QColor(164, 0, 1); + circle_color_1 = QColor(204, 0, 1); + arrow_color_0 = QColor(72, 1, 1); + arrow_color_1 = QColor(255, 255, 255); + } else { + circle_color_0 = QColor(22, 156, 69); + circle_color_1 = QColor(30, 215, 96); + arrow_color_0 = QColor(9, 56, 27); + arrow_color_1 = QColor(255, 255, 255); + } + + if (state == 1) { + circle_color = circle_color_1; + arrow_color = arrow_color_1; + } else if (state == 0) { + circle_color = circle_color_0; + arrow_color = arrow_color_0; + } + + // Draw the circle + int circleX = x; + int circleY = y; + painter.setPen(Qt::NoPen); + painter.setBrush(circle_color); + painter.drawEllipse(circleX, circleY, circle_size, circle_size); + + // Draw the arrow + int arrowSize = 50; + int arrowX = circleX + (circle_size - arrowSize) / 4; + int arrowY = circleY + (circle_size - arrowSize) / 2; + painter.setPen(Qt::NoPen); + painter.setBrush(arrow_color); + + // Draw the arrow shape + QPolygon arrowPolygon; + arrowPolygon << QPoint(arrowX + 10, arrowY + arrowSize / 2) + << QPoint(arrowX + arrowSize - 3, arrowY) + << QPoint(arrowX + arrowSize, arrowY) + << QPoint(arrowX + arrowSize, arrowY + arrowSize) + << QPoint(arrowX + arrowSize - 3, arrowY + arrowSize) + << QPoint(arrowX + 10, arrowY + arrowSize / 2); + painter.drawPolygon(arrowPolygon); + + // Draw the tail rectangle + int tailWidth = arrowSize / 2.25; + int tailHeight = arrowSize / 2; + QRect tailRect(arrowX + arrowSize - 3, arrowY + arrowSize / 4, tailWidth, tailHeight); + painter.fillRect(tailRect, arrow_color); +} + +void AnnotatedCameraWidgetSP::drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state) { + painter.setRenderHint(QPainter::Antialiasing, true); + + QColor circle_color, circle_color_0, circle_color_1; + QColor arrow_color, arrow_color_0, arrow_color_1; + if ((right_blindspot || lane_change_edge_block) && !(left_blinker && right_blinker)) { + circle_color_0 = QColor(164, 0, 1); + circle_color_1 = QColor(204, 0, 1); + arrow_color_0 = QColor(72, 1, 1); + arrow_color_1 = QColor(255, 255, 255); + } else { + circle_color_0 = QColor(22, 156, 69); + circle_color_1 = QColor(30, 215, 96); + arrow_color_0 = QColor(9, 56, 27); + arrow_color_1 = QColor(255, 255, 255); + } + + if (state == 1) { + circle_color = circle_color_1; + arrow_color = arrow_color_1; + } else if (state == 0) { + circle_color = circle_color_0; + arrow_color = arrow_color_0; + } + + + // Draw the circle + int circleX = x; + int circleY = y; + painter.setPen(Qt::NoPen); + painter.setBrush(circle_color); + painter.drawEllipse(circleX, circleY, circle_size, circle_size); + + // Draw the arrow + int arrowSize = 50; + int arrowX = circleX + (circle_size - arrowSize) / 2 + (arrowSize / 2.5) - 3; + int arrowY = circleY + (circle_size - arrowSize) / 2; + painter.setPen(Qt::NoPen); + painter.setBrush(arrow_color); + + // Draw the arrow shape + QPolygon arrowPolygon; + arrowPolygon << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2) + << QPoint(arrowX + 3, arrowY) + << QPoint(arrowX, arrowY) + << QPoint(arrowX, arrowY + arrowSize) + << QPoint(arrowX + 3, arrowY + arrowSize) + << QPoint(arrowX + arrowSize - 10, arrowY + arrowSize / 2); + painter.drawPolygon(arrowPolygon); + + // Draw the tail rectangle + int tailWidth = arrowSize / 2.25; + int tailHeight = arrowSize / 2; + QRect tailRect(arrowX - tailWidth + 3, arrowY + arrowSize / 4, tailWidth, tailHeight); + painter.fillRect(tailRect, arrow_color); +} + +int AnnotatedCameraWidgetSP::blinkerPulse(int frame) { + if (frame % UI_FREQ < (UI_FREQ / 2)) { + blinker_state = 1; + } else { + blinker_state = 0; + } + + return blinker_state; +} + +void AnnotatedCameraWidgetSP::speedLimitSignPulse(int frame) { + if (frame % UI_FREQ < (UI_FREQ / 2.5)) { + slcShowSign = false; + } else { + slcShowSign = true; + } +} + +void AnnotatedCameraWidgetSP::drawFeatureStatusText(QPainter &p, int x, int y) { + const FeatureStatusText feature_text; + const FeatureStatusColor feature_color; + const QColor text_color = whiteColor(); + const QColor shadow_color = blackColor(38); + const int text_height = 34; + const int drop_shadow_size = 2; + const int eclipse_x_offset = 25; + const int eclipse_y_offset = 20; + const int w = 16; + const int h = 16; + + const bool longitudinal = hasLongitudinalControl(car_params); + + p.setFont(InterFont(32, QFont::Bold)); + + // Define a function to draw a feature status button + auto drawFeatureStatusElement = [&](int value, const QStringList& text_list, const QStringList& color_list, bool condition, const QString& off_text, const QString& label) { + std::pair feature_status = getFeatureStatus(value, text_list, color_list, condition, off_text); + QRect btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); + QRect btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); + p.setPen(Qt::NoPen); + p.setBrush(shadow_color); + p.drawEllipse(btn_shadow); + p.setBrush(feature_status.second); + p.drawEllipse(btn); + QString status_text; + status_text.sprintf("%s: %s", label.toStdString().c_str(), (feature_status.first).toStdString().c_str()); + p.setPen(QPen(shadow_color, 2)); + p.drawText(x + drop_shadow_size, y + drop_shadow_size, status_text); + p.setPen(QPen(text_color, 2)); + p.drawText(x, y, status_text); + y += text_height; + }; + + // Driving Personality / Gap Adjust Cruise + if (longitudinal) { + drawFeatureStatusElement(longitudinalPersonality, feature_text.gac_list_text, feature_color.gac_list_color, longitudinal, "N/A", "GAP"); + } + + // Dynamic Lane Profile + if (drivingModelGen == cereal::ModelGeneration::ONE) { + drawFeatureStatusElement(dynamicLaneProfile, feature_text.dlp_list_text, feature_color.dlp_list_color, true, "OFF", "DLP"); + } + + // TODO: Add toggle variables to cereal, and parse from cereal + if (longitudinal) { + QColor dec_color((cruiseStateEnabled && dynamicExperimentalControlToggle) ? "#4bff66" : "#ffffff"); + QRect dec_btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); + QRect dec_btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); + p.setPen(Qt::NoPen); + p.setBrush(shadow_color); + p.drawEllipse(dec_btn_shadow); + p.setBrush(dec_color); + p.drawEllipse(dec_btn); + QString dec_status_text; + dec_status_text.sprintf("DEC: %s\n", dynamicExperimentalControlToggle ? (experimentalMode ? QString(mpcMode).toStdString().c_str() : QString("Inactive").toStdString().c_str()) : "OFF"); + p.setPen(QPen(shadow_color, 2)); + p.drawText(x + drop_shadow_size, y + drop_shadow_size, dec_status_text); + p.setPen(QPen(text_color, 2)); + p.drawText(x, y, dec_status_text); + y += text_height; + } + + // TODO: Add toggle variables to cereal, and parse from cereal + // Speed Limit Control + if (longitudinal || !car_params.getPcmCruiseSpeed()) { + drawFeatureStatusElement(int(slcState), feature_text.slc_list_text, feature_color.slc_list_color, speedLimitControlToggle, "OFF", "SLC"); + } +} + +void AnnotatedCameraWidgetSP::speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin) { + // PRE ACTIVE + if (slcState == cereal::LongitudinalPlanSP::SpeedLimitControlState::PRE_ACTIVE) { + int set_speed = std::nearbyint(setSpeed); + int speed_limit_offsetted = std::nearbyint(speedLimitSLC + speedLimitSLCOffset); + + // Calculate the vertical offset using a sinusoidal function for smooth bouncing + double bounce_frequency = 2.0 * M_PI / 20.0; // 20 frames for one full oscillation + int bounce_offset = 20 * sin(speed_limit_frame * bounce_frequency); // Adjust the amplitude (20 pixels) as needed + + if (set_speed < speed_limit_offsetted) { + QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - plus_arrow_up_img.height() / 2 + bounce_offset); + p.drawPixmap(iconPosition, plus_arrow_up_img); + } else if (set_speed > speed_limit_offsetted) { + QPoint iconPosition(sign_rect.right() + sign_margin * 3, sign_rect.center().y() - minus_arrow_down_img.height() / 2 - bounce_offset); + p.drawPixmap(iconPosition, minus_arrow_down_img); + } + + speed_limit_frame++; + speedLimitSignPulse(speed_limit_frame); + } + + // current speed over speed limit + else if (overSpeedLimit && speedLimitWarningFlash) { + speed_limit_frame++; + speedLimitSignPulse(speed_limit_frame); + } + + else { + speed_limit_frame = 0; + slcShowSign = true; + } +} + + +void AnnotatedCameraWidgetSP::initializeGL() { + CameraWidget::initializeGL(); + qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION)); + qInfo() << "OpenGL vendor:" << QString((const char*)glGetString(GL_VENDOR)); + qInfo() << "OpenGL renderer:" << QString((const char*)glGetString(GL_RENDERER)); + qInfo() << "OpenGL language version:" << QString((const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); + + prev_draw_t = millis_since_boot(); + setBackgroundColor(bg_colors[STATUS_DISENGAGED]); +} + +void AnnotatedCameraWidgetSP::updateFrameMat() { + CameraWidget::updateFrameMat(); + UIState *s = uiState(); + int w = width(), h = height(); + + s->fb_w = w; + s->fb_h = h; + + // Apply transformation such that video pixel coordinates match video + // 1) Put (0, 0) in the middle of the video + // 2) Apply same scaling as video + // 3) Put (0, 0) in top left corner of video + s->car_space_transform.reset(); + s->car_space_transform.translate(w / 2 - x_offset, h / 2 - y_offset) + .scale(zoom, zoom) + .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); +} + +void AnnotatedCameraWidgetSP::drawLaneLines(QPainter &painter, const UIState *s) { + painter.save(); + + const UIScene &scene = s->scene; + SubMaster &sm = *(s->sm); + + const auto car_state = sm["carState"].getCarState(); + + // Shane's colored lanelines + for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { + if (i == 1 || i == 2) { + // TODO: can we just use the projected vertices somehow? + const cereal::XYZTData::Reader &line = (*s->sm)["modelV2"].getModelV2().getLaneLines()[i]; + const float default_pos = 1.4; // when lane poly isn't available + const float lane_pos = line.getY().size() > 0 ? std::abs(line.getY()[5]) : default_pos; // get redder when line is closer to car + float hue = 332.5 * lane_pos - 332.5; // equivalent to {1.4, 1.0}: {133, 0} (green to red) + hue = std::fmin(133, fmax(0, hue)) / 360.; // clip and normalize + painter.setBrush(QColor::fromHslF(hue, 1.0, 0.50, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); + } else { + painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); + } + painter.drawPolygon(scene.lane_line_vertices[i]); + } + + // TODO: Fix empty spaces when curiving back on itself + painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.2)); + if (left_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[0]); + if (right_blindspot) painter.drawPolygon(scene.lane_barrier_vertices[1]); + + // road edges + for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { + painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0))); + painter.drawPolygon(scene.road_edge_vertices[i]); + } + + // paint path + QLinearGradient bg(0, height(), 0, 0); + if (madsEnabled || car_state.getCruiseState().getEnabled()) { + if (steerOverride && latActive) { + bg.setColorAt(0.0, QColor::fromHslF(20 / 360., 0.94, 0.51, 0.17)); + bg.setColorAt(0.5, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.17)); + bg.setColorAt(1.0, QColor::fromHslF(20 / 360., 1.0, 0.68, 0.0)); + } else if (!(latActive || car_state.getCruiseState().getEnabled())) { + bg.setColorAt(0, whiteColor()); + bg.setColorAt(1, whiteColor(0)); + } else if (sm["controlsState"].getControlsState().getExperimentalMode()) { + // The first half of track_vertices are the points for the right side of the path + const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration().getX(); + const int max_len = std::min(scene.track_vertices.length() / 2, acceleration.size()); + + for (int i = 0; i < max_len; ++i) { + // Some points are out of frame + if (scene.track_vertices[i].y() < 0 || scene.track_vertices[i].y() > height()) continue; + + // Flip so 0 is bottom of frame + float lin_grad_point = (height() - scene.track_vertices[i].y()) / height(); + + // speed up: 120, slow down: 0 + float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); + // FIXME: painter.drawPolygon can be slow if hue is not rounded + path_hue = int(path_hue * 100 + 0.5) / 100; + + float saturation = fmin(fabs(acceleration[i] * 1.5), 1); + float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey + float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade + bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha)); + + // Skip a point, unless next is last + i += (i + 2) < max_len ? 1 : 0; + } + + } else { + bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); + bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); + bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0)); + } + } else { + bg.setColorAt(0.0, whiteColor(102)); + bg.setColorAt(0.5, whiteColor(89)); + bg.setColorAt(1.0, whiteColor(0)); + } + + painter.setBrush(bg); + painter.drawPolygon(scene.track_vertices); + + // create path combining track vertices and track edge vertices + QPainterPath path; + path.addPolygon(scene.track_vertices); + path.addPolygon(scene.track_edge_vertices); + + // paint path edges + QLinearGradient pe(0, height(), 0, height() / 4); + if (!scene.dynamic_lane_profile_status) { + pe.setColorAt(0.0, QColor::fromHslF(240 / 360., 0.94, 0.51, 1.0)); + pe.setColorAt(0.5, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.5)); + pe.setColorAt(1.0, QColor::fromHslF(204 / 360., 1.0, 0.68, 0.0)); + + painter.setBrush(pe); + painter.drawPath(path); + } + + painter.restore(); +} + +void AnnotatedCameraWidgetSP::drawDriverState(QPainter &painter, const UIState *s) { + const UIScene &scene = s->scene; + + painter.save(); + + // base icon + int offset = UI_BORDER_SIZE + btn_size / 2; + int x = rightHandDM ? width() - offset : offset; + int y = height() - offset - rn_offset; + float opacity = dmActive ? 0.65 : 0.2; + drawIcon(painter, QPoint(x, y), dm_img, blackColor(70), opacity); + + // face + QPointF face_kpts_draw[std::size(default_face_kpts_3d)]; + float kp; + for (int i = 0; i < std::size(default_face_kpts_3d); ++i) { + kp = (scene.face_kpts_draw[i].v[2] - 8) / 120 + 1.0; + face_kpts_draw[i] = QPointF(scene.face_kpts_draw[i].v[0] * kp + x, scene.face_kpts_draw[i].v[1] * kp + y); + } + + painter.setPen(QPen(QColor::fromRgbF(1.0, 1.0, 1.0, opacity), 5.2, Qt::SolidLine, Qt::RoundCap)); + painter.drawPolyline(face_kpts_draw, std::size(default_face_kpts_3d)); + + // tracking arcs + const int arc_l = 133; + const float arc_t_default = 6.7; + const float arc_t_extend = 12.0; + QColor arc_color = QColor::fromRgbF(0.545 - 0.445 * s->engaged(), + 0.545 + 0.4 * s->engaged(), + 0.545 - 0.285 * s->engaged(), + 0.4 * (1.0 - dm_fade_state)); + float delta_x = -scene.driver_pose_sins[1] * arc_l / 2; + float delta_y = -scene.driver_pose_sins[0] * arc_l / 2; + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[1] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(std::fmin(x + delta_x, x), y - arc_l / 2, fabs(delta_x), arc_l), (scene.driver_pose_sins[1]>0 ? 90 : -90) * 16, 180 * 16); + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[0] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(x - arc_l / 2, std::fmin(y + delta_y, y), arc_l, fabs(delta_y)), (scene.driver_pose_sins[0]>0 ? 0 : 180) * 16, 180 * 16); + + painter.restore(); +} + +void AnnotatedCameraWidgetSP::rocketFuel(QPainter &p) { + + static const int ct_n = 1; + static float ct; + + int rect_w = rect().width(); + int rect_h = rect().height(); + + const int n = 15 + 1; //Add one off screen due to timing issues + static float t[n]; + int dim_n = (sin(ct / 5) + 1) * (n - 0.01); + t[dim_n] = 1.0; + t[(int)(ct/ct_n)] = 1.0; + + UIState *s = uiState(); + float vc_accel0 = (*s->sm)["carState"].getCarState().getAEgo(); + static float vc_accel; + vc_accel = vc_accel + (vc_accel0 - vc_accel) / 5; + float hha = 0; + if (vc_accel > 0) { + hha = 0.85 - 0.1 / vc_accel; // only extend up to 85% + p.setBrush(QColor(0, 245, 0, 200)); + } + if (vc_accel < 0) { + hha = 0.85 + 0.1 / vc_accel; // only extend up to 85% + p.setBrush(QColor(245, 0, 0, 200)); + } + if (hha < 0) { + hha = 0; + } + hha = hha * rect_h; + float wp = 28; + if (vc_accel > 0) { + QRect ra = QRect(rect_w - wp, rect_h / 2 - hha / 2, wp, hha / 2); + p.drawRect(ra); + } else { + QRect ra = QRect(rect_w - wp, rect_h / 2, wp, hha / 2); + p.drawRect(ra); + } +} + +void AnnotatedCameraWidgetSP::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, + int num, const cereal::CarState::Reader &car_data, int chevron_data) { + painter.save(); + + const float speedBuff = 10.; + const float leadBuff = 40.; + const float d_rel = lead_data.getDRel(); + const float v_rel = lead_data.getVRel(); + const float v_ego = car_data.getVEgo(); + + float fillAlpha = 0; + if (d_rel < leadBuff) { + fillAlpha = 255 * (1.0 - (d_rel / leadBuff)); + if (v_rel < 0) { + fillAlpha += 255 * (-1 * (v_rel / speedBuff)); + } + fillAlpha = (int)(fmin(fillAlpha, 255)); + } + + float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; + float x = std::clamp((float)vd.x(), 0.f, width() - sz / 2); + float y = std::fmin(height() - sz * .6, (float)vd.y()); + + float g_xo = sz / 5; + float g_yo = sz / 10; + + QPointF glow[] = {{x + (sz * 1.35) + g_xo, y + sz + g_yo}, {x, y - g_yo}, {x - (sz * 1.35) - g_xo, y + sz + g_yo}}; + painter.setBrush(QColor(218, 202, 37, 255)); + painter.drawPolygon(glow, std::size(glow)); + + // chevron + QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}}; + painter.setBrush(redColor(fillAlpha)); + painter.drawPolygon(chevron, std::size(chevron)); + + if (num == 0) { // Display metrics to the 0th lead car + int chevron_types = 2; + QStringList chevron_text[chevron_types]; + int position; + float val; + if (chevron_data == 1 || chevron_data == 3) { + 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) { + 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")); + } + + float str_w = 200; // Width of the text box, might need adjustment + float str_h = 50; // Height of the text box, adjust as necessary + painter.setFont(InterFont(45, QFont::Bold)); + // Calculate the center of the chevron and adjust the text box position + float text_y = y + sz + 12; // Position the text at the bottom of the chevron + QRect textRect(x - str_w / 2, text_y, str_w, str_h); // Adjust the rectangle to center the text horizontally at the chevron's bottom + QPoint shadow_offset(2, 2); + for (int i = 0; i < chevron_types; ++i) { + if (!chevron_text[i].isEmpty()) { + painter.setPen(QColor(0x0, 0x0, 0x0, 200)); // Draw shadow + painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y() + i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); + painter.setPen(QColor(0xff, 0xff, 0xff)); // Draw text + painter.drawText(textRect.translated(0, i * str_h), Qt::AlignBottom | Qt::AlignHCenter, chevron_text[i].at(0)); + painter.setPen(Qt::NoPen); // Reset pen to default + } + } + } + + painter.restore(); +} + +void AnnotatedCameraWidgetSP::paintGL() { +} + +void AnnotatedCameraWidgetSP::paintEvent(QPaintEvent *event) { + UIState *s = uiState(); + SubMaster &sm = *(s->sm); + const double start_draw_t = millis_since_boot(); + const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); + + QPainter painter(this); + + // draw camera frame + { + std::lock_guard lk(frame_lock); + + if (frames.empty()) { + if (skip_frame_count > 0) { + skip_frame_count--; + qDebug() << "skipping frame, not ready"; + return; + } + } else { + // skip drawing up to this many frames if we're + // missing camera frames. this smooths out the + // transitions from the narrow and wide cameras + skip_frame_count = 5; + } + + // Wide or narrow cam dependent on speed + bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); + if (has_wide_cam) { + float v_ego = sm["carState"].getCarState().getVEgo(); + float steer_angle = sm["carState"].getCarState().getSteeringAngleDeg(); + if ((v_ego < 10) || available_streams.size() == 1 || (std::fabs(steer_angle) > 45)) { + wide_cam_requested = true; + } else if ((v_ego > 15) && (std::fabs(steer_angle) < 30)) { + wide_cam_requested = false; + } + wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode(); + // for replay of old routes, never go to widecam + wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; + } + CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); + + if (reversing && s->scene.reverse_dm_cam) { + CameraWidget::setStreamType(VISION_STREAM_DRIVER, s->scene.reverse_dm_cam); + } + + s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD; + if (s->scene.calibration_valid) { + auto calib = s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib; + CameraWidget::updateCalibration(calib); + } else { + CameraWidget::updateCalibration(DEFAULT_CALIBRATION); + } + painter.beginNativePainting(); + CameraWidget::setFrameId(model.getFrameId()); + CameraWidget::paintGL(); + painter.endNativePainting(); + } + + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(Qt::NoPen); + + if (s->scene.world_objects_visible) { + update_model(s, model); + drawLaneLines(painter, s); + + if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) { + auto radar_state = sm["radarState"].getRadarState(); + auto car_state = sm["carState"].getCarState(); + update_leads(s, radar_state, model.getPosition()); + auto lead_one = radar_state.getLeadOne(); + auto lead_two = radar_state.getLeadTwo(); + if (lead_one.getStatus()) { + drawLead(painter, lead_one, s->scene.lead_vertices[0], 0, car_state, s->scene.chevron_data); + } + if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { + drawLead(painter, lead_two, s->scene.lead_vertices[1], 1, car_state, s->scene.chevron_data); + } + + rocketFuel(painter); + } + } + + // DMoji + if (!hideBottomIcons && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) { + update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM); + drawDriverState(painter, s); + } + + drawHud(painter); + + if (left_blinker || right_blinker) { + blinker_frame++; + int state = blinkerPulse(blinker_frame); + int blinker_x = splitPanelVisible ? 150 : 180; + int blinker_y = splitPanelVisible ? 220 : 90; + if (left_blinker) { + drawLeftTurnSignal(painter, rect().center().x() - (blinker_x + blinker_size), blinker_y, blinker_size, state); + } + if (right_blinker) { + drawRightTurnSignal(painter, rect().center().x() + blinker_x, blinker_y, blinker_size, state); + } + } else { + blinker_frame = 0; + } + + double cur_draw_t = millis_since_boot(); + double dt = cur_draw_t - prev_draw_t; + double fps = fps_filter.update(1. / dt * 1000); + if (fps < 15) { + LOGW("slow frame rate: %.2f fps", fps); + } + prev_draw_t = cur_draw_t; + + // publish debug msg + MessageBuilder msg; + auto m = msg.initEvent().initUiDebug(); + m.setDrawTimeMillis(cur_draw_t - start_draw_t); + pm->send("uiDebug", msg); + + MessageBuilder e2e_long_msg; + auto e2eLongStatus = e2e_long_msg.initEvent().initE2eLongStateSP(); + e2eLongStatus.setStatus(e2eStatus); + e2e_state->send("e2eLongStateSP", e2e_long_msg); +} + +void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) { + CameraWidget::showEvent(event); + + ui_update_params(uiState()); + prev_draw_t = millis_since_boot(); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h new file mode 100644 index 0000000000..4651c114a5 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h @@ -0,0 +1,227 @@ +#pragma once + +#include +#include +#include + +#include "selfdrive/ui/qt/onroad/buttons.h" +#include "selfdrive/ui/qt/widgets/cameraview.h" + +const int subsign_img_size = 35; +const int blinker_size = 120; + +class AnnotatedCameraWidgetSP : public CameraWidget { + Q_OBJECT + +public: + explicit AnnotatedCameraWidgetSP(VisionStreamType type, QWidget* parent = 0); + void updateState(const UIState &s); + + MapSettingsButton *map_settings_btn; + OnroadSettingsButton *onroad_settings_btn; + +private: + void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); + void drawCenteredText(QPainter &p, int x, int y, const QString &text, QColor color); + void drawVisionTurnControllerUI(QPainter &p, int x, int y, int size, const QColor &color, const QString &speed, + int alpha); + void drawCircle(QPainter &p, int x, int y, int r, QBrush bg); + void drawSpeedSign(QPainter &p, QRect rc, const QString &speed, const QString &sub_text, int subtext_size, + bool is_map_sourced, bool is_active); + void drawTrunSpeedSign(QPainter &p, QRect rc, const QString &speed, const QString &sub_text, int curv_sign, + bool is_active); + + void drawColoredText(QPainter &p, int x, int y, const QString &text, QColor color); + void drawStandstillTimer(QPainter &p, int x, int y); + + // ############################## DEV UI START ############################## + void drawRightDevUi(QPainter &p, int x, int y); + int drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); + int drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); + void drawNewDevUi2(QPainter &p, int x, int y); + void drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2); + struct UiElement { + QString value; + QString label; + QString units; + QColor color; + + UiElement(const QString &value, const QString &label, const QString &units, const QColor &color = QColor(255, 255, 255, 255)) + : value(value), label(label), units(units), color(color) {} + }; + + UiElement getDRel(); + UiElement getVRel(); + UiElement getSteeringAngleDeg(); + UiElement getActualLateralAccel(); + UiElement getSteeringAngleDesiredDeg(); + UiElement getMemoryUsagePercent(); + + UiElement getAEgo(); + UiElement getVEgoLead(); + UiElement getFrictionCoefficientFiltered(); + UiElement getLatAccelFactorFiltered(); + UiElement getSteeringTorqueEps(); + UiElement getBearingDeg(); + UiElement getAltitude(); + + // ############################## DEV UI END ############################## + + void drawE2eStatus(QPainter &p, int x, int y, int w, int h, int e2e_long_status); + + void drawLeftTurnSignal(QPainter &painter, int x, int y, int circle_size, int state); + void drawRightTurnSignal(QPainter &painter, int x, int y, int circle_size, int state); + int blinkerPulse(int frame); + void updateButtonsLayout(bool is_rhd); + + void drawFeatureStatusText(QPainter &p, int x, int y); + void speedLimitSignPulse(int frame); + void speedLimitWarning(QPainter &p, QRect sign_rect, const int sign_margin); + void mousePressEvent(QMouseEvent* e) override; + void drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color); + + QVBoxLayout *main_layout; + ExperimentalButton *experimental_btn; + QPixmap dm_img; + float speed; + QString speedUnit; + float setSpeed; + float speedLimit; + bool is_cruise_set = false; + bool is_metric = false; + bool dmActive = false; + bool hideBottomIcons = false; + bool rightHandDM = false; + float dm_fade_state = 1.0; + bool has_us_speed_limit = false; + bool has_eu_speed_limit = false; + bool v_ego_cluster_seen = false; + int status = STATUS_DISENGAGED; + std::unique_ptr pm; + + int skip_frame_count = 0; + bool wide_cam_requested = false; + + Params params; + QHBoxLayout *buttons_layout; + QPixmap map_img; + QPixmap left_img; + QPixmap right_img; + bool left_blindspot = false; + bool right_blindspot = false; + std::unique_ptr e2e_state; + + bool steerOverride = false; + bool gasOverride = false; + bool latActive = false; + bool madsEnabled = false; + + bool brakeLights; + + bool standStillTimer; + bool standStill; + float standstillElapsedTime; + + bool showVTC = false; + QString vtcSpeed; + QColor vtcColor; + + bool showDebugUI = false; + + QString roadName = ""; + + bool showSpeedLimit = false; + float speedLimitSLC; + float speedLimitSLCOffset; + float speedLimitWarningOffset; + QString slcSubText; + float slcSubTextSize = 0.0; + bool overSpeedLimit; + bool mapSourcedSpeedLimit = false; + bool slcActive = false; + + bool showTurnSpeedLimit = false; + QString turnSpeedLimit; + QString tscSubText; + bool tscActive = false; + int curveSign = 0; + + bool hideVEgoUi; + + bool splitPanelVisible; + + // ############################## DEV UI START ############################## + bool lead_status; + float lead_d_rel = 0; + float lead_v_rel = 0; + QString lateralState; + float angleSteers = 0; + float steerAngleDesired = 0; + float curvature; + float roll; + int memoryUsagePercent; + int devUiInfo; + float gpsAccuracy; + float altitude; + float vEgo; + float aEgo; + float steeringTorqueEps; + float bearingAccuracyDeg; + float bearingDeg; + bool torquedUseParams; + float latAccelFactorFiltered; + float frictionCoefficientFiltered; + bool liveValid; + // ############################## DEV UI END ############################## + + float btnPerc; + + bool reversing; + + int e2eState; + int e2eStatus; + + bool left_blinker, right_blinker, lane_change_edge_block; + int blinker_frame; + int blinker_state = 0; + + cereal::LongitudinalPlanSP::SpeedLimitControlState slcState; + int longitudinalPersonality; + int dynamicLaneProfile; + QString mpcMode; + + int speed_limit_frame; + bool slcShowSign = true; + QPixmap plus_arrow_up_img; + QPixmap minus_arrow_down_img; + QRect sl_sign_rect; + int rn_offset = 0; + bool e2eLongAlertUi, dynamicExperimentalControlToggle, speedLimitControlToggle, speedLimitWarningFlash; + cereal::CarParams::Reader car_params; + bool cruiseStateEnabled; + bool experimentalMode; + + bool featureStatusToggle; + + cereal::ModelGeneration drivingModelGen; + +protected: + void paintGL() override; + void initializeGL() override; + void showEvent(QShowEvent *event) override; + void updateFrameMat() override; + void drawLaneLines(QPainter &painter, const UIState *s); + void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, + int num, const cereal::CarState::Reader &car_data, int chevron_data); + void drawHud(QPainter &p); + void drawDriverState(QPainter &painter, const UIState *s); + inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } + inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } + inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } + + void paintEvent(QPaintEvent *event) override; + void rocketFuel(QPainter &p); + + double prev_draw_t = 0; + FirstOrderFilter fps_filter; +}; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc new file mode 100644 index 0000000000..8f2d6ae57b --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc @@ -0,0 +1,134 @@ +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" + +#include +#include + +#ifdef ENABLE_MAPS +#include "selfdrive/ui/qt/maps/map_helpers.h" +#include "selfdrive/ui/qt/maps/map_panel.h" +#endif +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" +#endif + +#include "selfdrive/ui/qt/util.h" + +OnroadWindowSP::OnroadWindowSP(QWidget *parent) : OnroadWindow(parent) { + + if (getenv("MAP_RENDER_VIEW")) { + CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); + split->insertWidget(0, map_render); //TODO: We MIGHT to override the split variable because it is added on onroad_home.cc and we need to access it before. I am not sure so we will need to test it before + } + QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindowSP::primeChanged); +} + +void OnroadWindowSP::updateState(const UIState &s) { + if (!s.scene.started) { + return; + } + + if (s.scene.map_on_left || s.scene.mapbox_fullscreen) { + split->setDirection(QBoxLayout::LeftToRight); + } else { + split->setDirection(QBoxLayout::RightToLeft); + } + + OnroadWindow::updateState(s); // Carry on with the parent class updateState method +} + + +void OnroadWindowSP::mousePressEvent(QMouseEvent* e) { +#ifdef ENABLE_MAPS + UIState *s = uiState(); + UIScene &scene = s->scene; + if (map != nullptr && !isOnroadSettingsVisible()) { + if (wakeScreenTimeout()) { + // Switch between map and sidebar when using navigate on openpilot + bool sidebarVisible = geometry().x() > 0; + bool show_map = uiState()->scene.navigate_on_openpilot_deprecated ? sidebarVisible : !sidebarVisible; + updateMapSize(scene); + map->setVisible(show_map && !map->isVisible()); + } + } +#endif +#ifdef SUNNYPILOT + if (onroad_settings != nullptr && !isMapVisible()) { + if (wakeScreenTimeout()) { + onroad_settings->setVisible(false); + } + } + // propagation event to parent(HomeWindow) + QWidget::mousePressEvent(e); +#endif +} + +void OnroadWindowSP::createMapWidget() { +#ifdef ENABLE_MAPS + auto m = new MapPanel(get_mapbox_settings()); + map = m; + + QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindowSP::mapPanelRequested); + QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindowSP::onroadSettingsPanelNotRequested); + QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings); + nvg->map_settings_btn->setEnabled(true); + + m->setFixedWidth(uiState()->scene.mapbox_fullscreen ? topWidget(this)->width() : + topWidget(this)->width() / 2 - UI_BORDER_SIZE); + split->insertWidget(0, m); + + // hidden by default, made visible when navRoute is published + m->setVisible(false); +#endif +} + +void OnroadWindowSP::createOnroadSettingsWidget() { +#ifdef SUNNYPILOT + auto os = new OnroadSettingsPanel(this); + onroad_settings = os; + + QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindowSP::onroadSettingsPanelRequested); + QObject::connect(os, &OnroadSettingsPanel::onroadSettingsPanelRequested, this, &OnroadWindowSP::mapPanelNotRequested); + QObject::connect(nvg->onroad_settings_btn, &OnroadSettingsButton::clicked, os, &OnroadSettingsPanel::toggleOnroadSettings); + nvg->onroad_settings_btn->setEnabled(true); + + os->setFixedWidth(topWidget(this)->width() / 2.6 - UI_BORDER_SIZE); + split->insertWidget(0, os); + + // hidden by default + os->setVisible(false); +#endif +} + +void OnroadWindowSP::offroadTransition(bool offroad) { + if (!offroad) { +#ifdef ENABLE_MAPS + if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { + createMapWidget(); + } +#endif + if (onroad_settings == nullptr) { + createOnroadSettingsWidget(); + } + } + + OnroadWindow::offroadTransition(offroad); // Carry on with the parent class offroadTransition method +} + +void OnroadWindowSP::updateMapSize(const UIScene &scene) { + map->setFixedWidth(scene.mapbox_fullscreen ? topWidget(this)->width() : + topWidget(this)->width() / 2 - UI_BORDER_SIZE); + split->insertWidget(0, map); +} + +void OnroadWindowSP::primeChanged(bool prime) { +#ifdef ENABLE_MAPS + if (map && (!prime && MAPBOX_TOKEN.isEmpty())) { + nvg->map_settings_btn->setEnabled(false); + nvg->map_settings_btn->setVisible(false); + map->deleteLater(); + map = nullptr; + } else if (!map && (prime || !MAPBOX_TOKEN.isEmpty())) { + createMapWidget(); + } +#endif +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h new file mode 100644 index 0000000000..6bf05d3d5d --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h @@ -0,0 +1,50 @@ +#pragma once + +#include "selfdrive/ui/qt/onroad/onroad_home.h" + +#include "common/params.h" +#include "selfdrive/ui/qt/onroad/alerts.h" +// #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h" + + +class OnroadWindowSP : public OnroadWindow { + Q_OBJECT + +public: + OnroadWindowSP(QWidget* parent = 0); + bool isMapVisible() const { return map && map->isVisible(); } + void showMapPanel(bool show) { if (map) map->setVisible(show); } + + bool isOnroadSettingsVisible() const { return onroad_settings && onroad_settings->isVisible(); } + bool isMapAvailable() const { return map; } + void mapPanelNotRequested() { if (map) map->setVisible(false); } + void onroadSettingsPanelNotRequested() { if (onroad_settings) onroad_settings->setVisible(false); } + + bool wakeScreenTimeout() { + if ((uiState()->scene.sleep_btn != 0 && uiState()->scene.sleep_btn_opacity != 0) || + (uiState()->scene.sleep_time != 0 && uiState()->scene.onroadScreenOff != -2)) { + return true; + } + return false; + } + +signals: + void mapPanelRequested(); + void onroadSettingsPanelRequested(); + +private: + void createMapWidget(); + void createOnroadSettingsWidget(); + void mousePressEvent(QMouseEvent* e) override; + // AnnotatedCameraWidget *nvg; //TODO: override? this is defined on onroad_home.h + QWidget *map = nullptr; + QWidget *onroad_settings = nullptr; + + Params params; + +protected slots: + void offroadTransition(bool offroad) override; + void updateState(const UIState &s) override; + void primeChanged(bool prime); + void updateMapSize(const UIScene &scene); +}; diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc new file mode 100644 index 0000000000..044d1b6ad9 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc @@ -0,0 +1,167 @@ +#include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" + +#include +#include +#include +#include + +#include "selfdrive/ui/qt/offroad/experimental_mode.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/prime.h" + +#ifdef ENABLE_MAPS +#include "selfdrive/ui/qt/maps/map_settings.h" +#endif + +// HomeWindowSP: the container for the offroad and onroad UIs +HomeWindowSP::HomeWindowSP(QWidget* parent) : HomeWindow(parent){ + QObject::connect(onroad, &OnroadWindow::mapPanelRequested, this, [=] { sidebar->hide(); }); + QObject::connect(onroad, &OnroadWindow::onroadSettingsPanelRequested, this, [=] { sidebar->hide(); }); +} + +void HomeWindowSP::showMapPanel(bool show) { + onroad->showMapPanel(show); +} + +void HomeWindowSP::updateState(const UIState &s) { //OVERRIDE + HomeWindow::updateState(s); + + uiState()->scene.map_visible = onroad->isMapVisible(); + uiState()->scene.onroad_settings_visible = onroad->isOnroadSettingsVisible(); +} + +void HomeWindowSP::mousePressEvent(QMouseEvent* e) { + HomeWindow::mousePressEvent(e); // We call it first so that we could potentially override whatever was done by parent + + if (uiState()->scene.started) { + if (uiState()->scene.onroadScreenOff != -2) { + uiState()->scene.touched2 = true; + QTimer::singleShot(500, []() { uiState()->scene.touched2 = false; }); + } + if (uiState()->scene.button_auto_hide) { + uiState()->scene.touch_to_wake = true; + uiState()->scene.sleep_btn_fading_in = true; + QTimer::singleShot(500, []() { uiState()->scene.touch_to_wake = false; }); + } + } + + // TODO: a very similar, but not identical call is made by parent. Which made me question if I should override it here... + // Will have to revisit later if this is not behaving as expected. + // Handle sidebar collapsing + if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) { + if (onroad->wakeScreenTimeout()) { + sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible()); + } + } +} + +// OffroadHome: the offroad home page +// +// OffroadHomeSP::OffroadHomePS(QWidget* parent) : QFrame(parent) { +// QVBoxLayout* main_layout = new QVBoxLayout(this); +// main_layout->setContentsMargins(40, 40, 40, 40); +// +// // top header +// QHBoxLayout* header_layout = new QHBoxLayout(); +// header_layout->setContentsMargins(0, 0, 0, 0); +// header_layout->setSpacing(16); +// +// update_notif = new QPushButton(tr("UPDATE")); +// update_notif->setVisible(false); +// update_notif->setStyleSheet("background-color: #364DEF;"); +// QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); +// header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); +// +// alert_notif = new QPushButton(); +// alert_notif->setVisible(false); +// alert_notif->setStyleSheet("background-color: #E22C2C;"); +// QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); }); +// header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); +// +// version = new ElidedLabel(); +// header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight); +// +// main_layout->addLayout(header_layout); +// +// // main content +// main_layout->addSpacing(25); +// center_layout = new QStackedLayout(); +// +// QWidget *home_widget = new QWidget(this); +// { +// QHBoxLayout *home_layout = new QHBoxLayout(home_widget); +// home_layout->setContentsMargins(0, 0, 0, 0); +// home_layout->setSpacing(30); +// +// // left: PrimeAdWidget +// QStackedWidget *left_widget = new QStackedWidget(this); +// QVBoxLayout *left_prime_layout = new QVBoxLayout(); +// QWidget *prime_user = new PrimeUserWidget(); +// prime_user->setStyleSheet(R"( +// border-radius: 10px; +// background-color: #333333; +// )"); +// left_prime_layout->addWidget(prime_user); +// left_prime_layout->addStretch(); +// left_widget->addWidget(new LayoutWidget(left_prime_layout)); +// left_widget->addWidget(new PrimeAdWidget); +// left_widget->setStyleSheet("border-radius: 10px;"); +// +// left_widget->setCurrentIndex(uiState()->hasPrime() ? 0 : 1); +// connect(uiState(), &UIState::primeChanged, [=](bool prime) { +// left_widget->setCurrentIndex(prime ? 0 : 1); +// }); +// +// home_layout->addWidget(left_widget, 1); +// +// // right: ExperimentalModeButton, SetupWidget +// QWidget* right_widget = new QWidget(this); +// QVBoxLayout* right_column = new QVBoxLayout(right_widget); +// right_column->setContentsMargins(0, 0, 0, 0); +// right_widget->setFixedWidth(750); +// right_column->setSpacing(30); +// +// ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); +// QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); +// right_column->addWidget(experimental_mode, 1); +// +// SetupWidget *setup_widget = new SetupWidget; +// QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings); +// right_column->addWidget(setup_widget, 1); +// +// home_layout->addWidget(right_widget, 1); +// } +// center_layout->addWidget(home_widget); +// +// // add update & alerts widgets +// update_widget = new UpdateAlert(); +// QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); +// center_layout->addWidget(update_widget); +// alerts_widget = new OffroadAlert(); +// QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); +// center_layout->addWidget(alerts_widget); +// +// main_layout->addLayout(center_layout, 1); +// +// // set up refresh timer +// timer = new QTimer(this); +// timer->callOnTimeout(this, &OffroadHome::refresh); +// +// setStyleSheet(R"( +// * { +// color: white; +// } +// OffroadHome { +// background-color: black; +// } +// OffroadHome > QPushButton { +// padding: 15px 30px; +// border-radius: 5px; +// font-size: 40px; +// font-weight: 500; +// } +// OffroadHome > QLabel { +// font-size: 55px; +// } +// )"); +// } \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h new file mode 100644 index 0000000000..dda4fd9cf6 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "common/params.h" +#include "selfdrive/ui/qt/offroad/driverview.h" +#include "selfdrive/ui/qt/body.h" +#include "selfdrive/ui/qt/sidebar.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/offroad_alerts.h" +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/home.h" + +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" +#define OnroadWindow OnroadWindowSP +#else +#include "selfdrive/ui/qt/onroad/onroad_home.h" +#endif + +class HomeWindowSP : public HomeWindow { + Q_OBJECT + +public: + explicit HomeWindowSP(QWidget* parent = 0); + +public slots: + void showMapPanel(bool show); + +protected: + void mousePressEvent(QMouseEvent* e) override; +private slots: + void updateState(const UIState &s) override; +}; diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc new file mode 100644 index 0000000000..388945957f --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc @@ -0,0 +1,35 @@ +#include "selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h" +#include "selfdrive/ui/qt/offroad/experimental_mode.h" +#include "selfdrive/ui/qt/widgets/prime.h" +#include + +#ifdef ENABLE_MAPS +#define LEFT_WIDGET MapSettings +#else +#define LEFT_WIDGET QWidget +#endif + +void OffroadHomeSP::replaceWidget(QWidget* old_widget, QWidget* new_widget) +{ + old_widget->parentWidget()->layout()->replaceWidget(old_widget, new_widget); + delete old_widget; +} + +void OffroadHomeSP::replaceLeftWidget(){ + auto* new_left_widget = new QStackedWidget(left_widget->parentWidget()); + new_left_widget->addWidget(new LEFT_WIDGET); + if (!custom_mapbox) + new_left_widget->addWidget(new PrimeAdWidget); + + new_left_widget->setStyleSheet("border-radius: 10px;"); + new_left_widget->setCurrentIndex((uiState()->hasPrime() || custom_mapbox) ? 0 : 1); + connect(uiState(), &UIState::primeChanged, [=](bool prime) { + new_left_widget->setCurrentIndex((prime || custom_mapbox) ? 0 : 1); + }); + replaceWidget(left_widget, new_left_widget); +} + +OffroadHomeSP::OffroadHomeSP(QWidget* parent) : OffroadHome(parent){ + custom_mapbox = QString::fromStdString(params.get("CustomMapboxTokenSk")) != ""; + replaceLeftWidget(); +} diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h new file mode 100644 index 0000000000..1456763450 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h @@ -0,0 +1,16 @@ +#pragma once +#include "selfdrive/ui/qt/offroad_home.h" + + +class OffroadHomeSP : public OffroadHome { + Q_OBJECT + +public: + void replaceLeftWidget(); + explicit OffroadHomeSP(QWidget* parent = 0); + +private: + static void replaceWidget(QWidget* old_widget, QWidget* new_widget); + Params params; + bool custom_mapbox; +}; \ No newline at end of file From 35faa3ba9580176dbcab449af481a6dcffffae26 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 17:03:50 -0400 Subject: [PATCH 383/575] fix define --- selfdrive/ui/sunnypilot/qt/sp_priv_home.cc | 4 ---- selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc index 044d1b6ad9..c8caade101 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc @@ -9,10 +9,6 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/prime.h" -#ifdef ENABLE_MAPS -#include "selfdrive/ui/qt/maps/map_settings.h" -#endif - // HomeWindowSP: the container for the offroad and onroad UIs HomeWindowSP::HomeWindowSP(QWidget* parent) : HomeWindow(parent){ QObject::connect(onroad, &OnroadWindow::mapPanelRequested, this, [=] { sidebar->hide(); }); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h index 1456763450..b5ef820107 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h @@ -1,6 +1,10 @@ #pragma once #include "selfdrive/ui/qt/offroad_home.h" +#ifdef ENABLE_MAPS +#include "selfdrive/ui/qt/maps/map_settings.h" +#endif + class OffroadHomeSP : public OffroadHome { Q_OBJECT From 058f7aa29dea2f7267b9b8365fe7c0331f776c61 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 19:46:35 -0400 Subject: [PATCH 384/575] SettingsWindow - SettingsWindowSP --- selfdrive/ui/qt/offroad/settings.cc | 74 +++------- selfdrive/ui/qt/offroad/settings.h | 10 +- selfdrive/ui/qt/window.cc | 4 + selfdrive/ui/qt/window.h | 8 ++ selfdrive/ui/sunnypilot/SConscript | 1 + .../sunnypilot/qt/offroad/sp_priv_settings.cc | 136 ++++++++++++++++++ .../sunnypilot/qt/offroad/sp_priv_settings.h | 19 +++ 7 files changed, 187 insertions(+), 65 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 13b486d8a7..358cbb88a9 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -609,23 +609,20 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { } SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { +#ifndef SUNNYPILOT // setup two main layouts sidebar_widget = new QWidget; QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget); panel_widget = new QStackedWidget(); - // setup layout for close button - QVBoxLayout *close_btn_layout = new QVBoxLayout; - close_btn_layout->setContentsMargins(0, 0, 0, 20); - // close button QPushButton *close_btn = new QPushButton(tr("×")); close_btn->setStyleSheet(R"( QPushButton { font-size: 140px; padding-bottom: 20px; - border-radius: 76px; + border-radius: 100px; background-color: #292929; font-weight: 400; } @@ -633,16 +630,11 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { background-color: #3B3B3B; } )"); - close_btn->setFixedSize(152, 152); - close_btn_layout->addWidget(close_btn, 0, Qt::AlignLeft); + close_btn->setFixedSize(200, 200); + sidebar_layout->addSpacing(45); + sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter); QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings); - // setup buttons widget - QWidget *buttons_widget = new QWidget; - QVBoxLayout *buttons_layout = new QVBoxLayout(buttons_widget); - buttons_layout->setMargin(0); - buttons_layout->addSpacing(10); - // setup panels DevicePanel *device = new DevicePanel(this); QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); @@ -651,51 +643,27 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { TogglesPanel *toggles = new TogglesPanel(this); QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); - QList panels = { - PanelInfo(" " + tr("Device"), device, "../assets/navigation/icon_home.svg"), - PanelInfo(" " + tr("Network"), new Networking(this), "../assets/offroad/icon_network.png"), -#ifdef SUNNYPILOT - PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/offroad/icon_wifi_strength_full.svg"), -#endif - PanelInfo(" " + tr("Toggles"), toggles, "../assets/offroad/icon_toggle.png"), -#ifdef SUNNYPILOT - PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../assets/offroad/icon_software.png"), -#else - PanelInfo(" " + tr("Software"), new SoftwarePanel(this), "../assets/offroad/icon_software.png"), -#endif -#ifdef SUNNYPILOT - PanelInfo(" " + tr("sunnypilot"), new SunnypilotPanel(this), "../assets/offroad/icon_openpilot.png"), - PanelInfo(" " + tr("OSM"), new OsmPanel(this), "../assets/offroad/icon_map.png"), - PanelInfo(" " + tr("Monitoring"), new MonitoringPanel(this), "../assets/offroad/icon_monitoring.png"), - PanelInfo(" " + tr("Visuals"), new VisualsPanel(this), "../assets/offroad/icon_visuals.png"), - PanelInfo(" " + tr("Display"), new DisplayPanel(this), "../assets/offroad/icon_display.png"), - PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../assets/offroad/icon_trips.png"), - PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../assets/offroad/icon_vehicle.png"), -#endif + QList> panels = { + {tr("Device"), device}, + {tr("Network"), new Networking(this)}, + {tr("Toggles"), toggles}, + {tr("Software"), new SoftwarePanel(this)}, }; nav_btns = new QButtonGroup(this); - for (auto &[name, panel, icon] : panels) { + for (auto &[name, panel] : panels) { QPushButton *btn = new QPushButton(name); btn->setCheckable(true); btn->setChecked(nav_btns->buttons().size() == 0); - btn->setIcon(QIcon(QPixmap(icon))); - btn->setIconSize(QSize(70, 70)); btn->setStyleSheet(R"( QPushButton { - border-radius: 20px; - width: 400px; - height: 98px; - color: #bdbdbd; + color: grey; border: none; background: none; - font-size: 50px; + font-size: 65px; font-weight: 500; - text-align: left; - padding-left: 22px; } QPushButton:checked { - background-color: #696868; color: white; } QPushButton:pressed { @@ -704,9 +672,9 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { )"); btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); nav_btns->addButton(btn); - buttons_layout->addWidget(btn, 0, Qt::AlignLeft | Qt::AlignBottom); + sidebar_layout->addWidget(btn, 0, Qt::AlignRight); - const int lr_margin = (name != (" " + tr("Network")) || (name != (" " + tr("sunnypilot")))) ? 50 : 0; // Network and sunnypilot panel handles its own margins + const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins panel->setContentsMargins(lr_margin, 25, lr_margin, 25); ScrollView *panel_frame = new ScrollView(panel, this); @@ -717,18 +685,11 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { panel_widget->setCurrentWidget(w); }); } - sidebar_layout->setContentsMargins(50, 50, 25, 50); + sidebar_layout->setContentsMargins(50, 50, 100, 50); // main settings layout, sidebar + main panel QHBoxLayout *main_layout = new QHBoxLayout(this); - // add layout for close button - sidebar_layout->addLayout(close_btn_layout); - - // add layout for buttons scrolling - ScrollView *buttons_scrollview = new ScrollView(buttons_widget, this); - sidebar_layout->addWidget(buttons_scrollview); - sidebar_widget->setFixedWidth(500); main_layout->addWidget(sidebar_widget); main_layout->addWidget(panel_widget); @@ -742,8 +703,9 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { background-color: black; } QStackedWidget, ScrollView { - background-color: black; + background-color: #292929; border-radius: 30px; } )"); +#endif } diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 06fbe8bfb9..0fead0c87c 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -32,19 +32,11 @@ signals: void showDriverView(); void expandToggleDescription(const QString ¶m); -private: +protected: QPushButton *sidebar_alert_widget; QWidget *sidebar_widget; QButtonGroup *nav_btns; QStackedWidget *panel_widget; - - struct PanelInfo { - QString name; - QWidget *widget; - QString icon; - - PanelInfo(const QString &name, QWidget *widget, const QString &icon) : name(name), widget(widget), icon(icon) {} - }; }; class DevicePanel : public ListWidget { diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 00b4693822..4ff91b1748 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -13,7 +13,11 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings); QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings); +#ifdef SUNNYPILOT + settingsWindow = new SettingsWindowSP(this); +#else settingsWindow = new SettingsWindow(this); +#endif main_layout->addWidget(settingsWindow); QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings); QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() { diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 515e9f362a..b13b250a9b 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -4,7 +4,11 @@ #include #include "selfdrive/ui/qt/offroad/onboarding.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" +#else #include "selfdrive/ui/qt/offroad/settings.h" +#endif #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" @@ -26,6 +30,10 @@ private: QStackedLayout *main_layout; HomeWindow *homeWindow; +#ifdef SUNNYPILOT + SettingsWindowSP *settingsWindow; +#else SettingsWindow *settingsWindow; +#endif OnboardingWindow *onboardingWindow; }; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index eab8fe62d7..d345af9412 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -27,6 +27,7 @@ network_src = [ qt_src = [ "sunnypilot/qt/sp_priv_home.cc", "sunnypilot/qt/sp_priv_offroad_home.cc", + "sunnypilot/qt/offroad/sp_priv_settings.cc", "sunnypilot/qt/onroad/onroad_settings.cc", "sunnypilot/qt/onroad/onroad_settings_panel.cc", "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc new file mode 100644 index 0000000000..7d6d08a229 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc @@ -0,0 +1,136 @@ +#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" + +#include "selfdrive/ui/qt/network/networking.h" +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" + +SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { + + // setup two main layouts + sidebar_widget = new QWidget; + QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget); + panel_widget = new QStackedWidget(); + + // setup layout for close button + QVBoxLayout *close_btn_layout = new QVBoxLayout; + close_btn_layout->setContentsMargins(0, 0, 0, 20); + + // close button + QPushButton *close_btn = new QPushButton(tr("×")); + close_btn->setStyleSheet(R"( + QPushButton { + font-size: 140px; + padding-bottom: 20px; + border-radius: 76px; + background-color: #292929; + font-weight: 400; + } + QPushButton:pressed { + background-color: #3B3B3B; + } + )"); + close_btn->setFixedSize(152, 152); + close_btn_layout->addWidget(close_btn, 0, Qt::AlignLeft); + QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings); + + // setup buttons widget + QWidget *buttons_widget = new QWidget; + QVBoxLayout *buttons_layout = new QVBoxLayout(buttons_widget); + buttons_layout->setMargin(0); + buttons_layout->addSpacing(10); + + // setup panels + DevicePanel *device = new DevicePanel(this); + QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); + QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); + + TogglesPanel *toggles = new TogglesPanel(this); + QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); + + QList panels = { + PanelInfo(" " + tr("Device"), device, "../assets/navigation/icon_home.svg"), + PanelInfo(" " + tr("Network"), new Networking(this), "../assets/offroad/icon_network.png"), + PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/offroad/icon_wifi_strength_full.svg"), + PanelInfo(" " + tr("Toggles"), toggles, "../assets/offroad/icon_toggle.png"), + PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../assets/offroad/icon_software.png"), + PanelInfo(" " + tr("sunnypilot"), new SunnypilotPanel(this), "../assets/offroad/icon_openpilot.png"), + PanelInfo(" " + tr("OSM"), new OsmPanel(this), "../assets/offroad/icon_map.png"), + PanelInfo(" " + tr("Monitoring"), new MonitoringPanel(this), "../assets/offroad/icon_monitoring.png"), + PanelInfo(" " + tr("Visuals"), new VisualsPanel(this), "../assets/offroad/icon_visuals.png"), + PanelInfo(" " + tr("Display"), new DisplayPanel(this), "../assets/offroad/icon_display.png"), + PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../assets/offroad/icon_trips.png"), + PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../assets/offroad/icon_vehicle.png"), + }; + + nav_btns = new QButtonGroup(this); + for (auto &[name, panel, icon] : panels) { + QPushButton *btn = new QPushButton(name); + btn->setCheckable(true); + btn->setChecked(nav_btns->buttons().size() == 0); + btn->setIcon(QIcon(QPixmap(icon))); + btn->setIconSize(QSize(70, 70)); + btn->setStyleSheet(R"( + QPushButton { + border-radius: 20px; + width: 400px; + height: 98px; + color: #bdbdbd; + border: none; + background: none; + font-size: 50px; + font-weight: 500; + text-align: left; + padding-left: 22px; + } + QPushButton:checked { + background-color: #696868; + color: white; + } + QPushButton:pressed { + color: #ADADAD; + } + )"); + btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + nav_btns->addButton(btn); + buttons_layout->addWidget(btn, 0, Qt::AlignLeft | Qt::AlignBottom); + + const int lr_margin = (name != (" " + tr("Network")) || (name != (" " + tr("sunnypilot")))) ? 50 : 0; // Network and sunnypilot panel handles its own margins + panel->setContentsMargins(lr_margin, 25, lr_margin, 25); + + ScrollView *panel_frame = new ScrollView(panel, this); + panel_widget->addWidget(panel_frame); + + QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { + btn->setChecked(true); + panel_widget->setCurrentWidget(w); + }); + } + sidebar_layout->setContentsMargins(50, 50, 25, 50); + + // main settings layout, sidebar + main panel + QHBoxLayout *main_layout = new QHBoxLayout(this); + + // add layout for close button + sidebar_layout->addLayout(close_btn_layout); + + // add layout for buttons scrolling + ScrollView *buttons_scrollview = new ScrollView(buttons_widget, this); + sidebar_layout->addWidget(buttons_scrollview); + + sidebar_widget->setFixedWidth(500); + main_layout->addWidget(sidebar_widget); + main_layout->addWidget(panel_widget); + + setStyleSheet(R"( + * { + color: white; + font-size: 50px; + } + SettingsWindow { + background-color: black; + } + QStackedWidget, ScrollView { + background-color: black; + border-radius: 30px; + } + )"); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h new file mode 100644 index 0000000000..1a0986331a --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h @@ -0,0 +1,19 @@ +#pragma once + +#include "selfdrive/ui/qt/offroad/settings.h" + +class SettingsWindowSP : public SettingsWindow { + Q_OBJECT + +public: + explicit SettingsWindowSP(QWidget* parent = nullptr); + +protected: + struct PanelInfo { + QString name; + QWidget *widget; + QString icon; + + PanelInfo(const QString &name, QWidget *widget, const QString &icon) : name(name), widget(widget), icon(icon) {} + }; +}; From 71a5e802047ccf443c3499ec8969fe94f691d9d8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 20:33:20 -0400 Subject: [PATCH 385/575] TogglesPanel - TogglesPanelSP --- selfdrive/ui/qt/offroad/settings.cc | 129 ++------ selfdrive/ui/qt/offroad/settings.h | 10 +- .../sunnypilot/qt/offroad/sp_priv_settings.cc | 281 +++++++++++++++++- .../sunnypilot/qt/offroad/sp_priv_settings.h | 17 ++ 4 files changed, 318 insertions(+), 119 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 358cbb88a9..f0bb4d703f 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -22,13 +22,14 @@ #endif TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { +#ifndef SUNNYPILOT // param, title, desc, icon std::vector> toggle_defs{ { "OpenpilotEnabledToggle", - tr("Enable sunnypilot"), - tr("Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), - "../assets/offroad/icon_blank.png", + tr("Enable openpilot"), + tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), + "../assets/offroad/icon_openpilot.png", }, { "ExperimentalLongitudinalEnabled", @@ -37,68 +38,43 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), - "../assets/offroad/icon_blank.png", - }, - { - "CustomStockLong", - tr("Custom Stock Longitudinal Control"), - tr("When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses.\nThis feature must be used along with SLC, and/or V-TSC, and/or M-TSC."), - "../assets/offroad/icon_blank.png", + "../assets/offroad/icon_speed_limit.png", }, { "ExperimentalMode", tr("Experimental Mode"), "", - "../assets/offroad/icon_blank.png", - }, - { - "DynamicExperimentalControl", - tr("Enable Dynamic Experimental Control"), - tr("Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal."), - "../assets/offroad/icon_blank.png", - }, - { - "DynamicPersonality", - tr("Enable Dynamic Personality"), - tr("Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your \"Driving Personality\" setting. " - "Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car."), - "../assets/offroad/icon_blank.png", + "../assets/img_experimental_white.svg", }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), tr("When enabled, pressing the accelerator pedal will disengage openpilot."), - "../assets/offroad/icon_blank.png", + "../assets/offroad/icon_disengage_on_accelerator.svg", }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), - "../assets/offroad/icon_blank.png", + "../assets/offroad/icon_warning.png", }, { "AlwaysOnDM", tr("Always-On Driver Monitoring"), tr("Enable driver monitoring even when openpilot is not engaged."), - "../assets/offroad/icon_blank.png", + "../assets/offroad/icon_monitoring.png", }, { "RecordFront", tr("Record and Upload Driver Camera"), tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), - "../assets/offroad/icon_blank.png", - }, - { - "DisableOnroadUploads", - tr("Disable Onroad Uploads"), - tr("Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC)."), - "../assets/offroad/icon_blank.png", + "../assets/offroad/icon_monitoring.png", }, { "IsMetric", tr("Use Metric System"), tr("Display speed in km/h instead of mph."), - "../assets/offroad/icon_blank.png", + "../assets/offroad/icon_metric.png", }, #ifdef ENABLE_MAPS { @@ -117,25 +93,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }; - std::vector longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")}; + std::vector longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")}; long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), - tr("Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " + tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " "your steering wheel distance button."), - "../assets/offroad/icon_blank.png", - longi_button_texts, - 380); - long_personality_setting->showDescription(); - - // accel controller - std::vector accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco"), tr("Stock")}; - accel_personality_setting = new ButtonParamControl("AccelPersonality", tr("Acceleration Personality"), - tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. " - "In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these " - "acceleration personality within Onroad Settings on the driving screen."), - "../assets/offroad/icon_blank.png", - accel_personality_texts); - accel_personality_setting->showDescription(); + "../assets/offroad/icon_speed_limit.png", + longi_button_texts); // set up uiState update for personality setting QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState); @@ -152,28 +116,18 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // insert longitudinal personality after NDOG toggle if (param == "DisengageOnAccelerator") { addItem(long_personality_setting); - addItem(accel_personality_setting); } } // Toggles with confirmation dialogs - //toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); + toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); toggles["ExperimentalMode"]->setConfirmation(true, true); toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); - toggles["CustomStockLong"]->setConfirmation(true, false); connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); }); - connect(toggles["CustomStockLong"], &ToggleControl::toggleFlipped, [=]() { - updateToggles(); - }); - - param_watcher = new ParamWatcher(this); - - QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { - updateToggles(); - }); +#endif } void TogglesPanel::updateState(const UIState &s) { @@ -186,14 +140,6 @@ void TogglesPanel::updateState(const UIState &s) { } uiState()->scene.personality = personality; } - - if (sm.updated("controlsStateSP")) { - auto accel_personality = sm["controlsStateSP"].getControlsStateSP().getAccelPersonality(); - if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) { - accel_personality_setting->setCheckedButton(static_cast(accel_personality)); - } - uiState()->scene.accel_personality = accel_personality; - } } void TogglesPanel::expandToggleDescription(const QString ¶m) { @@ -205,15 +151,8 @@ void TogglesPanel::showEvent(QShowEvent *event) { } void TogglesPanel::updateToggles() { - param_watcher->addParam("LongitudinalPersonality"); - - if (!isVisible()) return; - auto experimental_mode_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; - auto custom_stock_long_toggle = toggles["CustomStockLong"]; - auto dec_toggle = toggles["DynamicExperimentalControl"]; - auto dynamic_personality_toggle = toggles["DynamicPersonality"]; const QString e2e_description = QString("%1
" "

%2


" "%3
" @@ -238,35 +177,15 @@ void TogglesPanel::updateToggles() { params.remove("ExperimentalLongitudinalEnabled"); } op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release); - - if (!CP.getCustomStockLongAvailable()) { - params.remove("CustomStockLong"); - } - custom_stock_long_toggle->setVisible(CP.getCustomStockLongAvailable()); - if (hasLongitudinalControl(CP)) { // normal description and toggle experimental_mode_toggle->setEnabled(true); experimental_mode_toggle->setDescription(e2e_description); long_personality_setting->setEnabled(true); - accel_personality_setting->setEnabled(true); - op_long_toggle->setEnabled(true); - custom_stock_long_toggle->setEnabled(false); - params.remove("CustomStockLong"); - dec_toggle->setEnabled(true); - dynamic_personality_toggle->setEnabled(true); - } else if (custom_stock_long_toggle->isToggled()) { - op_long_toggle->setEnabled(false); - experimental_mode_toggle->setEnabled(false); - long_personality_setting->setEnabled(false); - accel_personality_setting->setEnabled(false); - params.remove("ExperimentalLongitudinalEnabled"); - params.remove("ExperimentalMode"); } else { // no long for now experimental_mode_toggle->setEnabled(false); long_personality_setting->setEnabled(false); - accel_personality_setting->setEnabled(false); params.remove("ExperimentalMode"); const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."); @@ -281,26 +200,12 @@ void TogglesPanel::updateToggles() { } } experimental_mode_toggle->setDescription("" + long_desc + "

" + e2e_description); - - op_long_toggle->setEnabled(CP.getExperimentalLongitudinalAvailable() && !is_release); - custom_stock_long_toggle->setEnabled(CP.getCustomStockLongAvailable()); - dec_toggle->setEnabled(false); - dynamic_personality_toggle->setEnabled(false); - params.remove("DynamicExperimentalControl"); - params.remove("DynamicPersonality"); } experimental_mode_toggle->refresh(); - op_long_toggle->refresh(); - custom_stock_long_toggle->refresh(); - dec_toggle->refresh(); - dynamic_personality_toggle->refresh(); } else { experimental_mode_toggle->setDescription(e2e_description); op_long_toggle->setVisible(false); - custom_stock_long_toggle->setVisible(false); - dec_toggle->setVisible(false); - dynamic_personality_toggle->setVisible(false); } } diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 0fead0c87c..f4ca65233e 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -79,18 +79,16 @@ public: public slots: void expandToggleDescription(const QString ¶m); - void updateToggles(); -private slots: - void updateState(const UIState &s); +protected slots: + virtual void updateState(const UIState &s); -private: +protected: Params params; std::map toggles; ButtonParamControl *long_personality_setting; - ButtonParamControl *accel_personality_setting; - ParamWatcher *param_watcher; + virtual void updateToggles(); }; class SoftwarePanel : public ListWidget { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc index 7d6d08a229..bccb332039 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc @@ -3,6 +3,285 @@ #include "selfdrive/ui/qt/network/networking.h" #include "selfdrive/ui/sunnypilot/sunnypilot_main.h" +TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { + // param, title, desc, icon + std::vector> toggle_defs{ + { + "OpenpilotEnabledToggle", + tr("Enable sunnypilot"), + tr("Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), + "../assets/offroad/icon_blank.png", + }, + { + "ExperimentalLongitudinalEnabled", + tr("openpilot Longitudinal Control (Alpha)"), + QString("%1

%2") + .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) + .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), + "../assets/offroad/icon_blank.png", + }, + { + "CustomStockLong", + tr("Custom Stock Longitudinal Control"), + tr("When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses.\nThis feature must be used along with SLC, and/or V-TSC, and/or M-TSC."), + "../assets/offroad/icon_blank.png", + }, + { + "ExperimentalMode", + tr("Experimental Mode"), + "", + "../assets/offroad/icon_blank.png", + }, + { + "DynamicExperimentalControl", + tr("Enable Dynamic Experimental Control"), + tr("Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal."), + "../assets/offroad/icon_blank.png", + }, + { + "DynamicPersonality", + tr("Enable Dynamic Personality"), + tr("Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your \"Driving Personality\" setting. " + "Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car."), + "../assets/offroad/icon_blank.png", + }, + { + "DisengageOnAccelerator", + tr("Disengage on Accelerator Pedal"), + tr("When enabled, pressing the accelerator pedal will disengage openpilot."), + "../assets/offroad/icon_blank.png", + }, + { + "IsLdwEnabled", + tr("Enable Lane Departure Warnings"), + tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), + "../assets/offroad/icon_blank.png", + }, + { + "AlwaysOnDM", + tr("Always-On Driver Monitoring"), + tr("Enable driver monitoring even when openpilot is not engaged."), + "../assets/offroad/icon_blank.png", + }, + { + "RecordFront", + tr("Record and Upload Driver Camera"), + tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), + "../assets/offroad/icon_blank.png", + }, + { + "DisableOnroadUploads", + tr("Disable Onroad Uploads"), + tr("Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC)."), + "../assets/offroad/icon_blank.png", + }, + { + "IsMetric", + tr("Use Metric System"), + tr("Display speed in km/h instead of mph."), + "../assets/offroad/icon_blank.png", + }, +#ifdef ENABLE_MAPS + { + "NavSettingTime24h", + tr("Show ETA in 24h Format"), + tr("Use 24h format instead of am/pm"), + "../assets/offroad/icon_blank.png", + }, + { + "NavSettingLeftSide", + tr("Show Map on Left Side of UI"), + tr("Show map on left side when in split screen view."), + "../assets/offroad/icon_blank.png", + }, +#endif + }; + + + std::vector longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")}; + long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), + tr("Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " + "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + "your steering wheel distance button."), + "../assets/offroad/icon_blank.png", + longi_button_texts, + 380); + long_personality_setting->showDescription(); + + // accel controller + std::vector accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco"), tr("Stock")}; + accel_personality_setting = new ButtonParamControl("AccelPersonality", tr("Acceleration Personality"), + tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. " + "In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these " + "acceleration personality within Onroad Settings on the driving screen."), + "../assets/offroad/icon_blank.png", + accel_personality_texts); + accel_personality_setting->showDescription(); + + // set up uiState update for personality setting + QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanelSP::updateState); + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, this); + + bool locked = params.getBool((param + "Lock").toStdString()); + toggle->setEnabled(!locked); + + addItem(toggle); + toggles[param.toStdString()] = toggle; + + // insert longitudinal personality after NDOG toggle + if (param == "DisengageOnAccelerator") { + addItem(long_personality_setting); + addItem(accel_personality_setting); + } + } + + // Toggles with confirmation dialogs + //toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); + toggles["ExperimentalMode"]->setConfirmation(true, true); + toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); + toggles["CustomStockLong"]->setConfirmation(true, false); + + connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { + updateToggles(); + }); + connect(toggles["CustomStockLong"], &ToggleControl::toggleFlipped, [=]() { + updateToggles(); + }); + + param_watcher = new ParamWatcher(this); + + QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { + updateToggles(); + }); +} + +void TogglesPanelSP::updateState(const UIState &s) { + const SubMaster &sm = *(s.sm); + + if (sm.updated("controlsState")) { + auto personality = sm["controlsState"].getControlsState().getPersonality(); + if (personality != s.scene.personality && s.scene.started && isVisible()) { + long_personality_setting->setCheckedButton(static_cast(personality)); + } + uiState()->scene.personality = personality; + } + + if (sm.updated("controlsStateSP")) { + auto accel_personality = sm["controlsStateSP"].getControlsStateSP().getAccelPersonality(); + if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) { + accel_personality_setting->setCheckedButton(static_cast(accel_personality)); + } + uiState()->scene.accel_personality = accel_personality; + } +} + +void TogglesPanelSP::showEvent(QShowEvent *event) { + updateToggles(); +} + +void TogglesPanelSP::updateToggles() { + param_watcher->addParam("LongitudinalPersonality"); + + if (!isVisible()) return; + + auto experimental_mode_toggle = toggles["ExperimentalMode"]; + auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; + auto custom_stock_long_toggle = toggles["CustomStockLong"]; + auto dec_toggle = toggles["DynamicExperimentalControl"]; + auto dynamic_personality_toggle = toggles["DynamicPersonality"]; + const QString e2e_description = QString("%1
" + "

%2


" + "%3
" + "

%4


" + "%5
") + .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) + .arg(tr("End-to-End Longitudinal Control")) + .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " + "mistakes should be expected.")) + .arg(tr("New Driving Visualization")) + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); + + const bool is_release = params.getBool("IsReleaseBranch"); + auto cp_bytes = params.get("CarParamsPersistent"); + if (!cp_bytes.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); + cereal::CarParams::Reader CP = cmsg.getRoot(); + + if (!CP.getExperimentalLongitudinalAvailable() || is_release) { + params.remove("ExperimentalLongitudinalEnabled"); + } + op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release); + + if (!CP.getCustomStockLongAvailable()) { + params.remove("CustomStockLong"); + } + custom_stock_long_toggle->setVisible(CP.getCustomStockLongAvailable()); + + if (hasLongitudinalControl(CP)) { + // normal description and toggle + experimental_mode_toggle->setEnabled(true); + experimental_mode_toggle->setDescription(e2e_description); + long_personality_setting->setEnabled(true); + accel_personality_setting->setEnabled(true); + op_long_toggle->setEnabled(true); + custom_stock_long_toggle->setEnabled(false); + params.remove("CustomStockLong"); + dec_toggle->setEnabled(true); + dynamic_personality_toggle->setEnabled(true); + } else if (custom_stock_long_toggle->isToggled()) { + op_long_toggle->setEnabled(false); + experimental_mode_toggle->setEnabled(false); + long_personality_setting->setEnabled(false); + accel_personality_setting->setEnabled(false); + params.remove("ExperimentalLongitudinalEnabled"); + params.remove("ExperimentalMode"); + } else { + // no long for now + experimental_mode_toggle->setEnabled(false); + long_personality_setting->setEnabled(false); + accel_personality_setting->setEnabled(false); + params.remove("ExperimentalMode"); + + const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."); + + QString long_desc = unavailable + " " + \ + tr("openpilot longitudinal control may come in a future update."); + if (CP.getExperimentalLongitudinalAvailable()) { + if (is_release) { + long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); + } else { + long_desc = tr("Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode."); + } + } + experimental_mode_toggle->setDescription("" + long_desc + "

" + e2e_description); + + op_long_toggle->setEnabled(CP.getExperimentalLongitudinalAvailable() && !is_release); + custom_stock_long_toggle->setEnabled(CP.getCustomStockLongAvailable()); + dec_toggle->setEnabled(false); + dynamic_personality_toggle->setEnabled(false); + params.remove("DynamicExperimentalControl"); + params.remove("DynamicPersonality"); + } + + experimental_mode_toggle->refresh(); + op_long_toggle->refresh(); + custom_stock_long_toggle->refresh(); + dec_toggle->refresh(); + dynamic_personality_toggle->refresh(); + } else { + experimental_mode_toggle->setDescription(e2e_description); + op_long_toggle->setVisible(false); + custom_stock_long_toggle->setVisible(false); + dec_toggle->setVisible(false); + dynamic_personality_toggle->setVisible(false); + } +} + SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { // setup two main layouts @@ -43,7 +322,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); - TogglesPanel *toggles = new TogglesPanel(this); + TogglesPanelSP *toggles = new TogglesPanelSP(this); QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); QList panels = { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h index 1a0986331a..6fbb2de6f0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h @@ -2,6 +2,23 @@ #include "selfdrive/ui/qt/offroad/settings.h" +class TogglesPanelSP : public TogglesPanel { + Q_OBJECT + +public: + explicit TogglesPanelSP(SettingsWindow *parent); + void showEvent(QShowEvent *event) override; + +private slots: + void updateState(const UIState &s) override; + +private: + ButtonParamControl *accel_personality_setting; + + ParamWatcher *param_watcher; + void updateToggles() override; +}; + class SettingsWindowSP : public SettingsWindow { Q_OBJECT From ff6ff674c6ce7c6d6c87f50f98dabbc3b0a6952b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 21:08:43 -0400 Subject: [PATCH 386/575] ParamControl - ParamControlSP --- selfdrive/ui/qt/offroad/settings.h | 4 ++++ selfdrive/ui/qt/widgets/controls.cc | 9 -------- selfdrive/ui/qt/widgets/controls.h | 8 +++++-- selfdrive/ui/sunnypilot/SConscript | 3 ++- .../qt/offroad/custom_offsets_settings.h | 4 ++-- .../sunnypilot/qt/offroad/display_settings.cc | 2 +- .../sunnypilot/qt/offroad/display_settings.h | 4 ++-- .../qt/offroad/lane_change_settings.cc | 2 +- .../qt/offroad/lane_change_settings.h | 4 ++-- .../ui/sunnypilot/qt/offroad/mads_settings.cc | 2 +- .../ui/sunnypilot/qt/offroad/mads_settings.h | 4 ++-- .../qt/offroad/monitoring_settings.cc | 2 +- .../qt/offroad/monitoring_settings.h | 4 ++-- .../ui/sunnypilot/qt/offroad/osm_settings.h | 4 ++-- .../sunnypilot/qt/offroad/sp_priv_settings.cc | 2 +- .../sunnypilot/qt/offroad/sp_priv_settings.h | 1 + .../qt/offroad/speed_limit_control_settings.h | 4 ++-- .../offroad/speed_limit_warning_settings.cc | 2 +- .../qt/offroad/speed_limit_warning_settings.h | 6 ++--- .../qt/offroad/sunnylink_settings.cc | 6 ++--- .../qt/offroad/sunnylink_settings.h | 4 ++-- .../qt/offroad/sunnypilot_settings.cc | 2 +- .../qt/offroad/sunnypilot_settings.h | 4 ++-- .../sunnypilot/qt/offroad/vehicle_settings.cc | 22 +++++++++---------- .../sunnypilot/qt/offroad/vehicle_settings.h | 6 ++--- .../sunnypilot/qt/offroad/visuals_settings.cc | 2 +- .../sunnypilot/qt/offroad/visuals_settings.h | 4 ++-- .../sunnypilot/qt/widgets/sp_priv_controls.cc | 14 ++++++++++++ .../sunnypilot/qt/widgets/sp_priv_controls.h | 12 ++++++++++ 29 files changed, 87 insertions(+), 60 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc create mode 100644 selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index f4ca65233e..037aa7070a 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -15,6 +15,10 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#endif + // ********** settings window + top-level panels ********** class SettingsWindow : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 238051cf5e..09c23eb80c 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -178,15 +178,6 @@ ParamControl::ParamControl(const QString ¶m, const QString &title, const QSt : ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, this, &ParamControl::toggleClicked); - - hlayout->removeWidget(&toggle); - hlayout->insertWidget(0, &toggle); - - hlayout->removeWidget(this->icon_label); - this->icon_pixmap = QPixmap(icon).scaledToWidth(20, Qt::SmoothTransformation); - this->icon_label->setPixmap(this->icon_pixmap); - this->icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); - hlayout->insertWidget(1, this->icon_label); } void ParamControl::toggleClicked(bool state) { diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 3466b67f5e..01f1eccbf9 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -181,8 +181,6 @@ public: refresh(); } - bool isToggled() { return params.getBool(key); } - private: void toggleClicked(bool state); void setIcon(bool state) { @@ -193,8 +191,14 @@ private: } } +#ifdef SUNNYPILOT +protected: +#endif std::string key; Params params; +#ifdef SUNNYPILOT +private: +#endif QPixmap active_icon_pixmap; bool confirm = false; bool store_confirm = false; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index d345af9412..8d5f1d5512 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -15,7 +15,8 @@ widgets_src = [ "sunnypilot/qt/offroad/models_fetcher.cc", "sunnypilot/qt/offroad/speed_limit_warning_settings.cc", "sunnypilot/qt/offroad/speed_limit_policy_settings.cc", - "sunnypilot/qt/offroad/sunnylink_settings.cc" + "sunnypilot/qt/offroad/sunnylink_settings.cc", + "sunnypilot/qt/widgets/sp_priv_controls.cc" ] network_src = [ diff --git a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h index 63a5a817be..0b693ffc0c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" class CameraOffset : public SPOptionControl { @@ -38,7 +38,7 @@ signals: private: Params params; - std::map toggles; + std::map toggles; CameraOffset *camera_offset; PathOffset *path_offset; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc index 02d723c363..cceb2ef7e9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc @@ -38,7 +38,7 @@ DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { connect(brightness_control, &SPOptionControl::updateLabels, brightness_control, &BrightnessControl::refresh); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); addItem(toggle); toggles[param.toStdString()] = toggle; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h index b8083b53fc..ee1e6b6308 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" class OnroadScreenOff : public SPOptionControl { Q_OBJECT @@ -62,7 +62,7 @@ public slots: private: Params params; - std::map toggles; + std::map toggles; OnroadScreenOff *onroad_screen_off; OnroadScreenOffBrightness *onroad_screen_off_brightness; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc index e950ab71c3..684568c57d 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc @@ -47,7 +47,7 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { connect(pause_lateral_speed, &SPOptionControl::updateLabels, pause_lateral_speed, &PauseLateralSpeed::refresh); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); list->addItem(toggle); toggles[param.toStdString()] = toggle; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h index dd8e1748bd..6c2d0d5708 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" class AutoLaneChangeTimer : public SPOptionControl { @@ -50,7 +50,7 @@ public slots: private: Params params; - std::map toggles; + std::map toggles; AutoLaneChangeTimer *auto_lane_change_timer; PauseLateralSpeed *pause_lateral_speed; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc index 0a3ced7c69..678b495d19 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc @@ -44,7 +44,7 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { list->addItem(dlob_settings); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); list->addItem(toggle); toggles[param.toStdString()] = toggle; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h index 2a949c97a4..afdf0caeed 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" class MadsSettings : public QWidget { @@ -19,7 +19,7 @@ public slots: private: Params params; - std::map toggles; + std::map toggles; ButtonParamControl *dlob_settings; }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc index 7d740a3586..6afa087eb4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc @@ -15,7 +15,7 @@ MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) { }; for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); list->addItem(toggle); toggles[param.toStdString()] = toggle; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h index 214d2f8ac0..8f184a4413 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" class MonitoringPanel : public QFrame { @@ -13,5 +13,5 @@ private: QStackedLayout* main_layout = nullptr; QWidget* monitoringScreen = nullptr; Params params; - std::map toggles; + std::map toggles; }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h index cc2556b150..587b214350 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h @@ -9,7 +9,7 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/network/wifi_manager.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" @@ -31,7 +31,7 @@ private: QWidget* osmScreen = nullptr; Params params; Params mem_params{ Hardware::PC() ? "": "/dev/shm/params"}; - std::map toggles; + std::map toggles; std::optional> mapSizeFuture; const SubMaster &sm = *uiState()->sm; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc index bccb332039..26fff880a6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc @@ -123,7 +123,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanelSP::updateState); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); bool locked = params.getBool((param + "Lock").toStdString()); toggle->setEnabled(!locked); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h index 6fbb2de6f0..f99b0ce075 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h @@ -13,6 +13,7 @@ private slots: void updateState(const UIState &s) override; private: + std::map toggles; ButtonParamControl *accel_personality_setting; ParamWatcher *param_watcher; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h index 877064e577..3e66956d0c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" @@ -33,7 +33,7 @@ public slots: private: Params params; - std::map toggles; + std::map toggles; SpeedLimitValueOffset *slvo; ButtonParamControl *speed_limit_offset_settings; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc index 16557c1228..7b7c9676ac 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc @@ -23,7 +23,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( speed_limit_warning_settings->showDescription(); list->addItem(speed_limit_warning_settings); - speed_limit_warning_flash = new ParamControl( + speed_limit_warning_flash = new ParamControlSP( "SpeedLimitWarningFlash", tr("Warning with speed limit flash"), tr("When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h index ce3c48ac3f..1a7d3c2144 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" @@ -33,11 +33,11 @@ public slots: private: Params params; - std::map toggles; + std::map toggles; SpeedLimitWarningValueOffset *slwvo; ButtonParamControl *speed_limit_warning_offset_settings; - ParamControl *speed_limit_warning_flash; + ParamControlSP *speed_limit_warning_flash; ButtonParamControl *speed_limit_warning_settings; ParamWatcher *param_watcher; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc index c363e5655f..3f00c92c37 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc @@ -17,7 +17,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { connect(uiState(), &UIState::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updateLabels); auto list = new ListWidget(this, false); - sunnylinkEnabledBtn = new ParamControl( + sunnylinkEnabledBtn = new ParamControlSP( "SunnylinkEnabled", tr("Enable sunnylink"), sunnylinkBtnDescription, @@ -29,12 +29,12 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { list->addItem(horizontal_line()); sunnylinkBtnDescription = tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that."); - connect(sunnylinkEnabledBtn, &ParamControl::showDescriptionEvent, [=]() { + connect(sunnylinkEnabledBtn, &ParamControlSP::showDescriptionEvent, [=]() { //resets the description to the default one for the easter egg sunnylinkEnabledBtn->setDescription(sunnylinkBtnDescription); }); - connect(sunnylinkEnabledBtn, &ParamControl::toggleFlipped, [=](bool enabled) { + connect(sunnylinkEnabledBtn, &ParamControlSP::toggleFlipped, [=](bool enabled) { if (enabled) { auto proud_description = ""+ tr("🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀")+ ""; sunnylinkEnabledBtn->showDescription(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h index 5a7ead697a..dbcaa73868 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h @@ -7,7 +7,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://stg.api.sunnypilot.ai").c_str(); @@ -78,7 +78,7 @@ public slots: private: - ParamControl* sunnylinkEnabledBtn; + ParamControlSP* sunnylinkEnabledBtn; QStackedLayout* main_layout = nullptr; QWidget* sunnylinkScreen = nullptr; ScrollView *scrollView = nullptr; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc index 055bb648a4..5240aa1949 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc @@ -251,7 +251,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { dlp_settings->showDescription(); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); list->addItem(toggle); toggles[param.toStdString()] = toggle; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index 379a9a2875..363ca8473e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -9,7 +9,7 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" class TorqueFriction : public SPOptionControl { @@ -60,7 +60,7 @@ private: LaneChangeSettings* lane_change_settings = nullptr; CustomOffsetsSettings* custom_offsets_settings = nullptr; Params params; - std::map toggles; + std::map toggles; ParamWatcher *param_watcher; TorqueFriction *friction; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc index 42222f0b2b..00a9dc4b26 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc @@ -72,7 +72,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge // Hyundai/Kia/Genesis addItem(new LabelControl(tr("Hyundai/Kia/Genesis"))); - auto hkgSmoothStop = new ParamControl( + auto hkgSmoothStop = new ParamControlSP( "HkgSmoothStop", tr("HKG CAN: Smoother Stopping Performance (Beta)"), tr("Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control."), @@ -83,7 +83,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge // Subaru addItem(new LabelControl(tr("Subaru"))); - auto subaruManualParkingBrakeSng = new ParamControl( + auto subaruManualParkingBrakeSng = new ParamControlSP( "SubaruManualParkingBrakeSng", tr("Manual Parking Brake: Stop and Go (Beta)"), tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!"), @@ -94,7 +94,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge // Toyota/Lexus addItem(new LabelControl(tr("Toyota/Lexus"))); - stockLongToyota = new ParamControl( + stockLongToyota = new ParamControlSP( "StockLongToyota", tr("Enable Stock Toyota Longitudinal Control"), tr("sunnypilot will not take over control of gas and brakes. Stock Toyota longitudinal control will be used."), @@ -103,7 +103,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge stockLongToyota->setConfirmation(true, false); addItem(stockLongToyota); - auto lkasToggle = new ParamControl( + auto lkasToggle = new ParamControlSP( "LkasToggle", tr("Allow M.A.D.S. toggling w/ LKAS Button (Beta)"), QString("%1
" @@ -115,7 +115,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge lkasToggle->setConfirmation(true, false); addItem(lkasToggle); - auto toyotaTss2LongTune = new ParamControl( + auto toyotaTss2LongTune = new ParamControlSP( "ToyotaTSS2Long", tr("Toyota TSS2 Longitudinal: Custom Tuning"), tr("Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation."), @@ -124,7 +124,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge toyotaTss2LongTune->setConfirmation(true, false); addItem(toyotaTss2LongTune); - auto toyotaAbh = new ParamControl( + auto toyotaAbh = new ParamControlSP( "ToyotaAutoHold", tr("Enable Automatic Brake Hold (AHB)"), QString("%1

%2

%3") @@ -136,7 +136,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge toyotaAbh->setConfirmation(true, false); addItem(toyotaAbh); - toyotaEnhancedBsm = new ParamControl( + toyotaEnhancedBsm = new ParamControlSP( "ToyotaEnhancedBsm", tr("Enable Enhanced Blind Spot Monitor"), "", @@ -145,7 +145,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge toyotaEnhancedBsm->setConfirmation(true, false); addItem(toyotaEnhancedBsm); - auto toyotaSngHack = new ParamControl( + auto toyotaSngHack = new ParamControlSP( "ToyotaSnG", tr("Enable Toyota Stop and Go Hack"), tr("sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk."), @@ -154,7 +154,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge toyotaSngHack->setConfirmation(true, false); addItem(toyotaSngHack); - auto toyotaAutoLock = new ParamControl( + auto toyotaAutoLock = new ParamControlSP( "ToyotaAutoLock", tr("Enable Toyota Door Auto Locking"), tr("sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph).\nReboot Required."), @@ -163,7 +163,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge toyotaAutoLock->setConfirmation(true, false); addItem(toyotaAutoLock); - auto toyotaAutoUnlock = new ParamControl( + auto toyotaAutoUnlock = new ParamControlSP( "ToyotaAutoUnlockByShifter", tr("Enable Toyota Door Auto Unlocking"), tr("sunnypilot will attempt to unlock the doors when shift to gear P.\nReboot Required."), @@ -174,7 +174,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge // Volkswagen addItem(new LabelControl(tr("Volkswagen"))); - auto volkswagenCCOnly = new ParamControl( + auto volkswagenCCOnly = new ParamControlSP( "VwCCOnly", tr("Enable CC Only support"), tr("sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h index b210225f49..a154d6df97 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h @@ -5,7 +5,7 @@ #include "common/watchdog.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" class VehiclePanel : public QWidget { @@ -44,8 +44,8 @@ private: Params params; bool is_onroad = false; - ParamControl *stockLongToyota; - ParamControl *toyotaEnhancedBsm; + ParamControlSP *stockLongToyota; + ParamControlSP *toyotaEnhancedBsm; const QString toyotaEnhancedBsmDescription = QString("%1

" "%2") diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc index 364c1e2f6d..1c535bf126 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc @@ -92,7 +92,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { chevron_info_settings->showDescription(); for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); + auto toggle = new ParamControlSP(param, title, desc, icon, this); addItem(toggle); toggles[param.toStdString()] = toggle; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h index 3ea5825f59..38367912c4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" class VisualsPanel : public ListWidget { Q_OBJECT @@ -11,7 +11,7 @@ public: private: Params params; - std::map toggles; + std::map toggles; ButtonParamControl *dev_ui_settings; ButtonParamControl *chevron_info_settings; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc new file mode 100644 index 0000000000..a43b1700f9 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc @@ -0,0 +1,14 @@ +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" + +ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent) + : ParamControl(param, title, desc, icon, parent) { + + hlayout->removeWidget(&toggle); + hlayout->insertWidget(0, &toggle); + + hlayout->removeWidget(this->icon_label); + this->icon_pixmap = QPixmap(icon).scaledToWidth(20, Qt::SmoothTransformation); + this->icon_label->setPixmap(this->icon_pixmap); + this->icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + hlayout->insertWidget(1, this->icon_label); +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h new file mode 100644 index 0000000000..624b88d6a1 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -0,0 +1,12 @@ +#pragma once + +#include "selfdrive/ui/qt/widgets/controls.h" + +class ParamControlSP : public ParamControl { + Q_OBJECT + +public: + ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr); + + bool isToggled() { return params.getBool(key); } +}; From a3635f567aeca20e7acb0ea3cb83c7092fa5adaf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 21:10:18 -0400 Subject: [PATCH 387/575] horizontal_line --- selfdrive/ui/qt/widgets/controls.cc | 12 ------------ selfdrive/ui/qt/widgets/controls.h | 2 -- .../ui/sunnypilot/qt/widgets/sp_priv_controls.cc | 12 ++++++++++++ .../ui/sunnypilot/qt/widgets/sp_priv_controls.h | 2 ++ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 09c23eb80c..f97cb40e13 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -3,18 +3,6 @@ #include #include -QFrame *horizontal_line(QWidget *parent) { - QFrame *line = new QFrame(parent); - line->setFrameShape(QFrame::StyledPanel); - line->setStyleSheet(R"( - border-width: 2px; - border-bottom-style: solid; - border-color: gray; - )"); - line->setFixedHeight(10); - return line; -} - AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setMargin(0); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 01f1eccbf9..381223a385 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -15,8 +15,6 @@ #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/toggle.h" -QFrame *horizontal_line(QWidget *parent = nullptr); - class ElidedLabel : public QLabel { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc index a43b1700f9..620e007096 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc @@ -1,5 +1,17 @@ #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +QFrame *horizontal_line(QWidget *parent) { + QFrame *line = new QFrame(parent); + line->setFrameShape(QFrame::StyledPanel); + line->setStyleSheet(R"( + border-width: 2px; + border-bottom-style: solid; + border-color: gray; + )"); + line->setFixedHeight(10); + return line; +} + ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent) : ParamControl(param, title, desc, icon, parent) { diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 624b88d6a1..55a37fa36d 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -2,6 +2,8 @@ #include "selfdrive/ui/qt/widgets/controls.h" +QFrame *horizontal_line(QWidget *parent = nullptr); + class ParamControlSP : public ParamControl { Q_OBJECT From d0ec155a9d11035c721513a308791677554e54c3 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 23:14:49 -0400 Subject: [PATCH 388/575] ElidedLabel, AbstractControl, ButtonControl --- selfdrive/ui/qt/widgets/controls.h | 30 +++++++++++++--------- selfdrive/ui/sunnypilot/qt/sp_priv_util.cc | 0 selfdrive/ui/sunnypilot/qt/sp_priv_util.h | 24 +++++++++++++++++ 3 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_util.cc create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_util.h diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 381223a385..c6a1b73552 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -15,6 +14,10 @@ #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/toggle.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#endif + class ElidedLabel : public QLabel { Q_OBJECT @@ -22,9 +25,9 @@ public: explicit ElidedLabel(QWidget *parent = 0); explicit ElidedLabel(const QString &text, QWidget *parent = 0); - void setColor(const QString &color) { - setStyleSheet("QLabel { color : " + color + "; }"); - } +#ifdef SUNNYPILOT + ELIDED_LABEL_SET_STUFF +#endif signals: void clicked(); @@ -53,12 +56,13 @@ public: title_label->setText(title); } - void setValue(const QString &val, std::optional color = std::nullopt) { +#ifdef SUNNYPILOT + ABSTRACT_CONTROL_FUNCTION_1 +#else + void setValue(const QString &val) { value->setText(val); - if (color.has_value()) { - value->setColor(color.value()); - } } +#endif const QString getDescription() { return description->text(); @@ -72,9 +76,9 @@ public slots: description->setVisible(true); } - void hideDescription() { - description->setVisible(false); - } +#ifdef SUNNYPILOT + ABSTRACT_CONTROL_FUNCTION_2 +#endif signals: void showDescriptionEvent(); @@ -115,7 +119,9 @@ public: ButtonControl(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr); inline void setText(const QString &text) { btn.setText(text); } inline QString text() const { return btn.text(); } - inline void click() { btn.click(); } +#ifdef SUNNYPILOT + BUTTON_CONTROL_FUNCTION_1 +#endif signals: void clicked(); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h new file mode 100644 index 0000000000..c6e74dba82 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#define ELIDED_LABEL_SET_STUFF \ + void setColor(const QString &color) { \ + setStyleSheet("QLabel { color : " + color + "; }"); \ + } \ + +#define ABSTRACT_CONTROL_FUNCTION_1 \ + void setValue(const QString &val, std::optional color = std::nullopt) { \ + value->setText(val); \ + if (color.has_value()) { \ + value->setColor(color.value()); \ + } \ + } \ + +#define ABSTRACT_CONTROL_FUNCTION_2 \ + void hideDescription() { \ + description->setVisible(false); \ + } \ + +#define BUTTON_CONTROL_FUNCTION_1 \ + inline void click() { btn.click(); } \ From 2e7942ef1678e8f366efa0758c224966b14bd466 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 23:32:36 -0400 Subject: [PATCH 389/575] AbstractControlSP, ButtonParamControlSP, OptionalControlSP --- selfdrive/ui/qt/widgets/controls.cc | 45 --- selfdrive/ui/qt/widgets/controls.h | 254 +-------------- .../qt/offroad/custom_offsets_settings.cc | 8 +- .../qt/offroad/custom_offsets_settings.h | 4 +- .../sunnypilot/qt/offroad/display_settings.cc | 18 +- .../sunnypilot/qt/offroad/display_settings.h | 8 +- .../qt/offroad/lane_change_settings.cc | 8 +- .../qt/offroad/lane_change_settings.h | 4 +- .../ui/sunnypilot/qt/offroad/mads_settings.cc | 4 +- .../ui/sunnypilot/qt/offroad/mads_settings.h | 2 +- .../sunnypilot/qt/offroad/sp_priv_settings.cc | 4 +- .../sunnypilot/qt/offroad/sp_priv_settings.h | 3 +- .../offroad/speed_limit_control_settings.cc | 12 +- .../qt/offroad/speed_limit_control_settings.h | 6 +- .../qt/offroad/speed_limit_policy_settings.cc | 4 +- .../qt/offroad/speed_limit_policy_settings.h | 4 +- .../offroad/speed_limit_warning_settings.cc | 14 +- .../qt/offroad/speed_limit_warning_settings.h | 6 +- .../qt/offroad/sunnypilot_settings.cc | 12 +- .../qt/offroad/sunnypilot_settings.h | 6 +- .../sunnypilot/qt/offroad/visuals_settings.cc | 6 +- .../sunnypilot/qt/offroad/visuals_settings.h | 6 +- .../sunnypilot/qt/widgets/sp_priv_controls.cc | 48 +++ .../sunnypilot/qt/widgets/sp_priv_controls.h | 304 ++++++++++++++++++ 24 files changed, 432 insertions(+), 358 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index f97cb40e13..3b95fe3184 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -62,51 +62,6 @@ void AbstractControl::hideEvent(QHideEvent *e) { } } -SPAbstractControl::SPAbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setMargin(0); - - hlayout = new QHBoxLayout; - hlayout->setMargin(0); - hlayout->setSpacing(0); - - // title - if (!title.isEmpty()) { - title_label = new QPushButton(title); - title_label->setFixedHeight(120); - title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;"); - main_layout->addWidget(title_label, 1); - - connect(title_label, &QPushButton::clicked, [=]() { - if (!description->isVisible()) { - emit showDescriptionEvent(); - } - - if (!description->text().isEmpty()) { - description->setVisible(!description->isVisible()); - } - }); - } else { - main_layout->addSpacing(20); - } - - main_layout->addLayout(hlayout); - main_layout->addSpacing(2); - - // description - description = new QLabel(desc); - description->setContentsMargins(0, 20, 40, 20); - description->setStyleSheet("font-size: 40px; color: grey"); - description->setWordWrap(true); - description->setVisible(false); - main_layout->addWidget(description); - - main_layout->addStretch(); -} - -void SPAbstractControl::hideEvent(QHideEvent *e) { -} - // controls ButtonControl::ButtonControl(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControl(title, desc, "", parent) { diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index c6a1b73552..4c5f08e1ae 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -208,71 +208,29 @@ private: bool store_confirm = false; }; -class SPAbstractControl : public QFrame { - Q_OBJECT - -public: - void setDescription(const QString &desc) { - if (description) description->setText(desc); - } - - void setTitle(const QString &title) { - title_label->setText(title); - } - - const QString getDescription() { - return description->text(); - } - -public slots: - void showDescription() { - description->setVisible(true); - } - - void hideDescription() { - description->setVisible(false); - } - -signals: - void showDescriptionEvent(); - -protected: - SPAbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); - void hideEvent(QHideEvent *e) override; - - QHBoxLayout *hlayout; - QPushButton *title_label; - -private: - QLabel *description = nullptr; -}; - -class ButtonParamControl : public SPAbstractControl { +class ButtonParamControl : public AbstractControl { Q_OBJECT public: ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const std::vector &button_texts, const int minimum_button_width = 300) : SPAbstractControl(title, desc, icon), button_texts(button_texts) { + const std::vector &button_texts, const int minimum_button_width = 225) : AbstractControl(title, desc, icon) { const QString style = R"( QPushButton { - border-radius: 20px; - font-size: 50px; - font-weight: 450; - height:150px; + border-radius: 50px; + font-size: 40px; + font-weight: 500; + height:100px; padding: 0 25 0 25; - color: #FFFFFF; + color: #E4E4E4; + background-color: #393939; } QPushButton:pressed { background-color: #4a4a4a; } QPushButton:checked:enabled { - background-color: #696868; + background-color: #33Ab4C; } QPushButton:disabled { - color: #33FFFFFF; - } - QPushButton:checked:disabled { - background-color: #121212; - color: #33FFFFFF; + color: #33E4E4E4; } )"; key = param.toStdString(); @@ -286,16 +244,12 @@ public: button->setChecked(i == value); button->setStyleSheet(style); button->setMinimumWidth(minimum_button_width); - if (i == 0) hlayout->addSpacing(2); hlayout->addWidget(button); button_group->addButton(button, i); } - hlayout->setAlignment(Qt::AlignLeft); - QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { params.put(key, std::to_string(id)); - emit buttonToggled(id); }); } @@ -303,9 +257,6 @@ public: for (auto btn : button_group->buttons()) { btn->setEnabled(enable); } - button_group_enabled = enable; - - update(); } void setCheckedButton(int id) { @@ -314,14 +265,6 @@ public: void refresh() { int value = atoi(params.get(key).c_str()); - - if (value >= button_texts.size()) { - value = button_texts.size() - 1; - } - if (value < 0) { - value = 0; - } - button_group->button(value)->setChecked(true); } @@ -329,53 +272,10 @@ public: refresh(); } - void setButton(QString param) { - key = param.toStdString(); - int value = atoi(params.get(key).c_str()); - for (int i = 0; i < button_group->buttons().size(); i++) { - button_group->buttons()[i]->setChecked(i == value); - } - } - - void setDisabledSelectedButton(std::string val) { - int value = atoi(val.c_str()); - for (int i = 0; i < button_group->buttons().size(); i++) { - button_group->buttons()[i]->setEnabled(i != value); - } - } - -protected: - void paintEvent(QPaintEvent *event) override { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - // Calculate the total width and height for the background rectangle - int w = 0; - int h = 150; - - for (int i = 0; i < hlayout->count(); ++i) { - QPushButton *button = qobject_cast(hlayout->itemAt(i)->widget()); - if (button) { - w += button->width(); - } - } - - // Draw the rectangle - QRect rect(0 + 2, h - 24, w, h); - p.setPen(QPen(QColor(button_group_enabled ? "#696868" : "#121212"), 3)); - p.drawRoundedRect(rect, 20, 20); - } - -signals: - void buttonToggled(int btn_id); - private: std::string key; Params params; QButtonGroup *button_group; - std::vector button_texts; - - bool button_group_enabled = true; }; class ListWidget : public QWidget { @@ -439,140 +339,6 @@ public: } }; -class SPOptionControl : public SPAbstractControl { - Q_OBJECT - -private: - struct MinMaxValue { - int min_value; - int max_value; - }; - -public: - SPOptionControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const MinMaxValue &range, const int per_value_change = 1) : _title(title), SPAbstractControl(title, desc, icon) { - const QString style = R"( - QPushButton { - border-radius: 20px; - font-size: 60px; - font-weight: 500; - width: 150px; - height: 150px; - padding: -3 25 3 25; - color: #FFFFFF; - font-weight: bold; - } - QPushButton:pressed { - color: #5C5C5C; - } - QPushButton:disabled { - color: #5C5C5C; - } - )"; - - label.setStyleSheet(label_enabled_style); - label.setFixedWidth(300); - label.setAlignment(Qt::AlignCenter); - - const std::vector button_texts{"-", "+"}; - - key = param.toStdString(); - value = atoi(params.get(key).c_str()); - - button_group = new QButtonGroup(this); - button_group->setExclusive(true); - for (int i = 0; i < button_texts.size(); i++) { - QPushButton *button = new QPushButton(button_texts[i], this); - button->setStyleSheet(style + ((i == 0) ? "QPushButton { text-align: left; }" : - "QPushButton { text-align: right; }")); - hlayout->addWidget(button, 0, ((i == 0) ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignVCenter); - if (i == 0) { - hlayout->addWidget(&label, 0, Qt::AlignCenter); - } - button_group->addButton(button, i); - - QObject::connect(button, &QPushButton::clicked, [=]() { - int change_value = (i == 0) ? -per_value_change : per_value_change; - key = param.toStdString(); - value = atoi(params.get(key).c_str()); - value += change_value; - value = std::clamp(value, range.min_value, range.max_value); - params.put(key, QString::number(value).toStdString()); - - button_group->button(0)->setEnabled(!(value <= range.min_value)); - button_group->button(1)->setEnabled(!(value >= range.max_value)); - - updateLabels(); - - if (request_update) { - emit updateOtherToggles(); - } - }); - } - - hlayout->setAlignment(Qt::AlignLeft); - } - - void setUpdateOtherToggles(bool _update) { - request_update = _update; - } - - inline void setLabel(const QString &text) { label.setText(text); } - - void setEnabled(bool enabled) { - for (auto btn : button_group->buttons()) { - btn->setEnabled(enabled); - } - label.setEnabled(enabled); - label.setStyleSheet(enabled ? label_enabled_style : label_disabled_style); - button_enabled = enabled; - - update(); - } - -protected: - void paintEvent(QPaintEvent *event) override { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - // Calculate the total width and height for the background rectangle - int w = 0; - int h = 150; - - for (int i = 0; i < hlayout->count(); ++i) { - QWidget *widget = qobject_cast(hlayout->itemAt(i)->widget()); - if (widget) { - w += widget->width(); - } - } - - // Draw the rectangle - QRect rect(0, !_title.isEmpty() ? (h - 24) : 20, w, h); - p.setBrush(QColor(button_enabled ? "#b24a4a4a" : "#121212")); // Background color - p.setPen(QPen(Qt::NoPen)); - p.drawRoundedRect(rect, 20, 20); - } - -signals: - void updateLabels(); - void updateOtherToggles(); - -private: - std::string key; - int value; - QButtonGroup *button_group; - QLabel label; - Params params; - std::map option_label = {}; - bool request_update = false; - QString _title = ""; - - const QString label_enabled_style = "font-size: 50px; font-weight: 450; color: #FFFFFF;"; - const QString label_disabled_style = "font-size: 50px; font-weight: 450; color: #5C5C5C;"; - - bool button_enabled = true; -}; - class SubPanelButton : public QPushButton { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc index 689f42bb11..73c6e8cf1a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc @@ -14,13 +14,13 @@ CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) // Controls: Camera Offset (cm) camera_offset = new CameraOffset(); - connect(camera_offset, &SPOptionControl::updateLabels, camera_offset, &CameraOffset::refresh); + connect(camera_offset, &OptionControlSP::updateLabels, camera_offset, &CameraOffset::refresh); camera_offset->showDescription(); list->addItem(camera_offset); // Controls: Path Offset (cm) path_offset = new PathOffset(); - connect(path_offset, &SPOptionControl::updateLabels, path_offset, &PathOffset::refresh); + connect(path_offset, &OptionControlSP::updateLabels, path_offset, &PathOffset::refresh); path_offset->showDescription(); list->addItem(path_offset); @@ -28,7 +28,7 @@ CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) } // Camera Offset Value -CameraOffset::CameraOffset() : SPOptionControl ( +CameraOffset::CameraOffset() : OptionControlSP ( "CameraOffset", tr("Camera Offset - Laneful Only"), tr("Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm"), @@ -48,7 +48,7 @@ void CameraOffset::refresh() { } // Path Offset Value -PathOffset::PathOffset() : SPOptionControl ( +PathOffset::PathOffset() : OptionControlSP ( "PathOffset", tr("Path Offset"), tr("Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h index 0b693ffc0c..5ddac07340 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h @@ -3,7 +3,7 @@ #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -class CameraOffset : public SPOptionControl { +class CameraOffset : public OptionControlSP { Q_OBJECT public: @@ -15,7 +15,7 @@ private: Params params; }; -class PathOffset : public SPOptionControl { +class PathOffset : public OptionControlSP { Q_OBJECT public: diff --git a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc index cceb2ef7e9..2a6d44bd34 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc @@ -18,24 +18,24 @@ DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { // General: Max Time Offroad (Shutdown timer) auto max_time_offroad = new MaxTimeOffroad(); - connect(max_time_offroad, &SPOptionControl::updateLabels, max_time_offroad, &MaxTimeOffroad::refresh); + connect(max_time_offroad, &OptionControlSP::updateLabels, max_time_offroad, &MaxTimeOffroad::refresh); addItem(max_time_offroad); // General: Onroad Screen Off (Auto Onroad Screen Timer) onroad_screen_off = new OnroadScreenOff(); onroad_screen_off->setUpdateOtherToggles(true); - connect(onroad_screen_off, &SPOptionControl::updateLabels, onroad_screen_off, &OnroadScreenOff::refresh); - connect(onroad_screen_off, &SPOptionControl::updateOtherToggles, this, &DisplayPanel::updateToggles); + connect(onroad_screen_off, &OptionControlSP::updateLabels, onroad_screen_off, &OnroadScreenOff::refresh); + connect(onroad_screen_off, &OptionControlSP::updateOtherToggles, this, &DisplayPanel::updateToggles); addItem(onroad_screen_off); // General: Onroad Screen Off Brightness onroad_screen_off_brightness = new OnroadScreenOffBrightness(); - connect(onroad_screen_off_brightness, &SPOptionControl::updateLabels, onroad_screen_off_brightness, &OnroadScreenOffBrightness::refresh); + connect(onroad_screen_off_brightness, &OptionControlSP::updateLabels, onroad_screen_off_brightness, &OnroadScreenOffBrightness::refresh); addItem(onroad_screen_off_brightness); // General: Brightness Control (Global) auto brightness_control = new BrightnessControl(); - connect(brightness_control, &SPOptionControl::updateLabels, brightness_control, &BrightnessControl::refresh); + connect(brightness_control, &OptionControlSP::updateLabels, brightness_control, &BrightnessControl::refresh); for (auto &[param, title, desc, icon] : toggle_defs) { auto toggle = new ParamControlSP(param, title, desc, icon, this); @@ -65,7 +65,7 @@ void DisplayPanel::updateToggles() { } // Max Time Offroad (Shutdown timer) -MaxTimeOffroad::MaxTimeOffroad() : SPOptionControl ( +MaxTimeOffroad::MaxTimeOffroad() : OptionControlSP ( "MaxTimeOffroad", tr("Max Time Offroad"), tr("Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road)."), @@ -110,7 +110,7 @@ void MaxTimeOffroad::refresh() { } // Onroad Screen Off (Auto Onroad Screen Timer) -OnroadScreenOff::OnroadScreenOff() : SPOptionControl ( +OnroadScreenOff::OnroadScreenOff() : OptionControlSP ( "OnroadScreenOff", tr("Driving Screen Off Timer"), tr("Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs."), @@ -136,7 +136,7 @@ void OnroadScreenOff::refresh() { } // Onroad Screen Off Brightness -OnroadScreenOffBrightness::OnroadScreenOffBrightness() : SPOptionControl ( +OnroadScreenOffBrightness::OnroadScreenOffBrightness() : OptionControlSP ( "OnroadScreenOffBrightness", tr("Driving Screen Off Brightness (%)"), tr("When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio."), @@ -157,7 +157,7 @@ void OnroadScreenOffBrightness::refresh() { } // Brightness Control (Global) -BrightnessControl::BrightnessControl() : SPOptionControl ( +BrightnessControl::BrightnessControl() : OptionControlSP ( "BrightnessControl", tr("Brightness"), tr("Manually adjusts the global brightness of the screen."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h index ee1e6b6308..41aac101bc 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h @@ -2,7 +2,7 @@ #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -class OnroadScreenOff : public SPOptionControl { +class OnroadScreenOff : public OptionControlSP { Q_OBJECT public: @@ -14,7 +14,7 @@ private: Params params; }; -class OnroadScreenOffBrightness : public SPOptionControl { +class OnroadScreenOffBrightness : public OptionControlSP { Q_OBJECT public: @@ -26,7 +26,7 @@ private: Params params; }; -class MaxTimeOffroad : public SPOptionControl { +class MaxTimeOffroad : public OptionControlSP { Q_OBJECT public: @@ -38,7 +38,7 @@ private: Params params; }; -class BrightnessControl : public SPOptionControl { +class BrightnessControl : public OptionControlSP { Q_OBJECT public: diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc index 684568c57d..10fcf2e1c6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc @@ -37,14 +37,14 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { auto_lane_change_timer = new AutoLaneChangeTimer(); auto_lane_change_timer->setUpdateOtherToggles(true); auto_lane_change_timer->showDescription(); - connect(auto_lane_change_timer, &SPOptionControl::updateLabels, auto_lane_change_timer, &AutoLaneChangeTimer::refresh); + connect(auto_lane_change_timer, &OptionControlSP::updateLabels, auto_lane_change_timer, &AutoLaneChangeTimer::refresh); connect(auto_lane_change_timer, &AutoLaneChangeTimer::updateOtherToggles, this, &LaneChangeSettings::updateToggles); list->addItem(auto_lane_change_timer); // Pause Lateral Below Speed w/ Blinker pause_lateral_speed = new PauseLateralSpeed(); pause_lateral_speed->showDescription(); - connect(pause_lateral_speed, &SPOptionControl::updateLabels, pause_lateral_speed, &PauseLateralSpeed::refresh); + connect(pause_lateral_speed, &OptionControlSP::updateLabels, pause_lateral_speed, &PauseLateralSpeed::refresh); for (auto &[param, title, desc, icon] : toggle_defs) { auto toggle = new ParamControlSP(param, title, desc, icon, this); @@ -97,7 +97,7 @@ void LaneChangeSettings::updateToggles() { } // Auto Lane Change Timer (ALCT) -AutoLaneChangeTimer::AutoLaneChangeTimer() : SPOptionControl ( +AutoLaneChangeTimer::AutoLaneChangeTimer() : OptionControlSP ( "AutoLaneChangeTimer", tr("Auto Lane Change by Blinker"), tr("Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.\nPlease use caution when using this feature. Only use the blinker when traffic and road conditions permit."), @@ -127,7 +127,7 @@ void AutoLaneChangeTimer::refresh() { } } -PauseLateralSpeed::PauseLateralSpeed() : SPOptionControl ( +PauseLateralSpeed::PauseLateralSpeed() : OptionControlSP ( "PauseLateralSpeed", "", tr("Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h index 6c2d0d5708..49d15fc02e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h @@ -4,7 +4,7 @@ #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -class AutoLaneChangeTimer : public SPOptionControl { +class AutoLaneChangeTimer : public OptionControlSP { Q_OBJECT public: @@ -19,7 +19,7 @@ private: Params params; }; -class PauseLateralSpeed : public SPOptionControl { +class PauseLateralSpeed : public OptionControlSP { Q_OBJECT public: diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc index 678b495d19..3b64fac1cf 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc @@ -32,7 +32,7 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { // Disengage Lateral on Brake (DLOB) std::vector dlob_settings_texts{tr("Remain Active"), tr("Pause Steering")}; - dlob_settings = new ButtonParamControl( + dlob_settings = new ButtonParamControlSP( "DisengageLateralOnBrake", tr("Steering Mode After Braking"), tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\nRemain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."), @@ -54,7 +54,7 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { } // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, dlob_settings, &ButtonParamControl::setEnabled); + connect(uiState(), &UIState::offroadTransition, dlob_settings, &ButtonParamControlSP::setEnabled); main_layout->addWidget(new ScrollView(list, this)); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h index afdf0caeed..1e01d41182 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h @@ -21,5 +21,5 @@ private: Params params; std::map toggles; - ButtonParamControl *dlob_settings; + ButtonParamControlSP *dlob_settings; }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc index 26fff880a6..ad4d666db0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc @@ -100,7 +100,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { std::vector longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")}; - long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), + long_personality_setting = new ButtonParamControlSP("LongitudinalPersonality", tr("Driving Personality"), tr("Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " "your steering wheel distance button."), @@ -111,7 +111,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { // accel controller std::vector accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco"), tr("Stock")}; - accel_personality_setting = new ButtonParamControl("AccelPersonality", tr("Acceleration Personality"), + accel_personality_setting = new ButtonParamControlSP("AccelPersonality", tr("Acceleration Personality"), tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. " "In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these " "acceleration personality within Onroad Settings on the driving screen."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h index f99b0ce075..718aeaa1a4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h @@ -14,7 +14,8 @@ private slots: private: std::map toggles; - ButtonParamControl *accel_personality_setting; + ButtonParamControlSP *long_personality_setting; + ButtonParamControlSP *accel_personality_setting; ParamWatcher *param_watcher; void updateToggles() override; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc index 13b1b715d4..e4c5b7e711 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc @@ -13,7 +13,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { ListWidget *list = new ListWidget(this, false); std::vector speed_limit_engage_texts{tr("Auto"), tr("User Confirm")}; - speed_limit_engage_settings = new ButtonParamControl( + speed_limit_engage_settings = new ButtonParamControlSP( "SpeedLimitEngageType", tr("Engage Mode"), "", "../assets/offroad/icon_blank.png", @@ -24,7 +24,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { list->addItem(speed_limit_engage_settings); std::vector speed_limit_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; - speed_limit_offset_settings = new ButtonParamControl( + speed_limit_offset_settings = new ButtonParamControlSP( "SpeedLimitOffsetType", tr("Limit Offset"), tr("Set speed limit slightly higher than actual speed limit for a more natural drive."), "../assets/offroad/icon_blank.png", speed_limit_offset_settings_texts, @@ -33,11 +33,11 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { list->addItem(speed_limit_offset_settings); slvo = new SpeedLimitValueOffset(); - connect(slvo, &SPOptionControl::updateLabels, slvo, &SpeedLimitValueOffset::refresh); + connect(slvo, &OptionControlSP::updateLabels, slvo, &SpeedLimitValueOffset::refresh); list->addItem(slvo); - connect(speed_limit_offset_settings, &ButtonParamControl::buttonToggled, this, &SlcSettings::updateToggles); - connect(speed_limit_offset_settings, &ButtonParamControl::buttonToggled, slvo, &SpeedLimitValueOffset::refresh); + connect(speed_limit_offset_settings, &ButtonParamControlSP::buttonToggled, this, &SlcSettings::updateToggles); + connect(speed_limit_offset_settings, &ButtonParamControlSP::buttonToggled, slvo, &SpeedLimitValueOffset::refresh); param_watcher = new ParamWatcher(this); @@ -101,7 +101,7 @@ void SlcSettings::updateToggles() { } // Speed Limit Control Custom Offset -SpeedLimitValueOffset::SpeedLimitValueOffset() : SPOptionControl ( +SpeedLimitValueOffset::SpeedLimitValueOffset() : OptionControlSP ( "SpeedLimitValueOffset", "", "", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h index 3e66956d0c..b3a77f649c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h @@ -5,7 +5,7 @@ #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" -class SpeedLimitValueOffset : public SPOptionControl { +class SpeedLimitValueOffset : public OptionControlSP { Q_OBJECT public: @@ -36,8 +36,8 @@ private: std::map toggles; SpeedLimitValueOffset *slvo; - ButtonParamControl *speed_limit_offset_settings; - ButtonParamControl *speed_limit_engage_settings; + ButtonParamControlSP *speed_limit_offset_settings; + ButtonParamControlSP *speed_limit_engage_settings; ParamWatcher *param_watcher; QString slcDescriptionBuilder(QString param, std::vector descriptions) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc index f93b14d62d..bc5ec30e73 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc @@ -12,7 +12,7 @@ SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(pa ListWidget *list = new ListWidget(this, false); - speed_limit_policy = new ButtonParamControl( + speed_limit_policy = new ButtonParamControlSP( "SpeedLimitControlPolicy", tr("Speed Limit Source Policy"), "", @@ -21,7 +21,7 @@ SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(pa 250 ); speed_limit_policy->showDescription(); - connect(speed_limit_policy, &ButtonParamControl::buttonToggled, this, &SpeedLimitPolicySettings::updateToggles); + connect(speed_limit_policy, &ButtonParamControlSP::buttonToggled, this, &SpeedLimitPolicySettings::updateToggles); list->addItem(speed_limit_policy); param_watcher = new ParamWatcher(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h index 5b7ef66a68..78d780db0b 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" @@ -23,7 +23,7 @@ public slots: private: Params params; - ButtonParamControl *speed_limit_policy; + ButtonParamControlSP *speed_limit_policy; ParamWatcher *param_watcher; QString speedLimitPolicyDescriptionBuilder(QString param, std::vector descriptions) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc index 7b7c9676ac..71566b8368 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc @@ -13,7 +13,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( ListWidget *list = new ListWidget(this, false); std::vector speed_limit_warning_texts{tr("Off"), tr("Display"), tr("Chime")}; - speed_limit_warning_settings = new ButtonParamControl( + speed_limit_warning_settings = new ButtonParamControlSP( "SpeedLimitWarningType", tr("Speed Limit Warning"), "", "../assets/offroad/icon_blank.png", @@ -32,7 +32,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( list->addItem(speed_limit_warning_flash); std::vector speed_limit_warning_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; - speed_limit_warning_offset_settings = new ButtonParamControl( + speed_limit_warning_offset_settings = new ButtonParamControlSP( "SpeedLimitWarningOffsetType", tr("Warning Offset"), tr("Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit."), "../assets/offroad/icon_blank.png", @@ -42,13 +42,13 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( list->addItem(speed_limit_warning_offset_settings); slwvo = new SpeedLimitWarningValueOffset(); - connect(slwvo, &SPOptionControl::updateLabels, slwvo, &SpeedLimitWarningValueOffset::refresh); + connect(slwvo, &OptionControlSP::updateLabels, slwvo, &SpeedLimitWarningValueOffset::refresh); list->addItem(slwvo); - connect(speed_limit_warning_settings, &ButtonParamControl::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); + connect(speed_limit_warning_settings, &ButtonParamControlSP::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); - connect(speed_limit_warning_offset_settings, &ButtonParamControl::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); - connect(speed_limit_warning_offset_settings, &ButtonParamControl::buttonToggled, slwvo, &SpeedLimitWarningValueOffset::refresh); + connect(speed_limit_warning_offset_settings, &ButtonParamControlSP::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); + connect(speed_limit_warning_offset_settings, &ButtonParamControlSP::buttonToggled, slwvo, &SpeedLimitWarningValueOffset::refresh); param_watcher = new ParamWatcher(this); @@ -86,7 +86,7 @@ void SpeedLimitWarningSettings::updateToggles() { } // Speed Limit Control Custom Offset -SpeedLimitWarningValueOffset::SpeedLimitWarningValueOffset() : SPOptionControl ( +SpeedLimitWarningValueOffset::SpeedLimitWarningValueOffset() : OptionControlSP ( "SpeedLimitWarningValueOffset", "", "", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h index 1a7d3c2144..6a17cf45b2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h @@ -5,7 +5,7 @@ #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" -class SpeedLimitWarningValueOffset : public SPOptionControl { +class SpeedLimitWarningValueOffset : public OptionControlSP { Q_OBJECT public: @@ -36,9 +36,9 @@ private: std::map toggles; SpeedLimitWarningValueOffset *slwvo; - ButtonParamControl *speed_limit_warning_offset_settings; + ButtonParamControlSP *speed_limit_warning_offset_settings; ParamControlSP *speed_limit_warning_flash; - ButtonParamControl *speed_limit_warning_settings; + ButtonParamControlSP *speed_limit_warning_settings; ParamWatcher *param_watcher; QString speedLimitWarningDescriptionBuilder(QString param, std::vector descriptions) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc index 5240aa1949..ef7c26f8db 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc @@ -235,14 +235,14 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { // Controls: Torque - FRICTION friction = new TorqueFriction(); - connect(friction, &SPOptionControl::updateLabels, friction, &TorqueFriction::refresh); + connect(friction, &OptionControlSP::updateLabels, friction, &TorqueFriction::refresh); // Controls: Torque - LAT_ACCEL_FACTOR lat_accel_factor = new TorqueMaxLatAccel(); - connect(lat_accel_factor, &SPOptionControl::updateLabels, lat_accel_factor, &TorqueMaxLatAccel::refresh); + connect(lat_accel_factor, &OptionControlSP::updateLabels, lat_accel_factor, &TorqueMaxLatAccel::refresh); std::vector dlp_settings_texts{tr("Laneful"), tr("Laneless"), tr("Auto")}; - dlp_settings = new ButtonParamControl( + dlp_settings = new ButtonParamControlSP( "DynamicLaneProfile", tr("Dynamic Lane Profile"), "", "../assets/offroad/icon_blank.png", dlp_settings_texts, @@ -567,7 +567,7 @@ void SunnypilotPanel::updateToggles() { } // toggle names to update when CustomTorqueLateral is flipped - std::vector customTorqueGroup{friction, lat_accel_factor}; + std::vector customTorqueGroup{friction, lat_accel_factor}; for (const auto& customTorqueControl : customTorqueGroup) { customTorqueControl->setVisible(!(nnff_toggle->isToggled() || !custom_torque_lateral->isToggled())); customTorqueControl->setEnabled(!(nnff_toggle->isToggled() || !custom_torque_lateral->isToggled())); @@ -581,7 +581,7 @@ void SunnypilotPanel::updateToggles() { dlp_settings->setDescription((model_use_lateral_planner ? "" : dlp_incompatible_desc + "

") + dlp_description); } -TorqueFriction::TorqueFriction() : SPOptionControl ( +TorqueFriction::TorqueFriction() : OptionControlSP ( "TorqueFriction", tr("FRICTION"), tr("Adjust Friction for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), @@ -597,7 +597,7 @@ void TorqueFriction::refresh() { setLabel(QString::number(torqueFrictionVal)); } -TorqueMaxLatAccel::TorqueMaxLatAccel() : SPOptionControl ( +TorqueMaxLatAccel::TorqueMaxLatAccel() : OptionControlSP ( "TorqueMaxLatAccel", tr("LAT_ACCEL_FACTOR"), tr("Adjust Max Lateral Acceleration for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index 363ca8473e..550b3fa9be 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -12,7 +12,7 @@ #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -class TorqueFriction : public SPOptionControl { +class TorqueFriction : public OptionControlSP { Q_OBJECT public: @@ -24,7 +24,7 @@ private: Params params; }; -class TorqueMaxLatAccel : public SPOptionControl { +class TorqueMaxLatAccel : public OptionControlSP { Q_OBJECT public: @@ -65,7 +65,7 @@ private: TorqueFriction *friction; TorqueMaxLatAccel *lat_accel_factor; - ButtonParamControl *dlp_settings; + ButtonParamControlSP *dlp_settings; ScrollView *scrollView = nullptr; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc index 1c535bf126..3ccd7e148f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc @@ -73,7 +73,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { // Visuals: Developer UI Info (Dev UI) std::vector dev_ui_settings_texts{tr("Off"), tr("5 Metrics"), tr("10 Metrics")}; - dev_ui_settings = new ButtonParamControl( + dev_ui_settings = new ButtonParamControlSP( "DevUIInfo", tr("Developer UI"), tr("Display real-time parameters and metrics from various sources."), "../assets/offroad/icon_blank.png", dev_ui_settings_texts, @@ -83,7 +83,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { // Visuals: Display Metrics above Chevron std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Distance\nSpeed")}; - chevron_info_settings = new ButtonParamControl( + chevron_info_settings = new ButtonParamControlSP( "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, @@ -107,7 +107,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { } std::vector sidebar_temp_texts{tr("Off"), tr("RAM"), tr("CPU"), tr("GPU"), tr("Max")}; - sidebar_temp_setting = new ButtonParamControl( + sidebar_temp_setting = new ButtonParamControlSP( "SidebarTemperatureOptions", tr("Display Temperature on Sidebar"), "", "../assets/offroad/icon_blank.png", sidebar_temp_texts, diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h index 38367912c4..5b20af11da 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h @@ -13,7 +13,7 @@ private: Params params; std::map toggles; - ButtonParamControl *dev_ui_settings; - ButtonParamControl *chevron_info_settings; - ButtonParamControl *sidebar_temp_setting; + ButtonParamControlSP *dev_ui_settings; + ButtonParamControlSP *chevron_info_settings; + ButtonParamControlSP *sidebar_temp_setting; }; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc index 620e007096..8b2eeac65b 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc @@ -12,6 +12,54 @@ QFrame *horizontal_line(QWidget *parent) { return line; } +AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setMargin(0); + + hlayout = new QHBoxLayout; + hlayout->setMargin(0); + hlayout->setSpacing(0); + + // title + if (!title.isEmpty()) { + title_label = new QPushButton(title); + title_label->setFixedHeight(120); + title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;"); + main_layout->addWidget(title_label, 1); + + connect(title_label, &QPushButton::clicked, [=]() { + if (!description->isVisible()) { + emit showDescriptionEvent(); + } + + if (!description->text().isEmpty()) { + description->setVisible(!description->isVisible()); + } + }); + } else { + main_layout->addSpacing(20); + } + + main_layout->addLayout(hlayout); + main_layout->addSpacing(2); + + // description + description = new QLabel(desc); + description->setContentsMargins(0, 20, 40, 20); + description->setStyleSheet("font-size: 40px; color: grey"); + description->setWordWrap(true); + description->setVisible(false); + main_layout->addWidget(description); + + main_layout->addStretch(); +} + +void AbstractControlSP::hideEvent(QHideEvent *e) { + if (description != nullptr) { + description->hide(); + } +} + ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent) : ParamControl(param, title, desc, icon, parent) { diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 55a37fa36d..fccd04cd40 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -12,3 +12,307 @@ public: bool isToggled() { return params.getBool(key); } }; + +class AbstractControlSP : public QFrame { + Q_OBJECT + +public: + void setDescription(const QString &desc) { + if (description) description->setText(desc); + } + + void setTitle(const QString &title) { + title_label->setText(title); + } + + const QString getDescription() { + return description->text(); + } + + public slots: + void showDescription() { + description->setVisible(true); + } + + void hideDescription() { + description->setVisible(false); + } + + signals: + void showDescriptionEvent(); + +protected: + AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); + void hideEvent(QHideEvent *e) override; + + QHBoxLayout *hlayout; + QPushButton *title_label; + +private: + QLabel *description = nullptr; +}; + +class ButtonParamControlSP : public AbstractControlSP { + Q_OBJECT +public: + ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const std::vector &button_texts, const int minimum_button_width = 300) : AbstractControlSP(title, desc, icon), button_texts(button_texts) { + const QString style = R"( + QPushButton { + border-radius: 20px; + font-size: 50px; + font-weight: 450; + height:150px; + padding: 0 25 0 25; + color: #FFFFFF; + } + QPushButton:pressed { + background-color: #4a4a4a; + } + QPushButton:checked:enabled { + background-color: #696868; + } + QPushButton:disabled { + color: #33FFFFFF; + } + QPushButton:checked:disabled { + background-color: #121212; + color: #33FFFFFF; + } + )"; + key = param.toStdString(); + int value = atoi(params.get(key).c_str()); + + button_group = new QButtonGroup(this); + button_group->setExclusive(true); + for (int i = 0; i < button_texts.size(); i++) { + QPushButton *button = new QPushButton(button_texts[i], this); + button->setCheckable(true); + button->setChecked(i == value); + button->setStyleSheet(style); + button->setMinimumWidth(minimum_button_width); + if (i == 0) hlayout->addSpacing(2); + hlayout->addWidget(button); + button_group->addButton(button, i); + } + + hlayout->setAlignment(Qt::AlignLeft); + + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { + params.put(key, std::to_string(id)); + emit buttonToggled(id); + }); + } + + void setEnabled(bool enable) { + for (auto btn : button_group->buttons()) { + btn->setEnabled(enable); + } + button_group_enabled = enable; + + update(); + } + + void setCheckedButton(int id) { + button_group->button(id)->setChecked(true); + } + + void refresh() { + int value = atoi(params.get(key).c_str()); + + if (value >= button_texts.size()) { + value = button_texts.size() - 1; + } + if (value < 0) { + value = 0; + } + + button_group->button(value)->setChecked(true); + } + + void showEvent(QShowEvent *event) override { + refresh(); + } + + void setButton(QString param) { + key = param.toStdString(); + int value = atoi(params.get(key).c_str()); + for (int i = 0; i < button_group->buttons().size(); i++) { + button_group->buttons()[i]->setChecked(i == value); + } + } + + void setDisabledSelectedButton(std::string val) { + int value = atoi(val.c_str()); + for (int i = 0; i < button_group->buttons().size(); i++) { + button_group->buttons()[i]->setEnabled(i != value); + } + } + +protected: + void paintEvent(QPaintEvent *event) override { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + // Calculate the total width and height for the background rectangle + int w = 0; + int h = 150; + + for (int i = 0; i < hlayout->count(); ++i) { + QPushButton *button = qobject_cast(hlayout->itemAt(i)->widget()); + if (button) { + w += button->width(); + } + } + + // Draw the rectangle + QRect rect(0 + 2, h - 24, w, h); + p.setPen(QPen(QColor(button_group_enabled ? "#696868" : "#121212"), 3)); + p.drawRoundedRect(rect, 20, 20); + } + +signals: + void buttonToggled(int btn_id); + +private: + std::string key; + Params params; + QButtonGroup *button_group; + std::vector button_texts; + + bool button_group_enabled = true; +}; + +class OptionControlSP : public AbstractControlSP { + Q_OBJECT + +private: + struct MinMaxValue { + int min_value; + int max_value; + }; + +public: + OptionControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const MinMaxValue &range, const int per_value_change = 1) : _title(title), AbstractControlSP(title, desc, icon) { + const QString style = R"( + QPushButton { + border-radius: 20px; + font-size: 60px; + font-weight: 500; + width: 150px; + height: 150px; + padding: -3 25 3 25; + color: #FFFFFF; + font-weight: bold; + } + QPushButton:pressed { + color: #5C5C5C; + } + QPushButton:disabled { + color: #5C5C5C; + } + )"; + + label.setStyleSheet(label_enabled_style); + label.setFixedWidth(300); + label.setAlignment(Qt::AlignCenter); + + const std::vector button_texts{"-", "+"}; + + key = param.toStdString(); + value = atoi(params.get(key).c_str()); + + button_group = new QButtonGroup(this); + button_group->setExclusive(true); + for (int i = 0; i < button_texts.size(); i++) { + QPushButton *button = new QPushButton(button_texts[i], this); + button->setStyleSheet(style + ((i == 0) ? "QPushButton { text-align: left; }" : + "QPushButton { text-align: right; }")); + hlayout->addWidget(button, 0, ((i == 0) ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignVCenter); + if (i == 0) { + hlayout->addWidget(&label, 0, Qt::AlignCenter); + } + button_group->addButton(button, i); + + QObject::connect(button, &QPushButton::clicked, [=]() { + int change_value = (i == 0) ? -per_value_change : per_value_change; + key = param.toStdString(); + value = atoi(params.get(key).c_str()); + value += change_value; + value = std::clamp(value, range.min_value, range.max_value); + params.put(key, QString::number(value).toStdString()); + + button_group->button(0)->setEnabled(!(value <= range.min_value)); + button_group->button(1)->setEnabled(!(value >= range.max_value)); + + updateLabels(); + + if (request_update) { + emit updateOtherToggles(); + } + }); + } + + hlayout->setAlignment(Qt::AlignLeft); + } + + void setUpdateOtherToggles(bool _update) { + request_update = _update; + } + + inline void setLabel(const QString &text) { label.setText(text); } + + void setEnabled(bool enabled) { + for (auto btn : button_group->buttons()) { + btn->setEnabled(enabled); + } + label.setEnabled(enabled); + label.setStyleSheet(enabled ? label_enabled_style : label_disabled_style); + button_enabled = enabled; + + update(); + } + +protected: + void paintEvent(QPaintEvent *event) override { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + // Calculate the total width and height for the background rectangle + int w = 0; + int h = 150; + + for (int i = 0; i < hlayout->count(); ++i) { + QWidget *widget = qobject_cast(hlayout->itemAt(i)->widget()); + if (widget) { + w += widget->width(); + } + } + + // Draw the rectangle + QRect rect(0, !_title.isEmpty() ? (h - 24) : 20, w, h); + p.setBrush(QColor(button_enabled ? "#b24a4a4a" : "#121212")); // Background color + p.setPen(QPen(Qt::NoPen)); + p.drawRoundedRect(rect, 20, 20); + } + +signals: + void updateLabels(); + void updateOtherToggles(); + +private: + std::string key; + int value; + QButtonGroup *button_group; + QLabel label; + Params params; + std::map option_label = {}; + bool request_update = false; + QString _title = ""; + + const QString label_enabled_style = "font-size: 50px; font-weight: 450; color: #FFFFFF;"; + const QString label_disabled_style = "font-size: 50px; font-weight: 450; color: #5C5C5C;"; + + bool button_enabled = true; +}; From 0ed13466f9641fb0778367f5d35ffc5dd6d2776b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 23:45:11 -0400 Subject: [PATCH 390/575] AbstractControl: Thicker texts --- selfdrive/ui/qt/widgets/controls.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 3b95fe3184..084add1dc1 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -3,6 +3,12 @@ #include #include +#ifdef SUNNYPILOT +#define TITLE_FONT_WEIGHT 450 +#else +#define TITLE_FONT_WEIGHT 400 +#endif + AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setMargin(0); @@ -24,7 +30,9 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons // title title_label = new QPushButton(title); title_label->setFixedHeight(120); - title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;"); + QString styleSheet = QString("font-size: 50px; font-weight: %1; text-align: left; border: none;") + .arg(TITLE_FONT_WEIGHT); + title_label->setStyleSheet(styleSheet); hlayout->addWidget(title_label, 1); // value next to control button From 7a1ff6cd01efe3c08213986d4335d2c729067097 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Jul 2024 23:48:49 -0400 Subject: [PATCH 391/575] SubPanelButton, PanelBackButton --- selfdrive/ui/qt/widgets/controls.h | 41 ------------------- .../sunnypilot/qt/widgets/sp_priv_controls.h | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 4c5f08e1ae..831345c838 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -338,44 +338,3 @@ public: setLayout(l); } }; - -class SubPanelButton : public QPushButton { - Q_OBJECT - -public: - SubPanelButton(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr) : QPushButton(text, parent) { - const QString buttonStyle = R"( - QPushButton { - border-radius: 20px; - font-size: 50px; - font-weight: 450; - height: 150px; - padding: 0 25px 0 25px; - color: #FFFFFF; - } - QPushButton:enabled { - background-color: #393939; - } - QPushButton:pressed { - background-color: #4A4A4A; - } - QPushButton:disabled { - background-color: #121212; - color: #5C5C5C; - } - )"; - - setStyleSheet(buttonStyle); - setFixedWidth(minimum_button_width); - } -}; - -class PanelBackButton : public QPushButton { - Q_OBJECT - -public: - PanelBackButton(const QString &label = "Back", QWidget *parent = nullptr) : QPushButton(label, parent) { - setObjectName("back_btn"); - setFixedSize(400, 100); - } -}; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index fccd04cd40..b65bbca962 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -316,3 +316,44 @@ private: bool button_enabled = true; }; + +class SubPanelButton : public QPushButton { + Q_OBJECT + +public: + explicit SubPanelButton(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr) : QPushButton(text, parent) { + const QString buttonStyle = R"( + QPushButton { + border-radius: 20px; + font-size: 50px; + font-weight: 450; + height: 150px; + padding: 0 25px 0 25px; + color: #FFFFFF; + } + QPushButton:enabled { + background-color: #393939; + } + QPushButton:pressed { + background-color: #4A4A4A; + } + QPushButton:disabled { + background-color: #121212; + color: #5C5C5C; + } + )"; + + setStyleSheet(buttonStyle); + setFixedWidth(minimum_button_width); + } +}; + +class PanelBackButton : public QPushButton { + Q_OBJECT + +public: + explicit PanelBackButton(const QString &label = "Back", QWidget *parent = nullptr) : QPushButton(label, parent) { + setObjectName("back_btn"); + setFixedSize(400, 100); + } +}; From ed862f012b3bf6a7ca9b40ea9e4d786cbc5f554c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Jul 2024 00:08:34 -0400 Subject: [PATCH 392/575] ListWidget - ListWidgetSP --- selfdrive/ui/qt/offroad/settings.h | 4 +++ selfdrive/ui/qt/widgets/controls.h | 31 +++++++------------ .../qt/offroad/custom_offsets_settings.cc | 2 +- .../sunnypilot/qt/offroad/display_settings.cc | 2 +- .../sunnypilot/qt/offroad/display_settings.h | 2 +- .../qt/offroad/lane_change_settings.cc | 2 +- .../ui/sunnypilot/qt/offroad/mads_settings.cc | 2 +- .../qt/offroad/monitoring_settings.cc | 2 +- .../ui/sunnypilot/qt/offroad/osm_settings.cc | 2 +- .../offroad/speed_limit_control_settings.cc | 2 +- .../qt/offroad/speed_limit_policy_settings.cc | 2 +- .../offroad/speed_limit_warning_settings.cc | 2 +- .../qt/offroad/sunnylink_settings.cc | 2 +- .../qt/offroad/sunnypilot_settings.cc | 2 +- .../sunnypilot/qt/offroad/vehicle_settings.cc | 2 +- .../sunnypilot/qt/offroad/vehicle_settings.h | 2 +- .../sunnypilot/qt/offroad/visuals_settings.cc | 2 +- .../sunnypilot/qt/offroad/visuals_settings.h | 2 +- selfdrive/ui/sunnypilot/qt/sp_priv_util.h | 22 +++++++++++++ .../sunnypilot/qt/widgets/sp_priv_controls.h | 24 ++++++++++++++ 20 files changed, 77 insertions(+), 36 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 037aa7070a..4f3f282005 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -13,7 +13,11 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 831345c838..4cf6154fe1 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -281,7 +281,7 @@ private: class ListWidget : public QWidget { Q_OBJECT public: - explicit ListWidget(QWidget *parent = 0, const bool split_line = true) : QWidget(parent), _split_line(split_line), outer_layout(this) { + explicit ListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) { outer_layout.setMargin(0); outer_layout.setSpacing(0); outer_layout.addLayout(&inner_layout); @@ -293,22 +293,11 @@ class ListWidget : public QWidget { inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); } inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } - inline void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); } - inline void RemoveWidgetAt(const int index) { - if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) { - if(item->widget()) delete item->widget(); - delete item; - } - } - - inline void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) { - if (const int index = inner_layout.indexOf(old_widget); index != -1) { - RemoveWidgetAt(index); - AddWidgetAt(index, new_widget); - } else { - addItem(new_widget); - } - } +#ifdef SUNNYPILOT + LIST_WIDGET_FUNCTION_1 + LIST_WIDGET_FUNCTION_2 + LIST_WIDGET_FUCNTION_3 +#endif private: void paintEvent(QPaintEvent *) override { @@ -316,7 +305,7 @@ private: p.setPen(Qt::gray); for (int i = 0; i < inner_layout.count() - 1; ++i) { QWidget *widget = inner_layout.itemAt(i)->widget(); - if ((widget == nullptr || widget->isVisible()) && _split_line) { + if (widget == nullptr || widget->isVisible()) { QRect r = inner_layout.itemAt(i)->geometry(); int bottom = r.bottom() + inner_layout.spacing() / 2; p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom); @@ -324,9 +313,11 @@ private: } } QVBoxLayout outer_layout; - QVBoxLayout inner_layout; - bool _split_line; +#ifdef SUNNYPILOT +protected: +#endif + QVBoxLayout inner_layout; }; // convenience class for wrapping layouts diff --git a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc index 73c6e8cf1a..7327183d0f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc @@ -10,7 +10,7 @@ CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // Controls: Camera Offset (cm) camera_offset = new CameraOffset(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc index 2a6d44bd34..7121ba31c2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc @@ -1,6 +1,6 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/display_settings.h" -DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { +DisplayPanel::DisplayPanel(QWidget *parent) : ListWidgetSP(parent, false) { // param, title, desc, icon std::vector> toggle_defs{ { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h index 41aac101bc..a68c58a463 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h @@ -50,7 +50,7 @@ private: Params params; }; -class DisplayPanel : public ListWidget { +class DisplayPanel : public ListWidgetSP { Q_OBJECT public: diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc index 10fcf2e1c6..29be4c065c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc @@ -10,7 +10,7 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // param, title, desc, icon std::vector> toggle_defs{ { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc index 3b64fac1cf..d6dfcaa809 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc @@ -10,7 +10,7 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // param, title, desc, icon std::vector> toggle_defs{ { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc index 6afa087eb4..6fbb0b9d7b 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc @@ -3,7 +3,7 @@ MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // param, title, desc, icon std::vector> toggle_defs{ { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc index be3837262a..f9310e6aa7 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc @@ -3,7 +3,7 @@ OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); - const auto list = new ListWidget(this, false); + const auto list = new ListWidgetSP(this, false); list->addItem(mapdVersion = new LabelControl(tr("Mapd Version"), "Loading...")); list->addItem(setupOsmDeleteMapsButton(parent)); list->addItem(offlineMapsETA = new LabelControl(tr("Offline Maps ETA"), "")); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc index e4c5b7e711..66f33348ef 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc @@ -10,7 +10,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); std::vector speed_limit_engage_texts{tr("Auto"), tr("User Confirm")}; speed_limit_engage_settings = new ButtonParamControlSP( diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc index bc5ec30e73..6cdcf3cdf3 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc @@ -10,7 +10,7 @@ SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(pa connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); speed_limit_policy = new ButtonParamControlSP( "SpeedLimitControlPolicy", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc index 71566b8368..9ef87848a4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc @@ -10,7 +10,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); std::vector speed_limit_warning_texts{tr("Off"), tr("Display"), tr("Chime")}; speed_limit_warning_settings = new ButtonParamControlSP( diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc index 3f00c92c37..67f18c6a21 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc @@ -16,7 +16,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { connect(uiState(), &UIState::sunnylinkRolesChanged, this, &SunnylinkPanel::updateLabels); connect(uiState(), &UIState::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updateLabels); - auto list = new ListWidget(this, false); + auto list = new ListWidgetSP(this, false); sunnylinkEnabledBtn = new ParamControlSP( "SunnylinkEnabled", tr("Enable sunnylink"), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc index ef7c26f8db..99af0f0d27 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc @@ -3,7 +3,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); - ListWidget *list = new ListWidget(this, false); + ListWidgetSP *list = new ListWidgetSP(this, false); // param, title, desc, icon std::vector> toggle_defs{ { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc index 00a9dc4b26..5bbdf843c5 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc @@ -67,7 +67,7 @@ void VehiclePanel::updateToggles() { setCarBtn->setText(((set == "=== Not Selected ===") || (set.length() == 0)) ? prompt_select : set); } -SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidget(parent, false) { +SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidgetSP(parent, false) { setSpacing(50); // Hyundai/Kia/Genesis diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h index a154d6df97..1e31636d73 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h @@ -31,7 +31,7 @@ private: QString prompt_select = tr("Select your car"); }; -class SPVehiclesTogglesPanel : public ListWidget { +class SPVehiclesTogglesPanel : public ListWidgetSP { Q_OBJECT public: explicit SPVehiclesTogglesPanel(VehiclePanel *parent); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc index 3ccd7e148f..d96f4d8ec2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc @@ -1,6 +1,6 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h" -VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { +VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { // param, title, desc, icon std::vector> toggle_defs{ { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h index 5b20af11da..244bc09b7e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h @@ -3,7 +3,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -class VisualsPanel : public ListWidget { +class VisualsPanel : public ListWidgetSP { Q_OBJECT public: diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h index c6e74dba82..3bc4bb7f65 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h @@ -22,3 +22,25 @@ #define BUTTON_CONTROL_FUNCTION_1 \ inline void click() { btn.click(); } \ + +// ListWidget +#define LIST_WIDGET_FUNCTION_1 \ + inline void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); } \ + +#define LIST_WIDGET_FUNCTION_2 \ + inline void RemoveWidgetAt(const int index) { \ + if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) { \ + if(item->widget()) delete item->widget(); \ + delete item; \ + } \ + } \ + +#define LIST_WIDGET_FUCNTION_3 \ + inline void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) { \ + if (const int index = inner_layout.indexOf(old_widget); index != -1) { \ + RemoveWidgetAt(index); \ + AddWidgetAt(index, new_widget); \ + } else { \ + addItem(new_widget); \ + } \ + } \ diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index b65bbca962..4d3baf8b3f 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -183,6 +183,30 @@ private: bool button_group_enabled = true; }; +class ListWidgetSP : public ListWidget { + Q_OBJECT + +public: + explicit ListWidgetSP(QWidget *parent = 0, const bool split_line = true) : ListWidget(parent), _split_line(split_line) { + } + +private: + void paintEvent(QPaintEvent *) override { + QPainter p(this); + p.setPen(Qt::gray); + for (int i = 0; i < inner_layout.count() - 1; ++i) { + QWidget *widget = inner_layout.itemAt(i)->widget(); + if ((widget == nullptr || widget->isVisible()) && _split_line) { + QRect r = inner_layout.itemAt(i)->geometry(); + int bottom = r.bottom() + inner_layout.spacing() / 2; + p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom); + } + } + } + + bool _split_line = false; +}; + class OptionControlSP : public AbstractControlSP { Q_OBJECT From 6fb4925b572cde61e3c57fb190c3443762b7738e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Jul 2024 00:24:46 -0400 Subject: [PATCH 393/575] ScrollView - ScrollViewSP --- selfdrive/ui/qt/widgets/scrollview.cc | 8 -------- selfdrive/ui/qt/widgets/scrollview.h | 7 ------- selfdrive/ui/sunnypilot/SConscript | 3 ++- .../qt/offroad/custom_offsets_settings.cc | 2 +- .../qt/offroad/custom_offsets_settings.h | 2 +- .../qt/offroad/lane_change_settings.cc | 2 +- .../qt/offroad/lane_change_settings.h | 2 +- .../ui/sunnypilot/qt/offroad/mads_settings.cc | 2 +- .../ui/sunnypilot/qt/offroad/mads_settings.h | 2 +- .../qt/offroad/monitoring_settings.cc | 2 +- .../sunnypilot/qt/offroad/monitoring_settings.h | 2 +- .../ui/sunnypilot/qt/offroad/osm_settings.cc | 2 +- .../ui/sunnypilot/qt/offroad/osm_settings.h | 2 +- .../sunnypilot/qt/offroad/sp_priv_settings.cc | 6 +++--- .../qt/offroad/speed_limit_control_settings.cc | 2 +- .../qt/offroad/speed_limit_control_settings.h | 2 +- .../qt/offroad/speed_limit_policy_settings.cc | 2 +- .../qt/offroad/speed_limit_policy_settings.h | 2 +- .../qt/offroad/speed_limit_warning_settings.cc | 2 +- .../qt/offroad/speed_limit_warning_settings.h | 2 +- .../sunnypilot/qt/offroad/sunnylink_settings.cc | 2 +- .../sunnypilot/qt/offroad/sunnylink_settings.h | 4 ++-- .../qt/offroad/sunnypilot_settings.cc | 2 +- .../sunnypilot/qt/offroad/sunnypilot_settings.h | 4 ++-- .../sunnypilot/qt/offroad/vehicle_settings.cc | 2 +- .../ui/sunnypilot/qt/offroad/vehicle_settings.h | 2 +- .../ui/sunnypilot/qt/onroad/onroad_settings.cc | 4 ++-- .../sunnypilot/qt/widgets/sp_priv_scrollview.cc | 14 ++++++++++++++ .../sunnypilot/qt/widgets/sp_priv_scrollview.h | 17 +++++++++++++++++ 29 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.cc create mode 100644 selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc index 7f6a5ff1cc..978bf83a63 100644 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ b/selfdrive/ui/qt/widgets/scrollview.cc @@ -47,11 +47,3 @@ ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { void ScrollView::hideEvent(QHideEvent *e) { verticalScrollBar()->setValue(0); } - -void ScrollView::setLastScrollPosition() { - lastScrollPosition = verticalScrollBar()->value(); -} - -void ScrollView::restoreScrollPosition() { - verticalScrollBar()->setValue(lastScrollPosition); -} diff --git a/selfdrive/ui/qt/widgets/scrollview.h b/selfdrive/ui/qt/widgets/scrollview.h index 7e4084412c..024331aa39 100644 --- a/selfdrive/ui/qt/widgets/scrollview.h +++ b/selfdrive/ui/qt/widgets/scrollview.h @@ -9,11 +9,4 @@ public: explicit ScrollView(QWidget *w = nullptr, QWidget *parent = nullptr); protected: void hideEvent(QHideEvent *e) override; - -public slots: - void setLastScrollPosition(); - void restoreScrollPosition(); - -private: - int lastScrollPosition = 0; }; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 8d5f1d5512..ac8e92ef1e 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -16,7 +16,8 @@ widgets_src = [ "sunnypilot/qt/offroad/speed_limit_warning_settings.cc", "sunnypilot/qt/offroad/speed_limit_policy_settings.cc", "sunnypilot/qt/offroad/sunnylink_settings.cc", - "sunnypilot/qt/widgets/sp_priv_controls.cc" + "sunnypilot/qt/widgets/sp_priv_controls.cc", + "sunnypilot/qt/widgets/sp_priv_scrollview.cc" ] network_src = [ diff --git a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc index 7327183d0f..5b762f3230 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc @@ -24,7 +24,7 @@ CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) path_offset->showDescription(); list->addItem(path_offset); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } // Camera Offset Value diff --git a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h index 5ddac07340..7463aa365e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" class CameraOffset : public OptionControlSP { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc index 29be4c065c..f2f0321c1e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc @@ -64,7 +64,7 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { pause_lateral_speed->setEnabled(toggles["BelowSpeedPause"]->isToggled()); pause_lateral_speed->setVisible(toggles["BelowSpeedPause"]->isToggled()); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void LaneChangeSettings::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h index 49d15fc02e..dcc9a457d0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h @@ -2,7 +2,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" class AutoLaneChangeTimer : public OptionControlSP { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc index d6dfcaa809..8106da5979 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc @@ -56,7 +56,7 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { // trigger offroadTransition when going onroad/offroad connect(uiState(), &UIState::offroadTransition, dlob_settings, &ButtonParamControlSP::setEnabled); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void MadsSettings::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h index 1e01d41182..374e404f29 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h @@ -2,7 +2,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" class MadsSettings : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc index 6fbb0b9d7b..5102d34167 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc @@ -25,6 +25,6 @@ MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) { QVBoxLayout* vlayout = new QVBoxLayout(monitoringScreen); vlayout->setContentsMargins(50, 20, 50, 20); - vlayout->addWidget(new ScrollView(list, this), 1); + vlayout->addWidget(new ScrollViewSP(list, this), 1); main_layout->addWidget(monitoringScreen); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h index 8f184a4413..542caf208f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" class MonitoringPanel : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc index f9310e6aa7..cf169cd995 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc @@ -24,7 +24,7 @@ OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { osmScreen = new QWidget(this); auto *vlayout = new QVBoxLayout(osmScreen); vlayout->setContentsMargins(50, 20, 50, 20); - vlayout->addWidget(new ScrollView(list, this), 1); + vlayout->addWidget(new ScrollViewSP(list, this), 1); main_layout->addWidget(osmScreen); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h index 587b214350..853033be7c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h @@ -11,7 +11,7 @@ #include "selfdrive/ui/qt/network/wifi_manager.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/ui.h" #include "system/hardware/hw.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc index ad4d666db0..a73d360028 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc @@ -375,7 +375,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { const int lr_margin = (name != (" " + tr("Network")) || (name != (" " + tr("sunnypilot")))) ? 50 : 0; // Network and sunnypilot panel handles its own margins panel->setContentsMargins(lr_margin, 25, lr_margin, 25); - ScrollView *panel_frame = new ScrollView(panel, this); + ScrollViewSP *panel_frame = new ScrollViewSP(panel, this); panel_widget->addWidget(panel_frame); QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { @@ -392,7 +392,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { sidebar_layout->addLayout(close_btn_layout); // add layout for buttons scrolling - ScrollView *buttons_scrollview = new ScrollView(buttons_widget, this); + ScrollViewSP *buttons_scrollview = new ScrollViewSP(buttons_widget, this); sidebar_layout->addWidget(buttons_scrollview); sidebar_widget->setFixedWidth(500); @@ -407,7 +407,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { SettingsWindow { background-color: black; } - QStackedWidget, ScrollView { + QStackedWidget, ScrollViewSP { background-color: black; border-radius: 30px; } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc index 66f33348ef..e88d757004 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc @@ -45,7 +45,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { updateToggles(); }); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void SlcSettings::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h index b3a77f649c..91ad4842db 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h @@ -2,7 +2,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" class SpeedLimitValueOffset : public OptionControlSP { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc index 6cdcf3cdf3..cd1d5f7772 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc @@ -30,7 +30,7 @@ SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(pa updateToggles(); }); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void SpeedLimitPolicySettings::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h index 78d780db0b..a5e8d3e393 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h @@ -2,7 +2,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" class SpeedLimitPolicySettings : public QWidget { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc index 9ef87848a4..7b1c12a536 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc @@ -56,7 +56,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( updateToggles(); }); - main_layout->addWidget(new ScrollView(list, this)); + main_layout->addWidget(new ScrollViewSP(list, this)); } void SpeedLimitWarningSettings::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h index 6a17cf45b2..07ee7d18fa 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h @@ -2,7 +2,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" class SpeedLimitWarningValueOffset : public OptionControlSP { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc index 67f18c6a21..634e3efe5d 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc @@ -134,7 +134,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { auto vlayout = new QVBoxLayout(sunnylinkScreen); vlayout->setContentsMargins(50, 20, 50, 20); - vlayout->addWidget(new ScrollView(list, this), 1); + vlayout->addWidget(new ScrollViewSP(list, this), 1); main_layout->addWidget(sunnylinkScreen); if (is_sunnylink_enabled) { startSunnylink(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h index dbcaa73868..1a3cb630d9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h @@ -8,7 +8,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://stg.api.sunnypilot.ai").c_str(); // sponsor QR code @@ -81,7 +81,7 @@ private: ParamControlSP* sunnylinkEnabledBtn; QStackedLayout* main_layout = nullptr; QWidget* sunnylinkScreen = nullptr; - ScrollView *scrollView = nullptr; + ScrollViewSP *scrollView = nullptr; BackupSettings *backup_settings; SubPanelButton *restoreSettings; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc index 99af0f0d27..36643fd708 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc @@ -384,7 +384,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { QVBoxLayout* vlayout = new QVBoxLayout(sunnypilotScreen); vlayout->setContentsMargins(50, 20, 50, 20); - scrollView = new ScrollView(list, this); + scrollView = new ScrollViewSP(list, this); vlayout->addWidget(scrollView, 1); main_layout->addWidget(sunnypilotScreen); main_layout->addWidget(mads_settings); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index 550b3fa9be..c42c0b6c66 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -10,7 +10,7 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" class TorqueFriction : public OptionControlSP { Q_OBJECT @@ -67,7 +67,7 @@ private: TorqueMaxLatAccel *lat_accel_factor; ButtonParamControlSP *dlp_settings; - ScrollView *scrollView = nullptr; + ScrollViewSP *scrollView = nullptr; const QString nnff_description = QString("%1

" "%2") diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc index 5bbdf843c5..845b223e69 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc @@ -29,7 +29,7 @@ VehiclePanel::VehiclePanel(QWidget *parent) : QWidget(parent) { QVBoxLayout* toggle_layout = new QVBoxLayout(home_widget); home_widget->setObjectName("homeWidget"); - ScrollView *scroller = new ScrollView(home_widget, this); + ScrollViewSP *scroller = new ScrollViewSP(home_widget, this); scroller->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); fcr_layout->addWidget(scroller, 1); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h index 1e31636d73..b66d29dcd6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h @@ -6,7 +6,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" class VehiclePanel : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc index 4ef4afc025..1db8c654b5 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc @@ -6,7 +6,7 @@ #include #include "common/util.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) { @@ -87,7 +87,7 @@ OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) options_layout->addStretch(); - ScrollView *options_scroller = new ScrollView(options_container, this); + ScrollViewSP *options_scroller = new ScrollViewSP(options_container, this); options_scroller->setFrameShape(QFrame::NoFrame); frame->addWidget(options_scroller); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.cc new file mode 100644 index 0000000000..05d1335c1c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.cc @@ -0,0 +1,14 @@ +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" + +#include + +ScrollViewSP::ScrollViewSP(QWidget *w, QWidget *parent) : ScrollView(w, parent) { +} + +void ScrollViewSP::setLastScrollPosition() { + lastScrollPosition = verticalScrollBar()->value(); +} + +void ScrollViewSP::restoreScrollPosition() { + verticalScrollBar()->setValue(lastScrollPosition); +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h new file mode 100644 index 0000000000..510dacec25 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h @@ -0,0 +1,17 @@ +#pragma once + +#include "selfdrive/ui/qt/widgets/scrollview.h" + +class ScrollViewSP : public ScrollView { + Q_OBJECT + +public: + explicit ScrollViewSP(QWidget *w = nullptr, QWidget *parent = nullptr); + +public slots: + void setLastScrollPosition(); + void restoreScrollPosition(); + +private: + int lastScrollPosition = 0; +}; From b8d26259c49c03c02d16771cf3076f200d981c31 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Jul 2024 04:08:28 -0400 Subject: [PATCH 394/575] Toggle - ToggleSP, TESLA TOGGLE DESIGN!!! --- selfdrive/ui/qt/widgets/controls.h | 11 +++++---- selfdrive/ui/qt/widgets/toggle.cc | 4 ++-- selfdrive/ui/qt/widgets/toggle.h | 4 +++- selfdrive/ui/sunnypilot/SConscript | 3 ++- .../sunnypilot/qt/widgets/sp_priv_toggle.cc | 23 +++++++++++++++++++ .../ui/sunnypilot/qt/widgets/sp_priv_toggle.h | 13 +++++++++++ 6 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.cc create mode 100644 selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 4cf6154fe1..a68484f905 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -12,10 +12,13 @@ #include "common/params.h" #include "selfdrive/ui/qt/widgets/input.h" -#include "selfdrive/ui/qt/widgets/toggle.h" - #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" +#define TOGGLE ToggleSP +#else +#include "selfdrive/ui/qt/widgets/toggle.h" +#define TOGGLE Toggle #endif class ElidedLabel : public QLabel { @@ -143,7 +146,7 @@ public: toggle.togglePosition(); } hlayout->addWidget(&toggle); - QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControl::toggleFlipped); + QObject::connect(&toggle, &TOGGLE::stateChanged, this, &ToggleControl::toggleFlipped); } void setEnabled(bool enabled) { @@ -155,7 +158,7 @@ signals: void toggleFlipped(bool state); protected: - Toggle toggle; + TOGGLE toggle; }; // widget to toggle params diff --git a/selfdrive/ui/qt/widgets/toggle.cc b/selfdrive/ui/qt/widgets/toggle.cc index c8f12bc272..82302ad5bc 100644 --- a/selfdrive/ui/qt/widgets/toggle.cc +++ b/selfdrive/ui/qt/widgets/toggle.cc @@ -75,9 +75,9 @@ void Toggle::setEnabled(bool value) { enabled = value; if (value) { circleColor.setRgb(0xfafafa); - green.setRgb(0x1e79e8); + green.setRgb(0x33ab4c); } else { circleColor.setRgb(0x888888); - green.setRgb(0x125db8); + green.setRgb(0x227722); } } diff --git a/selfdrive/ui/qt/widgets/toggle.h b/selfdrive/ui/qt/widgets/toggle.h index e7263a008f..49e0614b10 100644 --- a/selfdrive/ui/qt/widgets/toggle.h +++ b/selfdrive/ui/qt/widgets/toggle.h @@ -23,14 +23,16 @@ public: update(); } bool getEnabled(); - void setEnabled(bool value); + virtual void setEnabled(bool value); protected: void paintEvent(QPaintEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void enterEvent(QEvent*) override; +#ifndef SUNNYPILOT private: +#endif QColor circleColor; QColor green; bool enabled = true; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index ac8e92ef1e..69bee7c1d1 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -17,7 +17,8 @@ widgets_src = [ "sunnypilot/qt/offroad/speed_limit_policy_settings.cc", "sunnypilot/qt/offroad/sunnylink_settings.cc", "sunnypilot/qt/widgets/sp_priv_controls.cc", - "sunnypilot/qt/widgets/sp_priv_scrollview.cc" + "sunnypilot/qt/widgets/sp_priv_scrollview.cc", + "sunnypilot/qt/widgets/sp_priv_toggle.cc" ] network_src = [ diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.cc new file mode 100644 index 0000000000..b6b37e224b --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.cc @@ -0,0 +1,23 @@ +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" + +#include + +ToggleSP::ToggleSP(QWidget *parent) : Toggle(parent) { + _height_rect = 80; +} + +void ToggleSP::paintEvent(QPaintEvent *e) { + this->setFixedHeight(100); + QPainter p(this); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::Antialiasing, true); + + // Draw toggle background + enabled ? green.setRgb(0x1e79e8) : green.setRgb(0x125db8); + p.setBrush(on ? green : QColor(0x292929)); + p.drawRoundedRect(QRect(0, 10, width(), _height_rect),_height_rect/2, _height_rect/2); + + // Draw toggle circle + p.setBrush(circleColor); + p.drawEllipse(QRectF(_x_circle - _radius + 6, 16, 68, 68)); +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h new file mode 100644 index 0000000000..8b972b3cf0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h @@ -0,0 +1,13 @@ +#pragma once + +#include "selfdrive/ui/qt/widgets/toggle.h" + +class ToggleSP : public Toggle { + Q_OBJECT + +public: + explicit ToggleSP(QWidget* parent = nullptr); + +protected: + void paintEvent(QPaintEvent*) override; +}; From e3939247a73276cc4c5a70fa7945b9b99d1bf2fb Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Jul 2024 04:19:38 -0400 Subject: [PATCH 395/575] Nav stock icons --- selfdrive/ui/qt/offroad/settings.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index f0bb4d703f..c60068554b 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -81,13 +81,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "NavSettingTime24h", tr("Show ETA in 24h Format"), tr("Use 24h format instead of am/pm"), - "../assets/offroad/icon_blank.png", + "../assets/offroad/icon_metric.png", }, { "NavSettingLeftSide", tr("Show Map on Left Side of UI"), tr("Show map on left side when in split screen view."), - "../assets/offroad/icon_blank.png", + "../assets/offroad/icon_road.png", }, #endif }; From beef2f0be94930108507748f2c5cb8f587d89baf Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 11:15:31 +0200 Subject: [PATCH 396/575] Refactor SunnyPilot preprocessor directives in UI code This commit simplifies the conditional preprocessor directives related to SunnyPilot in various parts of the UI code. The previous use of #ifdef SUNNYPILOT has been consolidated to make the code cleaner and easier to read. Also, some unused code blocks have been removed. This refactoring simplifies the management of different configuration setups. --- selfdrive/ui/qt/widgets/controls.h | 9 +++------ selfdrive/ui/qt/widgets/toggle.h | 5 +---- selfdrive/ui/qt/window.cc | 4 ---- selfdrive/ui/qt/window.h | 18 ++++++------------ 4 files changed, 10 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index a68484f905..3c5766d137 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -15,10 +15,9 @@ #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" -#define TOGGLE ToggleSP +#define Toggle ToggleSP #else #include "selfdrive/ui/qt/widgets/toggle.h" -#define TOGGLE Toggle #endif class ElidedLabel : public QLabel { @@ -146,7 +145,7 @@ public: toggle.togglePosition(); } hlayout->addWidget(&toggle); - QObject::connect(&toggle, &TOGGLE::stateChanged, this, &ToggleControl::toggleFlipped); + QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControl::toggleFlipped); } void setEnabled(bool enabled) { @@ -158,7 +157,7 @@ signals: void toggleFlipped(bool state); protected: - TOGGLE toggle; + Toggle toggle; }; // widget to toggle params @@ -317,9 +316,7 @@ private: } QVBoxLayout outer_layout; -#ifdef SUNNYPILOT protected: -#endif QVBoxLayout inner_layout; }; diff --git a/selfdrive/ui/qt/widgets/toggle.h b/selfdrive/ui/qt/widgets/toggle.h index 49e0614b10..0c544b917f 100644 --- a/selfdrive/ui/qt/widgets/toggle.h +++ b/selfdrive/ui/qt/widgets/toggle.h @@ -29,10 +29,7 @@ protected: void paintEvent(QPaintEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void enterEvent(QEvent*) override; - -#ifndef SUNNYPILOT -private: -#endif + QColor circleColor; QColor green; bool enabled = true; diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 4ff91b1748..00b4693822 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -13,11 +13,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings); QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings); -#ifdef SUNNYPILOT - settingsWindow = new SettingsWindowSP(this); -#else settingsWindow = new SettingsWindow(this); -#endif main_layout->addWidget(settingsWindow); QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings); QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() { diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index b13b250a9b..016f0fd23d 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -4,17 +4,15 @@ #include #include "selfdrive/ui/qt/offroad/onboarding.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" -#else -#include "selfdrive/ui/qt/offroad/settings.h" -#endif #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" -#define HomeWindow HomeWindowSP + #include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" + #include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" + #define HomeWindow HomeWindowSP + #define SettingsWindow SettingsWindowSP #else -#include "selfdrive/ui/qt/home.h" + #include "selfdrive/ui/qt/offroad/settings.h" + #include "selfdrive/ui/qt/home.h" #endif class MainWindow : public QWidget { @@ -30,10 +28,6 @@ private: QStackedLayout *main_layout; HomeWindow *homeWindow; -#ifdef SUNNYPILOT - SettingsWindowSP *settingsWindow; -#else SettingsWindow *settingsWindow; -#endif OnboardingWindow *onboardingWindow; }; From efcd86529b936ded542c9062624a850d8306aac9 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 15:24:39 +0200 Subject: [PATCH 397/575] Refactor: Move replaceWidget to sunnypilot_main.h Moved the replaceWidget function to sunnypilot_main.h to allow for better code organization and reusability. Commented out the previous static declaration in sp_priv_offroad_home.h and updated replaceWidget calls to ReplaceWidget in sp_priv_offroad_home.cc. --- selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc | 9 ++------- selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h | 2 +- selfdrive/ui/sunnypilot/sunnypilot_main.h | 7 +++++++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc index 388945957f..914e6a43c4 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc @@ -2,6 +2,7 @@ #include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/widgets/prime.h" #include +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" #ifdef ENABLE_MAPS #define LEFT_WIDGET MapSettings @@ -9,12 +10,6 @@ #define LEFT_WIDGET QWidget #endif -void OffroadHomeSP::replaceWidget(QWidget* old_widget, QWidget* new_widget) -{ - old_widget->parentWidget()->layout()->replaceWidget(old_widget, new_widget); - delete old_widget; -} - void OffroadHomeSP::replaceLeftWidget(){ auto* new_left_widget = new QStackedWidget(left_widget->parentWidget()); new_left_widget->addWidget(new LEFT_WIDGET); @@ -26,7 +21,7 @@ void OffroadHomeSP::replaceLeftWidget(){ connect(uiState(), &UIState::primeChanged, [=](bool prime) { new_left_widget->setCurrentIndex((prime || custom_mapbox) ? 0 : 1); }); - replaceWidget(left_widget, new_left_widget); + ReplaceWidget(left_widget, new_left_widget); } OffroadHomeSP::OffroadHomeSP(QWidget* parent) : OffroadHome(parent){ diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h index b5ef820107..a86adc8020 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h @@ -14,7 +14,7 @@ public: explicit OffroadHomeSP(QWidget* parent = 0); private: - static void replaceWidget(QWidget* old_widget, QWidget* new_widget); + // static void replaceWidget(QWidget* old_widget, QWidget* new_widget); Params params; bool custom_mapbox; }; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/sunnypilot_main.h b/selfdrive/ui/sunnypilot/sunnypilot_main.h index c94e13050d..16224a0780 100644 --- a/selfdrive/ui/sunnypilot/sunnypilot_main.h +++ b/selfdrive/ui/sunnypilot/sunnypilot_main.h @@ -9,3 +9,10 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h" #include "selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h" + + +inline void ReplaceWidget(QWidget* old_widget, QWidget* new_widget) +{ + old_widget->parentWidget()->layout()->replaceWidget(old_widget, new_widget); + delete old_widget; +} From ddee284733da03408c73090dcb76d6e98a0b7e64 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 15:25:32 +0200 Subject: [PATCH 398/575] Removing maps from stock's toggles I believe this maps thing is a leftover we want to keep only on sp priv --- selfdrive/ui/qt/offroad/settings.cc | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index c60068554b..bbcf34ae6d 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -76,20 +76,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Display speed in km/h instead of mph."), "../assets/offroad/icon_metric.png", }, -#ifdef ENABLE_MAPS - { - "NavSettingTime24h", - tr("Show ETA in 24h Format"), - tr("Use 24h format instead of am/pm"), - "../assets/offroad/icon_metric.png", - }, - { - "NavSettingLeftSide", - tr("Show Map on Left Side of UI"), - tr("Show map on left side when in split screen view."), - "../assets/offroad/icon_road.png", - }, -#endif }; From 80ecce9d81cf068bd1eeb7f0133a4ab97ca44ef1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 15:43:21 +0200 Subject: [PATCH 399/575] Remove "Driving Model" label and adjust item management Eliminated the "Driving Model" label from SoftwarePanel and refactored item management to improve code clarity and functionality. Updated macro naming and added new list widget functions for better handling of UI elements. --- selfdrive/ui/qt/offroad/settings.h | 1 - selfdrive/ui/qt/offroad/software_settings.cc | 10 ++++------ selfdrive/ui/qt/widgets/controls.h | 4 +++- .../ui/sunnypilot/qt/offroad/software_settings_sp.cc | 2 +- selfdrive/ui/sunnypilot/qt/sp_priv_util.h | 8 ++++++-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 4f3f282005..7b8fd52976 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -112,7 +112,6 @@ protected: bool is_onroad = false; QLabel *onroadLbl; - LabelControl *currentModelLbl; LabelControl *versionLbl; ButtonControl *installBtn; ButtonControl *downloadBtn; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 685ca3555d..3045842d91 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -9,7 +9,6 @@ #include "common/params.h" #include "common/util.h" -#include "common/model.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" @@ -22,9 +21,6 @@ void SoftwarePanel::checkForUpdates() { } SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { - currentModelLbl = new LabelControl(tr("Driving Model"), CURRENT_MODEL); - addItem(currentModelLbl); - onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off.")); onroadLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;"); addItem(onroadLbl); @@ -74,7 +70,9 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { checkForUpdates(); } }); - addItem(targetBranchBtn); + if (!params.getBool("IsTestedBranch")) { + addItem(targetBranchBtn); + } // uninstall button auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL")); @@ -155,4 +153,4 @@ void SoftwarePanel::updateLabels() { installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes"))); update(); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 3c5766d137..bec83f48d5 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -295,10 +295,12 @@ class ListWidget : public QWidget { inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); } inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } +// TODO: move this out of the macros before going public. Looks phishy, and it's harder to debug it... #ifdef SUNNYPILOT LIST_WIDGET_FUNCTION_1 LIST_WIDGET_FUNCTION_2 - LIST_WIDGET_FUCNTION_3 + LIST_WIDGET_FUNCTION_3 + LIST_WIDGET_FUNCTION_4 #endif private: diff --git a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc index ce6a6712e6..5b00c55e20 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc @@ -63,7 +63,7 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { // Connect click event from currentModelLblBtn to local slot connect(currentModelLblBtn, &ButtonControl::clicked, this, &SoftwarePanelSP::handleCurrentModelLblBtnClicked); - ReplaceOrAddWidget(currentModelLbl, currentModelLblBtn); + addItem(currentModelLblBtn, 0); } void SoftwarePanelSP::handleDownloadFailed(const QString &modelType) { diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h index 3bc4bb7f65..dedec60666 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h @@ -35,12 +35,16 @@ } \ } \ -#define LIST_WIDGET_FUCNTION_3 \ +#define LIST_WIDGET_FUNCTION_3 \ + inline void addItem(QWidget *w, int index) { inner_layout.insertWidget(index, w); } \ + +#define LIST_WIDGET_FUNCTION_4 \ inline void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) { \ if (const int index = inner_layout.indexOf(old_widget); index != -1) { \ RemoveWidgetAt(index); \ AddWidgetAt(index, new_widget); \ } else { \ - addItem(new_widget); \ + addItem(new_widget, 0); \ } \ } \ + From 7030dc02cefe5f26a1a9a4f6c7e8e954143c0efd Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 16:04:48 +0200 Subject: [PATCH 400/575] Add handling for SUNNYPILOT in Qt settings Introduced the RETURN_IF_SUNNYPILOT macro to handle conditional returns in the settings code. This ensures that certain blocks of code are bypassed when SUNNYPILOT is defined, improving modularity and reducing preprocessor directives. --- selfdrive/ui/qt/offroad/settings.cc | 7 +++---- selfdrive/ui/qt/util.h | 6 ++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index bbcf34ae6d..4a293c32bb 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -22,7 +22,8 @@ #endif TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { -#ifndef SUNNYPILOT + RETURN_IF_SUNNYPILOT + // param, title, desc, icon std::vector> toggle_defs{ { @@ -113,7 +114,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); }); -#endif } void TogglesPanel::updateState(const UIState &s) { @@ -500,7 +500,7 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { } SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { -#ifndef SUNNYPILOT + RETURN_IF_SUNNYPILOT // setup two main layouts sidebar_widget = new QWidget; @@ -598,5 +598,4 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { border-radius: 30px; } )"); -#endif } diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 438b9677e7..d4d56560ea 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -13,6 +13,12 @@ #include "cereal/gen/cpp/car.capnp.h" #include "common/params.h" +#ifdef SUNNYPILOT +#define RETURN_IF_SUNNYPILOT return; +#else +#define RETURN_IF_SUNNYPILOT // Do nothing +#endif + QString getVersion(); QString getBrand(); QString getUserAgent(bool sunnylink = false); From f56a30d6ceb4b3fd664a4576231188478ce5ce12 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 16:40:18 +0200 Subject: [PATCH 401/575] Refactor ListWidgetSP functions from macros to inline methods Consolidate multiple macro-defined functions into inline methods within ListWidgetSP to improve readability and maintainability. This change ensures that the widget functionalities are more straightforward and easier to debug. Adjust related headers and implementation files accordingly. --- selfdrive/ui/qt/offroad/settings.h | 6 +++++ selfdrive/ui/qt/offroad/software_settings.cc | 4 ++++ selfdrive/ui/qt/widgets/controls.h | 14 ++++------- .../qt/offroad/software_settings_sp.cc | 2 +- selfdrive/ui/sunnypilot/qt/sp_priv_util.h | 24 ------------------- .../sunnypilot/qt/widgets/sp_priv_controls.h | 17 +++++++++++++ 6 files changed, 32 insertions(+), 35 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 7b8fd52976..41523664e6 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -99,6 +99,11 @@ protected: virtual void updateToggles(); }; +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#define ListWidget ListWidgetSP +#endif + class SoftwarePanel : public ListWidget { Q_OBJECT public: @@ -120,3 +125,4 @@ protected: Params params; ParamWatcher *fs_watch; }; +#undef ListWidget //To prevent this from unintentionally extending to other headers that included this one diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 3045842d91..a8562bfcfd 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -15,6 +15,10 @@ #include "selfdrive/ui/qt/widgets/input.h" #include "system/hardware/hw.h" +#ifdef SUNNYPILOT +#define ListWidget ListWidgetSP +#endif + void SoftwarePanel::checkForUpdates() { std::system("pkill -SIGUSR1 -f system.updated.updated"); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index bec83f48d5..e9616c3a35 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -291,17 +291,11 @@ class ListWidget : public QWidget { inner_layout.setSpacing(25); // default spacing is 25 outer_layout.addStretch(); } - inline void addItem(QWidget *w) { inner_layout.addWidget(w); } - inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); } - inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } + -// TODO: move this out of the macros before going public. Looks phishy, and it's harder to debug it... -#ifdef SUNNYPILOT - LIST_WIDGET_FUNCTION_1 - LIST_WIDGET_FUNCTION_2 - LIST_WIDGET_FUNCTION_3 - LIST_WIDGET_FUNCTION_4 -#endif + void addItem(QWidget *w) { inner_layout.addWidget(w); } + void addItem(QLayout *layout) { inner_layout.addLayout(layout); } + void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } private: void paintEvent(QPaintEvent *) override { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc index 5b00c55e20..d6ac446a07 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc @@ -63,7 +63,7 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { // Connect click event from currentModelLblBtn to local slot connect(currentModelLblBtn, &ButtonControl::clicked, this, &SoftwarePanelSP::handleCurrentModelLblBtnClicked); - addItem(currentModelLblBtn, 0); + AddWidgetAt(0, currentModelLblBtn); } void SoftwarePanelSP::handleDownloadFailed(const QString &modelType) { diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h index dedec60666..2337eaeef8 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h @@ -23,28 +23,4 @@ #define BUTTON_CONTROL_FUNCTION_1 \ inline void click() { btn.click(); } \ -// ListWidget -#define LIST_WIDGET_FUNCTION_1 \ - inline void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); } \ - -#define LIST_WIDGET_FUNCTION_2 \ - inline void RemoveWidgetAt(const int index) { \ - if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) { \ - if(item->widget()) delete item->widget(); \ - delete item; \ - } \ - } \ - -#define LIST_WIDGET_FUNCTION_3 \ - inline void addItem(QWidget *w, int index) { inner_layout.insertWidget(index, w); } \ - -#define LIST_WIDGET_FUNCTION_4 \ - inline void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) { \ - if (const int index = inner_layout.indexOf(old_widget); index != -1) { \ - RemoveWidgetAt(index); \ - AddWidgetAt(index, new_widget); \ - } else { \ - addItem(new_widget, 0); \ - } \ - } \ diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 4d3baf8b3f..d224ffc2e8 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -190,6 +190,23 @@ public: explicit ListWidgetSP(QWidget *parent = 0, const bool split_line = true) : ListWidget(parent), _split_line(split_line) { } + void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); } + void RemoveWidgetAt(const int index) { + if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) { + if(item->widget()) delete item->widget(); + delete item; + } + } + using ListWidget::addItem; + void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) { + if (const int index = inner_layout.indexOf(old_widget); index != -1) { + RemoveWidgetAt(index); + AddWidgetAt(index, new_widget); + } else { + AddWidgetAt(0, new_widget); + } + } + private: void paintEvent(QPaintEvent *) override { QPainter p(this); From d792be8999e8732f9401ecb6cee6c288786a7481 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 19:48:17 +0200 Subject: [PATCH 402/575] Refactor ListWidgetSP functions from macros to inline methods Consolidate multiple macro-defined functions into inline methods within ListWidgetSP to improve readability and maintainability. This change ensures that the widget functionalities are more straightforward and easier to debug. Adjust related headers and implementation files accordingly. --- selfdrive/ui/qt/home.h | 3 +- selfdrive/ui/qt/maps/map_settings.h | 4 + selfdrive/ui/qt/network/networking.cc | 1 - selfdrive/ui/qt/network/networking.h | 6 + selfdrive/ui/qt/offroad/settings.h | 15 +- selfdrive/ui/qt/offroad/software_settings.cc | 4 + selfdrive/ui/qt/offroad_home.h | 3 +- selfdrive/ui/qt/widgets/controls.cc | 12 +- selfdrive/ui/qt/widgets/controls.h | 39 +- selfdrive/ui/qt/widgets/ssh_keys.h | 4 + .../ui/sunnypilot/qt/offroad/mads_settings.cc | 2 +- .../ui/sunnypilot/qt/offroad/models_fetcher.h | 4 + .../ui/sunnypilot/qt/offroad/trips_settings.h | 4 + .../ui/sunnypilot/qt/onroad/onroad_settings.h | 4 + selfdrive/ui/sunnypilot/qt/sp_priv_home.h | 1 - .../sunnypilot/qt/widgets/sp_priv_controls.cc | 141 +- .../sunnypilot/qt/widgets/sp_priv_controls.h | 260 ++- selfdrive/ui/translations/main_ar.ts | 1947 +++++++++++++++- selfdrive/ui/translations/main_de.ts | 1947 +++++++++++++++- selfdrive/ui/translations/main_es.ts | 2029 ++++++++++++++++- selfdrive/ui/translations/main_fr.ts | 1947 +++++++++++++++- selfdrive/ui/translations/main_ja.ts | 1947 +++++++++++++++- selfdrive/ui/translations/main_ko.ts | 1947 +++++++++++++++- selfdrive/ui/translations/main_pt-BR.ts | 1947 +++++++++++++++- selfdrive/ui/translations/main_th.ts | 1947 +++++++++++++++- selfdrive/ui/translations/main_tr.ts | 1943 +++++++++++++++- selfdrive/ui/translations/main_zh-CHS.ts | 1947 +++++++++++++++- selfdrive/ui/translations/main_zh-CHT.ts | 1947 +++++++++++++++- tools/cabana/detailwidget.h | 4 + 29 files changed, 21816 insertions(+), 190 deletions(-) diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 02cb384823..c64592d49a 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -12,16 +12,17 @@ #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/sidebar.h" -#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" #include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #define OnroadWindow OnroadWindowSP #define OffroadHome OffroadHomeSP #else +#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/offroad_home.h" #endif diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index 0e151df4ad..123612b128 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -13,7 +13,11 @@ #include "common/params.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif const QString NAV_TYPE_FAVORITE = "favorite"; const QString NAV_TYPE_RECENT = "recent"; diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 484536acf2..a84460d5e9 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -9,7 +9,6 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" static const int ICON_WIDTH = 49; diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index 7444e9c28d..1709eb4610 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -6,6 +6,12 @@ #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/ssh_keys.h" #include "selfdrive/ui/qt/widgets/toggle.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#define ListWidget ListWidgetSP +#else +#include "selfdrive/ui/qt/widgets/controls.h" +#endif class WifiItem : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 41523664e6..6adba6834d 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -13,14 +13,14 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#else -#include "selfdrive/ui/qt/widgets/controls.h" -#endif #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#define ListWidget ListWidgetSP +#define ParamControl ParamControlSP +#define ButtonParamControl ButtonParamControlSP +#else +#include "selfdrive/ui/qt/widgets/controls.h" #endif // ********** settings window + top-level panels ********** @@ -99,10 +99,6 @@ protected: virtual void updateToggles(); }; -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#define ListWidget ListWidgetSP -#endif class SoftwarePanel : public ListWidget { Q_OBJECT @@ -125,4 +121,3 @@ protected: Params params; ParamWatcher *fs_watch; }; -#undef ListWidget //To prevent this from unintentionally extending to other headers that included this one diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index a8562bfcfd..fa39daa6de 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -11,7 +11,11 @@ #include "common/util.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif #include "selfdrive/ui/qt/widgets/input.h" #include "system/hardware/hw.h" diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h index edf2bc6cb7..b63d3d98c7 100644 --- a/selfdrive/ui/qt/offroad_home.h +++ b/selfdrive/ui/qt/offroad_home.h @@ -13,15 +13,16 @@ #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/sidebar.h" -#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" #include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #define OnroadWindow OnroadWindowSP #define OffroadHomeImp OffroadHomeSP #else +#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #endif diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 084add1dc1..3fbf506bb4 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -3,12 +3,6 @@ #include #include -#ifdef SUNNYPILOT -#define TITLE_FONT_WEIGHT 450 -#else -#define TITLE_FONT_WEIGHT 400 -#endif - AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setMargin(0); @@ -30,9 +24,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons // title title_label = new QPushButton(title); title_label->setFixedHeight(120); - QString styleSheet = QString("font-size: 50px; font-weight: %1; text-align: left; border: none;") - .arg(TITLE_FONT_WEIGHT); - title_label->setStyleSheet(styleSheet); + title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; border: none;"); hlayout->addWidget(title_label, 1); // value next to control button @@ -146,4 +138,4 @@ void ParamControl::toggleClicked(bool state) { } else { toggle.togglePosition(); } -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index e9616c3a35..98ee30389a 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -12,13 +12,7 @@ #include "common/params.h" #include "selfdrive/ui/qt/widgets/input.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" -#define Toggle ToggleSP -#else #include "selfdrive/ui/qt/widgets/toggle.h" -#endif class ElidedLabel : public QLabel { Q_OBJECT @@ -27,10 +21,6 @@ public: explicit ElidedLabel(QWidget *parent = 0); explicit ElidedLabel(const QString &text, QWidget *parent = 0); -#ifdef SUNNYPILOT - ELIDED_LABEL_SET_STUFF -#endif - signals: void clicked(); @@ -58,13 +48,9 @@ public: title_label->setText(title); } -#ifdef SUNNYPILOT - ABSTRACT_CONTROL_FUNCTION_1 -#else void setValue(const QString &val) { value->setText(val); } -#endif const QString getDescription() { return description->text(); @@ -78,10 +64,6 @@ public slots: description->setVisible(true); } -#ifdef SUNNYPILOT - ABSTRACT_CONTROL_FUNCTION_2 -#endif - signals: void showDescriptionEvent(); @@ -121,9 +103,6 @@ public: ButtonControl(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr); inline void setText(const QString &text) { btn.setText(text); } inline QString text() const { return btn.text(); } -#ifdef SUNNYPILOT - BUTTON_CONTROL_FUNCTION_1 -#endif signals: void clicked(); @@ -197,14 +176,8 @@ private: } } -#ifdef SUNNYPILOT -protected: -#endif std::string key; Params params; -#ifdef SUNNYPILOT -private: -#endif QPixmap active_icon_pixmap; bool confirm = false; bool store_confirm = false; @@ -291,11 +264,9 @@ class ListWidget : public QWidget { inner_layout.setSpacing(25); // default spacing is 25 outer_layout.addStretch(); } - - - void addItem(QWidget *w) { inner_layout.addWidget(w); } - void addItem(QLayout *layout) { inner_layout.addLayout(layout); } - void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } + inline void addItem(QWidget *w) { inner_layout.addWidget(w); } + inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); } + inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } private: void paintEvent(QPaintEvent *) override { @@ -311,8 +282,6 @@ private: } } QVBoxLayout outer_layout; - -protected: QVBoxLayout inner_layout; }; @@ -324,4 +293,4 @@ public: LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { setLayout(l); } -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h index 920bd651e2..8b7b732849 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.h +++ b/selfdrive/ui/qt/widgets/ssh_keys.h @@ -3,7 +3,11 @@ #include #include "system/hardware/hw.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif // SSH enable toggle class SshToggle : public ToggleControl { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc index 8106da5979..02f32c00f7 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc @@ -50,7 +50,7 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { toggles[param.toStdString()] = toggle; // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled); + connect(uiState(), &UIState::offroadTransition, toggle, &ParamControlSP::setEnabled); } // trigger offroadTransition when going onroad/offroad diff --git a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h index 8154065e6b..8780c8028a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h @@ -12,7 +12,11 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif #include "system/hardware/hw.h" static const QString MODELS_PATH = Hardware::PC() ? QDir::homePath() + "/.comma/media/0/models/" : "/data/media/0/models/"; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h index 539bdb2677..c4955400c9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h @@ -1,6 +1,10 @@ #pragma once +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif #include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" class TripsPanel : public QFrame { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h index fe881d1f07..17d0f461d7 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h @@ -8,7 +8,11 @@ #include "common/params.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif class OptionWidget; diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h index dda4fd9cf6..3adc26ffa3 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h @@ -11,7 +11,6 @@ #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/sidebar.h" -#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/home.h" diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc index 8b2eeac65b..4ba1f31c9e 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc @@ -1,5 +1,8 @@ #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include +#include + QFrame *horizontal_line(QWidget *parent) { QFrame *line = new QFrame(parent); line->setFrameShape(QFrame::StyledPanel); @@ -12,6 +15,65 @@ QFrame *horizontal_line(QWidget *parent) { return line; } +AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setMargin(0); + + hlayout = new QHBoxLayout; + hlayout->setMargin(0); + hlayout->setSpacing(20); + + // left icon + icon_label = new QLabel(this); + hlayout->addWidget(icon_label); + if (!icon.isEmpty()) { + icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + icon_label->setPixmap(icon_pixmap); + icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + } + icon_label->setVisible(!icon.isEmpty()); + + // title + title_label = new QPushButton(title); + title_label->setFixedHeight(120); + title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;"); + hlayout->addWidget(title_label, 1); + + // value next to control button + value = new ElidedLabel(); + value->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + value->setStyleSheet("color: #aaaaaa"); + hlayout->addWidget(value); + + main_layout->addLayout(hlayout); + + // description + description = new QLabel(desc); + description->setContentsMargins(40, 20, 40, 20); + description->setStyleSheet("font-size: 40px; color: grey"); + description->setWordWrap(true); + description->setVisible(false); + main_layout->addWidget(description); + + connect(title_label, &QPushButton::clicked, [=]() { + if (!description->isVisible()) { + emit showDescriptionEvent(); + } + + if (!description->text().isEmpty()) { + description->setVisible(!description->isVisible()); + } + }); + + main_layout->addStretch(); +} + +void AbstractControl::hideEvent(QHideEvent *e) { + if (description != nullptr) { + description->hide(); + } +} + AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setMargin(0); @@ -55,13 +117,67 @@ AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, } void AbstractControlSP::hideEvent(QHideEvent *e) { - if (description != nullptr) { - description->hide(); - } } +// controls + +ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControl(title, desc, "", parent) { + btn.setText(text); + btn.setStyleSheet(R"( + QPushButton { + padding: 0; + border-radius: 50px; + font-size: 35px; + font-weight: 500; + color: #E4E4E4; + background-color: #393939; + } + QPushButton:pressed { + background-color: #4a4a4a; + } + QPushButton:disabled { + color: #33E4E4E4; + } + )"); + btn.setFixedSize(250, 100); + QObject::connect(&btn, &QPushButton::clicked, this, &ButtonControlSP::clicked); + hlayout->addWidget(&btn); +} + +// ElidedLabelSP + +ElidedLabelSP::ElidedLabelSP(QWidget *parent) : ElidedLabelSP({}, parent) {} + +ElidedLabelSP::ElidedLabelSP(const QString &text, QWidget *parent) : QLabel(text.trimmed(), parent) { + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setMinimumWidth(1); +} + +void ElidedLabelSP::resizeEvent(QResizeEvent* event) { + QLabel::resizeEvent(event); + lastText_ = elidedText_ = ""; +} + +void ElidedLabelSP::paintEvent(QPaintEvent *event) { + const QString curText = text(); + if (curText != lastText_) { + elidedText_ = fontMetrics().elidedText(curText, Qt::ElideRight, contentsRect().width()); + lastText_ = curText; + } + + QPainter painter(this); + drawFrame(&painter); + QStyleOption opt; + opt.initFrom(this); + style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); +} + +// ParamControlSP + ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent) - : ParamControl(param, title, desc, icon, parent) { + : ToggleControlSP(title, desc, icon, false, parent) { + key = param.toStdString(); + QObject::connect(this, &ParamControlSP::toggleFlipped, this, &ParamControlSP::toggleClicked); hlayout->removeWidget(&toggle); hlayout->insertWidget(0, &toggle); @@ -72,3 +188,20 @@ ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const this->icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); hlayout->insertWidget(1, this->icon_label); } + +void ParamControlSP::toggleClicked(bool state) { + auto do_confirm = [this]() { + QString content("

" + title_label->text() + "


" + "

" + getDescription() + "

"); + return ConfirmationDialog(content, tr("Enable"), tr("Cancel"), true, this).exec(); + }; + + bool confirmed = store_confirm && params.getBool(key + "Confirmed"); + if (!confirm || confirmed || !state || do_confirm()) { + if (store_confirm && state) params.putBool(key + "Confirmed", true); + params.putBool(key, state); + setIcon(state); + } else { + toggle.togglePosition(); + } +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index d224ffc2e8..98dc6a34c9 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -1,18 +1,108 @@ #pragma once -#include "selfdrive/ui/qt/widgets/controls.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "common/params.h" +#include "selfdrive/ui/qt/widgets/input.h" +//#include "selfdrive/ui/qt/widgets/toggle.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" + +// This is for compatibility purposes, until we properly do inheritance splitting +// This is because some controls were needing LabelControlSP, but the original code was using LabelControl +// which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP +#define LabelControl LabelControlSP +#define ElidedLabel ElidedLabelSP +#define ButtonControl ButtonControlSP +#define Toggle ToggleSP +#define ToggleControl ToggleControlSP QFrame *horizontal_line(QWidget *parent = nullptr); -class ParamControlSP : public ParamControl { +class ElidedLabelSP : public QLabel { Q_OBJECT public: - ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr); + explicit ElidedLabelSP(QWidget *parent = 0); + explicit ElidedLabelSP(const QString &text, QWidget *parent = 0); - bool isToggled() { return params.getBool(key); } + void setColor(const QString &color) { + setStyleSheet("QLabel { color : " + color + "; }"); + } + +signals: + void clicked(); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent* event) override; + void mouseReleaseEvent(QMouseEvent *event) override { + if (rect().contains(event->pos())) { + emit clicked(); + } + } + QString lastText_, elidedText_; }; +class AbstractControl : public QFrame { + Q_OBJECT + +public: + void setDescription(const QString &desc) { + if (description) description->setText(desc); + } + + void setTitle(const QString &title) { + title_label->setText(title); + } + + void setValue(const QString &val, std::optional color = std::nullopt) { + value->setText(val); + if (color.has_value()) { + value->setColor(color.value()); + } + } + + const QString getDescription() { + return description->text(); + } + + QLabel *icon_label; + QPixmap icon_pixmap; + + public slots: + void showDescription() { + description->setVisible(true); + } + + void hideDescription() { + description->setVisible(false); + } + + signals: + void showDescriptionEvent(); + +protected: + AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); + void hideEvent(QHideEvent *e) override; + + QHBoxLayout *hlayout; + QPushButton *title_label; + +private: + ElidedLabel *value; + QLabel *description = nullptr; +}; + + class AbstractControlSP : public QFrame { Q_OBJECT @@ -52,6 +142,114 @@ private: QLabel *description = nullptr; }; + +// widget to display a value +class LabelControlSP : public AbstractControl { + Q_OBJECT + +public: + LabelControlSP(const QString &title, const QString &text = "", const QString &desc = "", QWidget *parent = nullptr) : AbstractControl(title, desc, "", parent) { + label.setText(text); + label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); + hlayout->addWidget(&label); + } + void setText(const QString &text) { label.setText(text); } + +private: + ElidedLabelSP label; +}; + +// widget for a button with a label +class ButtonControlSP : public AbstractControl { + Q_OBJECT + +public: + ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr); + inline void setText(const QString &text) { btn.setText(text); } + inline QString text() const { return btn.text(); } + inline void click() { btn.click(); } + +signals: + void clicked(); + +public slots: + void setEnabled(bool enabled) { btn.setEnabled(enabled); } + +private: + QPushButton btn; +}; + +class ToggleControlSP : public AbstractControl { + Q_OBJECT + +public: + ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControl(title, desc, icon, parent) { + toggle.setFixedSize(150, 100); + if (state) { + toggle.togglePosition(); + } + hlayout->addWidget(&toggle); + QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControlSP::toggleFlipped); + } + + void setEnabled(bool enabled) { + toggle.setEnabled(enabled); + toggle.update(); + } + +signals: + void toggleFlipped(bool state); + +protected: + Toggle toggle; +}; + +// widget to toggle params +class ParamControlSP : public ToggleControlSP { + Q_OBJECT + +public: + ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr); + void setConfirmation(bool _confirm, bool _store_confirm) { + confirm = _confirm; + store_confirm = _store_confirm; + } + + void setActiveIcon(const QString &icon) { + active_icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + } + + void refresh() { + bool state = params.getBool(key); + if (state != toggle.on) { + toggle.togglePosition(); + setIcon(state); + } + } + + void showEvent(QShowEvent *event) override { + refresh(); + } + + bool isToggled() { return params.getBool(key); } + +private: + void toggleClicked(bool state); + void setIcon(bool state) { + if (state && !active_icon_pixmap.isNull()) { + icon_label->setPixmap(active_icon_pixmap); + } else if (!icon_pixmap.isNull()) { + icon_label->setPixmap(icon_pixmap); + } + } + + std::string key; + Params params; + QPixmap active_icon_pixmap; + bool confirm = false; + bool store_confirm = false; +}; + class ButtonParamControlSP : public AbstractControlSP { Q_OBJECT public: @@ -183,30 +381,38 @@ private: bool button_group_enabled = true; }; -class ListWidgetSP : public ListWidget { +class ListWidgetSP : public QWidget { Q_OBJECT + public: + explicit ListWidgetSP(QWidget *parent = 0, const bool split_line = true) : QWidget(parent), _split_line(split_line), outer_layout(this) { + outer_layout.setMargin(0); + outer_layout.setSpacing(0); + outer_layout.addLayout(&inner_layout); + inner_layout.setMargin(0); + inner_layout.setSpacing(25); // default spacing is 25 + outer_layout.addStretch(); + } + inline void addItem(QWidget *w) { inner_layout.addWidget(w); } + inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); } + inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } -public: - explicit ListWidgetSP(QWidget *parent = 0, const bool split_line = true) : ListWidget(parent), _split_line(split_line) { + inline void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); } + inline void RemoveWidgetAt(const int index) { + if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) { + if(item->widget()) delete item->widget(); + delete item; + } } - void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); } - void RemoveWidgetAt(const int index) { - if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) { - if(item->widget()) delete item->widget(); - delete item; - } - } - using ListWidget::addItem; - void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) { + inline void ReplaceOrAddWidget(QWidget *old_widget, QWidget *new_widget) { if (const int index = inner_layout.indexOf(old_widget); index != -1) { RemoveWidgetAt(index); AddWidgetAt(index, new_widget); } else { - AddWidgetAt(0, new_widget); + addItem(new_widget); } } - + private: void paintEvent(QPaintEvent *) override { QPainter p(this); @@ -220,8 +426,20 @@ private: } } } + QVBoxLayout outer_layout; + QVBoxLayout inner_layout; - bool _split_line = false; + bool _split_line; +}; + +// convenience class for wrapping layouts +class LayoutWidget : public QWidget { + Q_OBJECT + +public: + LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { + setLayout(l); + } }; class OptionControlSP : public AbstractControlSP { @@ -362,7 +580,7 @@ class SubPanelButton : public QPushButton { Q_OBJECT public: - explicit SubPanelButton(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr) : QPushButton(text, parent) { + SubPanelButton(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr) : QPushButton(text, parent) { const QString buttonStyle = R"( QPushButton { border-radius: 20px; @@ -393,7 +611,7 @@ class PanelBackButton : public QPushButton { Q_OBJECT public: - explicit PanelBackButton(const QString &label = "Back", QWidget *parent = nullptr) : QPushButton(label, parent) { + PanelBackButton(const QString &label = "Back", QWidget *parent = nullptr) : QPushButton(label, parent) { setObjectName("back_btn"); setFixedSize(400, 100); } diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index d8146723a4..d425eb1d18 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -86,6 +86,18 @@ for "%1" من أجل "%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - SPEED + SPEED LIMIT - LIMIT + LIMIT + + + + AnnotatedCameraWidgetSP + + km/h + كم/س + + + mph + ميل/س + + + MAX + MAX + + + SPEED + SPEED + + + LIMIT + LIMIT + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ إلغاء + + CustomOffsetsSettings + + Back + السابق + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - يجب عليك قبول الشروط والأحكام من أجل استخدام openpilot. + يجب عليك قبول الشروط والأحكام من أجل استخدام openpilot. Back @@ -135,6 +262,10 @@ Decline, uninstall %1 رفض، إلغاء التثبيت %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - مراجعة الأدوار والميزات والقيود في openpilot + مراجعة الأدوار والميزات والقيود في openpilot Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - يحتاج openpilot أن يتم ضبط الجهاز ضمن حدود 4 درجات يميناً أو يساراً و5 درجات نحو الأعلى أو 9 نحو الأسفل. يقوم openpilot بالمعايرة باستمرار، ونادراً ما يحتاج إلى عملية إعادة الضبط. + يحتاج openpilot أن يتم ضبط الجهاز ضمن حدود 4 درجات يميناً أو يساراً و5 درجات نحو الأعلى أو 9 نحو الأسفل. يقوم openpilot بالمعايرة باستمرار، ونادراً ما يحتاج إلى عملية إعادة الضبط. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR إقران + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -349,6 +618,79 @@ جارٍ التثبيت... + + LaneChangeSettings + + Back + السابق + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -390,6 +732,48 @@ بانتظار الطريق + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + م + + + hr + س + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -419,6 +803,14 @@ Wrong password كلمة مرور خاطئة + + Scan + + + + Scanning... + + OffroadAlert @@ -472,6 +864,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. لقد اكتشف openpilot تغييراً في موقع تركيب الجهاز. تأكد من تثبيت الجهاز بشكل كامل في موقعه وتثبيته بإحكام على الزجاج الأمامي. + + 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. + +%1 + + + + 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. + + OffroadHome @@ -511,6 +913,186 @@ إعادة التشغيل + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + د + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + التحقق + + + Country + + + + SELECT + اختيار + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + تحديث + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -541,6 +1123,51 @@ إلغاء + + ParamControlSP + + Enable + تمكين + + + Cancel + إلغاء + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + كم/س + + + mph + ميل/س + + PrimeAdWidget @@ -595,7 +1222,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -650,6 +1277,30 @@ now الآن + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -692,6 +1343,147 @@ This may take up to a minute. تم تفعيل إعادة ضبط النظام. اضغط على تأكيد لمسح جميع المحتويات والإعدادات. اضغط على إلغاء لاستئناف التمهيد. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -715,6 +1507,61 @@ This may take up to a minute. البرنامج + + SettingsWindowSP + + × + × + + + Device + الجهاز + + + Network + الشبكة + + + sunnylink + + + + Toggles + المثبتتات + + + Software + البرنامج + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -907,6 +1754,69 @@ This may take up to a minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -983,6 +1893,233 @@ This may take up to a minute. أحدث نسخة، آخر تحقق %1 + + SoftwarePanelSP + + Driving Model + + + + SELECT + اختيار + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + إعادة ضبط المعايرة + + + Warning: You are on a metered connection! + + + + Continue + متابعة + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + كم/س + + + mph + ميل/س + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + غير متاح + + + km/h + كم/س + + + mph + ميل/س + + SshControl @@ -1029,6 +2166,399 @@ This may take up to a minute. تمكين SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + غير متاح + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + إقران + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1092,19 +2622,19 @@ This may take up to a minute. Show ETA in 24h Format - إظهار الوقت المقدر للوصول بصيغة 24 ساعة + إظهار الوقت المقدر للوصول بصيغة 24 ساعة Use 24h format instead of am/pm - استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء + استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء Show Map on Left Side of UI - عرض الخريطة على الجانب الأيسر من واجهة المستخدم + عرض الخريطة على الجانب الأيسر من واجهة المستخدم Show map on left side when in split screen view. - عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. + عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. openpilot Longitudinal Control (Alpha) @@ -1187,6 +2717,244 @@ This may take up to a minute. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + التحكم الطولي openpilot (ألفا) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + تحذير: التحكم الطولي في openpilot في المرحلة ألفا لهذه السيارة، وسيقوم بتعطيل مكابح الطوارئ الآلية (AEB). + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + في هذه السيارة يعمل openpilot افتراضياً بالشكل المدمج في التحكم التكيفي في السرعة بدلاً من التحكم الطولي. قم بتمكين هذا الخيار من أجل الانتقال إلى التحكم الطولي. يوصى بتمكين الوضع التجريبي عند استخدام وضع التحكم الطولي ألفا من openpilot. + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + الوضع التجريبي + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + فك الارتباط عن دواسة الوقود + + + When enabled, pressing the accelerator pedal will disengage openpilot. + عند تمكين هذه الميزة، فإن الضغط على دواسة الوقود سيؤدي إلى فك ارتباط openpilot. + + + Enable Lane Departure Warnings + قم بتمكين تحذيرات مغادرة المسار + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + تلقي التنبيهات من أجل الالتفاف للعودة إلى المسار عندما تنحرف سيارتك فوق الخط المحدد للمسار دون تشغيل إشارة الانعطاف عند القيادة لمسافة تزيد عن 31 ميل/سا (50 كم/سا). + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + + + Record and Upload Driver Camera + تسجيل وتحميل كاميرا السائق + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + تحميل البيانات من الكاميرا المواجهة للسائق، والمساعدة في تحسين خوارزمية مراقبة السائق. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + استخدام النظام المتري + + + Display speed in km/h instead of mph. + عرض السرعة بواحدات كم/سا بدلاً من ميل/سا. + + + Show ETA in 24h Format + إظهار الوقت المقدر للوصول بصيغة 24 ساعة + + + Use 24h format instead of am/pm + استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء + + + Show Map on Left Side of UI + عرض الخريطة على الجانب الأيسر من واجهة المستخدم + + + Show map on left side when in split screen view. + عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. + + + Aggressive + الهجومي + + + Moderate + + + + Standard + القياسي + + + Relaxed + الراحة + + + Driving Personality + شخصية القيادة + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + يتم وضع openpilot بشكل قياسي في <b>وضعية الراحة</b>. يمكن الوضع التجريبي <b>ميزات المستوى ألفا</b> التي لا تكون جاهزة في وضع الراحة: + + + End-to-End Longitudinal Control + التحكم الطولي من طرف إلى طرف + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + دع نظام القيادة يتحكم بالوقود والمكابح. سيقوم openpilot بالقيادة كما لو أنه كائن بشري، بما في ذلك التوقف عند الإشارة الحمراء، وإشارات التوقف. وبما أن نمط القيادة يحدد سرعة القيادة، فإن السرعة المضبوطة تشكل الحد الأقصى فقط. هذه خاصية الجودة ألفا، فيجب توقع حدوث الأخطاء. + + + New Driving Visualization + تصور القيادة الديد + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + الوضع التجريبي غير متوفر حالياً في هذه السيارة نظراً لاستخدام رصيد التحكم التكيفي بالسرعة من أجل التحكم الطولي. + + + openpilot longitudinal control may come in a future update. + قد يتم الحصول على التحكم الطولي في openpilot في عمليات التحديث المستقبلية. + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + يمكن اختبار نسخة ألفا من التحكم الطولي من openpilot، مع الوضع التجريبي، لكن على الفروع غير المطلقة. + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + تمكين التحكم الطولي من openpilot (ألفا) للسماح بالوضع التجريبي. + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1222,6 +2990,165 @@ This may take up to a minute. فشل التحديث + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 010aa4d304..05124209d8 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -86,6 +86,18 @@ for "%1" für "%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - Geschwindigkeit + Geschwindigkeit LIMIT - LIMIT + LIMIT + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + Geschwindigkeit + + + LIMIT + LIMIT + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ Abbrechen + + CustomOffsetsSettings + + Back + Zurück + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. + Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. Back @@ -135,6 +262,10 @@ Decline, uninstall %1 Ablehnen, deinstallieren %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - Wiederhole die Regeln, Fähigkeiten und Limitierungen von Openpilot + Wiederhole die Regeln, Fähigkeiten und Limitierungen von Openpilot Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 9° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig. + Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 9° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -345,6 +614,79 @@ Installiere... + + LaneChangeSettings + + Back + Zurück + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -386,6 +728,48 @@ + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + std + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +799,14 @@ Wrong password Falsches Passwort + + Scan + + + + Scanning... + + OffroadAlert @@ -467,6 +859,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +908,186 @@ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + min + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + ÜBERPRÜFEN + + + Country + + + + SELECT + AUSWÄHLEN + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + Aktualisieren + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1118,51 @@ Aktivieren + + ParamControlSP + + Enable + Aktivieren + + + Cancel + Abbrechen + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -590,7 +1217,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -633,6 +1260,30 @@ now + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -674,6 +1325,147 @@ This may take up to a minute. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -697,6 +1489,61 @@ This may take up to a minute. Software + + SettingsWindowSP + + × + x + + + Device + Gerät + + + Network + Netzwerk + + + sunnylink + + + + Toggles + Schalter + + + Software + Software + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -890,6 +1737,69 @@ This may take up to a minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -967,6 +1877,233 @@ This may take up to a minute. + + SoftwarePanelSP + + Driving Model + + + + SELECT + AUSWÄHLEN + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Neu kalibrieren + + + Warning: You are on a metered connection! + + + + Continue + Fortsetzen + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + Nicht verfügbar + + + km/h + km/h + + + mph + mph + + SshControl @@ -1013,6 +2150,399 @@ This may take up to a minute. SSH aktivieren + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + Nicht verfügbar + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1072,21 +2602,21 @@ This may take up to a minute. Use 24h format instead of am/pm - Benutze das 24Stunden Format anstatt am/pm + Benutze das 24Stunden Format anstatt am/pm Show Map on Left Side of UI Too long for UI - Zeige die Karte auf der linken Seite + Zeige die Karte auf der linken Seite Show map on left side when in split screen view. - Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. + Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. Show ETA in 24h Format Too long for UI - Zeige die Ankunftszeit im 24 Stunden Format + Zeige die Ankunftszeit im 24 Stunden Format Experimental Mode @@ -1173,6 +2703,244 @@ This may take up to a minute. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + Experimenteller Modus + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + Bei Gasbetätigung ausschalten + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Wenn aktiviert, deaktiviert sich Openpilot sobald das Gaspedal betätigt wird. + + + Enable Lane Departure Warnings + Spurverlassenswarnungen aktivieren + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Erhalte Warnungen, zurück in die Spur zu lenken, wenn dein Auto über eine erkannte Fahrstreifenmarkierung ohne aktivierten Blinker mit mehr als 50 km/h fährt. + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + + + Record and Upload Driver Camera + Fahrerkamera aufnehmen und hochladen + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Lade Daten der Fahreraufmerksamkeitsüberwachungskamera hoch, um die Fahreraufmerksamkeitsüberwachungsalgorithmen zu verbessern. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Benutze das metrische System + + + Display speed in km/h instead of mph. + Zeige die Geschwindigkeit in km/h anstatt von mph. + + + Show ETA in 24h Format + Zeige die Ankunftszeit im 24 Stunden Format + + + Use 24h format instead of am/pm + Benutze das 24Stunden Format anstatt am/pm + + + Show Map on Left Side of UI + Zeige die Karte auf der linken Seite + + + Show map on left side when in split screen view. + Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. + + + Aggressive + + + + Moderate + + + + Standard + + + + Relaxed + + + + Driving Personality + + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + Openpilot fährt standardmäßig im <b>entspannten Modus</b>. Der Experimentelle Modus aktiviert<b>Alpha-level Funktionen</b>, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden: + + + End-to-End Longitudinal Control + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + Lass das Fahrmodell Gas und Bremse kontrollieren. Openpilot wird so fahren, wie es dies von einem Menschen erwarten würde; inklusive des Anhaltens für Ampeln und Stoppschildern. Da das Fahrmodell entscheidet wie schnell es fährt stellt die gesetzte Geschwindigkeit lediglich das obere Limit dar. Dies ist ein Alpha-level Funktion. Fehler sind zu erwarten. + + + New Driving Visualization + Neue Fahrvisualisierung + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar da es den eingebauten adaptiven Tempomaten des Autos benutzt. + + + openpilot longitudinal control may come in a future update. + + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1208,6 +2976,165 @@ This may take up to a minute. Aktualisierung fehlgeschlagen + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index ee262c0953..e3e4bf5add 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -86,6 +86,18 @@ for "%1" para "%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - VELOCIDAD + VELOCIDAD LIMIT - LIMITE + LIMITE + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + VELOCIDAD + + + LIMIT + LIMITE + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ Cancelar + + CustomOffsetsSettings + + Back + + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Debe aceptar los terminos y condiciones para poder utilizar openpilot. + Debe aceptar los terminos y condiciones para poder utilizar openpilot. Back @@ -135,6 +262,37 @@ Decline, uninstall %1 Rechazar, desinstalar %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + + + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + DevicePanel @@ -200,7 +358,7 @@ Review the rules, features, and limitations of openpilot - Revisar las reglas, características y limitaciones de openpilot + Revisar las reglas, características y limitaciones de openpilot Are you sure you want to review the training guide? @@ -240,7 +398,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot requiere que el dispositivo sea montado entre 4° izquierda o derecha y entre 5° arriba o 9° abajo. openpilot está constantemente en calibración, reiniciar es rara vez necesario. + openpilot requiere que el dispositivo sea montado entre 4° izquierda o derecha y entre 5° arriba o 9° abajo. openpilot está constantemente en calibración, reiniciar es rara vez necesario. Your device is pointed %1° %2 and %3° %4. @@ -278,6 +436,144 @@ Disengage to Power Off Desactivar para apagar + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -318,6 +614,162 @@ Instalando... + + LaneChangeSettings + + Back + + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS + + + + Waiting for route + + + + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + + + + hr + + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -347,6 +799,14 @@ Wrong password Contraseña equivocada + + Scan + + + + Scanning... + + OffroadAlert @@ -400,6 +860,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot detectó un cambio en la posición de montaje del dispositivo. Asegúrese de que el dispositivo esté completamente asentado en el soporte y que el soporte esté firmemente asegurado al parabrisas. + + 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. + +%1 + + + + 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. + + OffroadHome @@ -439,6 +909,186 @@ Reiniciar Dispositivo + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + VERIFICAR + + + Country + + + + SELECT + SELECCIONAR + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + ACTUALIZAR + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -469,6 +1119,51 @@ Cancelar + + ParamControlSP + + Enable + Activar + + + Cancel + Cancelar + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -497,7 +1192,11 @@ Remote snapshots - Istantánea remota + Istantánea remota + + + Turn-by-turn navigation + @@ -523,7 +1222,7 @@ openpilot - openpilot + openpilot now @@ -550,6 +1249,46 @@ hace %n días + + km + + + + m + + + + mi + + + + ft + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -592,6 +1331,147 @@ Esto puede tardar hasta un minuto. No es posible montar una partición de datos. Partición corrompida. Confirme para borrar y reiniciar su dispositivo. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -615,6 +1495,61 @@ Esto puede tardar hasta un minuto. Software + + SettingsWindowSP + + × + × + + + Device + Dispositivo + + + Network + Red + + + sunnylink + + + + Toggles + Ajustes + + + Software + Software + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -807,6 +1742,69 @@ Esto puede tardar hasta un minuto. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -883,6 +1881,233 @@ Esto puede tardar hasta un minuto. actualizado, último chequeo %1 + + SoftwarePanelSP + + Driving Model + + + + SELECT + SELECCIONAR + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Reiniciar Calibración + + + Warning: You are on a metered connection! + + + + Continue + Continuar + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -929,6 +2154,399 @@ Esto puede tardar hasta un minuto. Habilitar SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + EMPAREJAR + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1071,6 +2689,244 @@ Esto puede tardar hasta un minuto. Activar el control longitudinal experimental para permitir el modo Experimental. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + Control longitudinal de openpilot (Alfa) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + Aviso: el control longitudinal de openpilot está en fase alfa para este automóvil y desactivará el Frenado Automático de Emergencia (AEB). + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + En este vehículo, openpilot se configura de manera predeterminada con el Control de Crucero Adaptativo (ACC) incorporado en el automóvil en lugar del control longitudinal de openpilot. Habilita esta opción para cambiar al control longitudinal de openpilot. Se recomienda activar el modo experimental al habilitar el control longitudinal alfa de openpilot. + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + Modo Experimental + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + Desactivar con el Acelerador + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Cuando esté activado, presionar el acelerador deshabilitará el openpilot. + + + Enable Lane Departure Warnings + Activar Aviso de Salida de Carril + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Recibir alertas para volver dentro del carril cuando su vehículo se sale fuera del carril sin que esté activado la señal de giro mientras esté conduciendo por encima de 50 km/h (31 mph). + + + Always-On Driver Monitoring + Monitoreo del Conductor Siempre Activo + + + Enable driver monitoring even when openpilot is not engaged. + Habilite el monitoreo del conductor incluso cuando Openpilot no esté activado. + + + Record and Upload Driver Camera + Grabar y Subir Cámara del Conductor + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Subir datos de la cámara del conductor para ayudar a mejorar el algoritmo de monitorización del conductor. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Usar Sistema Métrico + + + Display speed in km/h instead of mph. + Mostrar velocidad en km/h en vez de mph. + + + Show ETA in 24h Format + + + + Use 24h format instead of am/pm + + + + Show Map on Left Side of UI + + + + Show map on left side when in split screen view. + + + + Aggressive + Agresivo + + + Moderate + + + + Standard + Estándar + + + Relaxed + Relajado + + + Driving Personality + Personalidad de conducción + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot por defecto conduce en <b>modo chill</b>. El modo Experimental activa <b>recursos de nível-alfa</b> que no están listos para el modo chill. Los recursos del modo expeimental están listados abajo: + + + End-to-End Longitudinal Control + 🌮 Control Longitudinal de Punta a Punta 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + Dajar que el modelo de conducción controle la aceleración y el frenado. openpilot va a conducir como lo haría una persona, incluiyendo parar en los semaforos en rojo y las señales de stop. Dado que el modelo decide la velocidad de conducción, la velocidad de crucero establecida actuará como limitador. Este recurso es de una calidad alfa; errores pueden ocurrir. + + + New Driving Visualization + Nueva Visualización de la conducción + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + La visualización de la conducción cambiará a la cámara que enfoca la carretera a velocidades bajas para mostrar mejor los giros. El logo del modo experimental se mostrará en la esquina superior derecha. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + El modo Experimental no está disponible actualmente para este auto, ya que el ACC del auto está siendo usado para el control longitudinal. + + + openpilot longitudinal control may come in a future update. + El control longitudinal de openpilot podrá llegar en futuras actualizaciones. + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + Se puede probar una versión experimental del control longitudinal openpilot, junto con el modo Experimental, en ramas sin liberación. + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + Activar el control longitudinal experimental para permitir el modo Experimental. + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1106,6 +2962,165 @@ Esto puede tardar hasta un minuto. Actualización fallida + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index dde6adadd3..4c6741b6f0 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -86,6 +86,18 @@ for "%1" pour "%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - VITESSE + VITESSE LIMIT - LIMITE + LIMITE + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mi/h + + + MAX + MAX + + + SPEED + VITESSE + + + LIMIT + LIMITE + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ Annuler + + CustomOffsetsSettings + + Back + Retour + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Vous devez accepter les conditions générales pour utiliser openpilot. + Vous devez accepter les conditions générales pour utiliser openpilot. Back @@ -135,6 +262,10 @@ Decline, uninstall %1 Refuser, désinstaller %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -215,7 +346,7 @@ Review the rules, features, and limitations of openpilot - Revoir les règles, fonctionnalités et limitations d'openpilot + Revoir les règles, fonctionnalités et limitations d'openpilot Are you sure you want to review the training guide? @@ -255,7 +386,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot nécessite que l'appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 9° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire. + openpilot nécessite que l'appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 9° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -345,6 +614,79 @@ Installation... + + LaneChangeSettings + + Back + Retour + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -386,6 +728,48 @@ En attente d'un trajet + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + h + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +799,14 @@ Wrong password Mot de passe incorrect + + Scan + + + + Scanning... + + OffroadAlert @@ -468,6 +860,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot a détecté un changement dans la position de montage de l'appareil. Assurez-vous que l'appareil est totalement inséré dans le support et que le support est fermement fixé au pare-brise. + + 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. + +%1 + + + + 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. + + OffroadHome @@ -507,6 +909,186 @@ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + min + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + VÉRIFIER + + + Country + + + + SELECT + SÉLECTIONNER + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + MISE À JOUR + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -537,6 +1119,51 @@ Annuler + + ParamControlSP + + Enable + Activer + + + Cancel + Annuler + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mi/h + + PrimeAdWidget @@ -591,7 +1218,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -634,6 +1261,30 @@ now + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -676,6 +1327,147 @@ Cela peut prendre jusqu'à une minute. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -699,6 +1491,61 @@ Cela peut prendre jusqu'à une minute. Logiciel + + SettingsWindowSP + + × + × + + + Device + Appareil + + + Network + Réseau + + + sunnylink + + + + Toggles + Options + + + Software + Logiciel + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -891,6 +1738,69 @@ Cela peut prendre jusqu'à une minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -967,6 +1877,233 @@ Cela peut prendre jusqu'à une minute. à jour, dernière vérification %1 + + SoftwarePanelSP + + Driving Model + + + + SELECT + SÉLECTIONNER + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Réinitialiser la calibration + + + Warning: You are on a metered connection! + + + + Continue + Continuer + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mi/h + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mi/h + + SshControl @@ -1013,6 +2150,399 @@ Cela peut prendre jusqu'à une minute. Activer SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1092,19 +2622,19 @@ Cela peut prendre jusqu'à une minute. Show ETA in 24h Format - Afficher l'heure d'arrivée en format 24h + Afficher l'heure d'arrivée en format 24h Use 24h format instead of am/pm - Utiliser le format 24h plutôt que am/pm + Utiliser le format 24h plutôt que am/pm Show Map on Left Side of UI - Afficher la carte à gauche de l'interface + Afficher la carte à gauche de l'interface Show map on left side when in split screen view. - Afficher la carte à gauche en mode écran scindé. + Afficher la carte à gauche en mode écran scindé. Aggressive @@ -1171,6 +2701,244 @@ Cela peut prendre jusqu'à une minute. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + Contrôle longitudinal openpilot (Alpha) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + ATTENTION : le contrôle longitudinal openpilot est en alpha pour cette voiture et désactivera le freinage d'urgence automatique (AEB). + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + Sur cette voiture, openpilot utilise par défaut le régulateur de vitesse adaptatif intégré à la voiture plutôt que le contrôle longitudinal d'openpilot. Activez ceci pour passer au contrôle longitudinal openpilot. Il est recommandé d'activer le mode expérimental lors de l'activation du contrôle longitudinal openpilot alpha. + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + Mode expérimental + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + Désengager avec la pédale d'accélérateur + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Lorsqu'il est activé, appuyer sur la pédale d'accélérateur désengagera openpilot. + + + Enable Lane Departure Warnings + Activer les avertissements de sortie de voie + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Recevez des alertes pour revenir dans la voie lorsque votre véhicule dérive au-delà d'une ligne de voie détectée sans clignotant activé en roulant à plus de 31 mph (50 km/h). + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + + + Record and Upload Driver Camera + Enregistrer et télécharger la caméra conducteur + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Publiez les données de la caméra orientée vers le conducteur et aidez à améliorer l'algorithme de surveillance du conducteur. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Utiliser le système métrique + + + Display speed in km/h instead of mph. + Afficher la vitesse en km/h au lieu de mph. + + + Show ETA in 24h Format + Afficher l'heure d'arrivée en format 24h + + + Use 24h format instead of am/pm + Utiliser le format 24h plutôt que am/pm + + + Show Map on Left Side of UI + Afficher la carte à gauche de l'interface + + + Show map on left side when in split screen view. + Afficher la carte à gauche en mode écran scindé. + + + Aggressive + Aggressif + + + Moderate + + + + Standard + Standard + + + Relaxed + Détendu + + + Driving Personality + Personnalité de conduite + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + Par défaut, openpilot conduit en <b>mode détente</b>. Le mode expérimental permet d'activer des <b>fonctionnalités alpha</b> qui ne sont pas prêtes pour le mode détente. Les fonctionnalités expérimentales sont listées ci-dessous : + + + End-to-End Longitudinal Control + Contrôle longitudinal de bout en bout + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + Laissez le modèle de conduite contrôler l'accélérateur et les freins. openpilot conduira comme il pense qu'un humain le ferait, y compris s'arrêter aux feux rouges et aux panneaux stop. Comme le modèle de conduite décide de la vitesse à adopter, la vitesse définie ne servira que de limite supérieure. Cette fonctionnalité est de qualité alpha ; des erreurs sont à prévoir. + + + New Driving Visualization + Nouvelle visualisation de la conduite + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + La visualisation de la conduite passera sur la caméra grand angle dirigée vers la route à faible vitesse afin de mieux montrer certains virages. Le logo du mode expérimental s'affichera également dans le coin supérieur droit. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + Le mode expérimental est actuellement indisponible pour cette voiture car le régulateur de vitesse adaptatif d'origine est utilisé pour le contrôle longitudinal. + + + openpilot longitudinal control may come in a future update. + Le contrôle longitudinal openpilot pourrait être disponible dans une future mise à jour. + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + Une version alpha du contrôle longitudinal openpilot peut être testée, avec le mode expérimental, sur des branches non publiées. + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + Activer le contrôle longitudinal d'openpilot (en alpha) pour autoriser le mode expérimental. + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1206,6 +2974,165 @@ Cela peut prendre jusqu'à une minute. Échec de la mise à jour + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index e0fb60620b..5154d78b52 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -86,6 +86,18 @@ for "%1" ネットワーク名:%1 + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - 速度 + 速度 LIMIT - 制限速度 + 制限速度 + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + 最高速度 + + + SPEED + 速度 + + + LIMIT + 制限速度 + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ キャンセル + + CustomOffsetsSettings + + Back + 戻る + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - openpilot をご利用される前に、利用規約に同意する必要があります。 + openpilot をご利用される前に、利用規約に同意する必要があります。 Back @@ -135,6 +262,10 @@ Decline, uninstall %1 拒否して %1 をアンインストール + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - openpilot の特徴を見る + openpilot の特徴を見る Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilotの本体は、左右4°以内、上5°、下9°以内の角度で取付ける必要があります。継続してキャリブレーションを続けているので、手動でリセットを行う必要はほぼありません。 + openpilotの本体は、左右4°以内、上5°、下9°以内の角度で取付ける必要があります。継続してキャリブレーションを続けているので、手動でリセットを行う必要はほぼありません。 Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +613,79 @@ インストールしています... + + LaneChangeSettings + + Back + 戻る + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +727,48 @@ + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + メートル + + + hr + 時間 + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -414,6 +798,14 @@ Wrong password パスワードが間違っています + + Scan + + + + Scanning... + + OffroadAlert @@ -466,6 +858,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -505,6 +907,186 @@ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + 確認 + + + Country + + + + SELECT + 選択 + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + 更新 + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -535,6 +1117,51 @@ を有効化 + + ParamControlSP + + Enable + を有効化 + + + Cancel + キャンセル + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -589,7 +1216,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -629,6 +1256,30 @@ now + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -670,6 +1321,147 @@ This may take up to a minute. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -693,6 +1485,61 @@ This may take up to a minute. ソフトウェア + + SettingsWindowSP + + × + × + + + Device + デバイス + + + Network + ネットワーク + + + sunnylink + + + + Toggles + 機能設定 + + + Software + ソフトウェア + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -885,6 +1732,69 @@ This may take up to a minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -961,6 +1871,233 @@ This may take up to a minute. + + SoftwarePanelSP + + Driving Model + + + + SELECT + 選択 + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + キャリブレーションをリセット + + + Warning: You are on a metered connection! + + + + Continue + 続ける + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1007,6 +2144,399 @@ This may take up to a minute. SSH を有効化 + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1070,19 +2600,19 @@ This may take up to a minute. Show ETA in 24h Format - 24時間表示 + 24時間表示 Use 24h format instead of am/pm - AM/PM の代わりに24時間形式を使用します + AM/PM の代わりに24時間形式を使用します Show Map on Left Side of UI - ディスプレイの左側にマップを表示 + ディスプレイの左側にマップを表示 Show map on left side when in split screen view. - 分割画面表示の場合、ディスプレイの左側にマップを表示します。 + 分割画面表示の場合、ディスプレイの左側にマップを表示します。 Experimental Mode @@ -1165,6 +2695,244 @@ This may take up to a minute. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + 実験モード + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + アクセルを踏むと openpilot を中断 + + + When enabled, pressing the accelerator pedal will disengage openpilot. + この機能を有効化すると、openpilotを利用中にアクセルを踏むとopenpilotによる運転サポートを中断します。 + + + Enable Lane Departure Warnings + 車線逸脱警報機能を有効化 + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 時速31マイル(50km)を超えるスピードで走行中、ウインカーを作動させずに検出された車線ライン上に車両が触れた場合、手動で車線内に戻るように警告を行います。 + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + + + Record and Upload Driver Camera + 車内カメラの録画とアップロード + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 車内カメラの映像をアップロードし、ドライバー監視システムのアルゴリズムの向上に役立てます。 + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + メートル法を使用 + + + Display speed in km/h instead of mph. + 速度は mph ではなく km/h で表示されます。 + + + Show ETA in 24h Format + 24時間表示 + + + Use 24h format instead of am/pm + AM/PM の代わりに24時間形式を使用します + + + Show Map on Left Side of UI + ディスプレイの左側にマップを表示 + + + Show map on left side when in split screen view. + 分割画面表示の場合、ディスプレイの左側にマップを表示します。 + + + Aggressive + + + + Moderate + + + + Standard + + + + Relaxed + + + + Driving Personality + + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 + + + End-to-End Longitudinal Control + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + openpilotにアクセルとブレーキを任せます。openpilotは赤信号や一時停止サインでの停止を含み、人間と同じように考えて運転を行います。openpilotが運転速度を決定するため、あなたが設定する速度は上限速度になります。この機能は実験段階のため、openpilotの運転ミスに常に備えて注意してください。 + + + New Driving Visualization + 新しい運転画面 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + この車のACCがアクセル制御を行うため実験モードを利用することができません。 + + + openpilot longitudinal control may come in a future update. + + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1200,6 +2968,165 @@ This may take up to a minute. 更新失敗 + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 56fc5014ee..73eb2af9ff 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -86,6 +86,18 @@ for "%1" "%1"에 접속하려면 비밀번호가 필요합니다 + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - SPEED + SPEED LIMIT - LIMIT + LIMIT + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + SPEED + + + LIMIT + LIMIT + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ 취소 + + CustomOffsetsSettings + + Back + 뒤로 + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - openpilot을 사용하려면 이용약관에 동의해야 합니다. + openpilot을 사용하려면 이용약관에 동의해야 합니다. Back @@ -135,6 +262,10 @@ Decline, uninstall %1 거절, %1 제거 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - openpilot의 규칙, 기능 및 제한 다시 확인 + openpilot의 규칙, 기능 및 제한 다시 확인 Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot 장치는 좌우 4°, 위로 5°, 아래로 9° 이내 각도로 장착되어야 합니다. openpilot은 지속적으로 자동 보정되며 재설정은 거의 필요하지 않습니다. + openpilot 장치는 좌우 4°, 위로 5°, 아래로 9° 이내 각도로 장착되어야 합니다. openpilot은 지속적으로 자동 보정되며 재설정은 거의 필요하지 않습니다. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR 동기화 + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +613,79 @@ 설치 중... + + LaneChangeSettings + + Back + 뒤로 + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +727,48 @@ 경로를 기다리는 중 + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + 시간 + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -414,6 +798,14 @@ Wrong password 비밀번호가 틀렸습니다 + + Scan + + + + Scanning... + + OffroadAlert @@ -467,6 +859,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 장치 온도가 너무 높습니다. 시작하기 전에 온도를 낮춰주세요. 현재 내부 부품 온도: %1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +908,186 @@ 장치를 재부팅하세요 + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + 확인 + + + Country + + + + SELECT + 선택 + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + 업데이트 + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1118,51 @@ 활성화 + + ParamControlSP + + Enable + 활성화 + + + Cancel + 취소 + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -590,7 +1217,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -630,6 +1257,30 @@ now now + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -672,6 +1323,147 @@ This may take up to a minute. 시스템 재설정이 시작되었습니다. 모든 콘텐츠와 설정을 지우려면 확인을 누르시고 부팅을 재개하려면 취소를 누르세요. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -695,6 +1487,61 @@ This may take up to a minute. 소프트웨어 + + SettingsWindowSP + + × + × + + + Device + 장치 + + + Network + 네트워크 + + + sunnylink + + + + Toggles + 토글 + + + Software + 소프트웨어 + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -887,6 +1734,69 @@ This may take up to a minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -963,6 +1873,233 @@ This may take up to a minute. 업데이트 안함 + + SoftwarePanelSP + + Driving Model + + + + SELECT + 선택 + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + 캘리브레이션 초기화 + + + Warning: You are on a metered connection! + + + + Continue + 계속 + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1009,6 +2146,399 @@ This may take up to a minute. SSH 사용 + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 동기화 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1072,19 +2602,19 @@ This may take up to a minute. Show ETA in 24h Format - 24시간 형식으로 도착 예정 시간 표시 + 24시간 형식으로 도착 예정 시간 표시 Use 24h format instead of am/pm - 오전/오후 대신 24시간 형식 사용 + 오전/오후 대신 24시간 형식 사용 Show Map on Left Side of UI - UI 왼쪽에 지도 표시 + UI 왼쪽에 지도 표시 Show map on left side when in split screen view. - 분할 화면 보기에서 지도를 왼쪽에 표시합니다. + 분할 화면 보기에서 지도를 왼쪽에 표시합니다. Experimental Mode @@ -1167,6 +2697,244 @@ This may take up to a minute. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + openpilot 가감속 제어 (알파) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + 경고: openpilot 가감속 제어 알파 기능으로 차량의 자동긴급제동(AEB)을 비활성화합니다. + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + 이 차량은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot의 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파를 활성화하는 경우 실험 모드 활성화를 권장합니다. + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + 실험 모드 + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + 가속페달 조작 시 해제 + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 활성화된 경우 가속 페달을 밟으면 openpilot이 해제됩니다. + + + Enable Lane Departure Warnings + 차선 이탈 경고 활성화 + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 차량이 50km/h(31mph) 이상의 속도로 주행할 때 방향지시등이 켜지지 않은 상태에서 차선을 벗어나면 경고합니다. + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + + + Record and Upload Driver Camera + 운전자 카메라 녹화 및 업로드 + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 운전자 카메라의 영상 데이터를 업로드하여 운전자 모니터링 알고리즘을 개선합니다. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + 미터법 사용 + + + Display speed in km/h instead of mph. + mph 대신 km/h로 속도를 표시합니다. + + + Show ETA in 24h Format + 24시간 형식으로 도착 예정 시간 표시 + + + Use 24h format instead of am/pm + 오전/오후 대신 24시간 형식 사용 + + + Show Map on Left Side of UI + UI 왼쪽에 지도 표시 + + + Show map on left side when in split screen view. + 분할 화면 보기에서 지도를 왼쪽에 표시합니다. + + + Aggressive + 공격적 + + + Moderate + + + + Standard + 표준 + + + Relaxed + 편안한 + + + Driving Personality + 주행 모드 + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot은 기본적으로 <b>안정 모드</b>로 주행합니다. 실험 모드는 안정화되지 않은 <b>알파 수준의 기능</b>을 활성화합니다. 실험 모드의 기능은 아래와 같습니다: + + + End-to-End Longitudinal Control + E2E 가감속 제어 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + openpilot의 주행모델이 가감속을 제어합니다. openpilot은 신호등과 정지 표지판을 보고 멈추는 것을 포함하여 인간이 운전하는 것처럼 생각하고 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 최대 주행 속도로만 기능합니다. 이 기능은 알파 수준이므로 사용에 각별히 주의해야 합니다. + + + New Driving Visualization + 새로운 주행 시각화 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 차량에 장착된 ACC로 가감속을 제어하기 때문에 현재 이 차량에서는 실험 모드를 사용할 수 없습니다. + + + openpilot longitudinal control may come in a future update. + openpilot 가감속 제어는 향후 업데이트에서 지원될 수 있습니다. + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + openpilot 가감속 제어 알파 버전은 비 릴리즈 브랜치에서 실험 모드와 함께 테스트할 수 있습니다. + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + 실험 모드를 사용하려면 openpilot E2E 가감속 제어 (알파) 토글을 활성화하세요. + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1202,6 +2970,165 @@ This may take up to a minute. 업데이트 실패 + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index feaa6e86a1..7ac21a955f 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -86,6 +86,18 @@ for "%1" para "%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - MAX + MAX LIMIT - VELO + VELO + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + LIMITE + + + SPEED + MAX + + + LIMIT + VELO + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ Cancelar + + CustomOffsetsSettings + + Back + Voltar + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Você precisa aceitar os Termos e Condições para utilizar openpilot. + Você precisa aceitar os Termos e Condições para utilizar openpilot. Back @@ -135,6 +262,10 @@ Decline, uninstall %1 Rejeitar, desintalar %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - Revisar regras, aprimoramentos e limitações do openpilot + Revisar regras, aprimoramentos e limitações do openpilot Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 9° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário. + O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 9° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR PAREAR + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -345,6 +614,79 @@ Instalando... + + LaneChangeSettings + + Back + Voltar + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -386,6 +728,48 @@ Aguardando rota + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + hr + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +799,14 @@ Wrong password Senha incorreta + + Scan + + + + Scanning... + + OffroadAlert @@ -468,6 +860,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 Temperatura do dispositivo muito alta. O sistema está sendo resfriado antes de iniciar. A temperatura atual do componente interno é: %1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -507,6 +909,186 @@ Reinicie o Dispositivo + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + min + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + VERIFICAR + + + Country + + + + SELECT + SELECIONE + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + ATUALIZAÇÃO + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -537,6 +1119,51 @@ Ativar + + ParamControlSP + + Enable + Ativar + + + Cancel + Cancelar + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -591,7 +1218,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -634,6 +1261,30 @@ now agora + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -676,6 +1327,147 @@ Isso pode levar até um minuto. Reinicialização do sistema acionada. Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancel para retomar a inicialização. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -699,6 +1491,61 @@ Isso pode levar até um minuto. Software + + SettingsWindowSP + + × + × + + + Device + Dispositivo + + + Network + Rede + + + sunnylink + + + + Toggles + Ajustes + + + Software + Software + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -891,6 +1738,69 @@ Isso pode levar até um minuto. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -967,6 +1877,233 @@ Isso pode levar até um minuto. nunca + + SoftwarePanelSP + + Driving Model + + + + SELECT + SELECIONE + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Reinicializar Calibragem + + + Warning: You are on a metered connection! + + + + Continue + Continuar + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1013,6 +2150,399 @@ Isso pode levar até um minuto. Habilitar SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + PAREAR + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1076,19 +2606,19 @@ Isso pode levar até um minuto. Show ETA in 24h Format - Mostrar ETA em Formato 24h + Mostrar ETA em Formato 24h Use 24h format instead of am/pm - Use o formato 24h em vez de am/pm + Use o formato 24h em vez de am/pm Show Map on Left Side of UI - Exibir Mapa no Lado Esquerdo + Exibir Mapa no Lado Esquerdo Show map on left side when in split screen view. - Exibir mapa do lado esquerdo quando a tela for dividida. + Exibir mapa do lado esquerdo quando a tela for dividida. Experimental Mode @@ -1171,6 +2701,244 @@ Isso pode levar até um minuto. Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + Controle Longitudinal openpilot (Embrionário) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + AVISO: o controle longitudinal openpilot está em estado embrionário para este carro e desativará a Frenagem Automática de Emergência (AEB). + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o embrionário controle longitudinal openpilot. + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + Modo Experimental + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + Desacionar com Pedal do Acelerador + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Quando ativado, pressionar o pedal do acelerador desacionará o openpilot. + + + Enable Lane Departure Warnings + Ativar Avisos de Saída de Faixa + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Receba alertas para voltar para a pista se o seu veículo sair da faixa e a seta não tiver sido acionada previamente quando em velocidades superiores a 50 km/h. + + + Always-On Driver Monitoring + Monitoramento do Motorista Sempre Ativo + + + Enable driver monitoring even when openpilot is not engaged. + Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. + + + Record and Upload Driver Camera + Gravar e Upload Câmera Motorista + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Usar Sistema Métrico + + + Display speed in km/h instead of mph. + Exibir velocidade em km/h invés de mph. + + + Show ETA in 24h Format + Mostrar ETA em Formato 24h + + + Use 24h format instead of am/pm + Use o formato 24h em vez de am/pm + + + Show Map on Left Side of UI + Exibir Mapa no Lado Esquerdo + + + Show map on left side when in split screen view. + Exibir mapa do lado esquerdo quando a tela for dividida. + + + Aggressive + Disputa + + + Moderate + + + + Standard + Neutro + + + Relaxed + Calmo + + + Driving Personality + Temperamento de Direção + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-embrionário</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: + + + End-to-End Longitudinal Control + Controle Longitudinal de Ponta a Ponta + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade embrionária; erros devem ser esperados. + + + New Driving Visualization + Nova Visualização de Condução + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + A visualização de condução fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será mostrado no canto superior direito. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + O modo Experimental está atualmente indisponível para este carro já que o ACC original do carro é usado para controle longitudinal. + + + openpilot longitudinal control may come in a future update. + O controle longitudinal openpilot poderá vir em uma atualização futura. + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + Uma versão embrionária do controle longitudinal openpilot pode ser testada em conjunto com o modo Experimental, em branches que não sejam de produção. + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + Habilite o controle longitudinal (embrionário) openpilot para permitir o modo Experimental. + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1206,6 +2974,165 @@ Isso pode levar até um minuto. Falha na atualização + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index e594a6975f..6b3548a117 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -86,6 +86,18 @@ for "%1" สำหรับ "%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - ความเร็ว + ความเร็ว LIMIT - จำกัด + จำกัด + + + + AnnotatedCameraWidgetSP + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + + MAX + สูงสุด + + + SPEED + ความเร็ว + + + LIMIT + จำกัด + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ ยกเลิก + + CustomOffsetsSettings + + Back + ย้อนกลับ + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - คุณต้องยอมรับเงื่อนไขและข้อตกลง เพื่อใช้งาน openpilot + คุณต้องยอมรับเงื่อนไขและข้อตกลง เพื่อใช้งาน openpilot Back @@ -135,6 +262,10 @@ Decline, uninstall %1 ปฏิเสธ และถอนการติดตั้ง %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - ตรวจสอบกฎ คุณสมบัติ และข้อจำกัดของ openpilot + ตรวจสอบกฎ คุณสมบัติ และข้อจำกัดของ openpilot Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot กำหนดให้ติดตั้งอุปกรณ์ โดยสามารถเอียงด้านซ้ายหรือขวาไม่เกิน 4° และเอียงขึ้นด้านบนไม่เกิน 5° หรือเอียงลงด้านล่างไม่เกิน 9° openpilot ทำการคาลิเบรทอย่างต่อเนื่อง แทบจะไม่จำเป็นต้องทำการรีเซ็ตการคาลิเบรท + openpilot กำหนดให้ติดตั้งอุปกรณ์ โดยสามารถเอียงด้านซ้ายหรือขวาไม่เกิน 4° และเอียงขึ้นด้านบนไม่เกิน 5° หรือเอียงลงด้านล่างไม่เกิน 9° openpilot ทำการคาลิเบรทอย่างต่อเนื่อง แทบจะไม่จำเป็นต้องทำการรีเซ็ตการคาลิเบรท Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR จับคู่ + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +613,79 @@ กำลังติดตั้ง... + + LaneChangeSettings + + Back + ย้อนกลับ + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +727,48 @@ กำลังรอเส้นทาง + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + ม. + + + hr + ชม. + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -414,6 +798,14 @@ Wrong password รหัสผ่านผิด + + Scan + + + + Scanning... + + OffroadAlert @@ -467,6 +859,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot ตรวจพบการเปลี่ยนแปลงของตำแหน่งที่ติดตั้ง กรุณาตรวจสอบว่าได้เลื่อนอุปกรณ์เข้ากับจุดติดตั้งจนสุดแล้ว และจุดติดตั้งได้ยึดติดกับกระจกหน้าอย่างแน่นหนา + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +908,186 @@ รีบูตอุปกรณ์ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + นาที + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + ตรวจสอบ + + + Country + + + + SELECT + เลือก + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + อัปเดต + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1118,51 @@ ยกเลิก + + ParamControlSP + + Enable + เปิดใช้งาน + + + Cancel + ยกเลิก + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + PrimeAdWidget @@ -590,7 +1217,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -630,6 +1257,30 @@ now ตอนนี้ + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -672,6 +1323,147 @@ This may take up to a minute. ระบบถูกรีเซ็ต กดยืนยันเพื่อลบข้อมูลและการตั้งค่าทั้งหมด กดยกเลิกเพื่อบูตต่อ + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -695,6 +1487,61 @@ This may take up to a minute. ซอฟต์แวร์ + + SettingsWindowSP + + × + × + + + Device + อุปกรณ์ + + + Network + เครือข่าย + + + sunnylink + + + + Toggles + ตัวเลือก + + + Software + ซอฟต์แวร์ + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -887,6 +1734,69 @@ This may take up to a minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -963,6 +1873,233 @@ This may take up to a minute. ล่าสุดแล้ว ตรวจสอบครั้งสุดท้ายเมื่อ %1 + + SoftwarePanelSP + + Driving Model + + + + SELECT + เลือก + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + รีเซ็ตการคาลิเบรท + + + Warning: You are on a metered connection! + + + + Continue + ดำเนินการต่อ + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + ไม่มี + + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + SshControl @@ -1009,6 +2146,399 @@ This may take up to a minute. เปิดใช้งาน SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + ไม่มี + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + จับคู่ + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1072,19 +2602,19 @@ This may take up to a minute. Show ETA in 24h Format - แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง + แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง Use 24h format instead of am/pm - ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm + ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm Show Map on Left Side of UI - แสดงแผนที่ที่ด้านซ้ายของหน้าจอ + แสดงแผนที่ที่ด้านซ้ายของหน้าจอ Show map on left side when in split screen view. - แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ + แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ Experimental Mode @@ -1167,6 +2697,244 @@ This may take up to a minute. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + ระบบควบคุมการเร่ง/เบรคโดย openpilot (Alpha) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในสถานะ alpha และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในสถานะ alpha + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + โหมดทดลอง + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + ยกเลิกระบบช่วยขับเมื่อเหยียบคันเร่ง + + + When enabled, pressing the accelerator pedal will disengage openpilot. + เมื่อเปิดใช้งาน การกดแป้นคันเร่งจะเป็นการยกเลิกระบบช่วยขับโดย openpilot + + + Enable Lane Departure Warnings + เปิดใช้งานการเตือนการออกนอกเลน + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + รับการแจ้งเตือนให้เลี้ยวกลับเข้าเลนเมื่อรถของคุณตรวจพบการข้ามช่องจราจรโดยไม่เปิดสัญญาณไฟเลี้ยวในขณะขับขี่ที่ความเร็วเกิน 31 ไมล์ต่อชั่วโมง (50 กม./ชม) + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + + + Record and Upload Driver Camera + บันทึกและอัปโหลดภาพจากกล้องคนขับ + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + อัปโหลดข้อมูลจากกล้องที่หันหน้าไปทางคนขับ และช่วยปรับปรุงอัลกอริธึมการตรวจสอบผู้ขับขี่ + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + ใช้ระบบเมตริก + + + Display speed in km/h instead of mph. + แสดงความเร็วเป็น กม./ชม. แทน ไมล์/ชั่วโมง + + + Show ETA in 24h Format + แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง + + + Use 24h format instead of am/pm + ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm + + + Show Map on Left Side of UI + แสดงแผนที่ที่ด้านซ้ายของหน้าจอ + + + Show map on left side when in split screen view. + แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ + + + Aggressive + ดุดัน + + + Moderate + + + + Standard + มาตรฐาน + + + Relaxed + ผ่อนคลาย + + + Driving Personality + บุคลิกการขับขี่ + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + โดยปกติ openpilot จะขับใน<b>โหมดชิล</b> เปิดโหมดทดลองเพื่อใช้<b>ความสามารถในขั้นพัฒนา</b> ซึ่งยังไม่พร้อมสำหรับโหมดชิล ความสามารถในขั้นพัฒนามีดังนี้: + + + End-to-End Longitudinal Control + ควบคุมเร่ง/เบรคแบบ End-to-End + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + ให้ openpilot ควบคุมการเร่ง/เบรค โดย openpilot จะขับอย่างที่มนุษย์คิด รวมถึงการหยุดที่ไฟแดง และป้ายหยุดรถ เนื่องจาก openpilot จะกำหนดความเร็วในการขับด้วยตัวเอง การตั้งความเร็วจะเป็นเพียงการกำหนดความเร็วสูงสูดเท่านั้น ความสามารถนี้ยังอยู่ในขั้นพัฒนา อาจเกิดข้อผิดพลาดขึ้นได้ + + + New Driving Visualization + การแสดงภาพการขับขี่แบบใหม่ + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + การแสดงภาพการขับขี่จะเปลี่ยนไปใช้กล้องมุมกว้างที่หันหน้าไปทางถนนเมื่ออยู่ในความเร็วต่ำ เพื่อแสดงภาพการเลี้ยวที่ดีขึ้น โลโก้โหมดการทดลองจะแสดงที่มุมบนขวาด้วย + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + ขณะนี้โหมดทดลองไม่สามารถใช้งานได้ในรถคันนี้ เนื่องจากเปิดใช้ระบบควบคุมการเร่ง/เบรคของรถที่ติดตั้งจากโรงงานอยู่ + + + openpilot longitudinal control may come in a future update. + ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + ระบบควบคุมการเร่ง/เบรคโดย openpilot เวอร์ชัน alpha สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + เปิดระบบควบคุมการเร่ง/เบรคโดย openpilot (alpha) เพื่อเปิดใช้งานโหมดทดลอง + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1202,6 +2970,165 @@ This may take up to a minute. การอัปเดตล้มเหลว + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 48615f1699..19b9f15811 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -86,6 +86,18 @@ for "%1" için "%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -101,6 +113,29 @@ MAX MAX + + SPEED + HIZ + + + LIMIT + LİMİT + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + MAX + SPEED HIZ @@ -110,6 +145,91 @@ LİMİT + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + + + ConfirmationDialog @@ -121,11 +241,18 @@ Vazgeç + + CustomOffsetsSettings + + Back + + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Openpilotu kullanmak için Kullanıcı Koşullarını kabul etmelisiniz. + Openpilotu kullanmak için Kullanıcı Koşullarını kabul etmelisiniz. Back @@ -135,6 +262,10 @@ Decline, uninstall %1 Reddet, Kurulumu kaldır. %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - openpilot sisteminin kurallarını ve sınırlamalarını gözden geçirin. + openpilot sisteminin kurallarını ve sınırlamalarını gözden geçirin. Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot, cihazın 4° sola veya 5° yukarı yada 9° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir. + openpilot, cihazın 4° sola veya 5° yukarı yada 9° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +613,79 @@ Yükleniyor... + + LaneChangeSettings + + Back + + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +727,48 @@ + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + saat + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -414,6 +798,14 @@ Wrong password Yalnış parola + + Scan + + + + Scanning... + + OffroadAlert @@ -466,6 +858,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + 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. + +%1 + + + + 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. + + OffroadHome @@ -505,6 +907,186 @@ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + dk + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + KONTROL ET + + + Country + + + + SELECT + + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + GÜNCELLE + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -535,6 +1117,51 @@ + + ParamControlSP + + Enable + + + + Cancel + + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -589,7 +1216,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -629,6 +1256,30 @@ now + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -670,6 +1321,147 @@ This may take up to a minute. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -693,6 +1485,61 @@ This may take up to a minute. Yazılım + + SettingsWindowSP + + × + x + + + Device + Cihaz + + + Network + + + + sunnylink + + + + Toggles + Değiştirme + + + Software + Yazılım + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -885,6 +1732,69 @@ This may take up to a minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -961,6 +1871,233 @@ This may take up to a minute. + + SoftwarePanelSP + + Driving Model + + + + SELECT + + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Kalibrasyonu sıfırla + + + Warning: You are on a metered connection! + + + + Continue + Devam et + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1007,6 +2144,399 @@ This may take up to a minute. SSH aç + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1066,19 +2596,19 @@ This may take up to a minute. Show ETA in 24h Format - Tahmini varış süresini 24 saat formatı şeklinde göster + Tahmini varış süresini 24 saat formatı şeklinde göster Use 24h format instead of am/pm - 24 saat formatını kullan + 24 saat formatını kullan Show Map on Left Side of UI - Haritayı arayüzün sol tarafında göster + Haritayı arayüzün sol tarafında göster Show map on left side when in split screen view. - Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. + Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. openpilot Longitudinal Control (Alpha) @@ -1165,6 +2695,244 @@ This may take up to a minute. + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Aktifleştirilirse eğer gaz pedalına basınca openpilot devre dışı kalır. + + + Enable Lane Departure Warnings + Şerit ihlali uyarı alın + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 50 km/s (31 mph) hızın üzerinde sürüş sırasında aracınız dönüş sinyali vermeden algılanan bir sonraki başlatılışında çekilir. şerit çizgisi ihlalinde şeride geri dönmek için uyarılar alın. + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when openpilot is not engaged. + + + + Record and Upload Driver Camera + Sürücü kamerasını kayıt et. + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Sürücüye bakan kamera verisini yükleyin ve Cihazın algoritmasını geliştirmemize yardımcı olun. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Metrik sistemi kullan + + + Display speed in km/h instead of mph. + Hızı mph yerine km/h şeklinde görüntüleyin. + + + Show ETA in 24h Format + Tahmini varış süresini 24 saat formatı şeklinde göster + + + Use 24h format instead of am/pm + 24 saat formatını kullan + + + Show Map on Left Side of UI + Haritayı arayüzün sol tarafında göster + + + Show map on left side when in split screen view. + Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. + + + Aggressive + + + + Moderate + + + + Standard + + + + Relaxed + + + + Driving Personality + + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + End-to-End Longitudinal Control + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + + + + openpilot longitudinal control may come in a future update. + + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1200,6 +2968,165 @@ This may take up to a minute. Güncelleme başarız oldu + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 32119ee10f..9718fb6fa8 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -86,6 +86,18 @@ for "%1" 网络名称:"%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - SPEED + SPEED LIMIT - LIMIT + LIMIT + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + 最高定速 + + + SPEED + SPEED + + + LIMIT + LIMIT + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ 取消 + + CustomOffsetsSettings + + Back + 返回 + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - 您必须接受条款和条件以使用openpilot。 + 您必须接受条款和条件以使用openpilot。 Back @@ -135,6 +262,10 @@ Decline, uninstall %1 拒绝并卸载%1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - 查看 openpilot 的使用规则,以及其功能和限制 + 查看 openpilot 的使用规则,以及其功能和限制 Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot要求设备安装的偏航角在左4°和右4°之间,俯仰角在上5°和下9°之间。一般来说,openpilot会持续更新校准,很少需要重置。 + openpilot要求设备安装的偏航角在左4°和右4°之间,俯仰角在上5°和下9°之间。一般来说,openpilot会持续更新校准,很少需要重置。 Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR 配对 + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +613,79 @@ 正在安装…… + + LaneChangeSettings + + Back + 返回 + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +727,48 @@ 等待路线 + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + 小时 + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -414,6 +798,14 @@ Wrong password 密码错误 + + Scan + + + + Scanning... + + OffroadAlert @@ -467,6 +859,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 设备温度过高。系统正在冷却中,等冷却完毕后才会启动。目前内部组件温度:%1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +908,186 @@ 重启设备 + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + 分钟 + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + 查看 + + + Country + + + + SELECT + 选择 + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + 更新 + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1118,51 @@ 启用 + + ParamControlSP + + Enable + 启用 + + + Cancel + 取消 + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -590,7 +1217,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -630,6 +1257,30 @@ now 现在 + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -672,6 +1323,147 @@ This may take up to a minute. 系统重置已触发。按下“确认”以清除所有内容和设置,按下“取消”以继续启动。 + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -695,6 +1487,61 @@ This may take up to a minute. 软件 + + SettingsWindowSP + + × + × + + + Device + 设备 + + + Network + 网络 + + + sunnylink + + + + Toggles + 设定 + + + Software + 软件 + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -887,6 +1734,69 @@ This may take up to a minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -963,6 +1873,233 @@ This may take up to a minute. 从未更新 + + SoftwarePanelSP + + Driving Model + + + + SELECT + 选择 + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + 重置设备校准 + + + Warning: You are on a metered connection! + + + + Continue + 继续 + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1009,6 +2146,399 @@ This may take up to a minute. 启用SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 配对 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1072,19 +2602,19 @@ This may take up to a minute. Show ETA in 24h Format - 以24小时格式显示预计到达时间 + 以24小时格式显示预计到达时间 Use 24h format instead of am/pm - 使用24小时制代替am/pm + 使用24小时制代替am/pm Show Map on Left Side of UI - 在介面左侧显示地图 + 在介面左侧显示地图 Show map on left side when in split screen view. - 在分屏模式中,将地图置于屏幕左侧。 + 在分屏模式中,将地图置于屏幕左侧。 Experimental Mode @@ -1167,6 +2697,244 @@ This may take up to a minute. 即使在openpilot未激活时也启用驾驶员监控。 + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + openpilot纵向控制(Alpha 版) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + 警告:此车辆的 openpilot 纵向控制功能目前处于Alpha版本,使用此功能将会停用自动紧急制动(AEB)功能。 + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + 在这辆车上,openpilot 默认使用车辆内建的主动巡航控制(ACC),而非 openpilot 的纵向控制。启用此项功能可切换至 openpilot 的纵向控制。当启用 openpilot 纵向控制 Alpha 版本时,建议同时启用实验性模式(Experimental mode)。 + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + 测试模式 + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + 踩油门时取消控制 + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 启用后,踩下油门踏板将取消openpilot。 + + + Enable Lane Departure Warnings + 启用车道偏离警告 + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 车速超过31mph(50km/h)时,若检测到车辆越过车道线且未打转向灯,系统将发出警告以提醒您返回车道。 + + + Always-On Driver Monitoring + 驾驶员监控常开 + + + Enable driver monitoring even when openpilot is not engaged. + 即使在openpilot未激活时也启用驾驶员监控。 + + + Record and Upload Driver Camera + 录制并上传驾驶员摄像头 + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + 使用公制单位 + + + Display speed in km/h instead of mph. + 显示车速时,以km/h代替mph。 + + + Show ETA in 24h Format + 以24小时格式显示预计到达时间 + + + Use 24h format instead of am/pm + 使用24小时制代替am/pm + + + Show Map on Left Side of UI + 在介面左侧显示地图 + + + Show map on left side when in split screen view. + 在分屏模式中,将地图置于屏幕左侧。 + + + Aggressive + 积极 + + + Moderate + + + + Standard + 标准 + + + Relaxed + 舒适 + + + Driving Personality + 驾驶风格 + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: + + + End-to-End Longitudinal Control + 端到端纵向控制 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + 允许驾驶模型控制加速和制动,openpilot将模仿人类驾驶车辆,包括在红灯和停车让行标识前停车。鉴于驾驶模型确定行驶车速,所设定的车速仅作为上限。此功能尚处于早期测试状态,有可能会出现操作错误。 + + + New Driving Visualization + 新驾驶视角 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 在低速时,驾驶可视化将转换为道路朝向的广角摄像头,以更好地展示某些转弯。测试模式标志也将显示在右上角。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 + + + openpilot longitudinal control may come in a future update. + openpilot纵向控制可能会在未来的更新中提供。 + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + 在正式(release)版本以外的分支上,可以测试 openpilot 纵向控制的 Alpha 版本以及实验模式。 + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + 启用 openpilot 纵向控制(alpha)开关以允许实验模式。 + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1202,6 +2970,165 @@ This may take up to a minute. 更新失败 + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 9d1c16db9f..b511a2b6b4 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -86,6 +86,18 @@ for "%1" 給 "%1" + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Ngrok Service + + AnnotatedCameraWidget @@ -103,11 +115,119 @@ SPEED - 速度 + 速度 LIMIT - 速限 + 速限 + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + 最高 + + + SPEED + 速度 + + + LIMIT + 速限 + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +241,18 @@ 取消 + + CustomOffsetsSettings + + Back + 回上頁 + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - 您必須先接受條款和條件才能使用 openpilot。 + 您必須先接受條款和條件才能使用 openpilot。 Back @@ -135,6 +262,10 @@ Decline, uninstall %1 拒絕並解除安裝 %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -211,7 +342,7 @@ Review the rules, features, and limitations of openpilot - 觀看 openpilot 的使用規則、功能和限制 + 觀看 openpilot 的使用規則、功能和限制 Are you sure you want to review the training guide? @@ -247,7 +378,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以內或朝下偏差 9° 以內。鏡頭在後台會持續自動校準,很少有需要重置的情況。 + openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以內或朝下偏差 9° 以內。鏡頭在後台會持續自動校準,很少有需要重置的情況。 Your device is pointed %1° %2 and %3° %4. @@ -305,6 +436,144 @@ PAIR 配對 + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Review the rules, features, and limitations of sunnypilot + + + + Toggle Onroad/Offroad + + + + OFF + + + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +613,79 @@ 安裝中… + + LaneChangeSettings + + Back + 回上頁 + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +727,48 @@ 等待路線 + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + 小時 + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -414,6 +798,14 @@ Wrong password 密碼錯誤 + + Scan + + + + Scanning... + + OffroadAlert @@ -467,6 +859,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 裝置溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +908,186 @@ 請重新啟裝置 + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + 分鐘 + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + 檢查 + + + Country + + + + SELECT + 選取 + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + 更新 + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1118,51 @@ 啟用 + + ParamControlSP + + Enable + 啟用 + + + Cancel + 取消 + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -590,7 +1217,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -630,6 +1257,30 @@ now 現在 + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + + + sunnypilot + + Reset @@ -672,6 +1323,147 @@ This may take up to a minute. 系統重設已啟動。按下「確認」以清除所有內容和設定,或按下「取消」以繼續開機。 + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -695,6 +1487,61 @@ This may take up to a minute. 軟體 + + SettingsWindowSP + + × + × + + + Device + 裝置 + + + Network + 網路 + + + sunnylink + + + + Toggles + 設定 + + + Software + 軟體 + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -887,6 +1734,69 @@ This may take up to a minute. 5G 5G + + DISABLED + + + + REGIST... + + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + SoftwarePanel @@ -963,6 +1873,233 @@ This may take up to a minute. 從未更新 + + SoftwarePanelSP + + Driving Model + + + + SELECT + 選取 + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + 重設校準 + + + Warning: You are on a metered connection! + + + + Continue + 繼續 + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + 無法使用 + + + km/h + km/h + + + mph + mph + + SshControl @@ -1009,6 +2146,399 @@ This may take up to a minute. 啟用 SSH 服務 + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + 無法使用 + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 配對 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1072,19 +2602,19 @@ This may take up to a minute. Show ETA in 24h Format - 預計到達時間單位改用 24 小時制 + 預計到達時間單位改用 24 小時制 Use 24h format instead of am/pm - 使用 24 小時制。(預設值為 12 小時制) + 使用 24 小時制。(預設值為 12 小時制) Show Map on Left Side of UI - 將地圖顯示在畫面的左側 + 將地圖顯示在畫面的左側 Show map on left side when in split screen view. - 進入分割畫面後,地圖將會顯示在畫面的左側。 + 進入分割畫面後,地圖將會顯示在畫面的左側。 Experimental Mode @@ -1167,6 +2697,244 @@ This may take up to a minute. 即使在openpilot未激活時也啟用駕駛監控。 + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + openpilot 縱向控制 (Alpha 版) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + 警告:此車輛的 openpilot 縱向控制功能目前處於 Alpha 版本,使用此功能將會停用自動緊急煞車(AEB)功能。 + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + 在這輛車上,openpilot 預設使用車輛內建的主動巡航控制(ACC),而非 openpilot 的縱向控制。啟用此項功能可切換至 openpilot 的縱向控制。當啟用 openpilot 縱向控制 Alpha 版本時,建議同時啟用實驗性模式(Experimental mode)。 + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + 實驗模式 + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + 油門取消控車 + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 啟用後,踩踏油門將會取消 openpilot 控制。 + + + Enable Lane Departure Warnings + 啟用車道偏離警告 + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 車速在時速 50 公里 (31 英里) 以上且未打方向燈的情況下,如果偵測到車輛駛出目前車道線時,發出車道偏離警告。 + + + Always-On Driver Monitoring + 駕駛監控常開 + + + Enable driver monitoring even when openpilot is not engaged. + 即使在openpilot未激活時也啟用駕駛監控。 + + + Record and Upload Driver Camera + 記錄並上傳駕駛監控影像 + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 上傳駕駛監控的錄影來協助我們提升駕駛監控的準確率。 + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + 使用公制單位 + + + Display speed in km/h instead of mph. + 啟用後,速度單位顯示將從 mp/h 改為 km/h。 + + + Show ETA in 24h Format + 預計到達時間單位改用 24 小時制 + + + Use 24h format instead of am/pm + 使用 24 小時制。(預設值為 12 小時制) + + + Show Map on Left Side of UI + 將地圖顯示在畫面的左側 + + + Show map on left side when in split screen view. + 進入分割畫面後,地圖將會顯示在畫面的左側。 + + + Aggressive + 積極 + + + Moderate + + + + Standard + 標準 + + + Relaxed + 舒適 + + + Driving Personality + 駕駛風格 + + + Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: + + + End-to-End Longitudinal Control + 端到端縱向控制 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + 讓駕駛模型來控制油門及煞車。openpilot將會模擬人類的駕駛行為,包含在看見紅燈及停止標示時停車。由於車速將由駕駛模型決定,因此您設定的時速將成為速度上限。本功能仍在早期實驗階段,請預期模型有犯錯的可能性。 + + + New Driving Visualization + 新的駕駛視覺介面 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 在低速時,駕駛可視化將切換至道路朝向的廣角攝影機,以更好地顯示某些彎道。在右上角還會顯示「實驗模式」的標誌。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。 + + + openpilot longitudinal control may come in a future update. + openpilot 縱向控制可能會在未來的更新中提供。 + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + 在正式 (release) 版以外的分支上可以測試 openpilot 縱向控制的 Alpha 版本以及實驗模式。 + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + 啟用 openpilot 縱向控制(alpha)切換以允許實驗模式。 + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + Updater @@ -1202,6 +2970,165 @@ This may take up to a minute. 更新失敗 + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 15e1ee5f2f..58972d0860 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -6,7 +6,11 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#else #include "selfdrive/ui/qt/widgets/controls.h" +#endif #include "tools/cabana/binaryview.h" #include "tools/cabana/chart/chartswidget.h" #include "tools/cabana/historylog.h" From 4643d88af6721417428fff92c92ad7dbc499bd8a Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 20:15:12 +0200 Subject: [PATCH 403/575] Refactor settings to improve layout and functionality This commit introduces several layout and functionality changes in the settings. It extracts various functionalities into separate methods, including those related to on-road settings, and removes redundant panel definitions. The changes aim to make the settings more modular, manageable, and user-friendly. Furthermore, a new file for device panel settings is added to provide a more structured way of managing device-related settings. Please note that some functionalities, such as the fleet manager pin, file system watcher, and off-road button, have been moved and included in the device panel settings. These changes do not affect the functioning of the application but provide a more maintainable code structure. --- selfdrive/ui/qt/offroad/settings.cc | 154 +---------------- selfdrive/ui/qt/offroad/settings.h | 17 +- selfdrive/ui/sunnypilot/SConscript | 1 + .../sunnypilot/qt/offroad/sp_priv_settings.cc | 7 +- .../offroad/sp_priv_settings_device_panel.cc | 160 ++++++++++++++++++ .../offroad/sp_priv_settings_device_panel.h | 25 +++ 6 files changed, 202 insertions(+), 162 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 4a293c32bb..3e28106bbc 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -199,45 +199,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { setSpacing(50); addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A")))); addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str())); - - fleetManagerPin = new ButtonControl( - pin_title + pin, tr("TOGGLE"), - tr("Enable or disable PIN requirement for Fleet Manager access.")); - connect(fleetManagerPin, &ButtonControl::clicked, [=]() { - if (params.getBool("FleetManagerPin")) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to turn off PIN requirement?"), tr("Turn Off"), this)) { - params.remove("FleetManagerPin"); - refreshPin(); - } - } else { - params.putBool("FleetManagerPin", true); - refreshPin(); - } - }); - addItem(fleetManagerPin); - - fs_watch = new QFileSystemWatcher(this); - connect(fs_watch, &QFileSystemWatcher::fileChanged, this, &DevicePanel::onPinFileChanged); - - QString pin_path = "/data/otp/otp.conf"; - QString pin_require = "/data/params/d/FleetManagerPin"; - fs_watch->addPath(pin_path); - fs_watch->addPath(pin_require); - refreshPin(); - - // Error Troubleshoot - auto errorBtn = new ButtonControl( - tr("Error Troubleshoot"), tr("VIEW"), - tr("Display error from the tmux session when an error has occurred from a system process.")); - QFileInfo file("/data/community/crashes/error.txt"); - QDateTime modifiedTime = file.lastModified(); - QString modified_time = modifiedTime.toString("yyyy-MM-dd hh:mm:ss "); - connect(errorBtn, &ButtonControl::clicked, [=]() { - const std::string txt = util::read_file("/data/community/crashes/error.txt"); - ConfirmationDialog::rich(modified_time + QString::fromStdString(txt), this); - }); - addItem(errorBtn); - pair_device = new ButtonControl(tr("Pair Device"), tr("PAIR"), tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.")); connect(pair_device, &ButtonControl::clicked, [=]() { @@ -263,33 +224,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { }); addItem(resetCalibBtn); - auto resetMapboxTokenBtn = new ButtonControl(tr("Reset Access Tokens for Map Services"), tr("RESET"), tr("Reset self-service access tokens for Mapbox, Amap, and Google Maps.")); - connect(resetMapboxTokenBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reset access tokens for all map services?"), tr("Reset"), this)) { - std::vector tokens = { - "CustomMapboxTokenPk", - "CustomMapboxTokenSk", - "AmapKey1", - "AmapKey2", - "GmapKey" - }; - for (const auto& token : tokens) { - params.remove(token); - } - } - }); - addItem(resetMapboxTokenBtn); - - auto resetParamsBtn = new ButtonControl(tr("Reset sunnypilot Settings"), tr("RESET"), ""); - connect(resetParamsBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all sunnypilot settings?"), tr("Reset"), this)) { - std::system("sudo rm -rf /data/params/d/*"); - Hardware::reboot(); - } - }); - addItem(resetParamsBtn); - - auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of sunnypilot")); + auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); connect(retrainingBtn, &ButtonControl::clicked, [=]() { if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { emit reviewTrainingGuide(); @@ -322,16 +257,19 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { QObject::connect(uiState(), &UIState::primeTypeChanged, [this] (PrimeType type) { pair_device->setVisible(type == PrimeType::UNPAIRED); }); + +#ifndef SUNNYPILOT QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { - if ((btn != pair_device) && (btn != errorBtn)) { + if (btn != pair_device) { btn->setEnabled(offroad); } } }); +#endif // power buttons - QHBoxLayout *power_layout = new QHBoxLayout(); + power_layout = new QHBoxLayout(); power_layout->setSpacing(30); QPushButton *reboot_btn = new QPushButton(tr("Reboot")); @@ -348,52 +286,20 @@ 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(buttons_layout); - - updateLabels(); -} - -void DevicePanel::onPinFileChanged(const QString &file_path) { - if (file_path == "/data/params/d/FleetManagerPin") { - refreshPin(); - } else if (file_path == "/data/otp/otp.conf") { - refreshPin(); - } -} - -void DevicePanel::refreshPin() { - QFile f("/data/otp/otp.conf"); - QFile require("/data/params/d/FleetManagerPin"); - if (!require.exists()) { - setSpacing(50); - fleetManagerPin->setTitle(pin_title + tr("OFF")); - } else if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { - pin = f.readAll(); - f.close(); - setSpacing(50); - fleetManagerPin->setTitle(pin_title + pin); - } + RETURN_IF_SUNNYPILOT + addItem(power_layout); } void DevicePanel::updateCalibDescription() { QString desc = tr("sunnypilot requires the device to be mounted within 4° left or right and " - "within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required."); + "within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required."); std::string calib_bytes = params.get("CalibrationParams"); if (!calib_bytes.empty()) { try { @@ -440,51 +346,9 @@ 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 6adba6834d..734f47e01e 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -57,26 +57,15 @@ signals: void reviewTrainingGuide(); void showDriverView(); -private slots: +protected slots: void poweroff(); void reboot(); void updateCalibDescription(); - void onPinFileChanged(const QString &file_path); - void refreshPin(); - void forceoffroad(); - void updateLabels(); - -private: +protected: Params params; ButtonControl *pair_device; - - ButtonControl *fleetManagerPin; - QString pin_title = tr("Fleet Manager PIN:") + " "; - QString pin = "OFF"; - QFileSystemWatcher *fs_watch; - - QPushButton *offroad_btn; + QHBoxLayout *power_layout; }; class TogglesPanel : public ListWidget { diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 69bee7c1d1..426fc0df21 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -30,6 +30,7 @@ network_src = [ qt_src = [ "sunnypilot/qt/sp_priv_home.cc", "sunnypilot/qt/sp_priv_offroad_home.cc", + "sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc", "sunnypilot/qt/offroad/sp_priv_settings.cc", "sunnypilot/qt/onroad/onroad_settings.cc", "sunnypilot/qt/onroad/onroad_settings_panel.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc index a73d360028..c517521082 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc @@ -1,5 +1,6 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h" #include "selfdrive/ui/qt/network/networking.h" #include "selfdrive/ui/sunnypilot/sunnypilot_main.h" @@ -318,9 +319,9 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { buttons_layout->addSpacing(10); // setup panels - DevicePanel *device = new DevicePanel(this); - QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); - QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); + DevicePanelSP *device = new DevicePanelSP(this); + QObject::connect(device, &DevicePanelSP::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); + QObject::connect(device, &DevicePanelSP::showDriverView, this, &SettingsWindow::showDriverView); TogglesPanelSP *toggles = new TogglesPanelSP(this); QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc new file mode 100644 index 0000000000..06b1d96589 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc @@ -0,0 +1,160 @@ +#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h" +#include +#include "common/watchdog.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/widgets/prime.h" + +DevicePanelSP::DevicePanelSP(SettingsWindow *parent) : DevicePanel(parent) { + fleetManagerPin = new ButtonControl( + pin_title + pin, tr("TOGGLE"), + tr("Enable or disable PIN requirement for Fleet Manager access.")); + connect(fleetManagerPin, &ButtonControl::clicked, [=]() { + if (params.getBool("FleetManagerPin")) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to turn off PIN requirement?"), tr("Turn Off"), this)) { + params.remove("FleetManagerPin"); + refreshPin(); + } + } else { + params.putBool("FleetManagerPin", true); + refreshPin(); + } + }); + AddWidgetAt(2, fleetManagerPin); + + fs_watch = new QFileSystemWatcher(this); + connect(fs_watch, &QFileSystemWatcher::fileChanged, this, &DevicePanelSP::onPinFileChanged); + + QString pin_path = "/data/otp/otp.conf"; + QString pin_require = "/data/params/d/FleetManagerPin"; + fs_watch->addPath(pin_path); + fs_watch->addPath(pin_require); + refreshPin(); + + // Error Troubleshoot + auto errorBtn = new ButtonControl( + tr("Error Troubleshoot"), tr("VIEW"), + tr("Display error from the tmux session when an error has occurred from a system process.")); + QFileInfo file("/data/community/crashes/error.txt"); + QDateTime modifiedTime = file.lastModified(); + QString modified_time = modifiedTime.toString("yyyy-MM-dd hh:mm:ss "); + connect(errorBtn, &ButtonControl::clicked, [=]() { + const std::string txt = util::read_file("/data/community/crashes/error.txt"); + ConfirmationDialog::rich(modified_time + QString::fromStdString(txt), this); + }); + AddWidgetAt(3, errorBtn); + + + auto resetMapboxTokenBtn = new ButtonControl(tr("Reset Access Tokens for Map Services"), tr("RESET"), tr("Reset self-service access tokens for Mapbox, Amap, and Google Maps.")); + connect(resetMapboxTokenBtn, &ButtonControl::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reset access tokens for all map services?"), tr("Reset"), this)) { + std::vector tokens = { + "CustomMapboxTokenPk", + "CustomMapboxTokenSk", + "AmapKey1", + "AmapKey2", + "GmapKey" + }; + for (const auto& token : tokens) { + params.remove(token); + } + } + }); + AddWidgetAt(6, resetMapboxTokenBtn); + + auto resetParamsBtn = new ButtonControl(tr("Reset sunnypilot Settings"), tr("RESET"), ""); + connect(resetParamsBtn, &ButtonControl::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all sunnypilot settings?"), tr("Reset"), this)) { + std::system("sudo rm -rf /data/params/d/*"); + Hardware::reboot(); + } + }); + AddWidgetAt(6, resetParamsBtn); + + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + for (auto btn : findChildren()) { + if (btn != pair_device && btn != errorBtn) { + btn->setEnabled(offroad); + } + } + }); + + offroad_btn = new QPushButton(tr("Toggle Onroad/Offroad")); + offroad_btn->setObjectName("offroad_btn"); + QObject::connect(offroad_btn, &QPushButton::clicked, this, &DevicePanelSP::forceoffroad); + + QVBoxLayout *buttons_layout = new QVBoxLayout(); + buttons_layout->setSpacing(24); + buttons_layout->addLayout(power_layout); + buttons_layout->addWidget(offroad_btn); + addItem(buttons_layout); + + updateLabels(); +} + +void DevicePanelSP::onPinFileChanged(const QString &file_path) { + if (file_path == "/data/params/d/FleetManagerPin") { + refreshPin(); + } else if (file_path == "/data/otp/otp.conf") { + refreshPin(); + } +} + +void DevicePanelSP::refreshPin() { + QFile f("/data/otp/otp.conf"); + QFile require("/data/params/d/FleetManagerPin"); + if (!require.exists()) { + setSpacing(50); + fleetManagerPin->setTitle(pin_title + tr("OFF")); + } else if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { + pin = f.readAll(); + f.close(); + setSpacing(50); + fleetManagerPin->setTitle(pin_title + pin); + } +} + +void DevicePanelSP::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 DevicePanelSP::showEvent(QShowEvent *event) { + DevicePanel::showEvent(event); + updateLabels(); +} + +void DevicePanelSP::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); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h new file mode 100644 index 0000000000..e78b319ba4 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h @@ -0,0 +1,25 @@ +#pragma once + +#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" + +class DevicePanelSP : public DevicePanel { + Q_OBJECT +public: + explicit DevicePanelSP(SettingsWindow *parent); + void showEvent(QShowEvent *event) override; + +private slots: + void onPinFileChanged(const QString &file_path); + void refreshPin(); + void forceoffroad(); + + void updateLabels(); + +private: + ButtonControl *fleetManagerPin; + QString pin_title = tr("Fleet Manager PIN:") + " "; + QString pin = "OFF"; + QFileSystemWatcher *fs_watch; + + QPushButton *offroad_btn; +}; \ No newline at end of file From ba1c47b3200af9f6100b8000870862d5edb85e85 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 21:18:53 +0200 Subject: [PATCH 404/575] Cleaning up --- selfdrive/ui/sunnypilot/qt/sp_priv_util.cc | 0 selfdrive/ui/sunnypilot/qt/sp_priv_util.h | 26 ---------------------- 2 files changed, 26 deletions(-) delete mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_util.cc delete mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_util.h diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h deleted file mode 100644 index 2337eaeef8..0000000000 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include - -#define ELIDED_LABEL_SET_STUFF \ - void setColor(const QString &color) { \ - setStyleSheet("QLabel { color : " + color + "; }"); \ - } \ - -#define ABSTRACT_CONTROL_FUNCTION_1 \ - void setValue(const QString &val, std::optional color = std::nullopt) { \ - value->setText(val); \ - if (color.has_value()) { \ - value->setColor(color.value()); \ - } \ - } \ - -#define ABSTRACT_CONTROL_FUNCTION_2 \ - void hideDescription() { \ - description->setVisible(false); \ - } \ - -#define BUTTON_CONTROL_FUNCTION_1 \ - inline void click() { btn.click(); } \ - - From 66be7afbdb7ab33dbe38ecac64ca1863ef8cdd57 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 19:24:40 +0000 Subject: [PATCH 405/575] Apply 3 suggestion(s) to 2 file(s) --- selfdrive/ui/qt/offroad/settings.cc | 6 +++--- selfdrive/ui/qt/window.cc | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 3e28106bbc..fa43623c67 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -28,8 +28,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { std::vector> toggle_defs{ { "OpenpilotEnabledToggle", - tr("Enable openpilot"), - tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), + tr("Enable sunnypilot"), + tr("Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), "../assets/offroad/icon_openpilot.png", }, { @@ -299,7 +299,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { void DevicePanel::updateCalibDescription() { QString desc = tr("sunnypilot requires the device to be mounted within 4° left or right and " - "within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required."); + "within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required."); std::string calib_bytes = params.get("CalibrationParams"); if (!calib_bytes.empty()) { try { diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 00b4693822..323befb81f 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -75,7 +75,6 @@ void MainWindow::closeSettings() { if (uiState()->scene.started) { homeWindow->showSidebar(false); -//TODO: TBD if it's worth it to create our own sp_priv_window.cc to have it split from the OG code and not have preprocessor defs in the OG code, at least not the cc #ifdef SUNNYPILOT // Map is always shown when using navigate on openpilot if (uiState()->scene.navigate_on_openpilot_deprecated) { From dc6c20ba979cea33141cb96009827e8c8220ae1d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 21:31:49 +0200 Subject: [PATCH 406/575] Add separate Sidebar implementation for SunnyPilot The file structure of SidebarSP has been revamped. A separate SidebarSP class has been created instead of overriding pieces of Sidebar. The implementation of SidebarSP has been encapsulated in the new SidebarSP class files (sp_priv_sidebar.cc and sp_priv_sidebar.h). The code integrity of Sidebar and its usages in offroad_home.h and sp_priv_home.h have been maintained. This should increase the overall modularity of the code. --- selfdrive/ui/qt/offroad_home.h | 4 +- selfdrive/ui/qt/sidebar.cc | 89 +++------------ selfdrive/ui/qt/sidebar.h | 15 +-- selfdrive/ui/sunnypilot/SConscript | 1 + selfdrive/ui/sunnypilot/qt/sp_priv_home.h | 3 +- selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc | 101 ++++++++++++++++++ selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h | 33 ++++++ 7 files changed, 157 insertions(+), 89 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h index b63d3d98c7..f277d852c4 100644 --- a/selfdrive/ui/qt/offroad_home.h +++ b/selfdrive/ui/qt/offroad_home.h @@ -12,18 +12,20 @@ #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" -#include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" #include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" #define OnroadWindow OnroadWindowSP #define OffroadHomeImp OffroadHomeSP +#define Sidebar SidebarSP #else #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" +#include "selfdrive/ui/qt/sidebar.h" #endif class OffroadHome : public QFrame { diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 8d45e62ce4..615276be00 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -1,11 +1,8 @@ #include "selfdrive/ui/qt/sidebar.h" -#include - #include #include "selfdrive/ui/qt/util.h" -#include "common/params.h" void Sidebar::drawMetric(QPainter &p, const QPair &label, QColor c, int y) { const QRect rect = {30, y, 240, 126}; @@ -93,58 +90,16 @@ void Sidebar::updateState(const UIState &s) { } setProperty("connectStatus", QVariant::fromValue(connectStatus)); - if (sm.frame % UI_FREQ == 0) { // Update every 1 Hz - switch (s.scene.sidebar_temp_options) { - case 0: - break; - case 1: - sidebar_temp = QString::number((int)deviceState.getMemoryTempC()); - break; - case 2: { - const auto& cpu_temp_list = deviceState.getCpuTempC(); - float max_cpu_temp = std::numeric_limits::lowest(); - - for (const float& temp : cpu_temp_list) { - max_cpu_temp = std::max(max_cpu_temp, temp); - } - - if (max_cpu_temp >= 0) { - sidebar_temp = QString::number(std::nearbyint(max_cpu_temp)); - } - break; - } - case 3: { - const auto& gpu_temp_list = deviceState.getGpuTempC(); - float max_gpu_temp = std::numeric_limits::lowest(); - - for (const float& temp : gpu_temp_list) { - max_gpu_temp = std::max(max_gpu_temp, temp); - } - - if (max_gpu_temp >= 0) { - sidebar_temp = QString::number(std::nearbyint(max_gpu_temp)); - } - break; - } - case 4: - sidebar_temp = QString::number((int)deviceState.getMaxTempC()); - break; - default: - break; - } - - setProperty("sidebarTemp", sidebar_temp + "°C"); - } - - bool show_sidebar_temp = s.scene.sidebar_temp_options != 0; - ItemStatus tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("HIGH")}, danger_color}; +#ifndef SUNNYPILOT + ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color}; auto ts = deviceState.getThermalStatus(); if (ts == cereal::DeviceState::ThermalStatus::GREEN) { - tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("GOOD")}, good_color}; + tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color}; } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { - tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("OK")}, warning_color}; + tempStatus = {{tr("TEMP"), tr("OK")}, warning_color}; } setProperty("tempStatus", QVariant::fromValue(tempStatus)); +#endif ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color}; if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { @@ -153,32 +108,14 @@ void Sidebar::updateState(const UIState &s) { pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color}; } setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); - - ItemStatus sunnylinkStatus; - auto sl_dongle_id = getSunnylinkDongleId(); - auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime"); - auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str); - auto elapsed_sunnylink_ping = nanos_since_boot() - last_sunnylink_ping; - auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); - - QString status = tr("DISABLED"); - QColor color = disabled_color; - - if (sunnylink_enabled && last_sunnylink_ping == 0) { - // If sunnylink is enabled, but we don't have a dongle id, and we haven't received a ping yet, we are registering - status = sl_dongle_id.has_value() ? tr("OFFLINE") : tr("REGIST..."); - color = sl_dongle_id.has_value() ? warning_color : progress_color; - } else if (sunnylink_enabled) { - // If sunnylink is enabled, we are considered online if we have received a ping in the last 80 seconds, else error. - status = elapsed_sunnylink_ping < 80000000000ULL ? tr("ONLINE") : tr("ERROR"); - color = elapsed_sunnylink_ping < 80000000000ULL ? good_color : danger_color; - } - sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), status}, color }; - setProperty("sunnylinkStatus", QVariant::fromValue(sunnylinkStatus)); } void Sidebar::paintEvent(QPaintEvent *event) { QPainter p(this); + DrawSidebar(p); // Because derived classes implement this. Otherwise QPainter gets terminated before time. +} + +void Sidebar::DrawSidebar(QPainter &p){ p.setPen(Qt::NoPen); p.setRenderHint(QPainter::Antialiasing); @@ -204,10 +141,10 @@ void Sidebar::paintEvent(QPaintEvent *event) { p.setPen(QColor(0xff, 0xff, 0xff)); const QRect r = QRect(50, 247, 100, 50); p.drawText(r, Qt::AlignCenter, net_type); + RETURN_IF_SUNNYPILOT // Because we draw ourselves // metrics - drawMetric(p, temp_status.first, temp_status.second, 310); - drawMetric(p, panda_status.first, panda_status.second, 440); - drawMetric(p, connect_status.first, connect_status.second, 570); - drawMetric(p, sunnylink_status.first, sunnylink_status.second, 700); + drawMetric(p, temp_status.first, temp_status.second, 338); + drawMetric(p, panda_status.first, panda_status.second, 496); + drawMetric(p, connect_status.first, connect_status.second, 654); } diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 9012f25135..741b4214fa 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -13,12 +13,10 @@ Q_DECLARE_METATYPE(ItemStatus); class Sidebar : public QFrame { Q_OBJECT Q_PROPERTY(ItemStatus connectStatus MEMBER connect_status NOTIFY valueChanged); - Q_PROPERTY(ItemStatus sunnylinkStatus MEMBER sunnylink_status NOTIFY valueChanged); Q_PROPERTY(ItemStatus pandaStatus MEMBER panda_status NOTIFY valueChanged); Q_PROPERTY(ItemStatus tempStatus MEMBER temp_status NOTIFY valueChanged); Q_PROPERTY(QString netType MEMBER net_type NOTIFY valueChanged); Q_PROPERTY(int netStrength MEMBER net_strength NOTIFY valueChanged); - Q_PROPERTY(QString sidebarTemp MEMBER sidebar_temp_str NOTIFY valueChanged); public: explicit Sidebar(QWidget* parent = 0); @@ -29,13 +27,14 @@ signals: public slots: void offroadTransition(bool offroad); - void updateState(const UIState &s); + virtual void updateState(const UIState &s); protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void drawMetric(QPainter &p, const QPair &label, QColor c, int y); + virtual void DrawSidebar(QPainter &p); QPixmap home_img, flag_img, settings_img; bool onroad, flag_pressed, settings_pressed; @@ -52,19 +51,13 @@ protected: const QRect home_btn = QRect(60, 860, 180, 180); const QRect settings_btn = QRect(50, 35, 200, 117); const QColor good_color = QColor(255, 255, 255); - const QColor progress_color = QColor(3, 132, 252); const QColor warning_color = QColor(218, 202, 37); const QColor danger_color = QColor(201, 34, 49); - const QColor disabled_color = QColor(128, 128, 128); - ItemStatus connect_status, panda_status, temp_status, sunnylink_status; + ItemStatus connect_status, panda_status, temp_status; QString net_type; int net_strength = 0; private: - Params params; std::unique_ptr pm; - - QString sidebar_temp = "0"; - QString sidebar_temp_str = "0"; -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 426fc0df21..e941519e10 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -30,6 +30,7 @@ network_src = [ qt_src = [ "sunnypilot/qt/sp_priv_home.cc", "sunnypilot/qt/sp_priv_offroad_home.cc", + "sunnypilot/qt/sp_priv_sidebar.cc", "sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc", "sunnypilot/qt/offroad/sp_priv_settings.cc", "sunnypilot/qt/onroad/onroad_settings.cc", diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h index 3adc26ffa3..5f49ee4794 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h @@ -10,15 +10,16 @@ #include "common/params.h" #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" -#include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/home.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #define OnroadWindow OnroadWindowSP #else +#include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #endif diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc new file mode 100644 index 0000000000..e30f26325b --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc @@ -0,0 +1,101 @@ +#include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" + +#include + +#include "selfdrive/ui/qt/util.h" +#include "common/params.h" + + +SidebarSP::SidebarSP(QWidget *parent) : Sidebar(parent) {} + +void SidebarSP::updateState(const UIState &s) { + if (!isVisible()) return; + Sidebar::updateState(s); + auto &sm = *(s.sm); + auto deviceState = sm["deviceState"].getDeviceState(); + + if (sm.frame % UI_FREQ == 0) { // Update every 1 Hz + switch (s.scene.sidebar_temp_options) { + case 0: + break; + case 1: + sidebar_temp = QString::number((int)deviceState.getMemoryTempC()); + break; + case 2: { + const auto& cpu_temp_list = deviceState.getCpuTempC(); + float max_cpu_temp = std::numeric_limits::lowest(); + + for (const float& temp : cpu_temp_list) { + max_cpu_temp = std::max(max_cpu_temp, temp); + } + + if (max_cpu_temp >= 0) { + sidebar_temp = QString::number(std::nearbyint(max_cpu_temp)); + } + break; + } + case 3: { + const auto& gpu_temp_list = deviceState.getGpuTempC(); + float max_gpu_temp = std::numeric_limits::lowest(); + + for (const float& temp : gpu_temp_list) { + max_gpu_temp = std::max(max_gpu_temp, temp); + } + + if (max_gpu_temp >= 0) { + sidebar_temp = QString::number(std::nearbyint(max_gpu_temp)); + } + break; + } + case 4: + sidebar_temp = QString::number((int)deviceState.getMaxTempC()); + break; + default: + break; + } + + setProperty("sidebarTemp", sidebar_temp + "°C"); + } + + bool show_sidebar_temp = s.scene.sidebar_temp_options != 0; + ItemStatus tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("HIGH")}, danger_color}; + auto ts = deviceState.getThermalStatus(); + if (ts == cereal::DeviceState::ThermalStatus::GREEN) { + tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("GOOD")}, good_color}; + } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { + tempStatus = {{tr("TEMP"), show_sidebar_temp ? sidebar_temp_str : tr("OK")}, warning_color}; + } + setProperty("tempStatus", QVariant::fromValue(tempStatus)); + + ItemStatus sunnylinkStatus; + auto sl_dongle_id = getSunnylinkDongleId(); + auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime"); + auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str); + auto elapsed_sunnylink_ping = nanos_since_boot() - last_sunnylink_ping; + auto sunnylink_enabled = params.getBool("SunnylinkEnabled"); + + QString status = tr("DISABLED"); + QColor color = disabled_color; + + if (sunnylink_enabled && last_sunnylink_ping == 0) { + // If sunnylink is enabled, but we don't have a dongle id, and we haven't received a ping yet, we are registering + status = sl_dongle_id.has_value() ? tr("OFFLINE") : tr("REGIST..."); + color = sl_dongle_id.has_value() ? warning_color : progress_color; + } else if (sunnylink_enabled) { + // If sunnylink is enabled, we are considered online if we have received a ping in the last 80 seconds, else error. + status = elapsed_sunnylink_ping < 80000000000ULL ? tr("ONLINE") : tr("ERROR"); + color = elapsed_sunnylink_ping < 80000000000ULL ? good_color : danger_color; + } + sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), status}, color }; + setProperty("sunnylinkStatus", QVariant::fromValue(sunnylinkStatus)); +} + + +void SidebarSP::DrawSidebar(QPainter &p){ + Sidebar::DrawSidebar(p); + // metrics + drawMetric(p, temp_status.first, temp_status.second, 310); + drawMetric(p, panda_status.first, panda_status.second, 440); + drawMetric(p, connect_status.first, connect_status.second, 570); + drawMetric(p, sunnylink_status.first, sunnylink_status.second, 700); +} diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h new file mode 100644 index 0000000000..158f1bd96c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include +#include +#include "selfdrive/ui/qt/sidebar.h" + +#include "selfdrive/ui/ui.h" + +class SidebarSP : public Sidebar { + Q_OBJECT + Q_PROPERTY(ItemStatus sunnylinkStatus MEMBER sunnylink_status NOTIFY valueChanged); + Q_PROPERTY(QString sidebarTemp MEMBER sidebar_temp_str NOTIFY valueChanged); + +public: + explicit SidebarSP(QWidget* parent = 0); + +public slots: + void updateState(const UIState &s) override; + +protected: + const QColor progress_color = QColor(3, 132, 252); + const QColor disabled_color = QColor(128, 128, 128); + + ItemStatus sunnylink_status; + +private: + Params params; + void DrawSidebar(QPainter &p) override; + QString sidebar_temp = "0"; + QString sidebar_temp_str = "0"; +}; \ No newline at end of file From e02c4a60f94a5a9597f5b083c7385473ebd60285 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 14 Jul 2024 22:40:06 +0200 Subject: [PATCH 407/575] Rename instances of 'openpilot' to 'sunnypilot' in settings Updated references in the settings UI to replace 'openpilot' with 'sunnypilot' for consistency with branding. This includes various descriptions and warnings related to longitudinal control and driver monitoring. --- .../sunnypilot/qt/offroad/sp_priv_settings.cc | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc index c517521082..ae86a92c06 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc @@ -17,9 +17,9 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { "ExperimentalLongitudinalEnabled", tr("openpilot Longitudinal Control (Alpha)"), QString("%1

%2") - .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) - .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " - "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), + .arg(tr("WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) + .arg(tr("On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + "Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha.")), "../assets/offroad/icon_blank.png", }, { @@ -37,7 +37,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { { "DynamicExperimentalControl", tr("Enable Dynamic Experimental Control"), - tr("Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal."), + tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."), "../assets/offroad/icon_blank.png", }, { @@ -62,7 +62,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { { "AlwaysOnDM", tr("Always-On Driver Monitoring"), - tr("Enable driver monitoring even when openpilot is not engaged."), + tr("Enable driver monitoring even when sunnypilot is not engaged."), "../assets/offroad/icon_blank.png", }, { @@ -102,8 +102,8 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { std::vector longi_button_texts{tr("Aggressive"), tr("Moderate"), tr("Standard"), tr("Relaxed")}; long_personality_setting = new ButtonParamControlSP("LongitudinalPersonality", tr("Driving Personality"), - tr("Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " - "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + tr("Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. " + "In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " "your steering wheel distance button."), "../assets/offroad/icon_blank.png", longi_button_texts, @@ -200,7 +200,7 @@ void TogglesPanelSP::updateToggles() { "%5
") .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) .arg(tr("End-to-End Longitudinal Control")) - .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + .arg(tr("Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. " "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " "mistakes should be expected.")) .arg(tr("New Driving Visualization")) @@ -254,9 +254,9 @@ void TogglesPanelSP::updateToggles() { tr("openpilot longitudinal control may come in a future update."); if (CP.getExperimentalLongitudinalAvailable()) { if (is_release) { - long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); + long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); } else { - long_desc = tr("Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode."); + long_desc = tr("Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode."); } } experimental_mode_toggle->setDescription("" + long_desc + "

" + e2e_description); From f2c500249cae05291ff59a25893690d96f43d1dd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Jul 2024 16:48:26 -0400 Subject: [PATCH 408/575] Sidebar: missing import --- selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc index e30f26325b..f9a391b09f 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc @@ -1,5 +1,7 @@ #include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" +#include + #include #include "selfdrive/ui/qt/util.h" From 02b3ea49175700a4c19d68bb3ab2c54d5f5e66e0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Jul 2024 18:56:55 -0400 Subject: [PATCH 409/575] SP Controls: Fix duplicate classes --- selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 98dc6a34c9..e77e5a807e 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -19,6 +19,8 @@ // This is for compatibility purposes, until we properly do inheritance splitting // This is because some controls were needing LabelControlSP, but the original code was using LabelControl // which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP +#define AbstractControl AbstractControlCustomSP +#define LayoutWidget LayoutWidgetSP #define LabelControl LabelControlSP #define ElidedLabel ElidedLabelSP #define ButtonControl ButtonControlSP From 5c1c8332234c916c95efc4c9846b0f59e5a3f6a6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Jul 2024 19:07:11 -0400 Subject: [PATCH 410/575] Only when building map --- selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index e77e5a807e..2549384e3e 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -19,14 +19,21 @@ // This is for compatibility purposes, until we properly do inheritance splitting // This is because some controls were needing LabelControlSP, but the original code was using LabelControl // which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP -#define AbstractControl AbstractControlCustomSP -#define LayoutWidget LayoutWidgetSP #define LabelControl LabelControlSP #define ElidedLabel ElidedLabelSP #define ButtonControl ButtonControlSP #define Toggle ToggleSP #define ToggleControl ToggleControlSP +// To avoid multiple definition errors for AbstractControl and LayoutWidget when ENABLE_MAPS is defined, +// we redefine these classes with custom names (AbstractControlCustomSP and LayoutWidgetSP). +// This ensures that the linker does not encounter duplicate symbols for these classes from different sources. +// This redefinition is only necessary when building with map support; it is not needed if map functionality is not required. +#ifdef ENABLE_MAPS +#define AbstractControl AbstractControlCustomSP +#define LayoutWidget LayoutWidgetSP +#endif + QFrame *horizontal_line(QWidget *parent = nullptr); class ElidedLabelSP : public QLabel { From d367b346a035ecfc86f3d501414c13aa116fc713 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 19:09:17 -0400 Subject: [PATCH 411/575] bump panda lock fix --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index bc5e06d697..e5fcb32845 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit bc5e06d69769f719de0a189d3c84b92181317691 +Subproject commit e5fcb32845eabe72934a3dcdb9de746ae7135053 From 08449ddb77f3c391fead93c773cee6f2c32110b0 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 21:44:05 -0400 Subject: [PATCH 412/575] DeveloperUi: Split --- selfdrive/ui/sunnypilot/SConscript | 1 + .../developer_ui/sp_priv_developer_ui.cc | 170 ++++++++++++++++ .../developer_ui/sp_priv_developer_ui.h | 20 ++ .../onroad/developer_ui/sp_priv_ui_elements.h | 13 ++ .../qt/onroad/sp_priv_annotated_camera.cc | 191 ++---------------- .../qt/onroad/sp_priv_annotated_camera.h | 25 +-- 6 files changed, 218 insertions(+), 202 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_ui_elements.h diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index e941519e10..bcc296e496 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -37,6 +37,7 @@ qt_src = [ "sunnypilot/qt/onroad/onroad_settings_panel.cc", "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", "sunnypilot/qt/onroad/sp_priv_annotated_camera.cc", + "sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc", ] sp_widgets_src = widgets_src + network_src diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc new file mode 100644 index 0000000000..6b6a53799c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc @@ -0,0 +1,170 @@ +#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h" + +#include + +#include "common/util.h" + +UiElement DeveloperUi::getDRel(bool lead_status, float lead_d_rel) { + QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Orange if close, Red if very close + if (lead_d_rel < 5) { + color = QColor(255, 0, 0, 255); + } else if (lead_d_rel < 15) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "REL DIST", "m", color); +} + +UiElement DeveloperUi::getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit) { + QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Red if approaching faster than 10mph + // Orange if approaching (negative) + if (lead_v_rel < -4.4704) { + color = QColor(255, 0, 0, 255); + } else if (lead_v_rel < 0) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "REL SPEED", speed_unit, color); +} + +UiElement DeveloperUi::getSteeringAngleDeg(float angle_steers, bool mads_enabled, bool lat_active) { + QString value = QString("%1%2%3").arg(QString::number(angle_steers, 'f', 1)).arg("°").arg(""); + QColor color = (mads_enabled && lat_active) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + // Red if large steering angle + // Orange if moderate steering angle + if (std::fabs(angle_steers) > 180) { + color = QColor(255, 0, 0, 255); + } else if (std::fabs(angle_steers) > 90) { + color = QColor(255, 188, 0, 255); + } + + return UiElement(value, "REAL STEER", "", color); +} + +UiElement DeveloperUi::getActualLateralAccel(float curvature, float v_ego, float roll, bool mads_enabled, bool lat_active) { + double actualLateralAccel = (curvature * pow(v_ego, 2)) - (roll * 9.81); + + QString value = QString::number(actualLateralAccel, 'f', 2); + QColor color = (mads_enabled && lat_active) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "ACTUAL LAT", "m/s²", color); +} + +UiElement DeveloperUi::getSteeringAngleDesiredDeg(bool mads_enabled, bool lat_active, float steer_angle_desired, float angle_steers) { + QString value = (mads_enabled && lat_active) ? QString("%1%2%3").arg(QString::number(steer_angle_desired, 'f', 1)).arg("°").arg("") : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (mads_enabled && lat_active) { + // Red if large steering angle + // Orange if moderate steering angle + if (std::fabs(angle_steers) > 180) { + color = QColor(255, 0, 0, 255); + } else if (std::fabs(angle_steers) > 90) { + color = QColor(255, 188, 0, 255); + } else { + color = QColor(0, 255, 0, 255); + } + } + + return UiElement(value, "DESIRED STEER", "", color); +} + +UiElement DeveloperUi::getMemoryUsagePercent(int memory_usage_percent) { + QString value = QString("%1%2").arg(QString::number(memory_usage_percent, 'd', 0)).arg("%"); + QColor color = (memory_usage_percent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "RAM", "", color); +} + +UiElement DeveloperUi::getAEgo(float a_ego) { + QString value = QString::number(a_ego, 'f', 1); + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "ACC.", "m/s²", color); +} + +UiElement DeveloperUi::getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit) { + QString value = lead_status ? QString::number((lead_v_rel + v_ego) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; + QColor color = QColor(255, 255, 255, 255); + + if (lead_status) { + // Red if approaching faster than 10mph + // Orange if approaching (negative) + if (lead_v_rel < -4.4704) { + color = QColor(255, 0, 0, 255); + } else if (lead_v_rel < 0) { + color = QColor(255, 188, 0, 255); + } + } + + return UiElement(value, "L.S.", speed_unit, color); +} + +UiElement DeveloperUi::getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid) { + QString value = QString::number(friction_coefficient_filtered, 'f', 3); + QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "FRIC.", "", color); +} + +UiElement DeveloperUi::getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid) { + QString value = QString::number(lat_accel_factor_filtered, 'f', 3); + QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); + + return UiElement(value, "L.A.", "m/s²", color); +} + +UiElement DeveloperUi::getSteeringTorqueEps(float steering_torque_eps) { + QString value = QString::number(std::fabs(steering_torque_eps), 'f', 1); + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "E.T.", "N·dm", color); +} + +UiElement DeveloperUi::getBearingDeg(float bearing_accuracy_deg, float bearing_deg) { + QString value = (bearing_accuracy_deg != 180.00) ? QString("%1%2%3").arg(QString::number(bearing_deg, 'd', 0)).arg("°").arg("") : "-"; + QColor color = QColor(255, 255, 255, 255); + QString dir_value; + + if (bearing_accuracy_deg != 180.00) { + if (((bearing_deg >= 337.5) && (bearing_deg <= 360)) || ((bearing_deg >= 0) && (bearing_deg <= 22.5))) { + dir_value = "N"; + } else if ((bearing_deg > 22.5) && (bearing_deg < 67.5)) { + dir_value = "NE"; + } else if ((bearing_deg >= 67.5) && (bearing_deg <= 112.5)) { + dir_value = "E"; + } else if ((bearing_deg > 112.5) && (bearing_deg < 157.5)) { + dir_value = "SE"; + } else if ((bearing_deg >= 157.5) && (bearing_deg <= 202.5)) { + dir_value = "S"; + } else if ((bearing_deg > 202.5) && (bearing_deg < 247.5)) { + dir_value = "SW"; + } else if ((bearing_deg >= 247.5) && (bearing_deg <= 292.5)) { + dir_value = "W"; + } else if ((bearing_deg > 292.5) && (bearing_deg < 337.5)) { + dir_value = "NW"; + } + } else { + dir_value = "OFF"; + } + + return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color); +} + +UiElement DeveloperUi::getAltitude(float gps_accuracy, float altitude) { + QString value = (gps_accuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-"; + QColor color = QColor(255, 255, 255, 255); + + return UiElement(value, "ALT.", "m", color); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h new file mode 100644 index 0000000000..12f4c1d5dc --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h @@ -0,0 +1,20 @@ +#pragma once + +#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_ui_elements.h" + +class DeveloperUi { +public: + static UiElement getDRel(bool lead_status, float lead_d_rel); + static UiElement getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit); + static UiElement getSteeringAngleDeg(float angle_steers, bool mads_enabled, bool lat_active); + static UiElement getActualLateralAccel(float curvature, float v_ego, float roll, bool mads_enabled, bool lat_active); + static UiElement getSteeringAngleDesiredDeg(bool mads_enabled, bool lat_active, float steer_angle_desired, float angle_steers); + static UiElement getMemoryUsagePercent(int memory_usage_percent); + static UiElement getAEgo(float a_ego); + static UiElement getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit); + static UiElement getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid); + static UiElement getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid); + static UiElement getSteeringTorqueEps(float steering_torque_eps); + static UiElement getBearingDeg(float bearing_accuracy_deg, float bearing_deg); + static UiElement getAltitude(float gps_accuracy, float altitude); +}; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_ui_elements.h b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_ui_elements.h new file mode 100644 index 0000000000..be26f11cee --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_ui_elements.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +struct UiElement { + QString value{}; + QString label{}; + QString units{}; + QColor color{}; + + explicit UiElement(const QString &value = "", const QString &label = "", const QString &units = "", const QColor &color = QColor(255, 255, 255, 255)) + : value(value), label(label), units(units), color(color) {} +}; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc index 70812ae9e3..096ca62b7b 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc @@ -759,209 +759,44 @@ int AnnotatedCameraWidgetSP::drawDevUiElementRight(QPainter &p, int x, int y, co return 110; } -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getDRel() { - QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Orange if close, Red if very close - if (lead_d_rel < 5) { - color = QColor(255, 0, 0, 255); - } else if (lead_d_rel < 15) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "REL DIST", "m", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getVRel() { - QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Red if approaching faster than 10mph - // Orange if approaching (negative) - if (lead_v_rel < -4.4704) { - color = QColor(255, 0, 0, 255); - } else if (lead_v_rel < 0) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "REL SPEED", speedUnit, color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getSteeringAngleDeg() { - QString value = QString("%1%2%3").arg(QString::number(angleSteers, 'f', 1)).arg("°").arg(""); - QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - // Red if large steering angle - // Orange if moderate steering angle - if (std::fabs(angleSteers) > 180) { - color = QColor(255, 0, 0, 255); - } else if (std::fabs(angleSteers) > 90) { - color = QColor(255, 188, 0, 255); - } - - return UiElement(value, "REAL STEER", "", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getActualLateralAccel() { - float actualLateralAccel = (curvature * pow(vEgo, 2)) - (roll * 9.81); - - QString value = QString::number(actualLateralAccel, 'f', 2); - QColor color = (madsEnabled && latActive) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "ACTUAL LAT", "m/s²", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getSteeringAngleDesiredDeg() { - QString value = (madsEnabled && latActive) ? QString("%1%2%3").arg(QString::number(steerAngleDesired, 'f', 1)).arg("°").arg("") : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (madsEnabled && latActive) { - // Red if large steering angle - // Orange if moderate steering angle - if (std::fabs(angleSteers) > 180) { - color = QColor(255, 0, 0, 255); - } else if (std::fabs(angleSteers) > 90) { - color = QColor(255, 188, 0, 255); - } else { - color = QColor(0, 255, 0, 255); - } - } - - return UiElement(value, "DESIRED STEER", "", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getMemoryUsagePercent() { - QString value = QString("%1%2").arg(QString::number(memoryUsagePercent, 'd', 0)).arg("%"); - QColor color = (memoryUsagePercent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "RAM", "", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getAEgo() { - QString value = QString::number(aEgo, 'f', 1); - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "ACC.", "m/s²", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getVEgoLead() { - QString value = lead_status ? QString::number((lead_v_rel + vEgo) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; - QColor color = QColor(255, 255, 255, 255); - - if (lead_status) { - // Red if approaching faster than 10mph - // Orange if approaching (negative) - if (lead_v_rel < -4.4704) { - color = QColor(255, 0, 0, 255); - } else if (lead_v_rel < 0) { - color = QColor(255, 188, 0, 255); - } - } - - return UiElement(value, "L.S.", speedUnit, color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getFrictionCoefficientFiltered() { - QString value = QString::number(frictionCoefficientFiltered, 'f', 3); - QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "FRIC.", "", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getLatAccelFactorFiltered() { - QString value = QString::number(latAccelFactorFiltered, 'f', 3); - QColor color = liveValid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); - - return UiElement(value, "L.A.", "m/s²", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getSteeringTorqueEps() { - QString value = QString::number(std::fabs(steeringTorqueEps), 'f', 1); - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "E.T.", "N·dm", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getBearingDeg() { - QString value = (bearingAccuracyDeg != 180.00) ? QString("%1%2%3").arg(QString::number(bearingDeg, 'd', 0)).arg("°").arg("") : "-"; - QColor color = QColor(255, 255, 255, 255); - QString dir_value; - - if (bearingAccuracyDeg != 180.00) { - if (((bearingDeg >= 337.5) && (bearingDeg <= 360)) || ((bearingDeg >= 0) && (bearingDeg <= 22.5))) { - dir_value = "N"; - } else if ((bearingDeg > 22.5) && (bearingDeg < 67.5)) { - dir_value = "NE"; - } else if ((bearingDeg >= 67.5) && (bearingDeg <= 112.5)) { - dir_value = "E"; - } else if ((bearingDeg > 112.5) && (bearingDeg < 157.5)) { - dir_value = "SE"; - } else if ((bearingDeg >= 157.5) && (bearingDeg <= 202.5)) { - dir_value = "S"; - } else if ((bearingDeg > 202.5) && (bearingDeg < 247.5)) { - dir_value = "SW"; - } else if ((bearingDeg >= 247.5) && (bearingDeg <= 292.5)) { - dir_value = "W"; - } else if ((bearingDeg > 292.5) && (bearingDeg < 337.5)) { - dir_value = "NW"; - } - } else { - dir_value = "OFF"; - } - - return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color); -} - -AnnotatedCameraWidgetSP::UiElement AnnotatedCameraWidgetSP::getAltitude() { - QString value = (gpsAccuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-"; - QColor color = QColor(255, 255, 255, 255); - - return UiElement(value, "ALT.", "m", color); -} - void AnnotatedCameraWidgetSP::drawRightDevUi(QPainter &p, int x, int y) { int rh = 5; int ry = y; // Add Relative Distance to Primary Lead Car // Unit: Meters - UiElement dRelElement = getDRel(); + UiElement dRelElement = DeveloperUi::getDRel(lead_status, lead_d_rel); rh += drawDevUiElementRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color); ry = y + rh; // Add Relative Velocity vs Primary Lead Car // Unit: kph if metric, else mph - UiElement vRelElement = getVRel(); + UiElement vRelElement = DeveloperUi::getVRel(lead_status, lead_v_rel, is_metric, speedUnit); rh += drawDevUiElementRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color); ry = y + rh; // Add Real Steering Angle // Unit: Degrees - UiElement steeringAngleDegElement = getSteeringAngleDeg(); + UiElement steeringAngleDegElement = DeveloperUi::getSteeringAngleDeg(angleSteers, madsEnabled, latActive); rh += drawDevUiElementRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color); ry = y + rh; if (lateralState == "torque") { // Add Actual Lateral Acceleration (roll compensated) when using Torque // Unit: m/s² - UiElement actualLateralAccelElement = getActualLateralAccel(); + UiElement actualLateralAccelElement = DeveloperUi::getActualLateralAccel(curvature, vEgo, roll, madsEnabled, latActive); rh += drawDevUiElementRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color); } else { // Add Desired Steering Angle when using PID // Unit: Degrees - UiElement steeringAngleDesiredDegElement = getSteeringAngleDesiredDeg(); + UiElement steeringAngleDesiredDegElement = DeveloperUi::getSteeringAngleDesiredDeg(madsEnabled, latActive, steerAngleDesired, angleSteers); rh += drawDevUiElementRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color); } ry = y + rh; // Add Device Memory (RAM) Usage // Unit: Percent - UiElement memoryUsagePercentElement = getMemoryUsagePercent(); + UiElement memoryUsagePercentElement = DeveloperUi::getMemoryUsagePercent(memoryUsagePercent); rh += drawDevUiElementRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color); ry = y + rh; @@ -982,39 +817,39 @@ void AnnotatedCameraWidgetSP::drawNewDevUi2(QPainter &p, int x, int y) { // Add Acceleration from Car // Unit: Meters per Second Squared - UiElement aEgoElement = getAEgo(); + UiElement aEgoElement = DeveloperUi::getAEgo(aEgo); rw += drawNewDevUiElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color); // Add Velocity of Primary Lead Car // Unit: kph if metric, else mph - UiElement vEgoLeadElement = getVEgoLead(); + UiElement vEgoLeadElement = DeveloperUi::getVEgoLead(lead_status, lead_v_rel, vEgo, is_metric, speedUnit); rw += drawNewDevUiElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color); if (torquedUseParams) { // Add Friction Coefficient Raw from torqued // Unit: None - UiElement frictionCoefficientFilteredElement = getFrictionCoefficientFiltered(); + UiElement frictionCoefficientFilteredElement = DeveloperUi::getFrictionCoefficientFiltered(frictionCoefficientFiltered, liveValid); rw += drawNewDevUiElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color); // Add Lateral Acceleration Factor Raw from torqued // Unit: m/s² - UiElement latAccelFactorFilteredElement = getLatAccelFactorFiltered(); + UiElement latAccelFactorFilteredElement = DeveloperUi::getLatAccelFactorFiltered(latAccelFactorFiltered, liveValid); rw += drawNewDevUiElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color); } else { // Add Steering Torque from Car EPS // Unit: Newton Meters - UiElement steeringTorqueEpsElement = getSteeringTorqueEps(); + UiElement steeringTorqueEpsElement = DeveloperUi::getSteeringTorqueEps(steeringTorqueEps); rw += drawNewDevUiElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color); // Add Bearing Degree and Direction from Car (Compass) // Unit: Meters - UiElement bearingDegElement = getBearingDeg(); + UiElement bearingDegElement = DeveloperUi::getBearingDeg(bearingAccuracyDeg, bearingDeg); rw += drawNewDevUiElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color); } // Add Altitude of Current Location // Unit: Meters - UiElement altitudeElement = getAltitude(); + UiElement altitudeElement = DeveloperUi::getAltitude(gpsAccuracy, altitude); rw += drawNewDevUiElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color); } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h index 4651c114a5..3758ff28a2 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h @@ -6,6 +6,7 @@ #include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/widgets/cameraview.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h" const int subsign_img_size = 35; const int blinker_size = 120; @@ -40,30 +41,6 @@ private: int drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); void drawNewDevUi2(QPainter &p, int x, int y); void drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2); - struct UiElement { - QString value; - QString label; - QString units; - QColor color; - - UiElement(const QString &value, const QString &label, const QString &units, const QColor &color = QColor(255, 255, 255, 255)) - : value(value), label(label), units(units), color(color) {} - }; - - UiElement getDRel(); - UiElement getVRel(); - UiElement getSteeringAngleDeg(); - UiElement getActualLateralAccel(); - UiElement getSteeringAngleDesiredDeg(); - UiElement getMemoryUsagePercent(); - - UiElement getAEgo(); - UiElement getVEgoLead(); - UiElement getFrictionCoefficientFiltered(); - UiElement getLatAccelFactorFiltered(); - UiElement getSteeringTorqueEps(); - UiElement getBearingDeg(); - UiElement getAltitude(); // ############################## DEV UI END ############################## From 7a58f3026d68771257b5e5c9215eae3846bf4e86 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 21:52:43 -0400 Subject: [PATCH 413/575] Dev UI: rename --- .../qt/onroad/sp_priv_annotated_camera.cc | 30 +++++++++---------- .../qt/onroad/sp_priv_annotated_camera.h | 4 +-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc index 096ca62b7b..84e8fe1917 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc @@ -741,7 +741,7 @@ void AnnotatedCameraWidgetSP::drawCenteredLeftText(QPainter &p, int x, int y, co p.drawText(real_rect3, Qt::AlignLeft | Qt::AlignVCenter, text3); } -int AnnotatedCameraWidgetSP::drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { +int AnnotatedCameraWidgetSP::drawDevUiRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { p.setFont(InterFont(30 * 2, QFont::Bold)); drawColoredText(p, x + 92, y + 80, value, color); @@ -766,38 +766,38 @@ void AnnotatedCameraWidgetSP::drawRightDevUi(QPainter &p, int x, int y) { // Add Relative Distance to Primary Lead Car // Unit: Meters UiElement dRelElement = DeveloperUi::getDRel(lead_status, lead_d_rel); - rh += drawDevUiElementRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color); + rh += drawDevUiRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color); ry = y + rh; // Add Relative Velocity vs Primary Lead Car // Unit: kph if metric, else mph UiElement vRelElement = DeveloperUi::getVRel(lead_status, lead_v_rel, is_metric, speedUnit); - rh += drawDevUiElementRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color); + rh += drawDevUiRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color); ry = y + rh; // Add Real Steering Angle // Unit: Degrees UiElement steeringAngleDegElement = DeveloperUi::getSteeringAngleDeg(angleSteers, madsEnabled, latActive); - rh += drawDevUiElementRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color); + rh += drawDevUiRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color); ry = y + rh; if (lateralState == "torque") { // Add Actual Lateral Acceleration (roll compensated) when using Torque // Unit: m/s² UiElement actualLateralAccelElement = DeveloperUi::getActualLateralAccel(curvature, vEgo, roll, madsEnabled, latActive); - rh += drawDevUiElementRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color); + rh += drawDevUiRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color); } else { // Add Desired Steering Angle when using PID // Unit: Degrees UiElement steeringAngleDesiredDegElement = DeveloperUi::getSteeringAngleDesiredDeg(madsEnabled, latActive, steerAngleDesired, angleSteers); - rh += drawDevUiElementRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color); + rh += drawDevUiRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color); } ry = y + rh; // Add Device Memory (RAM) Usage // Unit: Percent UiElement memoryUsagePercentElement = DeveloperUi::getMemoryUsagePercent(memoryUsagePercent); - rh += drawDevUiElementRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color); + rh += drawDevUiRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color); ry = y + rh; rh += 25; @@ -805,7 +805,7 @@ void AnnotatedCameraWidgetSP::drawRightDevUi(QPainter &p, int x, int y) { QRect ldu(x, y, 184, rh); } -int AnnotatedCameraWidgetSP::drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { +int AnnotatedCameraWidgetSP::drawNewDevUi(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color) { p.setFont(InterFont(38, QFont::Bold)); drawCenteredLeftText(p, x, y, label, whiteColor(), value, units, color); @@ -818,39 +818,39 @@ void AnnotatedCameraWidgetSP::drawNewDevUi2(QPainter &p, int x, int y) { // Add Acceleration from Car // Unit: Meters per Second Squared UiElement aEgoElement = DeveloperUi::getAEgo(aEgo); - rw += drawNewDevUiElement(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color); + rw += drawNewDevUi(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color); // Add Velocity of Primary Lead Car // Unit: kph if metric, else mph UiElement vEgoLeadElement = DeveloperUi::getVEgoLead(lead_status, lead_v_rel, vEgo, is_metric, speedUnit); - rw += drawNewDevUiElement(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color); + rw += drawNewDevUi(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color); if (torquedUseParams) { // Add Friction Coefficient Raw from torqued // Unit: None UiElement frictionCoefficientFilteredElement = DeveloperUi::getFrictionCoefficientFiltered(frictionCoefficientFiltered, liveValid); - rw += drawNewDevUiElement(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color); + rw += drawNewDevUi(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color); // Add Lateral Acceleration Factor Raw from torqued // Unit: m/s² UiElement latAccelFactorFilteredElement = DeveloperUi::getLatAccelFactorFiltered(latAccelFactorFiltered, liveValid); - rw += drawNewDevUiElement(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color); + rw += drawNewDevUi(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color); } else { // Add Steering Torque from Car EPS // Unit: Newton Meters UiElement steeringTorqueEpsElement = DeveloperUi::getSteeringTorqueEps(steeringTorqueEps); - rw += drawNewDevUiElement(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color); + rw += drawNewDevUi(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color); // Add Bearing Degree and Direction from Car (Compass) // Unit: Meters UiElement bearingDegElement = DeveloperUi::getBearingDeg(bearingAccuracyDeg, bearingDeg); - rw += drawNewDevUiElement(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color); + rw += drawNewDevUi(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color); } // Add Altitude of Current Location // Unit: Meters UiElement altitudeElement = DeveloperUi::getAltitude(gpsAccuracy, altitude); - rw += drawNewDevUiElement(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color); + rw += drawNewDevUi(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color); } // ############################## DEV UI END ############################## diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h index 3758ff28a2..0210f259d7 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h @@ -37,8 +37,8 @@ private: // ############################## DEV UI START ############################## void drawRightDevUi(QPainter &p, int x, int y); - int drawDevUiElementRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); - int drawNewDevUiElement(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); + int drawDevUiRight(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); + int drawNewDevUi(QPainter &p, int x, int y, const QString &value, const QString &label, const QString &units, QColor &color); void drawNewDevUi2(QPainter &p, int x, int y); void drawCenteredLeftText(QPainter &p, int x, int y, const QString &text1, QColor color1, const QString &text2, const QString &text3, QColor color2); From 7ddc18d08f0c22f26694707618776ce232fd2e76 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 22:01:37 -0400 Subject: [PATCH 414/575] DeveloperUi: Add comments --- .../developer_ui/sp_priv_developer_ui.cc | 28 +++++++++++++++++++ .../qt/onroad/sp_priv_annotated_camera.cc | 26 ----------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc index 6b6a53799c..e251495273 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc @@ -4,6 +4,10 @@ #include "common/util.h" +// ********** metrics ********** + +// Add Relative Distance to Primary Lead Car +// Unit: Meters UiElement DeveloperUi::getDRel(bool lead_status, float lead_d_rel) { QString value = lead_status ? QString::number(lead_d_rel, 'f', 0) : "-"; QColor color = QColor(255, 255, 255, 255); @@ -20,6 +24,8 @@ UiElement DeveloperUi::getDRel(bool lead_status, float lead_d_rel) { return UiElement(value, "REL DIST", "m", color); } +// Add Relative Velocity vs Primary Lead Car +// Unit: kph if metric, else mph UiElement DeveloperUi::getVRel(bool lead_status, float lead_v_rel, bool is_metric, const QString &speed_unit) { QString value = lead_status ? QString::number(lead_v_rel * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; QColor color = QColor(255, 255, 255, 255); @@ -37,6 +43,8 @@ UiElement DeveloperUi::getVRel(bool lead_status, float lead_v_rel, bool is_metri return UiElement(value, "REL SPEED", speed_unit, color); } +// Add Real Steering Angle +// Unit: Degrees UiElement DeveloperUi::getSteeringAngleDeg(float angle_steers, bool mads_enabled, bool lat_active) { QString value = QString("%1%2%3").arg(QString::number(angle_steers, 'f', 1)).arg("°").arg(""); QColor color = (mads_enabled && lat_active) ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); @@ -52,6 +60,8 @@ UiElement DeveloperUi::getSteeringAngleDeg(float angle_steers, bool mads_enabled return UiElement(value, "REAL STEER", "", color); } +// Add Actual Lateral Acceleration (roll compensated) when using Torque +// Unit: m/s² UiElement DeveloperUi::getActualLateralAccel(float curvature, float v_ego, float roll, bool mads_enabled, bool lat_active) { double actualLateralAccel = (curvature * pow(v_ego, 2)) - (roll * 9.81); @@ -61,6 +71,8 @@ UiElement DeveloperUi::getActualLateralAccel(float curvature, float v_ego, float return UiElement(value, "ACTUAL LAT", "m/s²", color); } +// Add Desired Steering Angle when using PID +// Unit: Degrees UiElement DeveloperUi::getSteeringAngleDesiredDeg(bool mads_enabled, bool lat_active, float steer_angle_desired, float angle_steers) { QString value = (mads_enabled && lat_active) ? QString("%1%2%3").arg(QString::number(steer_angle_desired, 'f', 1)).arg("°").arg("") : "-"; QColor color = QColor(255, 255, 255, 255); @@ -80,6 +92,8 @@ UiElement DeveloperUi::getSteeringAngleDesiredDeg(bool mads_enabled, bool lat_ac return UiElement(value, "DESIRED STEER", "", color); } +// Add Device Memory (RAM) Usage +// Unit: Percent UiElement DeveloperUi::getMemoryUsagePercent(int memory_usage_percent) { QString value = QString("%1%2").arg(QString::number(memory_usage_percent, 'd', 0)).arg("%"); QColor color = (memory_usage_percent > 85) ? QColor(255, 188, 0, 255) : QColor(255, 255, 255, 255); @@ -87,6 +101,8 @@ UiElement DeveloperUi::getMemoryUsagePercent(int memory_usage_percent) { return UiElement(value, "RAM", "", color); } +// Add Vehicle Current Acceleration +// Unit: m/s² UiElement DeveloperUi::getAEgo(float a_ego) { QString value = QString::number(a_ego, 'f', 1); QColor color = QColor(255, 255, 255, 255); @@ -94,6 +110,8 @@ UiElement DeveloperUi::getAEgo(float a_ego) { return UiElement(value, "ACC.", "m/s²", color); } +// Add Relative Velocity to Primary Lead Car +// Unit: kph if metric, else mph UiElement DeveloperUi::getVEgoLead(bool lead_status, float lead_v_rel, float v_ego, bool is_metric, const QString &speed_unit) { QString value = lead_status ? QString::number((lead_v_rel + v_ego) * (is_metric ? MS_TO_KPH : MS_TO_MPH), 'f', 0) : "-"; QColor color = QColor(255, 255, 255, 255); @@ -111,6 +129,8 @@ UiElement DeveloperUi::getVEgoLead(bool lead_status, float lead_v_rel, float v_e return UiElement(value, "L.S.", speed_unit, color); } +// Add Friction Coefficient Raw from torqued +// Unit: None UiElement DeveloperUi::getFrictionCoefficientFiltered(float friction_coefficient_filtered, bool live_valid) { QString value = QString::number(friction_coefficient_filtered, 'f', 3); QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); @@ -118,6 +138,8 @@ UiElement DeveloperUi::getFrictionCoefficientFiltered(float friction_coefficient return UiElement(value, "FRIC.", "", color); } +// Add Lateral Acceleration Factor Raw from torqued +// Unit: m/s² UiElement DeveloperUi::getLatAccelFactorFiltered(float lat_accel_factor_filtered, bool live_valid) { QString value = QString::number(lat_accel_factor_filtered, 'f', 3); QColor color = live_valid ? QColor(0, 255, 0, 255) : QColor(255, 255, 255, 255); @@ -125,6 +147,8 @@ UiElement DeveloperUi::getLatAccelFactorFiltered(float lat_accel_factor_filtered return UiElement(value, "L.A.", "m/s²", color); } +// Add Steering Torque from Car EPS +// Unit: Newton Meters UiElement DeveloperUi::getSteeringTorqueEps(float steering_torque_eps) { QString value = QString::number(std::fabs(steering_torque_eps), 'f', 1); QColor color = QColor(255, 255, 255, 255); @@ -132,6 +156,8 @@ UiElement DeveloperUi::getSteeringTorqueEps(float steering_torque_eps) { return UiElement(value, "E.T.", "N·dm", color); } +// Add Bearing Degree and Direction from Car (Compass) +// Unit: Meters UiElement DeveloperUi::getBearingDeg(float bearing_accuracy_deg, float bearing_deg) { QString value = (bearing_accuracy_deg != 180.00) ? QString("%1%2%3").arg(QString::number(bearing_deg, 'd', 0)).arg("°").arg("") : "-"; QColor color = QColor(255, 255, 255, 255); @@ -162,6 +188,8 @@ UiElement DeveloperUi::getBearingDeg(float bearing_accuracy_deg, float bearing_d return UiElement(QString("%1 | %2").arg(dir_value).arg(value), "B.D.", "", color); } +// Add Altitude of Current Location +// Unit: Meters UiElement DeveloperUi::getAltitude(float gps_accuracy, float altitude) { QString value = (gps_accuracy != 0.00) ? QString::number(altitude, 'f', 1) : "-"; QColor color = QColor(255, 255, 255, 255); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc index 84e8fe1917..dd4b95dc57 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc @@ -763,39 +763,27 @@ void AnnotatedCameraWidgetSP::drawRightDevUi(QPainter &p, int x, int y) { int rh = 5; int ry = y; - // Add Relative Distance to Primary Lead Car - // Unit: Meters UiElement dRelElement = DeveloperUi::getDRel(lead_status, lead_d_rel); rh += drawDevUiRight(p, x, ry, dRelElement.value, dRelElement.label, dRelElement.units, dRelElement.color); ry = y + rh; - // Add Relative Velocity vs Primary Lead Car - // Unit: kph if metric, else mph UiElement vRelElement = DeveloperUi::getVRel(lead_status, lead_v_rel, is_metric, speedUnit); rh += drawDevUiRight(p, x, ry, vRelElement.value, vRelElement.label, vRelElement.units, vRelElement.color); ry = y + rh; - // Add Real Steering Angle - // Unit: Degrees UiElement steeringAngleDegElement = DeveloperUi::getSteeringAngleDeg(angleSteers, madsEnabled, latActive); rh += drawDevUiRight(p, x, ry, steeringAngleDegElement.value, steeringAngleDegElement.label, steeringAngleDegElement.units, steeringAngleDegElement.color); ry = y + rh; if (lateralState == "torque") { - // Add Actual Lateral Acceleration (roll compensated) when using Torque - // Unit: m/s² UiElement actualLateralAccelElement = DeveloperUi::getActualLateralAccel(curvature, vEgo, roll, madsEnabled, latActive); rh += drawDevUiRight(p, x, ry, actualLateralAccelElement.value, actualLateralAccelElement.label, actualLateralAccelElement.units, actualLateralAccelElement.color); } else { - // Add Desired Steering Angle when using PID - // Unit: Degrees UiElement steeringAngleDesiredDegElement = DeveloperUi::getSteeringAngleDesiredDeg(madsEnabled, latActive, steerAngleDesired, angleSteers); rh += drawDevUiRight(p, x, ry, steeringAngleDesiredDegElement.value, steeringAngleDesiredDegElement.label, steeringAngleDesiredDegElement.units, steeringAngleDesiredDegElement.color); } ry = y + rh; - // Add Device Memory (RAM) Usage - // Unit: Percent UiElement memoryUsagePercentElement = DeveloperUi::getMemoryUsagePercent(memoryUsagePercent); rh += drawDevUiRight(p, x, ry, memoryUsagePercentElement.value, memoryUsagePercentElement.label, memoryUsagePercentElement.units, memoryUsagePercentElement.color); ry = y + rh; @@ -815,40 +803,26 @@ int AnnotatedCameraWidgetSP::drawNewDevUi(QPainter &p, int x, int y, const QStri void AnnotatedCameraWidgetSP::drawNewDevUi2(QPainter &p, int x, int y) { int rw = 90; - // Add Acceleration from Car - // Unit: Meters per Second Squared UiElement aEgoElement = DeveloperUi::getAEgo(aEgo); rw += drawNewDevUi(p, rw, y, aEgoElement.value, aEgoElement.label, aEgoElement.units, aEgoElement.color); - // Add Velocity of Primary Lead Car - // Unit: kph if metric, else mph UiElement vEgoLeadElement = DeveloperUi::getVEgoLead(lead_status, lead_v_rel, vEgo, is_metric, speedUnit); rw += drawNewDevUi(p, rw, y, vEgoLeadElement.value, vEgoLeadElement.label, vEgoLeadElement.units, vEgoLeadElement.color); if (torquedUseParams) { - // Add Friction Coefficient Raw from torqued - // Unit: None UiElement frictionCoefficientFilteredElement = DeveloperUi::getFrictionCoefficientFiltered(frictionCoefficientFiltered, liveValid); rw += drawNewDevUi(p, rw, y, frictionCoefficientFilteredElement.value, frictionCoefficientFilteredElement.label, frictionCoefficientFilteredElement.units, frictionCoefficientFilteredElement.color); - // Add Lateral Acceleration Factor Raw from torqued - // Unit: m/s² UiElement latAccelFactorFilteredElement = DeveloperUi::getLatAccelFactorFiltered(latAccelFactorFiltered, liveValid); rw += drawNewDevUi(p, rw, y, latAccelFactorFilteredElement.value, latAccelFactorFilteredElement.label, latAccelFactorFilteredElement.units, latAccelFactorFilteredElement.color); } else { - // Add Steering Torque from Car EPS - // Unit: Newton Meters UiElement steeringTorqueEpsElement = DeveloperUi::getSteeringTorqueEps(steeringTorqueEps); rw += drawNewDevUi(p, rw, y, steeringTorqueEpsElement.value, steeringTorqueEpsElement.label, steeringTorqueEpsElement.units, steeringTorqueEpsElement.color); - // Add Bearing Degree and Direction from Car (Compass) - // Unit: Meters UiElement bearingDegElement = DeveloperUi::getBearingDeg(bearingAccuracyDeg, bearingDeg); rw += drawNewDevUi(p, rw, y, bearingDegElement.value, bearingDegElement.label, bearingDegElement.units, bearingDegElement.color); } - // Add Altitude of Current Location - // Unit: Meters UiElement altitudeElement = DeveloperUi::getAltitude(gpsAccuracy, altitude); rw += drawNewDevUi(p, rw, y, altitudeElement.value, altitudeElement.label, altitudeElement.units, altitudeElement.color); } From c4abe230a7cade982381224fc42f1f73f14f06d1 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 22:27:45 -0400 Subject: [PATCH 415/575] ui.cc|h: split completely (refactor later) (WIP) --- selfdrive/ui/SConscript | 3 + selfdrive/ui/mui.cc | 4 + selfdrive/ui/qt/body.h | 4 + selfdrive/ui/qt/home.h | 3 +- selfdrive/ui/qt/maps/map.cc | 6 + selfdrive/ui/qt/maps/map.h | 4 + selfdrive/ui/qt/maps/map_eta.cc | 4 + selfdrive/ui/qt/maps/map_instructions.cc | 4 + selfdrive/ui/qt/maps/map_panel.cc | 4 + selfdrive/ui/qt/network/networking.cc | 4 + selfdrive/ui/qt/network/wifi_manager.cc | 4 + selfdrive/ui/qt/offroad/experimental_mode.cc | 4 + selfdrive/ui/qt/offroad/settings.h | 3 +- selfdrive/ui/qt/offroad/software_settings.cc | 3 +- selfdrive/ui/qt/offroad_home.h | 3 +- selfdrive/ui/qt/onroad/alerts.h | 4 + selfdrive/ui/qt/onroad/buttons.h | 4 + selfdrive/ui/qt/request_repeater.h | 4 + selfdrive/ui/qt/sidebar.h | 4 + selfdrive/ui/qt/widgets/cameraview.h | 4 + selfdrive/ui/qt/widgets/wifi.h | 4 + selfdrive/ui/sunnypilot/SConscript | 3 +- .../qt/offroad/lane_change_settings.h | 4 + .../ui/sunnypilot/qt/offroad/mads_settings.h | 4 + .../ui/sunnypilot/qt/offroad/models_fetcher.h | 3 +- .../ui/sunnypilot/qt/offroad/osm_settings.h | 4 + .../qt/offroad/software_settings_sp.h | 4 + .../qt/offroad/speed_limit_control_settings.h | 4 + .../qt/offroad/speed_limit_policy_settings.h | 4 + .../qt/offroad/speed_limit_warning_settings.h | 4 + .../qt/offroad/sunnylink_settings.h | 4 + .../qt/offroad/sunnypilot_settings.h | 4 + .../sunnypilot/qt/offroad/vehicle_settings.h | 4 + .../sunnypilot/qt/offroad/visuals_settings.h | 4 + .../ui/sunnypilot/qt/onroad/onroad_settings.h | 3 +- .../qt/onroad/onroad_settings_panel.cc | 10 +- selfdrive/ui/sunnypilot/qt/sp_priv_home.h | 4 +- selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h | 4 + selfdrive/ui/sunnypilot/sp_priv_ui.cc | 530 ++++++++++++++++++ selfdrive/ui/sunnypilot/sp_priv_ui.h | 346 ++++++++++++ selfdrive/ui/tests/ui_snapshot.cc | 4 + selfdrive/ui/ui.cc | 188 +------ selfdrive/ui/ui.h | 160 +----- 43 files changed, 1038 insertions(+), 342 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/sp_priv_ui.cc create mode 100644 selfdrive/ui/sunnypilot/sp_priv_ui.h diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index f62e204136..dd7aad7a97 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -30,6 +30,9 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src +if GetOption('sunnypilot'): + widgets_src.remove("ui.cc") + qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if GetOption('sunnypilot') else [] if maps: base_libs += ['QMapLibre'] diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc index 98029ee311..96a086c785 100644 --- a/selfdrive/ui/mui.cc +++ b/selfdrive/ui/mui.cc @@ -3,7 +3,11 @@ #include #include "cereal/messaging/messaging.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/qt_window.h" int main(int argc, char *argv[]) { diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h index 567a54d49b..68495aaf73 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -5,7 +5,11 @@ #include #include "common/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class RecordButton : public QPushButton { Q_OBJECT diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index c64592d49a..80a4b79ef8 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -13,15 +13,16 @@ #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" -#include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #define OnroadWindow OnroadWindowSP #define OffroadHome OffroadHomeSP #else +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/offroad_home.h" diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index b5b731ef66..79b5581efe 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -8,7 +8,11 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const int INTERACTION_TIMEOUT = 100; @@ -110,6 +114,7 @@ void MapWindow::initLayers() { // TODO: remove, symbol-sort-key does not seem to matter outside of each layer m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0); } +#ifdef SUNNYPILOT if ((!m_map->layerExists("buildingsLayer")) && uiState()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage qDebug() << "Initializing buildingsLayer"; QVariantMap buildings; @@ -153,6 +158,7 @@ void MapWindow::initLayers() { m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase); m_map->setLayoutProperty("buildingsLayer", "visibility", "visible"); } +#endif } void MapWindow::updateState(const UIState &s) { diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 62871e79d2..77fbdf6e36 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -20,7 +20,11 @@ #include "cereal/messaging/messaging.h" #include "common/params.h" #include "common/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/maps/map_eta.h" #include "selfdrive/ui/qt/maps/map_instructions.h" diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc index 0eb77e36ce..868c6ca550 100644 --- a/selfdrive/ui/qt/maps/map_eta.cc +++ b/selfdrive/ui/qt/maps/map_eta.cc @@ -4,7 +4,11 @@ #include #include "selfdrive/ui/qt/maps/map_helpers.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const float MANEUVER_TRANSITION_THRESHOLD = 10; diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc index ba8cb356bd..6bfeefbafc 100644 --- a/selfdrive/ui/qt/maps/map_instructions.cc +++ b/selfdrive/ui/qt/maps/map_instructions.cc @@ -4,7 +4,11 @@ #include #include "selfdrive/ui/qt/maps/map_helpers.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const QString ICON_SUFFIX = ".png"; diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index c4cc20e21d..75dc85b01e 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -6,7 +6,11 @@ #include "selfdrive/ui/qt/maps/map.h" #include "selfdrive/ui/qt/maps/map_settings.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index a84460d5e9..acf6add481 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -6,7 +6,11 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/scrollview.h" diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc index 717da47096..7cfa17f774 100644 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -2,7 +2,11 @@ #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/widgets/prime.h" #include "common/params.h" diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index b2c6f3323a..6fd4e3a895 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -6,7 +6,11 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) { chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 734f47e01e..96fc04bbca 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -11,15 +11,16 @@ #include #include -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #define ListWidget ListWidgetSP #define ParamControl ParamControlSP #define ButtonParamControl ButtonParamControlSP #else +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index fa39daa6de..972005864a 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -9,11 +9,12 @@ #include "common/params.h" #include "common/util.h" -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #else +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif #include "selfdrive/ui/qt/widgets/input.h" diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h index f277d852c4..38cd8afa95 100644 --- a/selfdrive/ui/qt/offroad_home.h +++ b/selfdrive/ui/qt/offroad_home.h @@ -13,9 +13,9 @@ #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" -#include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" @@ -23,6 +23,7 @@ #define OffroadHomeImp OffroadHomeSP #define Sidebar SidebarSP #else +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/sidebar.h" diff --git a/selfdrive/ui/qt/onroad/alerts.h b/selfdrive/ui/qt/onroad/alerts.h index 1f76ba305b..a584a8682c 100644 --- a/selfdrive/ui/qt/onroad/alerts.h +++ b/selfdrive/ui/qt/onroad/alerts.h @@ -2,7 +2,11 @@ #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class OnroadAlerts : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h index feee1ba639..a2f6845a8d 100644 --- a/selfdrive/ui/qt/onroad/buttons.h +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -2,7 +2,11 @@ #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const int btn_size = 192; const int img_size = (btn_size / 4) * 3; diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h index 45cb643b13..43ce6492c9 100644 --- a/selfdrive/ui/qt/request_repeater.h +++ b/selfdrive/ui/qt/request_repeater.h @@ -3,7 +3,11 @@ #include "common/swaglog.h" #include "common/util.h" #include "selfdrive/ui/qt/api.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class RequestRepeater : public HttpRequest { diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 741b4214fa..d799747ca5 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -5,7 +5,11 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif typedef QPair, QColor> ItemStatus; Q_DECLARE_METATYPE(ItemStatus); diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 526c17e77c..09a1479bf6 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -24,7 +24,11 @@ #include "msgq/visionipc/visionipc_client.h" #include "system/camerad/cameras/camera_common.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const int FRAME_BUFFER_SIZE = 5; static_assert(FRAME_BUFFER_SIZE <= YUV_BUFFER_COUNT); diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h index 60c865f2b8..bdcd9aa2ca 100644 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -4,7 +4,11 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class WiFiPromptWidget : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index bcc296e496..170d87c4bf 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -18,7 +18,8 @@ widgets_src = [ "sunnypilot/qt/offroad/sunnylink_settings.cc", "sunnypilot/qt/widgets/sp_priv_controls.cc", "sunnypilot/qt/widgets/sp_priv_scrollview.cc", - "sunnypilot/qt/widgets/sp_priv_toggle.cc" + "sunnypilot/qt/widgets/sp_priv_toggle.cc", + "sunnypilot/sp_priv_ui.cc" ] network_src = [ diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h index dcc9a457d0..32a74e9e97 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h @@ -1,6 +1,10 @@ #pragma once +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h index 374e404f29..cd57ac48f9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h @@ -1,6 +1,10 @@ #pragma once +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h index 8780c8028a..ac007d3594 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h @@ -9,12 +9,13 @@ #include "common/swaglog.h" #include "common/util.h" -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #else +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif #include "system/hardware/hw.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h index 853033be7c..2de838f1e3 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h @@ -13,7 +13,11 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "system/hardware/hw.h" constexpr int FAST_REFRESH_INTERVAL = 1000; // ms diff --git a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h index 4f8fc0d22a..4351cd3859 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h @@ -1,7 +1,11 @@ #pragma once #include "common/model.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/offroad/settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h index 91ad4842db..4a5e230a56 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h @@ -1,6 +1,10 @@ #pragma once +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h index a5e8d3e393..d4f55bd5e3 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h @@ -1,6 +1,10 @@ #pragma once +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h index 07ee7d18fa..add408f8bc 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h @@ -1,6 +1,10 @@ #pragma once +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h index 1a3cb630d9..6a8faf5073 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h @@ -5,7 +5,11 @@ #include #include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index c42c0b6c66..061e1c6560 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -1,7 +1,11 @@ #pragma once #include "common/model.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h index b66d29dcd6..f4ce79808e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h @@ -3,7 +3,11 @@ #include #include "common/watchdog.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h index 244bc09b7e..3377c2840c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h @@ -1,6 +1,10 @@ #pragma once +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" class VisualsPanel : public ListWidgetSP { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h index 17d0f461d7..f2edfd387a 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h @@ -6,11 +6,12 @@ #include #include "common/params.h" -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #else +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc index 8a8283fad3..352b3e904f 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc @@ -1,11 +1,15 @@ -#include "../../../sunnypilot/qt/onroad/onroad_settings_panel.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" #include #include #include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" -#include "../../../qt/util.h" -#include "../../../ui.h" +#include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else +#include "selfdrive/ui/ui.h" +#endif OnroadSettingsPanel::OnroadSettingsPanel(QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h index 5f49ee4794..f99fd62439 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h @@ -11,14 +11,16 @@ #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" -#include "selfdrive/ui/ui.h" + #include "selfdrive/ui/qt/home.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #define OnroadWindow OnroadWindowSP #else +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #endif diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h index 158f1bd96c..dc79c58605 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h @@ -6,7 +6,11 @@ #include #include "selfdrive/ui/qt/sidebar.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class SidebarSP : public Sidebar { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.cc b/selfdrive/ui/sunnypilot/sp_priv_ui.cc new file mode 100644 index 0000000000..29331e872f --- /dev/null +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.cc @@ -0,0 +1,530 @@ +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" + +#include +#include +#include + +#include + +#include "common/transformations/orientation.hpp" +#include "common/params.h" +#include "common/swaglog.h" +#include "common/util.h" +#include "common/watchdog.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" +#include "system/hardware/hw.h" + +#define BACKLIGHT_DT 0.05 +#define BACKLIGHT_TS 10.00 + +// Projects a point in car to space to the corresponding point in full frame +// image space. +static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { + const float margin = 1000.0f; + const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; + + const vec3 pt = (vec3){{in_x, in_y, in_z}}; + const vec3 Ep = matvecmul3(s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib, pt); + const vec3 KEp = matvecmul3(s->scene.wide_cam ? ECAM_INTRINSIC_MATRIX : FCAM_INTRINSIC_MATRIX, Ep); + + // Project. + QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]}); + if (clip_region.contains(point)) { + *out = point; + return true; + } + return false; +} + +int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height) { + const auto line_x = line.getX(); + int max_idx = 0; + for (int i = 1; i < line_x.size() && line_x[i] <= path_height; ++i) { + max_idx = i; + } + return max_idx; +} + +void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line) { + for (int i = 0; i < 2; ++i) { + auto lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo(); + if (lead_data.getStatus()) { + float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())]; + calib_frame_to_full_frame(s, lead_data.getDRel(), -lead_data.getYRel(), z + 1.22, &s->scene.lead_vertices[i]); + } + } +} + +void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, + float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); + QPointF left, right; + pvd->clear(); + for (int i = 0; i <= max_idx; i++) { + // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera + if (line_x[i] < 0) continue; + + bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); + bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); + if (l && r) { + // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts + if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { + continue; + } + pvd->push_back(left); + pvd->push_front(right); + } + } +} + +void update_model(UIState *s, + const cereal::ModelDataV2::Reader &model) { + UIScene &scene = s->scene; + auto model_position = model.getPosition(); + float max_distance = std::clamp(*(model_position.getX().end() - 1), + MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); + + // update lane lines + const auto lane_lines = model.getLaneLines(); + const auto lane_line_probs = model.getLaneLineProbs(); + int max_idx = get_path_length_idx(lane_lines[0], max_distance); + for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { + scene.lane_line_probs[i] = lane_line_probs[i]; + update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, 0, &scene.lane_line_vertices[i], max_idx); + } + + // lane barriers for blind spot + int max_distance_barrier = 40; + int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); + update_line_data(s, lane_lines[1], 0, -0.05, -0.6, &scene.lane_barrier_vertices[0], max_idx_barrier, false); + update_line_data(s, lane_lines[2], 0, -0.05, -0.6, &scene.lane_barrier_vertices[1], max_idx_barrier, false); + + // update road edges + const auto road_edges = model.getRoadEdges(); + const auto road_edge_stds = model.getRoadEdgeStds(); + for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { + scene.road_edge_stds[i] = road_edge_stds[i]; + update_line_data(s, road_edges[i], 0.025, 0, 0, &scene.road_edge_vertices[i], max_idx); + } + + // update path + auto lead_one = (*s->sm)["radarState"].getRadarState().getLeadOne(); + if (lead_one.getStatus()) { + const float lead_d = lead_one.getDRel() * 2.; + max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); + } + max_idx = get_path_length_idx(model_position, max_distance); + update_line_data(s, model_position, 0.9 - scene.mads_path_range * scene.mads_path_scale, 1.22, 1.22, &scene.track_vertices, max_idx, false); + update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); +} + +void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { + UIScene &scene = s->scene; + const auto driver_orient = is_rhd ? driverstate.getRightDriverData().getFaceOrientation() : driverstate.getLeftDriverData().getFaceOrientation(); + for (int i = 0; i < std::size(scene.driver_pose_vals); i++) { + float v_this = (i == 0 ? (driver_orient[i] < 0 ? 0.7 : 0.9) : 0.4) * driver_orient[i]; + scene.driver_pose_diff[i] = fabs(scene.driver_pose_vals[i] - v_this); + scene.driver_pose_vals[i] = 0.8 * v_this + (1 - 0.8) * scene.driver_pose_vals[i]; + scene.driver_pose_sins[i] = sinf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); + scene.driver_pose_coss[i] = cosf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); + } + + auto [sin_y, sin_x, sin_z] = scene.driver_pose_sins; + auto [cos_y, cos_x, cos_z] = scene.driver_pose_coss; + + const mat3 r_xyz = (mat3){{ + cos_x * cos_z, + cos_x * sin_z, + -sin_x, + + -sin_y * sin_x * cos_z - cos_y * sin_z, + -sin_y * sin_x * sin_z + cos_y * cos_z, + -sin_y * cos_x, + + cos_y * sin_x * cos_z - sin_y * sin_z, + cos_y * sin_x * sin_z + sin_y * cos_z, + cos_y * cos_x, + }}; + + // transform vertices + for (int kpi = 0; kpi < std::size(default_face_kpts_3d); kpi++) { + vec3 kpt_this = matvecmul3(r_xyz, default_face_kpts_3d[kpi]); + scene.face_kpts_draw[kpi] = (vec3){{kpt_this.v[0], kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}}; + } +} + +static void update_sockets(UIState *s) { + s->sm->update(0); +} + +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(); + auto rpy_list = live_calib.getRpyCalib(); + auto wfde_list = live_calib.getWideFromDeviceEuler(); + Eigen::Vector3d rpy; + Eigen::Vector3d wfde; + if (rpy_list.size() == 3) rpy << rpy_list[0], rpy_list[1], rpy_list[2]; + if (wfde_list.size() == 3) wfde << wfde_list[0], wfde_list[1], wfde_list[2]; + Eigen::Matrix3d device_from_calib = euler2rot(rpy); + Eigen::Matrix3d wide_from_device = euler2rot(wfde); + Eigen::Matrix3d view_from_device; + view_from_device << 0, 1, 0, + 0, 0, 1, + 1, 0, 0; + Eigen::Matrix3d view_from_calib = view_from_device * device_from_calib; + Eigen::Matrix3d view_from_wide_calib = view_from_device * wide_from_device * device_from_calib; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + scene.view_from_calib.v[i*3 + j] = view_from_calib(i, j); + scene.view_from_wide_calib.v[i*3 + j] = view_from_wide_calib(i, j); + } + } + scene.calibration_valid = live_calib.getCalStatus() == cereal::LiveCalibrationData::Status::CALIBRATED; + scene.calibration_wide_valid = wfde_list.size() == 3; + } + if (sm.updated("pandaStates")) { + auto pandaStates = sm["pandaStates"].getPandaStates(); + if (pandaStates.size() > 0) { + scene.pandaType = pandaStates[0].getPandaType(); + + if (scene.pandaType != cereal::PandaState::PandaType::UNKNOWN) { + scene.ignition = false; + for (const auto& pandaState : pandaStates) { + scene.ignition |= pandaState.getIgnitionLine() || pandaState.getIgnitionCan(); + } + } + } + } else if ((s->sm->frame - s->sm->rcv_frame("pandaStates")) > 5*UI_FREQ) { + scene.pandaType = cereal::PandaState::PandaType::UNKNOWN; + } + if (sm.updated("carParams")) { + scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); + } + if (sm.updated("wideRoadCameraState")) { + auto cam_state = sm["wideRoadCameraState"].getWideRoadCameraState(); + 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); + } else if (!sm.allAliveAndValid({"wideRoadCameraState"})) { + scene.light_sensor = -1; + } + scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); + + scene.world_objects_visible = scene.world_objects_visible || + (scene.started && + sm.rcv_frame("liveCalibration") > scene.started_frame && + sm.rcv_frame("modelV2") > scene.started_frame); + // TODO: SP - Set this dynamically on init with manual toggle or driving model selection + if (sm.updated("lateralPlanSPDEPRECATED")) { + scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); + } + if (sm.updated("controlsState")) { + scene.controlsState = sm["controlsState"].getControlsState(); + } + if (sm.updated("longitudinalPlanSP")) { + for (int i = 0; i < std::size(scene.e2eX); i++) { + scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; + } + } + if (sm.updated("modelV2SP")) { + auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); + scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); + scene.driving_model_generation = model_v2_sp.getModelGeneration(); + scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); + } +} + +void ui_update_params(UIState *s) { + auto params = Params(); + s->scene.is_metric = params.getBool("IsMetric"); + s->scene.map_on_left = params.getBool("NavSettingLeftSide"); + + s->scene.visual_brake_lights = params.getBool("BrakeLights"); + s->scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); + s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); + s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); + s->scene.stand_still_timer = params.getBool("StandStillTimer"); + s->scene.show_debug_ui = params.getBool("ShowDebugUI"); + s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); + s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); + s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); + s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); + s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); + s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); + s->scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); + s->scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); + s->scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); + s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); + s->scene.live_torque_toggle = params.getBool("LiveTorque"); + s->scene.torqued_override = params.getBool("TorquedOverride"); + s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); + s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); + s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); + s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); + s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); + s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); + s->scene.feature_status_toggle = params.getBool("FeatureStatus"); + s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); + + // Handle Onroad Screen Off params + if (s->scene.onroadScreenOff > 0) { + s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ; + } else if (s->scene.onroadScreenOff == 0) { + s->scene.osoTimer = 30 * UI_FREQ; + } else if (s->scene.onroadScreenOff == -1) { + s->scene.osoTimer = 15 * UI_FREQ; + } else { + s->scene.osoTimer = -1; + } +} + +void UIState::updateStatus() { + auto params = Params(); + if (scene.started && sm->updated("controlsState")) { + auto controls_state = (*sm)["controlsState"].getControlsState(); + auto car_control = (*sm)["carControl"].getCarControl(); + auto car_state = (*sm)["carState"].getCarState(); + auto mads_enabled = car_state.getMadsEnabled(); + auto state = controls_state.getState(); + if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { + status = STATUS_OVERRIDE; + } else { + status = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED : STATUS_MADS : STATUS_DISENGAGED; + } + + if (mads_enabled != last_mads_enabled) { + mads_path_state = true; + } + last_mads_enabled = mads_enabled; + if (mads_path_state) { + if (mads_enabled) { + mads_path_count = fmax(mads_path_count - 1, 0); + if (mads_path_count == 0) { + mads_path_state = false; + } + } else { + mads_path_count = fmin(mads_path_count + 1, mads_path_timestep); + if (mads_path_count == mads_path_timestep) { + mads_path_state = false; + } + } + } + scene.mads_path_scale = mads_path_count * (1 / mads_path_timestep); + } + + // Handle onroad/offroad transition + if (scene.started != started_prev || sm->frame == 1) { + if (scene.started) { + status = STATUS_DISENGAGED; + scene.started_frame = sm->frame; + } + started_prev = scene.started; + scene.world_objects_visible = false; + emit offroadTransition(!scene.started); + } + + if (scene.started) { + // Auto hide UI button state machine + { + if (scene.button_auto_hide) { + if (scene.touch_to_wake) { + scene.sleep_btn = 30 * UI_FREQ; + } else if (scene.sleep_btn > 0) { + scene.sleep_btn--; + } else if (scene.sleep_btn == -1) { + scene.sleep_btn = 30 * UI_FREQ; + } + // Check if the sleep button should be fading in + if (scene.sleep_btn_fading_in) { + // Increase the opacity of the sleep button by a small amount + if (scene.sleep_btn_opacity < 20) { + scene.sleep_btn_opacity+= 10; + } + if (scene.sleep_btn_opacity >= 20) { + // If the opacity has reached its maximum value, stop fading in + scene.sleep_btn_fading_in = false; + scene.sleep_btn_opacity = 20; + } + } else if (scene.sleep_btn == 0) { + // Fade out the sleep button as before + if (scene.sleep_btn_opacity > 0) { + scene.sleep_btn_opacity-= 2; + } + } else { + // Set the opacity of the sleep button to its maximum value + scene.sleep_btn_opacity = 20; + } + } else { + scene.sleep_btn_opacity = 20; + } + } + + // Onroad Screen Off Brightness + Timer + Global Brightness + { + if (scene.onroadScreenOff != -2 && scene.touched2) { + scene.sleep_time = scene.osoTimer; + } else if (scene.onroadScreenOff != -2 && + ((scene.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && + ((scene.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene.onroadScreenOffEvent) || + (scene.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { + scene.sleep_time = scene.osoTimer; + } else if (scene.sleep_time > 0 && scene.onroadScreenOff != -2) { + scene.sleep_time--; + } else if (scene.sleep_time == -1 && scene.onroadScreenOff != -2) { + scene.sleep_time = scene.osoTimer; + } + } + } + + if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz + scene.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); + } + + scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); +} + +UIState::UIState(QObject *parent) : QObject(parent) { + sm = std::make_unique>({ + "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", + "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", + "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", + "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", + "controlsStateSP", "modelV2SP" + }); + + Params params; + language = QString::fromStdString(params.get("LanguageSetting")); + auto prime_value = params.get("PrimeType"); + if (!prime_value.empty()) { + prime_type = static_cast(std::atoi(prime_value.c_str())); + } + + // update timer + timer = new QTimer(this); + QObject::connect(timer, &QTimer::timeout, this, &UIState::update); + timer->start(1000 / UI_FREQ); +} + +void UIState::update() { + update_sockets(this); + update_state(this); + updateStatus(); + + if (sm->frame % UI_FREQ == 0) { + watchdog_kick(nanos_since_boot()); + } + emit uiUpdate(*this); +} + +void UIState::setPrimeType(PrimeType type) { + if (type != prime_type) { + bool prev_prime = hasPrime(); + + prime_type = type; + Params().put("PrimeType", std::to_string(prime_type)); + emit primeTypeChanged(prime_type); + + bool prime = hasPrime(); + if (prev_prime != prime) { + emit primeChanged(prime); + } + } +} + +void UIState::setSunnylinkRoles(const std::vector& roles) { + sunnylinkRoles = roles; + emit sunnylinkRolesChanged(roles); +} + +void UIState::setSunnylinkDeviceUsers(const std::vector& users) { + sunnylinkUsers = users; + emit sunnylinkDeviceUsersChanged(users); +} + +Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { + setAwake(true); + resetInteractiveTimeout(); + + QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); +} + +void Device::update(const UIState &s) { + updateBrightness(s); + updateWakefulness(s); +} + +void Device::setAwake(bool on) { + if (on != awake) { + awake = on; + Hardware::set_display_power(awake); + LOGD("setting display power %d", awake); + emit displayPowerChanged(awake); + } +} + +void Device::resetInteractiveTimeout(int timeout) { + if (timeout == -1) { + timeout = (ignition_on ? 10 : 30); + } + interactive_timeout = timeout * UI_FREQ; +} + +void Device::updateBrightness(const UIState &s) { + float clipped_brightness = offroad_brightness; + if (s.scene.started && s.scene.light_sensor > 0) { + clipped_brightness = s.scene.light_sensor; + + // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm + if (clipped_brightness <= 8) { + clipped_brightness = (clipped_brightness / 903.3); + } else { + clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0); + } + + // Scale back to 10% to 100% + clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); + } + + int brightness = brightness_filter.update(clipped_brightness); + if (!awake) { + brightness = 0; + } else if (s.scene.started && s.scene.sleep_time == 0 && s.scene.onroadScreenOff != -2) { + brightness = s.scene.onroadScreenOffBrightness * 0.01 * brightness; + } else if (s.scene.brightness) { + brightness = s.scene.brightness * 0.99; + } + + if (brightness != last_brightness) { + if (!brightness_future.isRunning()) { + brightness_future = QtConcurrent::run(Hardware::set_brightness, brightness); + last_brightness = brightness; + } + } +} + +void Device::updateWakefulness(const UIState &s) { + bool ignition_just_turned_off = !s.scene.ignition && ignition_on; + ignition_on = s.scene.ignition; + + if (ignition_just_turned_off) { + resetInteractiveTimeout(); + } else if (interactive_timeout > 0 && --interactive_timeout == 0) { + emit interactiveTimeout(); + } + + setAwake(s.scene.ignition || interactive_timeout > 0); +} + +UIState *uiState() { + static UIState ui_state; + return &ui_state; +} + +Device *device() { + static Device _device; + return &_device; +} diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.h b/selfdrive/ui/sunnypilot/sp_priv_ui.h new file mode 100644 index 0000000000..996c9fe5cc --- /dev/null +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.h @@ -0,0 +1,346 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "cereal/messaging/messaging.h" +#include "common/mat.h" +#include "common/params.h" +#include "common/timing.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h" +#include "system/hardware/hw.h" + +const int UI_BORDER_SIZE = 30; +const int UI_HEADER_HEIGHT = 420; + +const int UI_ROAD_NAME_MARGIN_X = 14; + +struct FeatureStatusText { + const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto"}; + const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed"}; + const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active"}; +}; + +struct FeatureStatusColor { + const QStringList dlp_list_color = { "#2020f8", "#0df87a", "#0df8f8" }; + const QStringList gac_list_color = { "#ff4b4b", "#fcff4b", "#4bff66", "#6a0ac9" }; + const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; +}; + +const float DRIVING_PATH_WIDE = 0.9; +const float DRIVING_PATH_NARROW = 0.25; + +const int UI_FREQ = 20; // Hz +const int BACKLIGHT_OFFROAD = 50; + +const float MIN_DRAW_DISTANCE = 10.0; +const float MAX_DRAW_DISTANCE = 100.0; +constexpr mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; +constexpr mat3 FCAM_INTRINSIC_MATRIX = (mat3){{2648.0, 0.0, 1928.0 / 2, + 0.0, 2648.0, 1208.0 / 2, + 0.0, 0.0, 1.0}}; +// tici ecam focal probably wrong? magnification is not consistent across frame +// Need to retrain model before this can be changed +constexpr mat3 ECAM_INTRINSIC_MATRIX = (mat3){{567.0, 0.0, 1928.0 / 2, + 0.0, 567.0, 1208.0 / 2, + 0.0, 0.0, 1.0}}; + + +constexpr vec3 default_face_kpts_3d[] = { + {-5.98, -51.20, 8.00}, {-17.64, -49.14, 8.00}, {-23.81, -46.40, 8.00}, {-29.98, -40.91, 8.00}, {-32.04, -37.49, 8.00}, + {-34.10, -32.00, 8.00}, {-36.16, -21.03, 8.00}, {-36.16, 6.40, 8.00}, {-35.47, 10.51, 8.00}, {-32.73, 19.43, 8.00}, + {-29.30, 26.29, 8.00}, {-24.50, 33.83, 8.00}, {-19.01, 41.37, 8.00}, {-14.21, 46.17, 8.00}, {-12.16, 47.54, 8.00}, + {-4.61, 49.60, 8.00}, {4.99, 49.60, 8.00}, {12.53, 47.54, 8.00}, {14.59, 46.17, 8.00}, {19.39, 41.37, 8.00}, + {24.87, 33.83, 8.00}, {29.67, 26.29, 8.00}, {33.10, 19.43, 8.00}, {35.84, 10.51, 8.00}, {36.53, 6.40, 8.00}, + {36.53, -21.03, 8.00}, {34.47, -32.00, 8.00}, {32.42, -37.49, 8.00}, {30.36, -40.91, 8.00}, {24.19, -46.40, 8.00}, + {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, +}; + + +typedef enum UIStatus { + STATUS_DISENGAGED, + STATUS_OVERRIDE, + STATUS_ENGAGED, + STATUS_MADS, +} UIStatus; + +enum PrimeType { + UNKNOWN = -2, + UNPAIRED = -1, + NONE = 0, + MAGENTA = 1, + LITE = 2, + BLUE = 3, + MAGENTA_NEW = 4, + PURPLE = 5, +}; + +const QColor bg_colors [] = { + [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), + [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), + [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), + [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), +}; + + +const QColor tcs_colors [] = { + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), +}; + +typedef struct UIScene { + bool calibration_valid = false; + bool calibration_wide_valid = false; + bool wide_cam = true; + mat3 view_from_calib = DEFAULT_CALIBRATION; + mat3 view_from_wide_calib = DEFAULT_CALIBRATION; + cereal::PandaState::PandaType pandaType; + cereal::ControlsState::Reader controlsState; + + // Debug UI + bool show_debug_ui; + + // Speed limit control + bool speed_limit_control_enabled; + int speed_limit_control_policy; + double last_speed_limit_sign_tap; + + // modelV2 + float lane_line_probs[4]; + float road_edge_stds[2]; + QPolygonF track_vertices; + QPolygonF track_edge_vertices; + QPolygonF lane_line_vertices[4]; + QPolygonF road_edge_vertices[2]; + QPolygonF lane_barrier_vertices[2]; + + // lead + QPointF lead_vertices[2]; + + // DMoji state + float driver_pose_vals[3]; + float driver_pose_diff[3]; + float driver_pose_sins[3]; + float driver_pose_coss[3]; + vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; + + bool navigate_on_openpilot_deprecated = false; + cereal::LongitudinalPersonality personality; + cereal::AccelerationPersonality accel_personality; + + float light_sensor = -1; + bool started, ignition, is_metric, map_on_left, longitudinal_control; + bool world_objects_visible = false; + uint64_t started_frame; + + int dynamic_lane_profile; + bool dynamic_lane_profile_status = true; + + bool visual_brake_lights; + + int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; + bool onroadScreenOffEvent; + int sleep_time = -1; + bool touched2 = false; + + bool stand_still_timer; + + bool hide_vego_ui, true_vego_ui; + + int chevron_data; + + bool gac; + int longitudinal_personality; + int longitudinal_accel_personality; + + bool map_visible; + int dev_ui_info; + bool live_torque_toggle; + + bool touch_to_wake = false; + int sleep_btn = -1; + bool sleep_btn_fading_in = false; + int sleep_btn_opacity = 20; + bool button_auto_hide; + + bool reverse_dm_cam; + + bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; + float e2eX[13] = {0}; + + int sidebar_temp_options; + + float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; + float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 + + bool onroad_settings_visible; + + bool map_3d_buildings; + + bool torqued_override; + + bool dynamic_experimental_control; + + int speed_limit_control_engage_type; + + bool mapbox_fullscreen; + + bool speed_limit_warning_flash; + int speed_limit_warning_type; + int speed_limit_warning_value_offset; + + bool custom_driving_model_valid; + cereal::ModelGeneration driving_model_generation; + uint32_t driving_model_capabilities; + + bool feature_status_toggle; + bool onroad_settings_toggle; + + bool dynamic_personality; +} UIScene; + +class UIState : public QObject { + Q_OBJECT + +public: + UIState(QObject* parent = 0); + void updateStatus(); + inline bool engaged() const { + return scene.started && (*sm)["controlsState"].getControlsState().getEnabled(); + } + + void setPrimeType(PrimeType type); + inline PrimeType primeType() const { return prime_type; } + inline bool hasPrime() const { return prime_type > PrimeType::NONE; } + + void setSunnylinkRoles(const std::vector &roles); + void setSunnylinkDeviceUsers(const std::vector &users); + + inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } + inline bool isSunnylinkAdmin() const { + return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { + return role.roleType == RoleType::Admin; + }); + } + inline bool isSunnylinkSponsor() const { + return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { + return role.roleType == RoleType::Sponsor && role.as().roleTier != SponsorTier::Free; + }); + } + inline SponsorRoleModel sunnylinkSponsorRole() const { + std::optional sponsorRoleWithHighestTier = std::nullopt; + for (const auto &role : sunnylinkRoles) { + if(role.roleType != RoleType::Sponsor) + continue; + + if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { + sponsorRoleWithHighestTier = sponsorRole; + } + } + return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free)); + } + inline SponsorTier sunnylinkSponsorTier() const { + return sunnylinkSponsorRole().roleTier; + } + inline std::vector sunnylinkDeviceUsers() const { return sunnylinkUsers; } + inline bool isSunnylinkPaired() const { + return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) { + return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; + }); + } + + int fb_w = 0, fb_h = 0; + + std::unique_ptr sm; + + UIStatus status; + UIScene scene = {}; + + QString language; + + QTransform car_space_transform; + +signals: + void uiUpdate(const UIState &s); + void offroadTransition(bool offroad); + void primeChanged(bool prime); + void primeTypeChanged(PrimeType prime_type); + + void sunnylinkRoleChanged(bool subscriber); + void sunnylinkRolesChanged(std::vector roles); + void sunnylinkDeviceUsersChanged(std::vector users); + +private slots: + void update(); + +private: + QTimer *timer; + bool started_prev = false; + PrimeType prime_type = PrimeType::UNKNOWN; + std::vector sunnylinkRoles = {}; + std::vector sunnylinkUsers = {}; + + bool last_mads_enabled = false; + bool mads_path_state = false; + float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] + float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] +}; + +UIState *uiState(); + +// device management class +class Device : public QObject { + Q_OBJECT + +public: + Device(QObject *parent = 0); + bool isAwake() { return awake; } + void setOffroadBrightness(int brightness) { + offroad_brightness = std::clamp(brightness, 0, 100); + } + +private: + bool awake = false; + int interactive_timeout = 0; + bool ignition_on = false; + + int offroad_brightness = BACKLIGHT_OFFROAD; + int last_brightness = 0; + FirstOrderFilter brightness_filter; + QFuture brightness_future; + + void updateBrightness(const UIState &s); + void updateWakefulness(const UIState &s); + void setAwake(bool on); + +signals: + void displayPowerChanged(bool on); + void interactiveTimeout(); + +public slots: + void resetInteractiveTimeout(int timeout = -1); + void update(const UIState &s); +}; + +Device *device(); + +void ui_update_params(UIState *s); +int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height); +void update_model(UIState *s, + const cereal::ModelDataV2::Reader &model); +void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); +void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); +void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, + float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); diff --git a/selfdrive/ui/tests/ui_snapshot.cc b/selfdrive/ui/tests/ui_snapshot.cc index 14e0fab835..9074249019 100644 --- a/selfdrive/ui/tests/ui_snapshot.cc +++ b/selfdrive/ui/tests/ui_snapshot.cc @@ -9,7 +9,11 @@ #include "selfdrive/ui/qt/home.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/window.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif void saveWidgetAsImage(QWidget *widget, const QString &fileName) { QImage image(widget->size(), QImage::Format_ARGB32); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 72571c4b85..bd26741193 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -11,7 +11,6 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" -#include "sunnypilot/qt/network/sunnylink/models/role_model.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 @@ -20,7 +19,7 @@ // Projects a point in car to space to the corresponding point in full frame // image space. static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { - const float margin = 1000.0f; + const float margin = 500.0f; const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; const vec3 pt = (vec3){{in_x, in_y, in_z}}; @@ -56,7 +55,7 @@ void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, con } void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); QPointF left, right; pvd->clear(); @@ -64,8 +63,8 @@ void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera if (line_x[i] < 0) continue; - bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); - bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); + bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); + bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right); if (l && r) { // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { @@ -90,21 +89,15 @@ void update_model(UIState *s, int max_idx = get_path_length_idx(lane_lines[0], max_distance); for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { scene.lane_line_probs[i] = lane_line_probs[i]; - update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, 0, &scene.lane_line_vertices[i], max_idx); + update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, &scene.lane_line_vertices[i], max_idx); } - // lane barriers for blind spot - int max_distance_barrier = 40; - int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); - update_line_data(s, lane_lines[1], 0, -0.05, -0.6, &scene.lane_barrier_vertices[0], max_idx_barrier, false); - update_line_data(s, lane_lines[2], 0, -0.05, -0.6, &scene.lane_barrier_vertices[1], max_idx_barrier, false); - // update road edges const auto road_edges = model.getRoadEdges(); const auto road_edge_stds = model.getRoadEdgeStds(); for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { scene.road_edge_stds[i] = road_edge_stds[i]; - update_line_data(s, road_edges[i], 0.025, 0, 0, &scene.road_edge_vertices[i], max_idx); + update_line_data(s, road_edges[i], 0.025, 0, &scene.road_edge_vertices[i], max_idx); } // update path @@ -114,8 +107,7 @@ void update_model(UIState *s, max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); } max_idx = get_path_length_idx(model_position, max_distance); - update_line_data(s, model_position, 0.9 - scene.mads_path_range * scene.mads_path_scale, 1.22, 1.22, &scene.track_vertices, max_idx, false); - update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); + update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); } void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { @@ -160,7 +152,6 @@ 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(); @@ -212,108 +203,28 @@ static void update_state(UIState *s) { } else if (!sm.allAliveAndValid({"wideRoadCameraState"})) { scene.light_sensor = -1; } - scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); + scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; scene.world_objects_visible = scene.world_objects_visible || (scene.started && sm.rcv_frame("liveCalibration") > scene.started_frame && sm.rcv_frame("modelV2") > scene.started_frame); - // TODO: SP - Set this dynamically on init with manual toggle or driving model selection - if (sm.updated("lateralPlanSPDEPRECATED")) { - scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); - } - if (sm.updated("controlsState")) { - scene.controlsState = sm["controlsState"].getControlsState(); - } - if (sm.updated("longitudinalPlanSP")) { - for (int i = 0; i < std::size(scene.e2eX); i++) { - scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; - } - } - if (sm.updated("modelV2SP")) { - auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); - scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); - scene.driving_model_generation = model_v2_sp.getModelGeneration(); - scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); - } } void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); - s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - - s->scene.visual_brake_lights = params.getBool("BrakeLights"); - s->scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); - s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); - s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); - s->scene.stand_still_timer = params.getBool("StandStillTimer"); - s->scene.show_debug_ui = params.getBool("ShowDebugUI"); - s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); - s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); - s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); - s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); - s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); - s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); - s->scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); - s->scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); - s->scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); - s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); - s->scene.live_torque_toggle = params.getBool("LiveTorque"); - s->scene.torqued_override = params.getBool("TorquedOverride"); - s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); - s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); - s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); - s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); - s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); - s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); - s->scene.feature_status_toggle = params.getBool("FeatureStatus"); - s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); - - // Handle Onroad Screen Off params - if (s->scene.onroadScreenOff > 0) { - s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ; - } else if (s->scene.onroadScreenOff == 0) { - s->scene.osoTimer = 30 * UI_FREQ; - } else if (s->scene.onroadScreenOff == -1) { - s->scene.osoTimer = 15 * UI_FREQ; - } else { - s->scene.osoTimer = -1; - } } void UIState::updateStatus() { - auto params = Params(); if (scene.started && sm->updated("controlsState")) { auto controls_state = (*sm)["controlsState"].getControlsState(); - auto car_control = (*sm)["carControl"].getCarControl(); - auto car_state = (*sm)["carState"].getCarState(); - auto mads_enabled = car_state.getMadsEnabled(); auto state = controls_state.getState(); if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { status = STATUS_OVERRIDE; } else { - status = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED : STATUS_MADS : STATUS_DISENGAGED; + status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; } - - if (mads_enabled != last_mads_enabled) { - mads_path_state = true; - } - last_mads_enabled = mads_enabled; - if (mads_path_state) { - if (mads_enabled) { - mads_path_count = fmax(mads_path_count - 1, 0); - if (mads_path_count == 0) { - mads_path_state = false; - } - } else { - mads_path_count = fmin(mads_path_count + 1, mads_path_timestep); - if (mads_path_count == mads_path_timestep) { - mads_path_state = false; - } - } - } - scene.mads_path_scale = mads_path_count * (1 / mads_path_timestep); } // Handle onroad/offroad transition @@ -326,74 +237,13 @@ void UIState::updateStatus() { scene.world_objects_visible = false; emit offroadTransition(!scene.started); } - - if (scene.started) { - // Auto hide UI button state machine - { - if (scene.button_auto_hide) { - if (scene.touch_to_wake) { - scene.sleep_btn = 30 * UI_FREQ; - } else if (scene.sleep_btn > 0) { - scene.sleep_btn--; - } else if (scene.sleep_btn == -1) { - scene.sleep_btn = 30 * UI_FREQ; - } - // Check if the sleep button should be fading in - if (scene.sleep_btn_fading_in) { - // Increase the opacity of the sleep button by a small amount - if (scene.sleep_btn_opacity < 20) { - scene.sleep_btn_opacity+= 10; - } - if (scene.sleep_btn_opacity >= 20) { - // If the opacity has reached its maximum value, stop fading in - scene.sleep_btn_fading_in = false; - scene.sleep_btn_opacity = 20; - } - } else if (scene.sleep_btn == 0) { - // Fade out the sleep button as before - if (scene.sleep_btn_opacity > 0) { - scene.sleep_btn_opacity-= 2; - } - } else { - // Set the opacity of the sleep button to its maximum value - scene.sleep_btn_opacity = 20; - } - } else { - scene.sleep_btn_opacity = 20; - } - } - - // Onroad Screen Off Brightness + Timer + Global Brightness - { - if (scene.onroadScreenOff != -2 && scene.touched2) { - scene.sleep_time = scene.osoTimer; - } else if (scene.onroadScreenOff != -2 && - ((scene.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && - ((scene.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene.onroadScreenOffEvent) || - (scene.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { - scene.sleep_time = scene.osoTimer; - } else if (scene.sleep_time > 0 && scene.onroadScreenOff != -2) { - scene.sleep_time--; - } else if (scene.sleep_time == -1 && scene.onroadScreenOff != -2) { - scene.sleep_time = scene.osoTimer; - } - } - } - - if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz - scene.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); - } - - scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); } UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", - "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", - "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", - "controlsStateSP", "modelV2SP" + "wideRoadCameraState", "managerState", "clocks", }); Params params; @@ -414,6 +264,10 @@ void UIState::update() { update_state(this); updateStatus(); + if (std::getenv("PRIME_TYPE")) { + setPrimeType((PrimeType)atoi(std::getenv("PRIME_TYPE"))); + } + if (sm->frame % UI_FREQ == 0) { watchdog_kick(nanos_since_boot()); } @@ -435,16 +289,6 @@ void UIState::setPrimeType(PrimeType type) { } } -void UIState::setSunnylinkRoles(const std::vector& roles) { - sunnylinkRoles = roles; - emit sunnylinkRolesChanged(roles); -} - -void UIState::setSunnylinkDeviceUsers(const std::vector& users) { - sunnylinkUsers = users; - emit sunnylinkDeviceUsersChanged(users); -} - Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { setAwake(true); resetInteractiveTimeout(); @@ -492,10 +336,6 @@ void Device::updateBrightness(const UIState &s) { int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; - } else if (s.scene.started && s.scene.sleep_time == 0 && s.scene.onroadScreenOff != -2) { - brightness = s.scene.onroadScreenOffBrightness * 0.01 * brightness; - } else if (s.scene.brightness) { - brightness = s.scene.brightness * 0.99; } if (brightness != last_brightness) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index eb02b8d2d8..bc8b8f5ed4 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -15,31 +14,11 @@ #include "common/mat.h" #include "common/params.h" #include "common/timing.h" -#include "sunnypilot/qt/network/sunnylink/models/role_model.h" -#include "sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h" -#include "sunnypilot/qt/network/sunnylink/models/user_model.h" #include "system/hardware/hw.h" const int UI_BORDER_SIZE = 30; const int UI_HEADER_HEIGHT = 420; -const int UI_ROAD_NAME_MARGIN_X = 14; - -struct FeatureStatusText { - const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto"}; - const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed"}; - const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active"}; -}; - -struct FeatureStatusColor { - const QStringList dlp_list_color = { "#2020f8", "#0df87a", "#0df8f8" }; - const QStringList gac_list_color = { "#ff4b4b", "#fcff4b", "#4bff66", "#6a0ac9" }; - const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; -}; - -const float DRIVING_PATH_WIDE = 0.9; -const float DRIVING_PATH_NARROW = 0.25; - const int UI_FREQ = 20; // Hz const int BACKLIGHT_OFFROAD = 50; @@ -71,7 +50,6 @@ typedef enum UIStatus { STATUS_DISENGAGED, STATUS_OVERRIDE, STATUS_ENGAGED, - STATUS_MADS, } UIStatus; enum PrimeType { @@ -88,18 +66,10 @@ enum PrimeType { const QColor bg_colors [] = { [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), - [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), - [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), + [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), }; -const QColor tcs_colors [] = { - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), -}; - typedef struct UIScene { bool calibration_valid = false; bool calibration_wide_valid = false; @@ -107,24 +77,13 @@ typedef struct UIScene { mat3 view_from_calib = DEFAULT_CALIBRATION; mat3 view_from_wide_calib = DEFAULT_CALIBRATION; cereal::PandaState::PandaType pandaType; - cereal::ControlsState::Reader controlsState; - - // Debug UI - bool show_debug_ui; - - // Speed limit control - bool speed_limit_control_enabled; - int speed_limit_control_policy; - double last_speed_limit_sign_tap; // modelV2 float lane_line_probs[4]; float road_edge_stds[2]; QPolygonF track_vertices; - QPolygonF track_edge_vertices; QPolygonF lane_line_vertices[4]; QPolygonF road_edge_vertices[2]; - QPolygonF lane_barrier_vertices[2]; // lead QPointF lead_vertices[2]; @@ -138,77 +97,11 @@ typedef struct UIScene { bool navigate_on_openpilot_deprecated = false; cereal::LongitudinalPersonality personality; - cereal::AccelerationPersonality accel_personality; float light_sensor = -1; - bool started, ignition, is_metric, map_on_left, longitudinal_control; + bool started, ignition, is_metric, longitudinal_control; bool world_objects_visible = false; uint64_t started_frame; - - int dynamic_lane_profile; - bool dynamic_lane_profile_status = true; - - bool visual_brake_lights; - - int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; - bool onroadScreenOffEvent; - int sleep_time = -1; - bool touched2 = false; - - bool stand_still_timer; - - bool hide_vego_ui, true_vego_ui; - - int chevron_data; - - bool gac; - int longitudinal_personality; - int longitudinal_accel_personality; - - bool map_visible; - int dev_ui_info; - bool live_torque_toggle; - - bool touch_to_wake = false; - int sleep_btn = -1; - bool sleep_btn_fading_in = false; - int sleep_btn_opacity = 20; - bool button_auto_hide; - - bool reverse_dm_cam; - - bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; - float e2eX[13] = {0}; - - int sidebar_temp_options; - - float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; - float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 - - bool onroad_settings_visible; - - bool map_3d_buildings; - - bool torqued_override; - - bool dynamic_experimental_control; - - int speed_limit_control_engage_type; - - bool mapbox_fullscreen; - - bool speed_limit_warning_flash; - int speed_limit_warning_type; - int speed_limit_warning_value_offset; - - bool custom_driving_model_valid; - cereal::ModelGeneration driving_model_generation; - uint32_t driving_model_capabilities; - - bool feature_status_toggle; - bool onroad_settings_toggle; - - bool dynamic_personality; } UIScene; class UIState : public QObject { @@ -225,42 +118,6 @@ public: inline PrimeType primeType() const { return prime_type; } inline bool hasPrime() const { return prime_type > PrimeType::NONE; } - void setSunnylinkRoles(const std::vector &roles); - void setSunnylinkDeviceUsers(const std::vector &users); - - inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } - inline bool isSunnylinkAdmin() const { - return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { - return role.roleType == RoleType::Admin; - }); - } - inline bool isSunnylinkSponsor() const { - return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { - return role.roleType == RoleType::Sponsor && role.as().roleTier != SponsorTier::Free; - }); - } - inline SponsorRoleModel sunnylinkSponsorRole() const { - std::optional sponsorRoleWithHighestTier = std::nullopt; - for (const auto &role : sunnylinkRoles) { - if(role.roleType != RoleType::Sponsor) - continue; - - if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { - sponsorRoleWithHighestTier = sponsorRole; - } - } - return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free)); - } - inline SponsorTier sunnylinkSponsorTier() const { - return sunnylinkSponsorRole().roleTier; - } - inline std::vector sunnylinkDeviceUsers() const { return sunnylinkUsers; } - inline bool isSunnylinkPaired() const { - return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) { - return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; - }); - } - int fb_w = 0, fb_h = 0; std::unique_ptr sm; @@ -278,10 +135,6 @@ signals: void primeChanged(bool prime); void primeTypeChanged(PrimeType prime_type); - void sunnylinkRoleChanged(bool subscriber); - void sunnylinkRolesChanged(std::vector roles); - void sunnylinkDeviceUsersChanged(std::vector users); - private slots: void update(); @@ -289,13 +142,6 @@ private: QTimer *timer; bool started_prev = false; PrimeType prime_type = PrimeType::UNKNOWN; - std::vector sunnylinkRoles = {}; - std::vector sunnylinkUsers = {}; - - bool last_mads_enabled = false; - bool mads_path_state = false; - float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] - float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] }; UIState *uiState(); @@ -343,4 +189,4 @@ void update_model(UIState *s, void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); From ec0c2edd6655844c152e92506bdc3cbbce25cdbe Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 22:35:37 -0400 Subject: [PATCH 416/575] Revert "Only when building map" This reverts commit 5c1c8332234c916c95efc4c9846b0f59e5a3f6a6. --- selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 2549384e3e..e77e5a807e 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -19,21 +19,14 @@ // This is for compatibility purposes, until we properly do inheritance splitting // This is because some controls were needing LabelControlSP, but the original code was using LabelControl // which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP +#define AbstractControl AbstractControlCustomSP +#define LayoutWidget LayoutWidgetSP #define LabelControl LabelControlSP #define ElidedLabel ElidedLabelSP #define ButtonControl ButtonControlSP #define Toggle ToggleSP #define ToggleControl ToggleControlSP -// To avoid multiple definition errors for AbstractControl and LayoutWidget when ENABLE_MAPS is defined, -// we redefine these classes with custom names (AbstractControlCustomSP and LayoutWidgetSP). -// This ensures that the linker does not encounter duplicate symbols for these classes from different sources. -// This redefinition is only necessary when building with map support; it is not needed if map functionality is not required. -#ifdef ENABLE_MAPS -#define AbstractControl AbstractControlCustomSP -#define LayoutWidget LayoutWidgetSP -#endif - QFrame *horizontal_line(QWidget *parent = nullptr); class ElidedLabelSP : public QLabel { From 142db222cee018fe3654a522e9827baa7ef7753b Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 22:35:37 -0400 Subject: [PATCH 417/575] Revert "SP Controls: Fix duplicate classes" This reverts commit 02b3ea49175700a4c19d68bb3ab2c54d5f5e66e0. --- selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index e77e5a807e..98dc6a34c9 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -19,8 +19,6 @@ // This is for compatibility purposes, until we properly do inheritance splitting // This is because some controls were needing LabelControlSP, but the original code was using LabelControl // which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP -#define AbstractControl AbstractControlCustomSP -#define LayoutWidget LayoutWidgetSP #define LabelControl LabelControlSP #define ElidedLabel ElidedLabelSP #define ButtonControl ButtonControlSP From 4e1ef0dca7f89bd894a43e38b1033204735cefb0 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 22:38:27 -0400 Subject: [PATCH 418/575] Remove stock classes from compiling --- selfdrive/ui/SConscript | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index dd7aad7a97..fc61cd56a2 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -31,7 +31,8 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src if GetOption('sunnypilot'): - widgets_src.remove("ui.cc") + remove_src = ["ui.cc", "qt/widgets/controls.cc"] + widgets_src = [item for item in widgets_src if item not in remove_src] qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if GetOption('sunnypilot') else [] if maps: From 134f5854821497d9e6a0d9c4f3bfa68a0635df0c Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 23:01:44 -0400 Subject: [PATCH 419/575] Revert "Remove stock classes from compiling" This reverts commit 4e1ef0dca7f89bd894a43e38b1033204735cefb0. --- selfdrive/ui/SConscript | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index fc61cd56a2..dd7aad7a97 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -31,8 +31,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src if GetOption('sunnypilot'): - remove_src = ["ui.cc", "qt/widgets/controls.cc"] - widgets_src = [item for item in widgets_src if item not in remove_src] + widgets_src.remove("ui.cc") qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if GetOption('sunnypilot') else [] if maps: From 1386d1215b80f5c889d8e8a1ac910ec38192dcc6 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 23:01:44 -0400 Subject: [PATCH 420/575] Reapply "SP Controls: Fix duplicate classes" This reverts commit 142db222cee018fe3654a522e9827baa7ef7753b. --- selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 98dc6a34c9..e77e5a807e 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -19,6 +19,8 @@ // This is for compatibility purposes, until we properly do inheritance splitting // This is because some controls were needing LabelControlSP, but the original code was using LabelControl // which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP +#define AbstractControl AbstractControlCustomSP +#define LayoutWidget LayoutWidgetSP #define LabelControl LabelControlSP #define ElidedLabel ElidedLabelSP #define ButtonControl ButtonControlSP From 7a2778f3f169aac91d5011ced36e879f30cc438e Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 23:01:45 -0400 Subject: [PATCH 421/575] Reapply "Only when building map" This reverts commit ec0c2edd6655844c152e92506bdc3cbbce25cdbe. --- selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index e77e5a807e..2549384e3e 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -19,14 +19,21 @@ // This is for compatibility purposes, until we properly do inheritance splitting // This is because some controls were needing LabelControlSP, but the original code was using LabelControl // which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP -#define AbstractControl AbstractControlCustomSP -#define LayoutWidget LayoutWidgetSP #define LabelControl LabelControlSP #define ElidedLabel ElidedLabelSP #define ButtonControl ButtonControlSP #define Toggle ToggleSP #define ToggleControl ToggleControlSP +// To avoid multiple definition errors for AbstractControl and LayoutWidget when ENABLE_MAPS is defined, +// we redefine these classes with custom names (AbstractControlCustomSP and LayoutWidgetSP). +// This ensures that the linker does not encounter duplicate symbols for these classes from different sources. +// This redefinition is only necessary when building with map support; it is not needed if map functionality is not required. +#ifdef ENABLE_MAPS +#define AbstractControl AbstractControlCustomSP +#define LayoutWidget LayoutWidgetSP +#endif + QFrame *horizontal_line(QWidget *parent = nullptr); class ElidedLabelSP : public QLabel { From 3507e627ac374079f08f999ddde7af884177544c Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Sun, 14 Jul 2024 23:03:20 -0400 Subject: [PATCH 422/575] Revert "ui.cc|h: split completely (refactor later) (WIP)" This reverts commit c4abe230a7cade982381224fc42f1f73f14f06d1. --- selfdrive/ui/SConscript | 3 - selfdrive/ui/mui.cc | 4 - selfdrive/ui/qt/body.h | 4 - selfdrive/ui/qt/home.h | 3 +- selfdrive/ui/qt/maps/map.cc | 6 - selfdrive/ui/qt/maps/map.h | 4 - selfdrive/ui/qt/maps/map_eta.cc | 4 - selfdrive/ui/qt/maps/map_instructions.cc | 4 - selfdrive/ui/qt/maps/map_panel.cc | 4 - selfdrive/ui/qt/network/networking.cc | 4 - selfdrive/ui/qt/network/wifi_manager.cc | 4 - selfdrive/ui/qt/offroad/experimental_mode.cc | 4 - selfdrive/ui/qt/offroad/settings.h | 3 +- selfdrive/ui/qt/offroad/software_settings.cc | 3 +- selfdrive/ui/qt/offroad_home.h | 3 +- selfdrive/ui/qt/onroad/alerts.h | 4 - selfdrive/ui/qt/onroad/buttons.h | 4 - selfdrive/ui/qt/request_repeater.h | 4 - selfdrive/ui/qt/sidebar.h | 4 - selfdrive/ui/qt/widgets/cameraview.h | 4 - selfdrive/ui/qt/widgets/wifi.h | 4 - selfdrive/ui/sunnypilot/SConscript | 3 +- .../qt/offroad/lane_change_settings.h | 4 - .../ui/sunnypilot/qt/offroad/mads_settings.h | 4 - .../ui/sunnypilot/qt/offroad/models_fetcher.h | 3 +- .../ui/sunnypilot/qt/offroad/osm_settings.h | 4 - .../qt/offroad/software_settings_sp.h | 4 - .../qt/offroad/speed_limit_control_settings.h | 4 - .../qt/offroad/speed_limit_policy_settings.h | 4 - .../qt/offroad/speed_limit_warning_settings.h | 4 - .../qt/offroad/sunnylink_settings.h | 4 - .../qt/offroad/sunnypilot_settings.h | 4 - .../sunnypilot/qt/offroad/vehicle_settings.h | 4 - .../sunnypilot/qt/offroad/visuals_settings.h | 4 - .../ui/sunnypilot/qt/onroad/onroad_settings.h | 3 +- .../qt/onroad/onroad_settings_panel.cc | 10 +- selfdrive/ui/sunnypilot/qt/sp_priv_home.h | 4 +- selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h | 4 - selfdrive/ui/sunnypilot/sp_priv_ui.cc | 530 ------------------ selfdrive/ui/sunnypilot/sp_priv_ui.h | 346 ------------ selfdrive/ui/tests/ui_snapshot.cc | 4 - selfdrive/ui/ui.cc | 188 ++++++- selfdrive/ui/ui.h | 160 +++++- 43 files changed, 342 insertions(+), 1038 deletions(-) delete mode 100644 selfdrive/ui/sunnypilot/sp_priv_ui.cc delete mode 100644 selfdrive/ui/sunnypilot/sp_priv_ui.h diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index dd7aad7a97..f62e204136 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -30,9 +30,6 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src -if GetOption('sunnypilot'): - widgets_src.remove("ui.cc") - qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if GetOption('sunnypilot') else [] if maps: base_libs += ['QMapLibre'] diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc index 96a086c785..98029ee311 100644 --- a/selfdrive/ui/mui.cc +++ b/selfdrive/ui/mui.cc @@ -3,11 +3,7 @@ #include #include "cereal/messaging/messaging.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/qt/qt_window.h" int main(int argc, char *argv[]) { diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h index 68495aaf73..567a54d49b 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -5,11 +5,7 @@ #include #include "common/util.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif class RecordButton : public QPushButton { Q_OBJECT diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 80a4b79ef8..c64592d49a 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -13,16 +13,15 @@ #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" +#include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #define OnroadWindow OnroadWindowSP #define OffroadHome OffroadHomeSP #else -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/offroad_home.h" diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 79b5581efe..b5b731ef66 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -8,11 +8,7 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/util.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif const int INTERACTION_TIMEOUT = 100; @@ -114,7 +110,6 @@ void MapWindow::initLayers() { // TODO: remove, symbol-sort-key does not seem to matter outside of each layer m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0); } -#ifdef SUNNYPILOT if ((!m_map->layerExists("buildingsLayer")) && uiState()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage qDebug() << "Initializing buildingsLayer"; QVariantMap buildings; @@ -158,7 +153,6 @@ void MapWindow::initLayers() { m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase); m_map->setLayoutProperty("buildingsLayer", "visibility", "visible"); } -#endif } void MapWindow::updateState(const UIState &s) { diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 77fbdf6e36..62871e79d2 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -20,11 +20,7 @@ #include "cereal/messaging/messaging.h" #include "common/params.h" #include "common/util.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/qt/maps/map_eta.h" #include "selfdrive/ui/qt/maps/map_instructions.h" diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc index 868c6ca550..0eb77e36ce 100644 --- a/selfdrive/ui/qt/maps/map_eta.cc +++ b/selfdrive/ui/qt/maps/map_eta.cc @@ -4,11 +4,7 @@ #include #include "selfdrive/ui/qt/maps/map_helpers.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif const float MANEUVER_TRANSITION_THRESHOLD = 10; diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc index 6bfeefbafc..ba8cb356bd 100644 --- a/selfdrive/ui/qt/maps/map_instructions.cc +++ b/selfdrive/ui/qt/maps/map_instructions.cc @@ -4,11 +4,7 @@ #include #include "selfdrive/ui/qt/maps/map_helpers.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif const QString ICON_SUFFIX = ".png"; diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index 75dc85b01e..c4cc20e21d 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -6,11 +6,7 @@ #include "selfdrive/ui/qt/maps/map.h" #include "selfdrive/ui/qt/maps/map_settings.h" #include "selfdrive/ui/qt/util.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index acf6add481..a84460d5e9 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -6,11 +6,7 @@ #include #include -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/scrollview.h" diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc index 7cfa17f774..717da47096 100644 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -2,11 +2,7 @@ #include -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/qt/widgets/prime.h" #include "common/params.h" diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index 6fd4e3a895..b2c6f3323a 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -6,11 +6,7 @@ #include #include -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) { chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 96fc04bbca..734f47e01e 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -11,16 +11,15 @@ #include #include +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #define ListWidget ListWidgetSP #define ParamControl ParamControlSP #define ButtonParamControl ButtonParamControlSP #else -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 972005864a..fa39daa6de 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -9,12 +9,11 @@ #include "common/params.h" #include "common/util.h" +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #else -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif #include "selfdrive/ui/qt/widgets/input.h" diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h index 38cd8afa95..f277d852c4 100644 --- a/selfdrive/ui/qt/offroad_home.h +++ b/selfdrive/ui/qt/offroad_home.h @@ -13,9 +13,9 @@ #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" +#include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" @@ -23,7 +23,6 @@ #define OffroadHomeImp OffroadHomeSP #define Sidebar SidebarSP #else -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/sidebar.h" diff --git a/selfdrive/ui/qt/onroad/alerts.h b/selfdrive/ui/qt/onroad/alerts.h index a584a8682c..1f76ba305b 100644 --- a/selfdrive/ui/qt/onroad/alerts.h +++ b/selfdrive/ui/qt/onroad/alerts.h @@ -2,11 +2,7 @@ #include -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif class OnroadAlerts : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h index a2f6845a8d..feee1ba639 100644 --- a/selfdrive/ui/qt/onroad/buttons.h +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -2,11 +2,7 @@ #include -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif const int btn_size = 192; const int img_size = (btn_size / 4) * 3; diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h index 43ce6492c9..45cb643b13 100644 --- a/selfdrive/ui/qt/request_repeater.h +++ b/selfdrive/ui/qt/request_repeater.h @@ -3,11 +3,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "selfdrive/ui/qt/api.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif class RequestRepeater : public HttpRequest { diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index d799747ca5..741b4214fa 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -5,11 +5,7 @@ #include #include -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif typedef QPair, QColor> ItemStatus; Q_DECLARE_METATYPE(ItemStatus); diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 09a1479bf6..526c17e77c 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -24,11 +24,7 @@ #include "msgq/visionipc/visionipc_client.h" #include "system/camerad/cameras/camera_common.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif const int FRAME_BUFFER_SIZE = 5; static_assert(FRAME_BUFFER_SIZE <= YUV_BUFFER_COUNT); diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h index bdcd9aa2ca..60c865f2b8 100644 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -4,11 +4,7 @@ #include #include -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif class WiFiPromptWidget : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 170d87c4bf..bcc296e496 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -18,8 +18,7 @@ widgets_src = [ "sunnypilot/qt/offroad/sunnylink_settings.cc", "sunnypilot/qt/widgets/sp_priv_controls.cc", "sunnypilot/qt/widgets/sp_priv_scrollview.cc", - "sunnypilot/qt/widgets/sp_priv_toggle.cc", - "sunnypilot/sp_priv_ui.cc" + "sunnypilot/qt/widgets/sp_priv_toggle.cc" ] network_src = [ diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h index 32a74e9e97..dcc9a457d0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h @@ -1,10 +1,6 @@ #pragma once -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h index cd57ac48f9..374e404f29 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h @@ -1,10 +1,6 @@ #pragma once -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h index ac007d3594..8780c8028a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h @@ -9,13 +9,12 @@ #include "common/swaglog.h" #include "common/util.h" +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #else -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif #include "system/hardware/hw.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h index 2de838f1e3..853033be7c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h @@ -13,11 +13,7 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "system/hardware/hw.h" constexpr int FAST_REFRESH_INTERVAL = 1000; // ms diff --git a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h index 4351cd3859..4f8fc0d22a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h @@ -1,11 +1,7 @@ #pragma once #include "common/model.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/qt/offroad/settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h index 4a5e230a56..91ad4842db 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h @@ -1,10 +1,6 @@ #pragma once -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h index d4f55bd5e3..a5e8d3e393 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h @@ -1,10 +1,6 @@ #pragma once -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h index add408f8bc..07ee7d18fa 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h @@ -1,10 +1,6 @@ #pragma once -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h index 6a8faf5073..1a3cb630d9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h @@ -5,11 +5,7 @@ #include #include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index 061e1c6560..c42c0b6c66 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -1,11 +1,7 @@ #pragma once #include "common/model.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h index f4ce79808e..b66d29dcd6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h @@ -3,11 +3,7 @@ #include #include "common/watchdog.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h index 3377c2840c..244bc09b7e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h @@ -1,10 +1,6 @@ #pragma once -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" class VisualsPanel : public ListWidgetSP { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h index f2edfd387a..17d0f461d7 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h @@ -6,12 +6,11 @@ #include #include "common/params.h" +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #else -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc index 352b3e904f..8a8283fad3 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc @@ -1,15 +1,11 @@ -#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" +#include "../../../sunnypilot/qt/onroad/onroad_settings_panel.h" #include #include #include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" -#include "selfdrive/ui/qt/util.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else -#include "selfdrive/ui/ui.h" -#endif +#include "../../../qt/util.h" +#include "../../../ui.h" OnroadSettingsPanel::OnroadSettingsPanel(QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h index f99fd62439..5f49ee4794 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h @@ -11,16 +11,14 @@ #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" - +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/home.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" #define OnroadWindow OnroadWindowSP #else -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #endif diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h index dc79c58605..158f1bd96c 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h @@ -6,11 +6,7 @@ #include #include "selfdrive/ui/qt/sidebar.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif class SidebarSP : public Sidebar { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.cc b/selfdrive/ui/sunnypilot/sp_priv_ui.cc deleted file mode 100644 index 29331e872f..0000000000 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.cc +++ /dev/null @@ -1,530 +0,0 @@ -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" - -#include -#include -#include - -#include - -#include "common/transformations/orientation.hpp" -#include "common/params.h" -#include "common/swaglog.h" -#include "common/util.h" -#include "common/watchdog.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" -#include "system/hardware/hw.h" - -#define BACKLIGHT_DT 0.05 -#define BACKLIGHT_TS 10.00 - -// Projects a point in car to space to the corresponding point in full frame -// image space. -static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { - const float margin = 1000.0f; - const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; - - const vec3 pt = (vec3){{in_x, in_y, in_z}}; - const vec3 Ep = matvecmul3(s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib, pt); - const vec3 KEp = matvecmul3(s->scene.wide_cam ? ECAM_INTRINSIC_MATRIX : FCAM_INTRINSIC_MATRIX, Ep); - - // Project. - QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]}); - if (clip_region.contains(point)) { - *out = point; - return true; - } - return false; -} - -int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height) { - const auto line_x = line.getX(); - int max_idx = 0; - for (int i = 1; i < line_x.size() && line_x[i] <= path_height; ++i) { - max_idx = i; - } - return max_idx; -} - -void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line) { - for (int i = 0; i < 2; ++i) { - auto lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo(); - if (lead_data.getStatus()) { - float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())]; - calib_frame_to_full_frame(s, lead_data.getDRel(), -lead_data.getYRel(), z + 1.22, &s->scene.lead_vertices[i]); - } - } -} - -void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { - const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); - QPointF left, right; - pvd->clear(); - for (int i = 0; i <= max_idx; i++) { - // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera - if (line_x[i] < 0) continue; - - bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); - bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); - if (l && r) { - // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts - if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { - continue; - } - pvd->push_back(left); - pvd->push_front(right); - } - } -} - -void update_model(UIState *s, - const cereal::ModelDataV2::Reader &model) { - UIScene &scene = s->scene; - auto model_position = model.getPosition(); - float max_distance = std::clamp(*(model_position.getX().end() - 1), - MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); - - // update lane lines - const auto lane_lines = model.getLaneLines(); - const auto lane_line_probs = model.getLaneLineProbs(); - int max_idx = get_path_length_idx(lane_lines[0], max_distance); - for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { - scene.lane_line_probs[i] = lane_line_probs[i]; - update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, 0, &scene.lane_line_vertices[i], max_idx); - } - - // lane barriers for blind spot - int max_distance_barrier = 40; - int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); - update_line_data(s, lane_lines[1], 0, -0.05, -0.6, &scene.lane_barrier_vertices[0], max_idx_barrier, false); - update_line_data(s, lane_lines[2], 0, -0.05, -0.6, &scene.lane_barrier_vertices[1], max_idx_barrier, false); - - // update road edges - const auto road_edges = model.getRoadEdges(); - const auto road_edge_stds = model.getRoadEdgeStds(); - for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { - scene.road_edge_stds[i] = road_edge_stds[i]; - update_line_data(s, road_edges[i], 0.025, 0, 0, &scene.road_edge_vertices[i], max_idx); - } - - // update path - auto lead_one = (*s->sm)["radarState"].getRadarState().getLeadOne(); - if (lead_one.getStatus()) { - const float lead_d = lead_one.getDRel() * 2.; - max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); - } - max_idx = get_path_length_idx(model_position, max_distance); - update_line_data(s, model_position, 0.9 - scene.mads_path_range * scene.mads_path_scale, 1.22, 1.22, &scene.track_vertices, max_idx, false); - update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); -} - -void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { - UIScene &scene = s->scene; - const auto driver_orient = is_rhd ? driverstate.getRightDriverData().getFaceOrientation() : driverstate.getLeftDriverData().getFaceOrientation(); - for (int i = 0; i < std::size(scene.driver_pose_vals); i++) { - float v_this = (i == 0 ? (driver_orient[i] < 0 ? 0.7 : 0.9) : 0.4) * driver_orient[i]; - scene.driver_pose_diff[i] = fabs(scene.driver_pose_vals[i] - v_this); - scene.driver_pose_vals[i] = 0.8 * v_this + (1 - 0.8) * scene.driver_pose_vals[i]; - scene.driver_pose_sins[i] = sinf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); - scene.driver_pose_coss[i] = cosf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); - } - - auto [sin_y, sin_x, sin_z] = scene.driver_pose_sins; - auto [cos_y, cos_x, cos_z] = scene.driver_pose_coss; - - const mat3 r_xyz = (mat3){{ - cos_x * cos_z, - cos_x * sin_z, - -sin_x, - - -sin_y * sin_x * cos_z - cos_y * sin_z, - -sin_y * sin_x * sin_z + cos_y * cos_z, - -sin_y * cos_x, - - cos_y * sin_x * cos_z - sin_y * sin_z, - cos_y * sin_x * sin_z + sin_y * cos_z, - cos_y * cos_x, - }}; - - // transform vertices - for (int kpi = 0; kpi < std::size(default_face_kpts_3d); kpi++) { - vec3 kpt_this = matvecmul3(r_xyz, default_face_kpts_3d[kpi]); - scene.face_kpts_draw[kpi] = (vec3){{kpt_this.v[0], kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}}; - } -} - -static void update_sockets(UIState *s) { - s->sm->update(0); -} - -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(); - auto rpy_list = live_calib.getRpyCalib(); - auto wfde_list = live_calib.getWideFromDeviceEuler(); - Eigen::Vector3d rpy; - Eigen::Vector3d wfde; - if (rpy_list.size() == 3) rpy << rpy_list[0], rpy_list[1], rpy_list[2]; - if (wfde_list.size() == 3) wfde << wfde_list[0], wfde_list[1], wfde_list[2]; - Eigen::Matrix3d device_from_calib = euler2rot(rpy); - Eigen::Matrix3d wide_from_device = euler2rot(wfde); - Eigen::Matrix3d view_from_device; - view_from_device << 0, 1, 0, - 0, 0, 1, - 1, 0, 0; - Eigen::Matrix3d view_from_calib = view_from_device * device_from_calib; - Eigen::Matrix3d view_from_wide_calib = view_from_device * wide_from_device * device_from_calib; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - scene.view_from_calib.v[i*3 + j] = view_from_calib(i, j); - scene.view_from_wide_calib.v[i*3 + j] = view_from_wide_calib(i, j); - } - } - scene.calibration_valid = live_calib.getCalStatus() == cereal::LiveCalibrationData::Status::CALIBRATED; - scene.calibration_wide_valid = wfde_list.size() == 3; - } - if (sm.updated("pandaStates")) { - auto pandaStates = sm["pandaStates"].getPandaStates(); - if (pandaStates.size() > 0) { - scene.pandaType = pandaStates[0].getPandaType(); - - if (scene.pandaType != cereal::PandaState::PandaType::UNKNOWN) { - scene.ignition = false; - for (const auto& pandaState : pandaStates) { - scene.ignition |= pandaState.getIgnitionLine() || pandaState.getIgnitionCan(); - } - } - } - } else if ((s->sm->frame - s->sm->rcv_frame("pandaStates")) > 5*UI_FREQ) { - scene.pandaType = cereal::PandaState::PandaType::UNKNOWN; - } - if (sm.updated("carParams")) { - scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); - } - if (sm.updated("wideRoadCameraState")) { - auto cam_state = sm["wideRoadCameraState"].getWideRoadCameraState(); - 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); - } else if (!sm.allAliveAndValid({"wideRoadCameraState"})) { - scene.light_sensor = -1; - } - scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); - - scene.world_objects_visible = scene.world_objects_visible || - (scene.started && - sm.rcv_frame("liveCalibration") > scene.started_frame && - sm.rcv_frame("modelV2") > scene.started_frame); - // TODO: SP - Set this dynamically on init with manual toggle or driving model selection - if (sm.updated("lateralPlanSPDEPRECATED")) { - scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); - } - if (sm.updated("controlsState")) { - scene.controlsState = sm["controlsState"].getControlsState(); - } - if (sm.updated("longitudinalPlanSP")) { - for (int i = 0; i < std::size(scene.e2eX); i++) { - scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; - } - } - if (sm.updated("modelV2SP")) { - auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); - scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); - scene.driving_model_generation = model_v2_sp.getModelGeneration(); - scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); - } -} - -void ui_update_params(UIState *s) { - auto params = Params(); - s->scene.is_metric = params.getBool("IsMetric"); - s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - - s->scene.visual_brake_lights = params.getBool("BrakeLights"); - s->scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); - s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); - s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); - s->scene.stand_still_timer = params.getBool("StandStillTimer"); - s->scene.show_debug_ui = params.getBool("ShowDebugUI"); - s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); - s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); - s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); - s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); - s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); - s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); - s->scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); - s->scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); - s->scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); - s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); - s->scene.live_torque_toggle = params.getBool("LiveTorque"); - s->scene.torqued_override = params.getBool("TorquedOverride"); - s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); - s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); - s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); - s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); - s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); - s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); - s->scene.feature_status_toggle = params.getBool("FeatureStatus"); - s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); - - // Handle Onroad Screen Off params - if (s->scene.onroadScreenOff > 0) { - s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ; - } else if (s->scene.onroadScreenOff == 0) { - s->scene.osoTimer = 30 * UI_FREQ; - } else if (s->scene.onroadScreenOff == -1) { - s->scene.osoTimer = 15 * UI_FREQ; - } else { - s->scene.osoTimer = -1; - } -} - -void UIState::updateStatus() { - auto params = Params(); - if (scene.started && sm->updated("controlsState")) { - auto controls_state = (*sm)["controlsState"].getControlsState(); - auto car_control = (*sm)["carControl"].getCarControl(); - auto car_state = (*sm)["carState"].getCarState(); - auto mads_enabled = car_state.getMadsEnabled(); - auto state = controls_state.getState(); - if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { - status = STATUS_OVERRIDE; - } else { - status = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED : STATUS_MADS : STATUS_DISENGAGED; - } - - if (mads_enabled != last_mads_enabled) { - mads_path_state = true; - } - last_mads_enabled = mads_enabled; - if (mads_path_state) { - if (mads_enabled) { - mads_path_count = fmax(mads_path_count - 1, 0); - if (mads_path_count == 0) { - mads_path_state = false; - } - } else { - mads_path_count = fmin(mads_path_count + 1, mads_path_timestep); - if (mads_path_count == mads_path_timestep) { - mads_path_state = false; - } - } - } - scene.mads_path_scale = mads_path_count * (1 / mads_path_timestep); - } - - // Handle onroad/offroad transition - if (scene.started != started_prev || sm->frame == 1) { - if (scene.started) { - status = STATUS_DISENGAGED; - scene.started_frame = sm->frame; - } - started_prev = scene.started; - scene.world_objects_visible = false; - emit offroadTransition(!scene.started); - } - - if (scene.started) { - // Auto hide UI button state machine - { - if (scene.button_auto_hide) { - if (scene.touch_to_wake) { - scene.sleep_btn = 30 * UI_FREQ; - } else if (scene.sleep_btn > 0) { - scene.sleep_btn--; - } else if (scene.sleep_btn == -1) { - scene.sleep_btn = 30 * UI_FREQ; - } - // Check if the sleep button should be fading in - if (scene.sleep_btn_fading_in) { - // Increase the opacity of the sleep button by a small amount - if (scene.sleep_btn_opacity < 20) { - scene.sleep_btn_opacity+= 10; - } - if (scene.sleep_btn_opacity >= 20) { - // If the opacity has reached its maximum value, stop fading in - scene.sleep_btn_fading_in = false; - scene.sleep_btn_opacity = 20; - } - } else if (scene.sleep_btn == 0) { - // Fade out the sleep button as before - if (scene.sleep_btn_opacity > 0) { - scene.sleep_btn_opacity-= 2; - } - } else { - // Set the opacity of the sleep button to its maximum value - scene.sleep_btn_opacity = 20; - } - } else { - scene.sleep_btn_opacity = 20; - } - } - - // Onroad Screen Off Brightness + Timer + Global Brightness - { - if (scene.onroadScreenOff != -2 && scene.touched2) { - scene.sleep_time = scene.osoTimer; - } else if (scene.onroadScreenOff != -2 && - ((scene.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && - ((scene.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene.onroadScreenOffEvent) || - (scene.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { - scene.sleep_time = scene.osoTimer; - } else if (scene.sleep_time > 0 && scene.onroadScreenOff != -2) { - scene.sleep_time--; - } else if (scene.sleep_time == -1 && scene.onroadScreenOff != -2) { - scene.sleep_time = scene.osoTimer; - } - } - } - - if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz - scene.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); - } - - scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); -} - -UIState::UIState(QObject *parent) : QObject(parent) { - sm = std::make_unique>({ - "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", - "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", - "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", - "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", - "controlsStateSP", "modelV2SP" - }); - - Params params; - language = QString::fromStdString(params.get("LanguageSetting")); - auto prime_value = params.get("PrimeType"); - if (!prime_value.empty()) { - prime_type = static_cast(std::atoi(prime_value.c_str())); - } - - // update timer - timer = new QTimer(this); - QObject::connect(timer, &QTimer::timeout, this, &UIState::update); - timer->start(1000 / UI_FREQ); -} - -void UIState::update() { - update_sockets(this); - update_state(this); - updateStatus(); - - if (sm->frame % UI_FREQ == 0) { - watchdog_kick(nanos_since_boot()); - } - emit uiUpdate(*this); -} - -void UIState::setPrimeType(PrimeType type) { - if (type != prime_type) { - bool prev_prime = hasPrime(); - - prime_type = type; - Params().put("PrimeType", std::to_string(prime_type)); - emit primeTypeChanged(prime_type); - - bool prime = hasPrime(); - if (prev_prime != prime) { - emit primeChanged(prime); - } - } -} - -void UIState::setSunnylinkRoles(const std::vector& roles) { - sunnylinkRoles = roles; - emit sunnylinkRolesChanged(roles); -} - -void UIState::setSunnylinkDeviceUsers(const std::vector& users) { - sunnylinkUsers = users; - emit sunnylinkDeviceUsersChanged(users); -} - -Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { - setAwake(true); - resetInteractiveTimeout(); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); -} - -void Device::update(const UIState &s) { - updateBrightness(s); - updateWakefulness(s); -} - -void Device::setAwake(bool on) { - if (on != awake) { - awake = on; - Hardware::set_display_power(awake); - LOGD("setting display power %d", awake); - emit displayPowerChanged(awake); - } -} - -void Device::resetInteractiveTimeout(int timeout) { - if (timeout == -1) { - timeout = (ignition_on ? 10 : 30); - } - interactive_timeout = timeout * UI_FREQ; -} - -void Device::updateBrightness(const UIState &s) { - float clipped_brightness = offroad_brightness; - if (s.scene.started && s.scene.light_sensor > 0) { - clipped_brightness = s.scene.light_sensor; - - // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm - if (clipped_brightness <= 8) { - clipped_brightness = (clipped_brightness / 903.3); - } else { - clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0); - } - - // Scale back to 10% to 100% - clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); - } - - int brightness = brightness_filter.update(clipped_brightness); - if (!awake) { - brightness = 0; - } else if (s.scene.started && s.scene.sleep_time == 0 && s.scene.onroadScreenOff != -2) { - brightness = s.scene.onroadScreenOffBrightness * 0.01 * brightness; - } else if (s.scene.brightness) { - brightness = s.scene.brightness * 0.99; - } - - if (brightness != last_brightness) { - if (!brightness_future.isRunning()) { - brightness_future = QtConcurrent::run(Hardware::set_brightness, brightness); - last_brightness = brightness; - } - } -} - -void Device::updateWakefulness(const UIState &s) { - bool ignition_just_turned_off = !s.scene.ignition && ignition_on; - ignition_on = s.scene.ignition; - - if (ignition_just_turned_off) { - resetInteractiveTimeout(); - } else if (interactive_timeout > 0 && --interactive_timeout == 0) { - emit interactiveTimeout(); - } - - setAwake(s.scene.ignition || interactive_timeout > 0); -} - -UIState *uiState() { - static UIState ui_state; - return &ui_state; -} - -Device *device() { - static Device _device; - return &_device; -} diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.h b/selfdrive/ui/sunnypilot/sp_priv_ui.h deleted file mode 100644 index 996c9fe5cc..0000000000 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.h +++ /dev/null @@ -1,346 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "cereal/messaging/messaging.h" -#include "common/mat.h" -#include "common/params.h" -#include "common/timing.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h" -#include "system/hardware/hw.h" - -const int UI_BORDER_SIZE = 30; -const int UI_HEADER_HEIGHT = 420; - -const int UI_ROAD_NAME_MARGIN_X = 14; - -struct FeatureStatusText { - const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto"}; - const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed"}; - const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active"}; -}; - -struct FeatureStatusColor { - const QStringList dlp_list_color = { "#2020f8", "#0df87a", "#0df8f8" }; - const QStringList gac_list_color = { "#ff4b4b", "#fcff4b", "#4bff66", "#6a0ac9" }; - const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; -}; - -const float DRIVING_PATH_WIDE = 0.9; -const float DRIVING_PATH_NARROW = 0.25; - -const int UI_FREQ = 20; // Hz -const int BACKLIGHT_OFFROAD = 50; - -const float MIN_DRAW_DISTANCE = 10.0; -const float MAX_DRAW_DISTANCE = 100.0; -constexpr mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; -constexpr mat3 FCAM_INTRINSIC_MATRIX = (mat3){{2648.0, 0.0, 1928.0 / 2, - 0.0, 2648.0, 1208.0 / 2, - 0.0, 0.0, 1.0}}; -// tici ecam focal probably wrong? magnification is not consistent across frame -// Need to retrain model before this can be changed -constexpr mat3 ECAM_INTRINSIC_MATRIX = (mat3){{567.0, 0.0, 1928.0 / 2, - 0.0, 567.0, 1208.0 / 2, - 0.0, 0.0, 1.0}}; - - -constexpr vec3 default_face_kpts_3d[] = { - {-5.98, -51.20, 8.00}, {-17.64, -49.14, 8.00}, {-23.81, -46.40, 8.00}, {-29.98, -40.91, 8.00}, {-32.04, -37.49, 8.00}, - {-34.10, -32.00, 8.00}, {-36.16, -21.03, 8.00}, {-36.16, 6.40, 8.00}, {-35.47, 10.51, 8.00}, {-32.73, 19.43, 8.00}, - {-29.30, 26.29, 8.00}, {-24.50, 33.83, 8.00}, {-19.01, 41.37, 8.00}, {-14.21, 46.17, 8.00}, {-12.16, 47.54, 8.00}, - {-4.61, 49.60, 8.00}, {4.99, 49.60, 8.00}, {12.53, 47.54, 8.00}, {14.59, 46.17, 8.00}, {19.39, 41.37, 8.00}, - {24.87, 33.83, 8.00}, {29.67, 26.29, 8.00}, {33.10, 19.43, 8.00}, {35.84, 10.51, 8.00}, {36.53, 6.40, 8.00}, - {36.53, -21.03, 8.00}, {34.47, -32.00, 8.00}, {32.42, -37.49, 8.00}, {30.36, -40.91, 8.00}, {24.19, -46.40, 8.00}, - {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, -}; - - -typedef enum UIStatus { - STATUS_DISENGAGED, - STATUS_OVERRIDE, - STATUS_ENGAGED, - STATUS_MADS, -} UIStatus; - -enum PrimeType { - UNKNOWN = -2, - UNPAIRED = -1, - NONE = 0, - MAGENTA = 1, - LITE = 2, - BLUE = 3, - MAGENTA_NEW = 4, - PURPLE = 5, -}; - -const QColor bg_colors [] = { - [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), - [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), - [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), - [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), -}; - - -const QColor tcs_colors [] = { - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), -}; - -typedef struct UIScene { - bool calibration_valid = false; - bool calibration_wide_valid = false; - bool wide_cam = true; - mat3 view_from_calib = DEFAULT_CALIBRATION; - mat3 view_from_wide_calib = DEFAULT_CALIBRATION; - cereal::PandaState::PandaType pandaType; - cereal::ControlsState::Reader controlsState; - - // Debug UI - bool show_debug_ui; - - // Speed limit control - bool speed_limit_control_enabled; - int speed_limit_control_policy; - double last_speed_limit_sign_tap; - - // modelV2 - float lane_line_probs[4]; - float road_edge_stds[2]; - QPolygonF track_vertices; - QPolygonF track_edge_vertices; - QPolygonF lane_line_vertices[4]; - QPolygonF road_edge_vertices[2]; - QPolygonF lane_barrier_vertices[2]; - - // lead - QPointF lead_vertices[2]; - - // DMoji state - float driver_pose_vals[3]; - float driver_pose_diff[3]; - float driver_pose_sins[3]; - float driver_pose_coss[3]; - vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; - - bool navigate_on_openpilot_deprecated = false; - cereal::LongitudinalPersonality personality; - cereal::AccelerationPersonality accel_personality; - - float light_sensor = -1; - bool started, ignition, is_metric, map_on_left, longitudinal_control; - bool world_objects_visible = false; - uint64_t started_frame; - - int dynamic_lane_profile; - bool dynamic_lane_profile_status = true; - - bool visual_brake_lights; - - int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; - bool onroadScreenOffEvent; - int sleep_time = -1; - bool touched2 = false; - - bool stand_still_timer; - - bool hide_vego_ui, true_vego_ui; - - int chevron_data; - - bool gac; - int longitudinal_personality; - int longitudinal_accel_personality; - - bool map_visible; - int dev_ui_info; - bool live_torque_toggle; - - bool touch_to_wake = false; - int sleep_btn = -1; - bool sleep_btn_fading_in = false; - int sleep_btn_opacity = 20; - bool button_auto_hide; - - bool reverse_dm_cam; - - bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; - float e2eX[13] = {0}; - - int sidebar_temp_options; - - float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; - float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 - - bool onroad_settings_visible; - - bool map_3d_buildings; - - bool torqued_override; - - bool dynamic_experimental_control; - - int speed_limit_control_engage_type; - - bool mapbox_fullscreen; - - bool speed_limit_warning_flash; - int speed_limit_warning_type; - int speed_limit_warning_value_offset; - - bool custom_driving_model_valid; - cereal::ModelGeneration driving_model_generation; - uint32_t driving_model_capabilities; - - bool feature_status_toggle; - bool onroad_settings_toggle; - - bool dynamic_personality; -} UIScene; - -class UIState : public QObject { - Q_OBJECT - -public: - UIState(QObject* parent = 0); - void updateStatus(); - inline bool engaged() const { - return scene.started && (*sm)["controlsState"].getControlsState().getEnabled(); - } - - void setPrimeType(PrimeType type); - inline PrimeType primeType() const { return prime_type; } - inline bool hasPrime() const { return prime_type > PrimeType::NONE; } - - void setSunnylinkRoles(const std::vector &roles); - void setSunnylinkDeviceUsers(const std::vector &users); - - inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } - inline bool isSunnylinkAdmin() const { - return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { - return role.roleType == RoleType::Admin; - }); - } - inline bool isSunnylinkSponsor() const { - return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { - return role.roleType == RoleType::Sponsor && role.as().roleTier != SponsorTier::Free; - }); - } - inline SponsorRoleModel sunnylinkSponsorRole() const { - std::optional sponsorRoleWithHighestTier = std::nullopt; - for (const auto &role : sunnylinkRoles) { - if(role.roleType != RoleType::Sponsor) - continue; - - if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { - sponsorRoleWithHighestTier = sponsorRole; - } - } - return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free)); - } - inline SponsorTier sunnylinkSponsorTier() const { - return sunnylinkSponsorRole().roleTier; - } - inline std::vector sunnylinkDeviceUsers() const { return sunnylinkUsers; } - inline bool isSunnylinkPaired() const { - return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) { - return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; - }); - } - - int fb_w = 0, fb_h = 0; - - std::unique_ptr sm; - - UIStatus status; - UIScene scene = {}; - - QString language; - - QTransform car_space_transform; - -signals: - void uiUpdate(const UIState &s); - void offroadTransition(bool offroad); - void primeChanged(bool prime); - void primeTypeChanged(PrimeType prime_type); - - void sunnylinkRoleChanged(bool subscriber); - void sunnylinkRolesChanged(std::vector roles); - void sunnylinkDeviceUsersChanged(std::vector users); - -private slots: - void update(); - -private: - QTimer *timer; - bool started_prev = false; - PrimeType prime_type = PrimeType::UNKNOWN; - std::vector sunnylinkRoles = {}; - std::vector sunnylinkUsers = {}; - - bool last_mads_enabled = false; - bool mads_path_state = false; - float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] - float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] -}; - -UIState *uiState(); - -// device management class -class Device : public QObject { - Q_OBJECT - -public: - Device(QObject *parent = 0); - bool isAwake() { return awake; } - void setOffroadBrightness(int brightness) { - offroad_brightness = std::clamp(brightness, 0, 100); - } - -private: - bool awake = false; - int interactive_timeout = 0; - bool ignition_on = false; - - int offroad_brightness = BACKLIGHT_OFFROAD; - int last_brightness = 0; - FirstOrderFilter brightness_filter; - QFuture brightness_future; - - void updateBrightness(const UIState &s); - void updateWakefulness(const UIState &s); - void setAwake(bool on); - -signals: - void displayPowerChanged(bool on); - void interactiveTimeout(); - -public slots: - void resetInteractiveTimeout(int timeout = -1); - void update(const UIState &s); -}; - -Device *device(); - -void ui_update_params(UIState *s); -int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height); -void update_model(UIState *s, - const cereal::ModelDataV2::Reader &model); -void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); -void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); -void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); diff --git a/selfdrive/ui/tests/ui_snapshot.cc b/selfdrive/ui/tests/ui_snapshot.cc index 9074249019..14e0fab835 100644 --- a/selfdrive/ui/tests/ui_snapshot.cc +++ b/selfdrive/ui/tests/ui_snapshot.cc @@ -9,11 +9,7 @@ #include "selfdrive/ui/qt/home.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/window.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#else #include "selfdrive/ui/ui.h" -#endif void saveWidgetAsImage(QWidget *widget, const QString &fileName) { QImage image(widget->size(), QImage::Format_ARGB32); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index bd26741193..72571c4b85 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -11,6 +11,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" +#include "sunnypilot/qt/network/sunnylink/models/role_model.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 @@ -19,7 +20,7 @@ // Projects a point in car to space to the corresponding point in full frame // image space. static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { - const float margin = 500.0f; + const float margin = 1000.0f; const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; const vec3 pt = (vec3){{in_x, in_y, in_z}}; @@ -55,7 +56,7 @@ void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, con } void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); QPointF left, right; pvd->clear(); @@ -63,8 +64,8 @@ void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera if (line_x[i] < 0) continue; - bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); - bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right); + bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); + bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); if (l && r) { // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { @@ -89,15 +90,21 @@ void update_model(UIState *s, int max_idx = get_path_length_idx(lane_lines[0], max_distance); for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { scene.lane_line_probs[i] = lane_line_probs[i]; - update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, &scene.lane_line_vertices[i], max_idx); + update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, 0, &scene.lane_line_vertices[i], max_idx); } + // lane barriers for blind spot + int max_distance_barrier = 40; + int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); + update_line_data(s, lane_lines[1], 0, -0.05, -0.6, &scene.lane_barrier_vertices[0], max_idx_barrier, false); + update_line_data(s, lane_lines[2], 0, -0.05, -0.6, &scene.lane_barrier_vertices[1], max_idx_barrier, false); + // update road edges const auto road_edges = model.getRoadEdges(); const auto road_edge_stds = model.getRoadEdgeStds(); for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { scene.road_edge_stds[i] = road_edge_stds[i]; - update_line_data(s, road_edges[i], 0.025, 0, &scene.road_edge_vertices[i], max_idx); + update_line_data(s, road_edges[i], 0.025, 0, 0, &scene.road_edge_vertices[i], max_idx); } // update path @@ -107,7 +114,8 @@ void update_model(UIState *s, max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); } max_idx = get_path_length_idx(model_position, max_distance); - update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); + update_line_data(s, model_position, 0.9 - scene.mads_path_range * scene.mads_path_scale, 1.22, 1.22, &scene.track_vertices, max_idx, false); + update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); } void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { @@ -152,6 +160,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(); @@ -203,28 +212,108 @@ static void update_state(UIState *s) { } else if (!sm.allAliveAndValid({"wideRoadCameraState"})) { scene.light_sensor = -1; } - 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 && sm.rcv_frame("liveCalibration") > scene.started_frame && sm.rcv_frame("modelV2") > scene.started_frame); + // TODO: SP - Set this dynamically on init with manual toggle or driving model selection + if (sm.updated("lateralPlanSPDEPRECATED")) { + scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); + } + if (sm.updated("controlsState")) { + scene.controlsState = sm["controlsState"].getControlsState(); + } + if (sm.updated("longitudinalPlanSP")) { + for (int i = 0; i < std::size(scene.e2eX); i++) { + scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; + } + } + if (sm.updated("modelV2SP")) { + auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); + scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); + scene.driving_model_generation = model_v2_sp.getModelGeneration(); + scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); + } } void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); + s->scene.map_on_left = params.getBool("NavSettingLeftSide"); + + s->scene.visual_brake_lights = params.getBool("BrakeLights"); + s->scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); + s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); + s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); + s->scene.stand_still_timer = params.getBool("StandStillTimer"); + s->scene.show_debug_ui = params.getBool("ShowDebugUI"); + s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); + s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); + s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); + s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); + s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); + s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); + s->scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); + s->scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); + s->scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); + s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); + s->scene.live_torque_toggle = params.getBool("LiveTorque"); + s->scene.torqued_override = params.getBool("TorquedOverride"); + s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); + s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); + s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); + s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); + s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); + s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); + s->scene.feature_status_toggle = params.getBool("FeatureStatus"); + s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); + + // Handle Onroad Screen Off params + if (s->scene.onroadScreenOff > 0) { + s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ; + } else if (s->scene.onroadScreenOff == 0) { + s->scene.osoTimer = 30 * UI_FREQ; + } else if (s->scene.onroadScreenOff == -1) { + s->scene.osoTimer = 15 * UI_FREQ; + } else { + s->scene.osoTimer = -1; + } } void UIState::updateStatus() { + auto params = Params(); if (scene.started && sm->updated("controlsState")) { auto controls_state = (*sm)["controlsState"].getControlsState(); + auto car_control = (*sm)["carControl"].getCarControl(); + auto car_state = (*sm)["carState"].getCarState(); + auto mads_enabled = car_state.getMadsEnabled(); auto state = controls_state.getState(); if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { status = STATUS_OVERRIDE; } else { - status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; + status = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED : STATUS_MADS : STATUS_DISENGAGED; } + + if (mads_enabled != last_mads_enabled) { + mads_path_state = true; + } + last_mads_enabled = mads_enabled; + if (mads_path_state) { + if (mads_enabled) { + mads_path_count = fmax(mads_path_count - 1, 0); + if (mads_path_count == 0) { + mads_path_state = false; + } + } else { + mads_path_count = fmin(mads_path_count + 1, mads_path_timestep); + if (mads_path_count == mads_path_timestep) { + mads_path_state = false; + } + } + } + scene.mads_path_scale = mads_path_count * (1 / mads_path_timestep); } // Handle onroad/offroad transition @@ -237,13 +326,74 @@ void UIState::updateStatus() { scene.world_objects_visible = false; emit offroadTransition(!scene.started); } + + if (scene.started) { + // Auto hide UI button state machine + { + if (scene.button_auto_hide) { + if (scene.touch_to_wake) { + scene.sleep_btn = 30 * UI_FREQ; + } else if (scene.sleep_btn > 0) { + scene.sleep_btn--; + } else if (scene.sleep_btn == -1) { + scene.sleep_btn = 30 * UI_FREQ; + } + // Check if the sleep button should be fading in + if (scene.sleep_btn_fading_in) { + // Increase the opacity of the sleep button by a small amount + if (scene.sleep_btn_opacity < 20) { + scene.sleep_btn_opacity+= 10; + } + if (scene.sleep_btn_opacity >= 20) { + // If the opacity has reached its maximum value, stop fading in + scene.sleep_btn_fading_in = false; + scene.sleep_btn_opacity = 20; + } + } else if (scene.sleep_btn == 0) { + // Fade out the sleep button as before + if (scene.sleep_btn_opacity > 0) { + scene.sleep_btn_opacity-= 2; + } + } else { + // Set the opacity of the sleep button to its maximum value + scene.sleep_btn_opacity = 20; + } + } else { + scene.sleep_btn_opacity = 20; + } + } + + // Onroad Screen Off Brightness + Timer + Global Brightness + { + if (scene.onroadScreenOff != -2 && scene.touched2) { + scene.sleep_time = scene.osoTimer; + } else if (scene.onroadScreenOff != -2 && + ((scene.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && + ((scene.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene.onroadScreenOffEvent) || + (scene.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { + scene.sleep_time = scene.osoTimer; + } else if (scene.sleep_time > 0 && scene.onroadScreenOff != -2) { + scene.sleep_time--; + } else if (scene.sleep_time == -1 && scene.onroadScreenOff != -2) { + scene.sleep_time = scene.osoTimer; + } + } + } + + if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz + scene.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); + } + + scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); } UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", - "wideRoadCameraState", "managerState", "clocks", + "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", + "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", + "controlsStateSP", "modelV2SP" }); Params params; @@ -264,10 +414,6 @@ void UIState::update() { update_state(this); updateStatus(); - if (std::getenv("PRIME_TYPE")) { - setPrimeType((PrimeType)atoi(std::getenv("PRIME_TYPE"))); - } - if (sm->frame % UI_FREQ == 0) { watchdog_kick(nanos_since_boot()); } @@ -289,6 +435,16 @@ void UIState::setPrimeType(PrimeType type) { } } +void UIState::setSunnylinkRoles(const std::vector& roles) { + sunnylinkRoles = roles; + emit sunnylinkRolesChanged(roles); +} + +void UIState::setSunnylinkDeviceUsers(const std::vector& users) { + sunnylinkUsers = users; + emit sunnylinkDeviceUsersChanged(users); +} + Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { setAwake(true); resetInteractiveTimeout(); @@ -336,6 +492,10 @@ void Device::updateBrightness(const UIState &s) { int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; + } else if (s.scene.started && s.scene.sleep_time == 0 && s.scene.onroadScreenOff != -2) { + brightness = s.scene.onroadScreenOffBrightness * 0.01 * brightness; + } else if (s.scene.brightness) { + brightness = s.scene.brightness * 0.99; } if (brightness != last_brightness) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index bc8b8f5ed4..eb02b8d2d8 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -14,11 +15,31 @@ #include "common/mat.h" #include "common/params.h" #include "common/timing.h" +#include "sunnypilot/qt/network/sunnylink/models/role_model.h" +#include "sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h" +#include "sunnypilot/qt/network/sunnylink/models/user_model.h" #include "system/hardware/hw.h" const int UI_BORDER_SIZE = 30; const int UI_HEADER_HEIGHT = 420; +const int UI_ROAD_NAME_MARGIN_X = 14; + +struct FeatureStatusText { + const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto"}; + const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed"}; + const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active"}; +}; + +struct FeatureStatusColor { + const QStringList dlp_list_color = { "#2020f8", "#0df87a", "#0df8f8" }; + const QStringList gac_list_color = { "#ff4b4b", "#fcff4b", "#4bff66", "#6a0ac9" }; + const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; +}; + +const float DRIVING_PATH_WIDE = 0.9; +const float DRIVING_PATH_NARROW = 0.25; + const int UI_FREQ = 20; // Hz const int BACKLIGHT_OFFROAD = 50; @@ -50,6 +71,7 @@ typedef enum UIStatus { STATUS_DISENGAGED, STATUS_OVERRIDE, STATUS_ENGAGED, + STATUS_MADS, } UIStatus; enum PrimeType { @@ -66,10 +88,18 @@ enum PrimeType { const QColor bg_colors [] = { [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), - [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), + [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), + [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), }; +const QColor tcs_colors [] = { + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), +}; + typedef struct UIScene { bool calibration_valid = false; bool calibration_wide_valid = false; @@ -77,13 +107,24 @@ typedef struct UIScene { mat3 view_from_calib = DEFAULT_CALIBRATION; mat3 view_from_wide_calib = DEFAULT_CALIBRATION; cereal::PandaState::PandaType pandaType; + cereal::ControlsState::Reader controlsState; + + // Debug UI + bool show_debug_ui; + + // Speed limit control + bool speed_limit_control_enabled; + int speed_limit_control_policy; + double last_speed_limit_sign_tap; // modelV2 float lane_line_probs[4]; float road_edge_stds[2]; QPolygonF track_vertices; + QPolygonF track_edge_vertices; QPolygonF lane_line_vertices[4]; QPolygonF road_edge_vertices[2]; + QPolygonF lane_barrier_vertices[2]; // lead QPointF lead_vertices[2]; @@ -97,11 +138,77 @@ typedef struct UIScene { bool navigate_on_openpilot_deprecated = false; cereal::LongitudinalPersonality personality; + cereal::AccelerationPersonality accel_personality; float light_sensor = -1; - bool started, ignition, is_metric, longitudinal_control; + bool started, ignition, is_metric, map_on_left, longitudinal_control; bool world_objects_visible = false; uint64_t started_frame; + + int dynamic_lane_profile; + bool dynamic_lane_profile_status = true; + + bool visual_brake_lights; + + int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; + bool onroadScreenOffEvent; + int sleep_time = -1; + bool touched2 = false; + + bool stand_still_timer; + + bool hide_vego_ui, true_vego_ui; + + int chevron_data; + + bool gac; + int longitudinal_personality; + int longitudinal_accel_personality; + + bool map_visible; + int dev_ui_info; + bool live_torque_toggle; + + bool touch_to_wake = false; + int sleep_btn = -1; + bool sleep_btn_fading_in = false; + int sleep_btn_opacity = 20; + bool button_auto_hide; + + bool reverse_dm_cam; + + bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; + float e2eX[13] = {0}; + + int sidebar_temp_options; + + float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; + float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 + + bool onroad_settings_visible; + + bool map_3d_buildings; + + bool torqued_override; + + bool dynamic_experimental_control; + + int speed_limit_control_engage_type; + + bool mapbox_fullscreen; + + bool speed_limit_warning_flash; + int speed_limit_warning_type; + int speed_limit_warning_value_offset; + + bool custom_driving_model_valid; + cereal::ModelGeneration driving_model_generation; + uint32_t driving_model_capabilities; + + bool feature_status_toggle; + bool onroad_settings_toggle; + + bool dynamic_personality; } UIScene; class UIState : public QObject { @@ -118,6 +225,42 @@ public: inline PrimeType primeType() const { return prime_type; } inline bool hasPrime() const { return prime_type > PrimeType::NONE; } + void setSunnylinkRoles(const std::vector &roles); + void setSunnylinkDeviceUsers(const std::vector &users); + + inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } + inline bool isSunnylinkAdmin() const { + return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { + return role.roleType == RoleType::Admin; + }); + } + inline bool isSunnylinkSponsor() const { + return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { + return role.roleType == RoleType::Sponsor && role.as().roleTier != SponsorTier::Free; + }); + } + inline SponsorRoleModel sunnylinkSponsorRole() const { + std::optional sponsorRoleWithHighestTier = std::nullopt; + for (const auto &role : sunnylinkRoles) { + if(role.roleType != RoleType::Sponsor) + continue; + + if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { + sponsorRoleWithHighestTier = sponsorRole; + } + } + return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free)); + } + inline SponsorTier sunnylinkSponsorTier() const { + return sunnylinkSponsorRole().roleTier; + } + inline std::vector sunnylinkDeviceUsers() const { return sunnylinkUsers; } + inline bool isSunnylinkPaired() const { + return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) { + return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; + }); + } + int fb_w = 0, fb_h = 0; std::unique_ptr sm; @@ -135,6 +278,10 @@ signals: void primeChanged(bool prime); void primeTypeChanged(PrimeType prime_type); + void sunnylinkRoleChanged(bool subscriber); + void sunnylinkRolesChanged(std::vector roles); + void sunnylinkDeviceUsersChanged(std::vector users); + private slots: void update(); @@ -142,6 +289,13 @@ private: QTimer *timer; bool started_prev = false; PrimeType prime_type = PrimeType::UNKNOWN; + std::vector sunnylinkRoles = {}; + std::vector sunnylinkUsers = {}; + + bool last_mads_enabled = false; + bool mads_path_state = false; + float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] + float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] }; UIState *uiState(); @@ -189,4 +343,4 @@ void update_model(UIState *s, void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); + float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); From f04c0198431e86b68f04da4bdf2969f699bf830e Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Mon, 15 Jul 2024 23:07:27 -0400 Subject: [PATCH 423/575] sync --- SConstruct | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/SConstruct b/SConstruct index 2dfe1efc6a..13da37c7a8 100644 --- a/SConstruct +++ b/SConstruct @@ -58,39 +58,6 @@ def is_internal_developer(debug=False): return sunnypilot -def is_sunnypilot_developer(): - """Check if the current user is a SunnyPilot developer.""" - - def collect_required_gpg_key_ids(keys_dir): - try: - key_ids = [filename.split('.')[0] for filename in os.listdir(keys_dir) if filename.endswith(".gpg")] - print(f"Required GPG key IDs: {key_ids}") - return key_ids - except OSError as e: - print(f"Failed to read GPG key IDs from {keys_dir}. Error: {e}") - return [] - - def is_sunnypilot_key_available(required_gpg_key_ids): - for key_id in required_gpg_key_ids: - try: - result = subprocess.check_output(['gpg', '--list-keys', key_id], stderr=subprocess.STDOUT) - if key_id in result.decode(): - print(f"GPG key {key_id} is available.") - return True - except subprocess.CalledProcessError as e: - print(f"Failed to list GPG key {key_id}. Error:", e.output.decode().strip()) - return False - - keys_dir = os.path.join(BASEDIR, ".git-crypt/keys/default/0") - required_gpg_key_ids = collect_required_gpg_key_ids(keys_dir) - - sunnypilot = is_sunnypilot_key_available(required_gpg_key_ids) - - if not sunnypilot: - print("None of the required GPG keys are available.") - - return sunnypilot - Decider('MD5-timestamp') SetOption('num_jobs', int(os.cpu_count()/2)) @@ -145,12 +112,6 @@ AddOption('--minimal', dest='extras', default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS) help='the minimum build to run openpilot. no tests, tools, etc.') - -AddOption('--sunnypilot', - action='store_true', - dest='sunnypilot', - default=is_sunnypilot_developer(), # check if the current user is a SunnyPilot developer - help='Will make sure it builds SP ui and other SP specific things that are not public (encrypted sources)') AddOption('--sunnypilot', action='store_true', From dbaa14dec11b748ebfb38cf298415d3390e30ae4 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Mon, 15 Jul 2024 23:40:54 -0400 Subject: [PATCH 424/575] revert translations --- selfdrive/ui/translations/main_ar.ts | 1947 +-------------------- selfdrive/ui/translations/main_de.ts | 1947 +-------------------- selfdrive/ui/translations/main_es.ts | 2029 +--------------------- selfdrive/ui/translations/main_fr.ts | 1947 +-------------------- selfdrive/ui/translations/main_ja.ts | 1947 +-------------------- selfdrive/ui/translations/main_ko.ts | 1947 +-------------------- selfdrive/ui/translations/main_pt-BR.ts | 1947 +-------------------- selfdrive/ui/translations/main_th.ts | 1947 +-------------------- selfdrive/ui/translations/main_tr.ts | 1943 +-------------------- selfdrive/ui/translations/main_zh-CHS.ts | 1947 +-------------------- selfdrive/ui/translations/main_zh-CHT.ts | 1947 +-------------------- 11 files changed, 105 insertions(+), 21390 deletions(-) diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index d425eb1d18..d8146723a4 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -86,18 +86,6 @@ for "%1" من أجل "%1"
- - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - -
AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - SPEED + SPEED LIMIT - LIMIT - - - - AnnotatedCameraWidgetSP - - km/h - كم/س - - - mph - ميل/س - - - MAX - MAX - - - SPEED - SPEED - - - LIMIT - LIMIT - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + LIMIT @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an إلغاء - - CustomOffsetsSettings - - Back - السابق - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - يجب عليك قبول الشروط والأحكام من أجل استخدام openpilot. + يجب عليك قبول الشروط والأحكام من أجل استخدام openpilot. Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 رفض، إلغاء التثبيت %1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - مراجعة الأدوار والميزات والقيود في openpilot + مراجعة الأدوار والميزات والقيود في openpilot Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - يحتاج openpilot أن يتم ضبط الجهاز ضمن حدود 4 درجات يميناً أو يساراً و5 درجات نحو الأعلى أو 9 نحو الأسفل. يقوم openpilot بالمعايرة باستمرار، ونادراً ما يحتاج إلى عملية إعادة الضبط. + يحتاج openpilot أن يتم ضبط الجهاز ضمن حدود 4 درجات يميناً أو يساراً و5 درجات نحو الأعلى أو 9 نحو الأسفل. يقوم openpilot بالمعايرة باستمرار، ونادراً ما يحتاج إلى عملية إعادة الضبط. Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR إقران - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -618,79 +349,6 @@ Please use caution when using this feature. Only use the blinker when traffic an جارٍ التثبيت... - - LaneChangeSettings - - Back - السابق - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -732,48 +390,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.
بانتظار الطريق - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - م - - - hr - س - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -803,14 +419,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.
Wrong password كلمة مرور خاطئة - - Scan - - - - Scanning... - - OffroadAlert @@ -864,16 +472,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.
openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. لقد اكتشف openpilot تغييراً في موقع تركيب الجهاز. تأكد من تثبيت الجهاز بشكل كامل في موقعه وتثبيته بإحكام على الزجاج الأمامي. - - 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. - -%1 - - - - 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. - - OffroadHome @@ -913,186 +511,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.
إعادة التشغيل - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - د - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - التحقق - - - Country - - - - SELECT - اختيار - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - تحديث - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1123,51 +541,6 @@ Warning: You are on a metered connection! إلغاء - - ParamControlSP - - Enable - تمكين - - - Cancel - إلغاء - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - كم/س - - - mph - ميل/س - - PrimeAdWidget @@ -1222,7 +595,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1277,30 +650,6 @@ Warning: You are on a metered connection! now الآن - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1343,147 +692,6 @@ This may take up to a minute. تم تفعيل إعادة ضبط النظام. اضغط على تأكيد لمسح جميع المحتويات والإعدادات. اضغط على إلغاء لاستئناف التمهيد. - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1507,61 +715,6 @@ Reboot Required. البرنامج - - SettingsWindowSP - - × - × - - - Device - الجهاز - - - Network - الشبكة - - - sunnylink - - - - Toggles - المثبتتات - - - Software - البرنامج - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1754,69 +907,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1893,233 +983,6 @@ Reboot Required. أحدث نسخة، آخر تحقق %1 - - SoftwarePanelSP - - Driving Model - - - - SELECT - اختيار - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - إعادة ضبط المعايرة - - - Warning: You are on a metered connection! - - - - Continue - متابعة - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - كم/س - - - mph - ميل/س - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - غير متاح - - - km/h - كم/س - - - mph - ميل/س - - SshControl @@ -2166,399 +1029,6 @@ Reboot Required. تمكين SSH - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - غير متاح - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - إقران - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2622,19 +1092,19 @@ Reboot Required. Show ETA in 24h Format - إظهار الوقت المقدر للوصول بصيغة 24 ساعة + إظهار الوقت المقدر للوصول بصيغة 24 ساعة Use 24h format instead of am/pm - استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء + استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء Show Map on Left Side of UI - عرض الخريطة على الجانب الأيسر من واجهة المستخدم + عرض الخريطة على الجانب الأيسر من واجهة المستخدم Show map on left side when in split screen view. - عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. + عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. openpilot Longitudinal Control (Alpha) @@ -2717,244 +1187,6 @@ Reboot Required. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - التحكم الطولي openpilot (ألفا) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - تحذير: التحكم الطولي في openpilot في المرحلة ألفا لهذه السيارة، وسيقوم بتعطيل مكابح الطوارئ الآلية (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - في هذه السيارة يعمل openpilot افتراضياً بالشكل المدمج في التحكم التكيفي في السرعة بدلاً من التحكم الطولي. قم بتمكين هذا الخيار من أجل الانتقال إلى التحكم الطولي. يوصى بتمكين الوضع التجريبي عند استخدام وضع التحكم الطولي ألفا من openpilot. - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - الوضع التجريبي - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - فك الارتباط عن دواسة الوقود - - - When enabled, pressing the accelerator pedal will disengage openpilot. - عند تمكين هذه الميزة، فإن الضغط على دواسة الوقود سيؤدي إلى فك ارتباط openpilot. - - - Enable Lane Departure Warnings - قم بتمكين تحذيرات مغادرة المسار - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - تلقي التنبيهات من أجل الالتفاف للعودة إلى المسار عندما تنحرف سيارتك فوق الخط المحدد للمسار دون تشغيل إشارة الانعطاف عند القيادة لمسافة تزيد عن 31 ميل/سا (50 كم/سا). - - - Always-On Driver Monitoring - - - - Enable driver monitoring even when openpilot is not engaged. - - - - Record and Upload Driver Camera - تسجيل وتحميل كاميرا السائق - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - تحميل البيانات من الكاميرا المواجهة للسائق، والمساعدة في تحسين خوارزمية مراقبة السائق. - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - استخدام النظام المتري - - - Display speed in km/h instead of mph. - عرض السرعة بواحدات كم/سا بدلاً من ميل/سا. - - - Show ETA in 24h Format - إظهار الوقت المقدر للوصول بصيغة 24 ساعة - - - Use 24h format instead of am/pm - استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء - - - Show Map on Left Side of UI - عرض الخريطة على الجانب الأيسر من واجهة المستخدم - - - Show map on left side when in split screen view. - عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. - - - Aggressive - الهجومي - - - Moderate - - - - Standard - القياسي - - - Relaxed - الراحة - - - Driving Personality - شخصية القيادة - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - يتم وضع openpilot بشكل قياسي في <b>وضعية الراحة</b>. يمكن الوضع التجريبي <b>ميزات المستوى ألفا</b> التي لا تكون جاهزة في وضع الراحة: - - - End-to-End Longitudinal Control - التحكم الطولي من طرف إلى طرف - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - دع نظام القيادة يتحكم بالوقود والمكابح. سيقوم openpilot بالقيادة كما لو أنه كائن بشري، بما في ذلك التوقف عند الإشارة الحمراء، وإشارات التوقف. وبما أن نمط القيادة يحدد سرعة القيادة، فإن السرعة المضبوطة تشكل الحد الأقصى فقط. هذه خاصية الجودة ألفا، فيجب توقع حدوث الأخطاء. - - - New Driving Visualization - تصور القيادة الديد - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - الوضع التجريبي غير متوفر حالياً في هذه السيارة نظراً لاستخدام رصيد التحكم التكيفي بالسرعة من أجل التحكم الطولي. - - - openpilot longitudinal control may come in a future update. - قد يتم الحصول على التحكم الطولي في openpilot في عمليات التحديث المستقبلية. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - يمكن اختبار نسخة ألفا من التحكم الطولي من openpilot، مع الوضع التجريبي، لكن على الفروع غير المطلقة. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - تمكين التحكم الطولي من openpilot (ألفا) للسماح بالوضع التجريبي. - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2990,165 +1222,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. فشل التحديث - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 05124209d8..010aa4d304 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -86,18 +86,6 @@ for "%1" für "%1" - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - Geschwindigkeit + Geschwindigkeit LIMIT - LIMIT - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mph - - - MAX - MAX - - - SPEED - Geschwindigkeit - - - LIMIT - LIMIT - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + LIMIT @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an Abbrechen - - CustomOffsetsSettings - - Back - Zurück - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. + Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 Ablehnen, deinstallieren %1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - Wiederhole die Regeln, Fähigkeiten und Limitierungen von Openpilot + Wiederhole die Regeln, Fähigkeiten und Limitierungen von Openpilot Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 9° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig. + Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 9° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig. Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -614,79 +345,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Installiere... - - LaneChangeSettings - - Back - Zurück - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -728,48 +386,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.
- - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - m - - - hr - std - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -799,14 +415,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.
Wrong password Falsches Passwort - - Scan - - - - Scanning... - - OffroadAlert @@ -859,16 +467,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.
Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - - 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. - -%1 - - - - 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. - - OffroadHome @@ -908,186 +506,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - min - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - ÜBERPRÜFEN - - - Country - - - - SELECT - AUSWÄHLEN - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - Aktualisieren - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1118,51 +536,6 @@ Warning: You are on a metered connection! Aktivieren - - ParamControlSP - - Enable - Aktivieren - - - Cancel - Abbrechen - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mph - - PrimeAdWidget @@ -1217,7 +590,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1260,30 +633,6 @@ Warning: You are on a metered connection! now - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1325,147 +674,6 @@ This may take up to a minute. - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1489,61 +697,6 @@ Reboot Required. Software - - SettingsWindowSP - - × - x - - - Device - Gerät - - - Network - Netzwerk - - - sunnylink - - - - Toggles - Schalter - - - Software - Software - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1737,69 +890,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1877,233 +967,6 @@ Reboot Required. - - SoftwarePanelSP - - Driving Model - - - - SELECT - AUSWÄHLEN - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - Neu kalibrieren - - - Warning: You are on a metered connection! - - - - Continue - Fortsetzen - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mph - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - Nicht verfügbar - - - km/h - km/h - - - mph - mph - - SshControl @@ -2150,399 +1013,6 @@ Reboot Required. SSH aktivieren - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - Nicht verfügbar - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2602,21 +1072,21 @@ Reboot Required. Use 24h format instead of am/pm - Benutze das 24Stunden Format anstatt am/pm + Benutze das 24Stunden Format anstatt am/pm Show Map on Left Side of UI Too long for UI - Zeige die Karte auf der linken Seite + Zeige die Karte auf der linken Seite Show map on left side when in split screen view. - Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. + Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. Show ETA in 24h Format Too long for UI - Zeige die Ankunftszeit im 24 Stunden Format + Zeige die Ankunftszeit im 24 Stunden Format Experimental Mode @@ -2703,244 +1173,6 @@ Reboot Required. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - Experimenteller Modus - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - Bei Gasbetätigung ausschalten - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Wenn aktiviert, deaktiviert sich Openpilot sobald das Gaspedal betätigt wird. - - - Enable Lane Departure Warnings - Spurverlassenswarnungen aktivieren - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - Erhalte Warnungen, zurück in die Spur zu lenken, wenn dein Auto über eine erkannte Fahrstreifenmarkierung ohne aktivierten Blinker mit mehr als 50 km/h fährt. - - - Always-On Driver Monitoring - - - - Enable driver monitoring even when openpilot is not engaged. - - - - Record and Upload Driver Camera - Fahrerkamera aufnehmen und hochladen - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Lade Daten der Fahreraufmerksamkeitsüberwachungskamera hoch, um die Fahreraufmerksamkeitsüberwachungsalgorithmen zu verbessern. - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - Benutze das metrische System - - - Display speed in km/h instead of mph. - Zeige die Geschwindigkeit in km/h anstatt von mph. - - - Show ETA in 24h Format - Zeige die Ankunftszeit im 24 Stunden Format - - - Use 24h format instead of am/pm - Benutze das 24Stunden Format anstatt am/pm - - - Show Map on Left Side of UI - Zeige die Karte auf der linken Seite - - - Show map on left side when in split screen view. - Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. - - - Aggressive - - - - Moderate - - - - Standard - - - - Relaxed - - - - Driving Personality - - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - Openpilot fährt standardmäßig im <b>entspannten Modus</b>. Der Experimentelle Modus aktiviert<b>Alpha-level Funktionen</b>, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden: - - - End-to-End Longitudinal Control - - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Lass das Fahrmodell Gas und Bremse kontrollieren. Openpilot wird so fahren, wie es dies von einem Menschen erwarten würde; inklusive des Anhaltens für Ampeln und Stoppschildern. Da das Fahrmodell entscheidet wie schnell es fährt stellt die gesetzte Geschwindigkeit lediglich das obere Limit dar. Dies ist ein Alpha-level Funktion. Fehler sind zu erwarten. - - - New Driving Visualization - Neue Fahrvisualisierung - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar da es den eingebauten adaptiven Tempomaten des Autos benutzt. - - - openpilot longitudinal control may come in a future update. - - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2976,165 +1208,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. Aktualisierung fehlgeschlagen - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index e3e4bf5add..ee262c0953 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -86,18 +86,6 @@ for "%1" para "%1" - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - VELOCIDAD + VELOCIDAD LIMIT - LIMITE - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mph - - - MAX - MAX - - - SPEED - VELOCIDAD - - - LIMIT - LIMITE - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + LIMITE @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an Cancelar - - CustomOffsetsSettings - - Back - - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Debe aceptar los terminos y condiciones para poder utilizar openpilot. + Debe aceptar los terminos y condiciones para poder utilizar openpilot. Back @@ -262,37 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 Rechazar, desinstalar %1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - - - - DestinationWidget - - Home - - - - Work - - - - No destination set - - - - home - - - - work - - - - No %1 location set - - DevicePanel @@ -358,7 +200,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - Revisar las reglas, características y limitaciones de openpilot + Revisar las reglas, características y limitaciones de openpilot Are you sure you want to review the training guide? @@ -398,7 +240,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot requiere que el dispositivo sea montado entre 4° izquierda o derecha y entre 5° arriba o 9° abajo. openpilot está constantemente en calibración, reiniciar es rara vez necesario. + openpilot requiere que el dispositivo sea montado entre 4° izquierda o derecha y entre 5° arriba o 9° abajo. openpilot está constantemente en calibración, reiniciar es rara vez necesario. Your device is pointed %1° %2 and %3° %4. @@ -436,144 +278,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Disengage to Power Off Desactivar para apagar - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -614,162 +318,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Instalando... - - LaneChangeSettings - - Back - - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - - - MapETA - - eta - - - - min - - - - hr - - - - - MapSettings - - NAVIGATION - - - - Manage at connect.comma.ai - - - - - MapWindow - - Map Loading - - - - Waiting for GPS - - - - Waiting for route - - - - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - - - - hr - - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -799,14 +347,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password Contraseña equivocada - - Scan - - - - Scanning... - - OffroadAlert @@ -860,16 +400,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot detectó un cambio en la posición de montaje del dispositivo. Asegúrese de que el dispositivo esté completamente asentado en el soporte y que el soporte esté firmemente asegurado al parabrisas. - - 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. - -%1 - - - - 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. - - OffroadHome @@ -909,186 +439,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Reiniciar Dispositivo - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - VERIFICAR - - - Country - - - - SELECT - SELECCIONAR - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - ACTUALIZAR - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1119,51 +469,6 @@ Warning: You are on a metered connection! Cancelar - - ParamControlSP - - Enable - Activar - - - Cancel - Cancelar - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mph - - PrimeAdWidget @@ -1192,11 +497,7 @@ Warning: You are on a metered connection! Remote snapshots - Istantánea remota - - - Turn-by-turn navigation - + Istantánea remota @@ -1222,7 +523,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot now @@ -1249,46 +550,6 @@ Warning: You are on a metered connection! hace %n días - - km - - - - m - - - - mi - - - - ft - - - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1331,147 +592,6 @@ Esto puede tardar hasta un minuto. No es posible montar una partición de datos. Partición corrompida. Confirme para borrar y reiniciar su dispositivo. - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1495,61 +615,6 @@ Reboot Required. Software - - SettingsWindowSP - - × - × - - - Device - Dispositivo - - - Network - Red - - - sunnylink - - - - Toggles - Ajustes - - - Software - Software - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1742,69 +807,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1881,233 +883,6 @@ Reboot Required. actualizado, último chequeo %1 - - SoftwarePanelSP - - Driving Model - - - - SELECT - SELECCIONAR - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - Reiniciar Calibración - - - Warning: You are on a metered connection! - - - - Continue - Continuar - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mph - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - N/A - - - km/h - km/h - - - mph - mph - - SshControl @@ -2154,399 +929,6 @@ Reboot Required. Habilitar SSH - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - N/A - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - EMPAREJAR - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2689,244 +1071,6 @@ Reboot Required. Activar el control longitudinal experimental para permitir el modo Experimental. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - Control longitudinal de openpilot (Alfa) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - Aviso: el control longitudinal de openpilot está en fase alfa para este automóvil y desactivará el Frenado Automático de Emergencia (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - En este vehículo, openpilot se configura de manera predeterminada con el Control de Crucero Adaptativo (ACC) incorporado en el automóvil en lugar del control longitudinal de openpilot. Habilita esta opción para cambiar al control longitudinal de openpilot. Se recomienda activar el modo experimental al habilitar el control longitudinal alfa de openpilot. - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - Modo Experimental - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - Desactivar con el Acelerador - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Cuando esté activado, presionar el acelerador deshabilitará el openpilot. - - - Enable Lane Departure Warnings - Activar Aviso de Salida de Carril - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - Recibir alertas para volver dentro del carril cuando su vehículo se sale fuera del carril sin que esté activado la señal de giro mientras esté conduciendo por encima de 50 km/h (31 mph). - - - Always-On Driver Monitoring - Monitoreo del Conductor Siempre Activo - - - Enable driver monitoring even when openpilot is not engaged. - Habilite el monitoreo del conductor incluso cuando Openpilot no esté activado. - - - Record and Upload Driver Camera - Grabar y Subir Cámara del Conductor - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Subir datos de la cámara del conductor para ayudar a mejorar el algoritmo de monitorización del conductor. - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - Usar Sistema Métrico - - - Display speed in km/h instead of mph. - Mostrar velocidad en km/h en vez de mph. - - - Show ETA in 24h Format - - - - Use 24h format instead of am/pm - - - - Show Map on Left Side of UI - - - - Show map on left side when in split screen view. - - - - Aggressive - Agresivo - - - Moderate - - - - Standard - Estándar - - - Relaxed - Relajado - - - Driving Personality - Personalidad de conducción - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot por defecto conduce en <b>modo chill</b>. El modo Experimental activa <b>recursos de nível-alfa</b> que no están listos para el modo chill. Los recursos del modo expeimental están listados abajo: - - - End-to-End Longitudinal Control - 🌮 Control Longitudinal de Punta a Punta 🌮 - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Dajar que el modelo de conducción controle la aceleración y el frenado. openpilot va a conducir como lo haría una persona, incluiyendo parar en los semaforos en rojo y las señales de stop. Dado que el modelo decide la velocidad de conducción, la velocidad de crucero establecida actuará como limitador. Este recurso es de una calidad alfa; errores pueden ocurrir. - - - New Driving Visualization - Nueva Visualización de la conducción - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - La visualización de la conducción cambiará a la cámara que enfoca la carretera a velocidades bajas para mostrar mejor los giros. El logo del modo experimental se mostrará en la esquina superior derecha. - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - El modo Experimental no está disponible actualmente para este auto, ya que el ACC del auto está siendo usado para el control longitudinal. - - - openpilot longitudinal control may come in a future update. - El control longitudinal de openpilot podrá llegar en futuras actualizaciones. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Se puede probar una versión experimental del control longitudinal openpilot, junto con el modo Experimental, en ramas sin liberación. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - Activar el control longitudinal experimental para permitir el modo Experimental. - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2962,165 +1106,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. Actualización fallida - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 4c6741b6f0..dde6adadd3 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -86,18 +86,6 @@ for "%1" pour "%1" - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - VITESSE + VITESSE LIMIT - LIMITE - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mi/h - - - MAX - MAX - - - SPEED - VITESSE - - - LIMIT - LIMITE - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + LIMITE @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an Annuler - - CustomOffsetsSettings - - Back - Retour - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Vous devez accepter les conditions générales pour utiliser openpilot. + Vous devez accepter les conditions générales pour utiliser openpilot. Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 Refuser, désinstaller %1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -346,7 +215,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - Revoir les règles, fonctionnalités et limitations d'openpilot + Revoir les règles, fonctionnalités et limitations d'openpilot Are you sure you want to review the training guide? @@ -386,7 +255,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot nécessite que l'appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 9° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire. + openpilot nécessite que l'appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 9° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire. Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -614,79 +345,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Installation... - - LaneChangeSettings - - Back - Retour - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -728,48 +386,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.En attente d'un trajet - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - m - - - hr - h - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -799,14 +415,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password Mot de passe incorrect - - Scan - - - - Scanning... - - OffroadAlert @@ -860,16 +468,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot a détecté un changement dans la position de montage de l'appareil. Assurez-vous que l'appareil est totalement inséré dans le support et que le support est fermement fixé au pare-brise. - - 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. - -%1 - - - - 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. - - OffroadHome @@ -909,186 +507,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - min - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - VÉRIFIER - - - Country - - - - SELECT - SÉLECTIONNER - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - MISE À JOUR - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1119,51 +537,6 @@ Warning: You are on a metered connection! Annuler - - ParamControlSP - - Enable - Activer - - - Cancel - Annuler - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mi/h - - PrimeAdWidget @@ -1218,7 +591,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1261,30 +634,6 @@ Warning: You are on a metered connection! now - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1327,147 +676,6 @@ Cela peut prendre jusqu'à une minute. - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1491,61 +699,6 @@ Reboot Required. Logiciel - - SettingsWindowSP - - × - × - - - Device - Appareil - - - Network - Réseau - - - sunnylink - - - - Toggles - Options - - - Software - Logiciel - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1738,69 +891,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1877,233 +967,6 @@ Reboot Required. à jour, dernière vérification %1 - - SoftwarePanelSP - - Driving Model - - - - SELECT - SÉLECTIONNER - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - Réinitialiser la calibration - - - Warning: You are on a metered connection! - - - - Continue - Continuer - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mi/h - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - N/A - - - km/h - km/h - - - mph - mi/h - - SshControl @@ -2150,399 +1013,6 @@ Reboot Required. Activer SSH - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - N/A - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2622,19 +1092,19 @@ Reboot Required. Show ETA in 24h Format - Afficher l'heure d'arrivée en format 24h + Afficher l'heure d'arrivée en format 24h Use 24h format instead of am/pm - Utiliser le format 24h plutôt que am/pm + Utiliser le format 24h plutôt que am/pm Show Map on Left Side of UI - Afficher la carte à gauche de l'interface + Afficher la carte à gauche de l'interface Show map on left side when in split screen view. - Afficher la carte à gauche en mode écran scindé. + Afficher la carte à gauche en mode écran scindé. Aggressive @@ -2701,244 +1171,6 @@ Reboot Required. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - Contrôle longitudinal openpilot (Alpha) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - ATTENTION : le contrôle longitudinal openpilot est en alpha pour cette voiture et désactivera le freinage d'urgence automatique (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - Sur cette voiture, openpilot utilise par défaut le régulateur de vitesse adaptatif intégré à la voiture plutôt que le contrôle longitudinal d'openpilot. Activez ceci pour passer au contrôle longitudinal openpilot. Il est recommandé d'activer le mode expérimental lors de l'activation du contrôle longitudinal openpilot alpha. - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - Mode expérimental - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - Désengager avec la pédale d'accélérateur - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Lorsqu'il est activé, appuyer sur la pédale d'accélérateur désengagera openpilot. - - - Enable Lane Departure Warnings - Activer les avertissements de sortie de voie - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - Recevez des alertes pour revenir dans la voie lorsque votre véhicule dérive au-delà d'une ligne de voie détectée sans clignotant activé en roulant à plus de 31 mph (50 km/h). - - - Always-On Driver Monitoring - - - - Enable driver monitoring even when openpilot is not engaged. - - - - Record and Upload Driver Camera - Enregistrer et télécharger la caméra conducteur - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Publiez les données de la caméra orientée vers le conducteur et aidez à améliorer l'algorithme de surveillance du conducteur. - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - Utiliser le système métrique - - - Display speed in km/h instead of mph. - Afficher la vitesse en km/h au lieu de mph. - - - Show ETA in 24h Format - Afficher l'heure d'arrivée en format 24h - - - Use 24h format instead of am/pm - Utiliser le format 24h plutôt que am/pm - - - Show Map on Left Side of UI - Afficher la carte à gauche de l'interface - - - Show map on left side when in split screen view. - Afficher la carte à gauche en mode écran scindé. - - - Aggressive - Aggressif - - - Moderate - - - - Standard - Standard - - - Relaxed - Détendu - - - Driving Personality - Personnalité de conduite - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - Par défaut, openpilot conduit en <b>mode détente</b>. Le mode expérimental permet d'activer des <b>fonctionnalités alpha</b> qui ne sont pas prêtes pour le mode détente. Les fonctionnalités expérimentales sont listées ci-dessous : - - - End-to-End Longitudinal Control - Contrôle longitudinal de bout en bout - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Laissez le modèle de conduite contrôler l'accélérateur et les freins. openpilot conduira comme il pense qu'un humain le ferait, y compris s'arrêter aux feux rouges et aux panneaux stop. Comme le modèle de conduite décide de la vitesse à adopter, la vitesse définie ne servira que de limite supérieure. Cette fonctionnalité est de qualité alpha ; des erreurs sont à prévoir. - - - New Driving Visualization - Nouvelle visualisation de la conduite - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - La visualisation de la conduite passera sur la caméra grand angle dirigée vers la route à faible vitesse afin de mieux montrer certains virages. Le logo du mode expérimental s'affichera également dans le coin supérieur droit. - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - Le mode expérimental est actuellement indisponible pour cette voiture car le régulateur de vitesse adaptatif d'origine est utilisé pour le contrôle longitudinal. - - - openpilot longitudinal control may come in a future update. - Le contrôle longitudinal openpilot pourrait être disponible dans une future mise à jour. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Une version alpha du contrôle longitudinal openpilot peut être testée, avec le mode expérimental, sur des branches non publiées. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - Activer le contrôle longitudinal d'openpilot (en alpha) pour autoriser le mode expérimental. - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2974,165 +1206,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. Échec de la mise à jour - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 5154d78b52..e0fb60620b 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -86,18 +86,6 @@ for "%1" ネットワーク名:%1 - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - 速度 + 速度 LIMIT - 制限速度 - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mph - - - MAX - 最高速度 - - - SPEED - 速度 - - - LIMIT - 制限速度 - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + 制限速度 @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an キャンセル - - CustomOffsetsSettings - - Back - 戻る - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - openpilot をご利用される前に、利用規約に同意する必要があります。 + openpilot をご利用される前に、利用規約に同意する必要があります。 Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 拒否して %1 をアンインストール - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - openpilot の特徴を見る + openpilot の特徴を見る Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilotの本体は、左右4°以内、上5°、下9°以内の角度で取付ける必要があります。継続してキャリブレーションを続けているので、手動でリセットを行う必要はほぼありません。 + openpilotの本体は、左右4°以内、上5°、下9°以内の角度で取付ける必要があります。継続してキャリブレーションを続けているので、手動でリセットを行う必要はほぼありません。 Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -613,79 +344,6 @@ Please use caution when using this feature. Only use the blinker when traffic an インストールしています... - - LaneChangeSettings - - Back - 戻る - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -727,48 +385,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - メートル - - - hr - 時間 - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -798,14 +414,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password パスワードが間違っています - - Scan - - - - Scanning... - - OffroadAlert @@ -858,16 +466,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - - 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. - -%1 - - - - 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. - - OffroadHome @@ -907,186 +505,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - 確認 - - - Country - - - - SELECT - 選択 - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - 更新 - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1117,51 +535,6 @@ Warning: You are on a metered connection! を有効化 - - ParamControlSP - - Enable - を有効化 - - - Cancel - キャンセル - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mph - - PrimeAdWidget @@ -1216,7 +589,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1256,30 +629,6 @@ Warning: You are on a metered connection! now - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1321,147 +670,6 @@ This may take up to a minute. - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1485,61 +693,6 @@ Reboot Required. ソフトウェア - - SettingsWindowSP - - × - × - - - Device - デバイス - - - Network - ネットワーク - - - sunnylink - - - - Toggles - 機能設定 - - - Software - ソフトウェア - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1732,69 +885,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1871,233 +961,6 @@ Reboot Required. - - SoftwarePanelSP - - Driving Model - - - - SELECT - 選択 - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - キャリブレーションをリセット - - - Warning: You are on a metered connection! - - - - Continue - 続ける - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mph - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - N/A - - - km/h - km/h - - - mph - mph - - SshControl @@ -2144,399 +1007,6 @@ Reboot Required. SSH を有効化 - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - N/A - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2600,19 +1070,19 @@ Reboot Required. Show ETA in 24h Format - 24時間表示 + 24時間表示 Use 24h format instead of am/pm - AM/PM の代わりに24時間形式を使用します + AM/PM の代わりに24時間形式を使用します Show Map on Left Side of UI - ディスプレイの左側にマップを表示 + ディスプレイの左側にマップを表示 Show map on left side when in split screen view. - 分割画面表示の場合、ディスプレイの左側にマップを表示します。 + 分割画面表示の場合、ディスプレイの左側にマップを表示します。 Experimental Mode @@ -2695,244 +1165,6 @@ Reboot Required. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - 実験モード - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - アクセルを踏むと openpilot を中断 - - - When enabled, pressing the accelerator pedal will disengage openpilot. - この機能を有効化すると、openpilotを利用中にアクセルを踏むとopenpilotによる運転サポートを中断します。 - - - Enable Lane Departure Warnings - 車線逸脱警報機能を有効化 - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - 時速31マイル(50km)を超えるスピードで走行中、ウインカーを作動させずに検出された車線ライン上に車両が触れた場合、手動で車線内に戻るように警告を行います。 - - - Always-On Driver Monitoring - - - - Enable driver monitoring even when openpilot is not engaged. - - - - Record and Upload Driver Camera - 車内カメラの録画とアップロード - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 車内カメラの映像をアップロードし、ドライバー監視システムのアルゴリズムの向上に役立てます。 - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - メートル法を使用 - - - Display speed in km/h instead of mph. - 速度は mph ではなく km/h で表示されます。 - - - Show ETA in 24h Format - 24時間表示 - - - Use 24h format instead of am/pm - AM/PM の代わりに24時間形式を使用します - - - Show Map on Left Side of UI - ディスプレイの左側にマップを表示 - - - Show map on left side when in split screen view. - 分割画面表示の場合、ディスプレイの左側にマップを表示します。 - - - Aggressive - - - - Moderate - - - - Standard - - - - Relaxed - - - - Driving Personality - - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 - - - End-to-End Longitudinal Control - - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - openpilotにアクセルとブレーキを任せます。openpilotは赤信号や一時停止サインでの停止を含み、人間と同じように考えて運転を行います。openpilotが運転速度を決定するため、あなたが設定する速度は上限速度になります。この機能は実験段階のため、openpilotの運転ミスに常に備えて注意してください。 - - - New Driving Visualization - 新しい運転画面 - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - この車のACCがアクセル制御を行うため実験モードを利用することができません。 - - - openpilot longitudinal control may come in a future update. - - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2968,165 +1200,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. 更新失敗 - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 73eb2af9ff..56fc5014ee 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -86,18 +86,6 @@ for "%1" "%1"에 접속하려면 비밀번호가 필요합니다 - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - SPEED + SPEED LIMIT - LIMIT - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mph - - - MAX - MAX - - - SPEED - SPEED - - - LIMIT - LIMIT - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + LIMIT @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an 취소 - - CustomOffsetsSettings - - Back - 뒤로 - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - openpilot을 사용하려면 이용약관에 동의해야 합니다. + openpilot을 사용하려면 이용약관에 동의해야 합니다. Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 거절, %1 제거 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - openpilot의 규칙, 기능 및 제한 다시 확인 + openpilot의 규칙, 기능 및 제한 다시 확인 Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot 장치는 좌우 4°, 위로 5°, 아래로 9° 이내 각도로 장착되어야 합니다. openpilot은 지속적으로 자동 보정되며 재설정은 거의 필요하지 않습니다. + openpilot 장치는 좌우 4°, 위로 5°, 아래로 9° 이내 각도로 장착되어야 합니다. openpilot은 지속적으로 자동 보정되며 재설정은 거의 필요하지 않습니다. Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR 동기화 - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -613,79 +344,6 @@ Please use caution when using this feature. Only use the blinker when traffic an 설치 중... - - LaneChangeSettings - - Back - 뒤로 - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -727,48 +385,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.경로를 기다리는 중 - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - m - - - hr - 시간 - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -798,14 +414,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password 비밀번호가 틀렸습니다 - - Scan - - - - Scanning... - - OffroadAlert @@ -859,16 +467,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Device temperature too high. System cooling down before starting. Current internal component temperature: %1 장치 온도가 너무 높습니다. 시작하기 전에 온도를 낮춰주세요. 현재 내부 부품 온도: %1 - - 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. - -%1 - - - - 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. - - OffroadHome @@ -908,186 +506,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.장치를 재부팅하세요 - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - 확인 - - - Country - - - - SELECT - 선택 - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - 업데이트 - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1118,51 +536,6 @@ Warning: You are on a metered connection! 활성화 - - ParamControlSP - - Enable - 활성화 - - - Cancel - 취소 - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mph - - PrimeAdWidget @@ -1217,7 +590,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1257,30 +630,6 @@ Warning: You are on a metered connection! now now - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1323,147 +672,6 @@ This may take up to a minute. 시스템 재설정이 시작되었습니다. 모든 콘텐츠와 설정을 지우려면 확인을 누르시고 부팅을 재개하려면 취소를 누르세요. - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1487,61 +695,6 @@ Reboot Required. 소프트웨어 - - SettingsWindowSP - - × - × - - - Device - 장치 - - - Network - 네트워크 - - - sunnylink - - - - Toggles - 토글 - - - Software - 소프트웨어 - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1734,69 +887,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1873,233 +963,6 @@ Reboot Required. 업데이트 안함 - - SoftwarePanelSP - - Driving Model - - - - SELECT - 선택 - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - 캘리브레이션 초기화 - - - Warning: You are on a metered connection! - - - - Continue - 계속 - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mph - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - N/A - - - km/h - km/h - - - mph - mph - - SshControl @@ -2146,399 +1009,6 @@ Reboot Required. SSH 사용 - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - N/A - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - 동기화 - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2602,19 +1072,19 @@ Reboot Required. Show ETA in 24h Format - 24시간 형식으로 도착 예정 시간 표시 + 24시간 형식으로 도착 예정 시간 표시 Use 24h format instead of am/pm - 오전/오후 대신 24시간 형식 사용 + 오전/오후 대신 24시간 형식 사용 Show Map on Left Side of UI - UI 왼쪽에 지도 표시 + UI 왼쪽에 지도 표시 Show map on left side when in split screen view. - 분할 화면 보기에서 지도를 왼쪽에 표시합니다. + 분할 화면 보기에서 지도를 왼쪽에 표시합니다. Experimental Mode @@ -2697,244 +1167,6 @@ Reboot Required. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - openpilot 가감속 제어 (알파) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 경고: openpilot 가감속 제어 알파 기능으로 차량의 자동긴급제동(AEB)을 비활성화합니다. - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 이 차량은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot의 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파를 활성화하는 경우 실험 모드 활성화를 권장합니다. - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - 실험 모드 - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - 가속페달 조작 시 해제 - - - When enabled, pressing the accelerator pedal will disengage openpilot. - 활성화된 경우 가속 페달을 밟으면 openpilot이 해제됩니다. - - - Enable Lane Departure Warnings - 차선 이탈 경고 활성화 - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - 차량이 50km/h(31mph) 이상의 속도로 주행할 때 방향지시등이 켜지지 않은 상태에서 차선을 벗어나면 경고합니다. - - - Always-On Driver Monitoring - - - - Enable driver monitoring even when openpilot is not engaged. - - - - Record and Upload Driver Camera - 운전자 카메라 녹화 및 업로드 - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 운전자 카메라의 영상 데이터를 업로드하여 운전자 모니터링 알고리즘을 개선합니다. - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - 미터법 사용 - - - Display speed in km/h instead of mph. - mph 대신 km/h로 속도를 표시합니다. - - - Show ETA in 24h Format - 24시간 형식으로 도착 예정 시간 표시 - - - Use 24h format instead of am/pm - 오전/오후 대신 24시간 형식 사용 - - - Show Map on Left Side of UI - UI 왼쪽에 지도 표시 - - - Show map on left side when in split screen view. - 분할 화면 보기에서 지도를 왼쪽에 표시합니다. - - - Aggressive - 공격적 - - - Moderate - - - - Standard - 표준 - - - Relaxed - 편안한 - - - Driving Personality - 주행 모드 - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot은 기본적으로 <b>안정 모드</b>로 주행합니다. 실험 모드는 안정화되지 않은 <b>알파 수준의 기능</b>을 활성화합니다. 실험 모드의 기능은 아래와 같습니다: - - - End-to-End Longitudinal Control - E2E 가감속 제어 - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - openpilot의 주행모델이 가감속을 제어합니다. openpilot은 신호등과 정지 표지판을 보고 멈추는 것을 포함하여 인간이 운전하는 것처럼 생각하고 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 최대 주행 속도로만 기능합니다. 이 기능은 알파 수준이므로 사용에 각별히 주의해야 합니다. - - - New Driving Visualization - 새로운 주행 시각화 - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - 차량에 장착된 ACC로 가감속을 제어하기 때문에 현재 이 차량에서는 실험 모드를 사용할 수 없습니다. - - - openpilot longitudinal control may come in a future update. - openpilot 가감속 제어는 향후 업데이트에서 지원될 수 있습니다. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - openpilot 가감속 제어 알파 버전은 비 릴리즈 브랜치에서 실험 모드와 함께 테스트할 수 있습니다. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - 실험 모드를 사용하려면 openpilot E2E 가감속 제어 (알파) 토글을 활성화하세요. - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2970,165 +1202,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. 업데이트 실패 - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 7ac21a955f..feaa6e86a1 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -86,18 +86,6 @@ for "%1" para "%1" - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - MAX + MAX LIMIT - VELO - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mph - - - MAX - LIMITE - - - SPEED - MAX - - - LIMIT - VELO - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + VELO @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an Cancelar - - CustomOffsetsSettings - - Back - Voltar - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Você precisa aceitar os Termos e Condições para utilizar openpilot. + Você precisa aceitar os Termos e Condições para utilizar openpilot. Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 Rejeitar, desintalar %1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - Revisar regras, aprimoramentos e limitações do openpilot + Revisar regras, aprimoramentos e limitações do openpilot Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 9° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário. + O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 9° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário. Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR PAREAR - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -614,79 +345,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Instalando... - - LaneChangeSettings - - Back - Voltar - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -728,48 +386,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Aguardando rota - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - m - - - hr - hr - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -799,14 +415,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password Senha incorreta - - Scan - - - - Scanning... - - OffroadAlert @@ -860,16 +468,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Device temperature too high. System cooling down before starting. Current internal component temperature: %1 Temperatura do dispositivo muito alta. O sistema está sendo resfriado antes de iniciar. A temperatura atual do componente interno é: %1 - - 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. - -%1 - - - - 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. - - OffroadHome @@ -909,186 +507,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Reinicie o Dispositivo - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - min - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - VERIFICAR - - - Country - - - - SELECT - SELECIONE - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - ATUALIZAÇÃO - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1119,51 +537,6 @@ Warning: You are on a metered connection! Ativar - - ParamControlSP - - Enable - Ativar - - - Cancel - Cancelar - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mph - - PrimeAdWidget @@ -1218,7 +591,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1261,30 +634,6 @@ Warning: You are on a metered connection! now agora - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1327,147 +676,6 @@ Isso pode levar até um minuto. Reinicialização do sistema acionada. Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancel para retomar a inicialização. - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1491,61 +699,6 @@ Reboot Required. Software - - SettingsWindowSP - - × - × - - - Device - Dispositivo - - - Network - Rede - - - sunnylink - - - - Toggles - Ajustes - - - Software - Software - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1738,69 +891,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1877,233 +967,6 @@ Reboot Required. nunca - - SoftwarePanelSP - - Driving Model - - - - SELECT - SELECIONE - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - Reinicializar Calibragem - - - Warning: You are on a metered connection! - - - - Continue - Continuar - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mph - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - N/A - - - km/h - km/h - - - mph - mph - - SshControl @@ -2150,399 +1013,6 @@ Reboot Required. Habilitar SSH - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - N/A - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - PAREAR - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2606,19 +1076,19 @@ Reboot Required. Show ETA in 24h Format - Mostrar ETA em Formato 24h + Mostrar ETA em Formato 24h Use 24h format instead of am/pm - Use o formato 24h em vez de am/pm + Use o formato 24h em vez de am/pm Show Map on Left Side of UI - Exibir Mapa no Lado Esquerdo + Exibir Mapa no Lado Esquerdo Show map on left side when in split screen view. - Exibir mapa do lado esquerdo quando a tela for dividida. + Exibir mapa do lado esquerdo quando a tela for dividida. Experimental Mode @@ -2701,244 +1171,6 @@ Reboot Required. Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - Controle Longitudinal openpilot (Embrionário) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - AVISO: o controle longitudinal openpilot está em estado embrionário para este carro e desativará a Frenagem Automática de Emergência (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o embrionário controle longitudinal openpilot. - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - Modo Experimental - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - Desacionar com Pedal do Acelerador - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Quando ativado, pressionar o pedal do acelerador desacionará o openpilot. - - - Enable Lane Departure Warnings - Ativar Avisos de Saída de Faixa - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - Receba alertas para voltar para a pista se o seu veículo sair da faixa e a seta não tiver sido acionada previamente quando em velocidades superiores a 50 km/h. - - - Always-On Driver Monitoring - Monitoramento do Motorista Sempre Ativo - - - Enable driver monitoring even when openpilot is not engaged. - Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. - - - Record and Upload Driver Camera - Gravar e Upload Câmera Motorista - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor. - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - Usar Sistema Métrico - - - Display speed in km/h instead of mph. - Exibir velocidade em km/h invés de mph. - - - Show ETA in 24h Format - Mostrar ETA em Formato 24h - - - Use 24h format instead of am/pm - Use o formato 24h em vez de am/pm - - - Show Map on Left Side of UI - Exibir Mapa no Lado Esquerdo - - - Show map on left side when in split screen view. - Exibir mapa do lado esquerdo quando a tela for dividida. - - - Aggressive - Disputa - - - Moderate - - - - Standard - Neutro - - - Relaxed - Calmo - - - Driving Personality - Temperamento de Direção - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-embrionário</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: - - - End-to-End Longitudinal Control - Controle Longitudinal de Ponta a Ponta - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade embrionária; erros devem ser esperados. - - - New Driving Visualization - Nova Visualização de Condução - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - A visualização de condução fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será mostrado no canto superior direito. - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - O modo Experimental está atualmente indisponível para este carro já que o ACC original do carro é usado para controle longitudinal. - - - openpilot longitudinal control may come in a future update. - O controle longitudinal openpilot poderá vir em uma atualização futura. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Uma versão embrionária do controle longitudinal openpilot pode ser testada em conjunto com o modo Experimental, em branches que não sejam de produção. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - Habilite o controle longitudinal (embrionário) openpilot para permitir o modo Experimental. - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2974,165 +1206,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. Falha na atualização - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 6b3548a117..e594a6975f 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -86,18 +86,6 @@ for "%1" สำหรับ "%1" - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - ความเร็ว + ความเร็ว LIMIT - จำกัด - - - - AnnotatedCameraWidgetSP - - km/h - กม./ชม. - - - mph - ไมล์/ชม. - - - MAX - สูงสุด - - - SPEED - ความเร็ว - - - LIMIT - จำกัด - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + จำกัด @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an ยกเลิก - - CustomOffsetsSettings - - Back - ย้อนกลับ - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - คุณต้องยอมรับเงื่อนไขและข้อตกลง เพื่อใช้งาน openpilot + คุณต้องยอมรับเงื่อนไขและข้อตกลง เพื่อใช้งาน openpilot Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 ปฏิเสธ และถอนการติดตั้ง %1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - ตรวจสอบกฎ คุณสมบัติ และข้อจำกัดของ openpilot + ตรวจสอบกฎ คุณสมบัติ และข้อจำกัดของ openpilot Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot กำหนดให้ติดตั้งอุปกรณ์ โดยสามารถเอียงด้านซ้ายหรือขวาไม่เกิน 4° และเอียงขึ้นด้านบนไม่เกิน 5° หรือเอียงลงด้านล่างไม่เกิน 9° openpilot ทำการคาลิเบรทอย่างต่อเนื่อง แทบจะไม่จำเป็นต้องทำการรีเซ็ตการคาลิเบรท + openpilot กำหนดให้ติดตั้งอุปกรณ์ โดยสามารถเอียงด้านซ้ายหรือขวาไม่เกิน 4° และเอียงขึ้นด้านบนไม่เกิน 5° หรือเอียงลงด้านล่างไม่เกิน 9° openpilot ทำการคาลิเบรทอย่างต่อเนื่อง แทบจะไม่จำเป็นต้องทำการรีเซ็ตการคาลิเบรท Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR จับคู่ - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -613,79 +344,6 @@ Please use caution when using this feature. Only use the blinker when traffic an กำลังติดตั้ง... - - LaneChangeSettings - - Back - ย้อนกลับ - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -727,48 +385,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.กำลังรอเส้นทาง - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - ม. - - - hr - ชม. - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -798,14 +414,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password รหัสผ่านผิด - - Scan - - - - Scanning... - - OffroadAlert @@ -859,16 +467,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot ตรวจพบการเปลี่ยนแปลงของตำแหน่งที่ติดตั้ง กรุณาตรวจสอบว่าได้เลื่อนอุปกรณ์เข้ากับจุดติดตั้งจนสุดแล้ว และจุดติดตั้งได้ยึดติดกับกระจกหน้าอย่างแน่นหนา - - 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. - -%1 - - - - 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. - - OffroadHome @@ -908,186 +506,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.รีบูตอุปกรณ์ - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - นาที - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - ตรวจสอบ - - - Country - - - - SELECT - เลือก - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - อัปเดต - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1118,51 +536,6 @@ Warning: You are on a metered connection! ยกเลิก - - ParamControlSP - - Enable - เปิดใช้งาน - - - Cancel - ยกเลิก - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - กม./ชม. - - - mph - ไมล์/ชม. - - PrimeAdWidget @@ -1217,7 +590,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1257,30 +630,6 @@ Warning: You are on a metered connection! now ตอนนี้ - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1323,147 +672,6 @@ This may take up to a minute. ระบบถูกรีเซ็ต กดยืนยันเพื่อลบข้อมูลและการตั้งค่าทั้งหมด กดยกเลิกเพื่อบูตต่อ - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1487,61 +695,6 @@ Reboot Required. ซอฟต์แวร์ - - SettingsWindowSP - - × - × - - - Device - อุปกรณ์ - - - Network - เครือข่าย - - - sunnylink - - - - Toggles - ตัวเลือก - - - Software - ซอฟต์แวร์ - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1734,69 +887,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1873,233 +963,6 @@ Reboot Required. ล่าสุดแล้ว ตรวจสอบครั้งสุดท้ายเมื่อ %1 - - SoftwarePanelSP - - Driving Model - - - - SELECT - เลือก - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - รีเซ็ตการคาลิเบรท - - - Warning: You are on a metered connection! - - - - Continue - ดำเนินการต่อ - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - กม./ชม. - - - mph - ไมล์/ชม. - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - ไม่มี - - - km/h - กม./ชม. - - - mph - ไมล์/ชม. - - SshControl @@ -2146,399 +1009,6 @@ Reboot Required. เปิดใช้งาน SSH - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - ไม่มี - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - จับคู่ - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2602,19 +1072,19 @@ Reboot Required. Show ETA in 24h Format - แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง + แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง Use 24h format instead of am/pm - ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm + ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm Show Map on Left Side of UI - แสดงแผนที่ที่ด้านซ้ายของหน้าจอ + แสดงแผนที่ที่ด้านซ้ายของหน้าจอ Show map on left side when in split screen view. - แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ + แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ Experimental Mode @@ -2697,244 +1167,6 @@ Reboot Required. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - ระบบควบคุมการเร่ง/เบรคโดย openpilot (Alpha) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในสถานะ alpha และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในสถานะ alpha - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - โหมดทดลอง - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - ยกเลิกระบบช่วยขับเมื่อเหยียบคันเร่ง - - - When enabled, pressing the accelerator pedal will disengage openpilot. - เมื่อเปิดใช้งาน การกดแป้นคันเร่งจะเป็นการยกเลิกระบบช่วยขับโดย openpilot - - - Enable Lane Departure Warnings - เปิดใช้งานการเตือนการออกนอกเลน - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - รับการแจ้งเตือนให้เลี้ยวกลับเข้าเลนเมื่อรถของคุณตรวจพบการข้ามช่องจราจรโดยไม่เปิดสัญญาณไฟเลี้ยวในขณะขับขี่ที่ความเร็วเกิน 31 ไมล์ต่อชั่วโมง (50 กม./ชม) - - - Always-On Driver Monitoring - - - - Enable driver monitoring even when openpilot is not engaged. - - - - Record and Upload Driver Camera - บันทึกและอัปโหลดภาพจากกล้องคนขับ - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - อัปโหลดข้อมูลจากกล้องที่หันหน้าไปทางคนขับ และช่วยปรับปรุงอัลกอริธึมการตรวจสอบผู้ขับขี่ - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - ใช้ระบบเมตริก - - - Display speed in km/h instead of mph. - แสดงความเร็วเป็น กม./ชม. แทน ไมล์/ชั่วโมง - - - Show ETA in 24h Format - แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง - - - Use 24h format instead of am/pm - ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm - - - Show Map on Left Side of UI - แสดงแผนที่ที่ด้านซ้ายของหน้าจอ - - - Show map on left side when in split screen view. - แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ - - - Aggressive - ดุดัน - - - Moderate - - - - Standard - มาตรฐาน - - - Relaxed - ผ่อนคลาย - - - Driving Personality - บุคลิกการขับขี่ - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - โดยปกติ openpilot จะขับใน<b>โหมดชิล</b> เปิดโหมดทดลองเพื่อใช้<b>ความสามารถในขั้นพัฒนา</b> ซึ่งยังไม่พร้อมสำหรับโหมดชิล ความสามารถในขั้นพัฒนามีดังนี้: - - - End-to-End Longitudinal Control - ควบคุมเร่ง/เบรคแบบ End-to-End - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - ให้ openpilot ควบคุมการเร่ง/เบรค โดย openpilot จะขับอย่างที่มนุษย์คิด รวมถึงการหยุดที่ไฟแดง และป้ายหยุดรถ เนื่องจาก openpilot จะกำหนดความเร็วในการขับด้วยตัวเอง การตั้งความเร็วจะเป็นเพียงการกำหนดความเร็วสูงสูดเท่านั้น ความสามารถนี้ยังอยู่ในขั้นพัฒนา อาจเกิดข้อผิดพลาดขึ้นได้ - - - New Driving Visualization - การแสดงภาพการขับขี่แบบใหม่ - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - การแสดงภาพการขับขี่จะเปลี่ยนไปใช้กล้องมุมกว้างที่หันหน้าไปทางถนนเมื่ออยู่ในความเร็วต่ำ เพื่อแสดงภาพการเลี้ยวที่ดีขึ้น โลโก้โหมดการทดลองจะแสดงที่มุมบนขวาด้วย - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - ขณะนี้โหมดทดลองไม่สามารถใช้งานได้ในรถคันนี้ เนื่องจากเปิดใช้ระบบควบคุมการเร่ง/เบรคของรถที่ติดตั้งจากโรงงานอยู่ - - - openpilot longitudinal control may come in a future update. - ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - ระบบควบคุมการเร่ง/เบรคโดย openpilot เวอร์ชัน alpha สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - เปิดระบบควบคุมการเร่ง/เบรคโดย openpilot (alpha) เพื่อเปิดใช้งานโหมดทดลอง - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2970,165 +1202,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. การอัปเดตล้มเหลว - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 19b9f15811..48615f1699 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -86,18 +86,6 @@ for "%1" için "%1" - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -113,29 +101,6 @@ MAX MAX - - SPEED - HIZ - - - LIMIT - LİMİT - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mph - - - MAX - MAX - SPEED HIZ @@ -145,91 +110,6 @@ LİMİT - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - - - ConfirmationDialog @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an Vazgeç - - CustomOffsetsSettings - - Back - - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Openpilotu kullanmak için Kullanıcı Koşullarını kabul etmelisiniz. + Openpilotu kullanmak için Kullanıcı Koşullarını kabul etmelisiniz. Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 Reddet, Kurulumu kaldır. %1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - openpilot sisteminin kurallarını ve sınırlamalarını gözden geçirin. + openpilot sisteminin kurallarını ve sınırlamalarını gözden geçirin. Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot, cihazın 4° sola veya 5° yukarı yada 9° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir. + openpilot, cihazın 4° sola veya 5° yukarı yada 9° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir. Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -613,79 +344,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Yükleniyor... - - LaneChangeSettings - - Back - - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -727,48 +385,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - m - - - hr - saat - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -798,14 +414,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password Yalnış parola - - Scan - - - - Scanning... - - OffroadAlert @@ -858,16 +466,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - - 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. - -%1 - - - - 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. - - OffroadHome @@ -907,186 +505,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - dk - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - KONTROL ET - - - Country - - - - SELECT - - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - GÜNCELLE - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1117,51 +535,6 @@ Warning: You are on a metered connection! - - ParamControlSP - - Enable - - - - Cancel - - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mph - - PrimeAdWidget @@ -1216,7 +589,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1256,30 +629,6 @@ Warning: You are on a metered connection! now - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1321,147 +670,6 @@ This may take up to a minute. - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1485,61 +693,6 @@ Reboot Required. Yazılım - - SettingsWindowSP - - × - x - - - Device - Cihaz - - - Network - - - - sunnylink - - - - Toggles - Değiştirme - - - Software - Yazılım - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1732,69 +885,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1871,233 +961,6 @@ Reboot Required. - - SoftwarePanelSP - - Driving Model - - - - SELECT - - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - Kalibrasyonu sıfırla - - - Warning: You are on a metered connection! - - - - Continue - Devam et - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mph - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - N/A - - - km/h - km/h - - - mph - mph - - SshControl @@ -2144,399 +1007,6 @@ Reboot Required. SSH aç - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - N/A - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2596,19 +1066,19 @@ Reboot Required. Show ETA in 24h Format - Tahmini varış süresini 24 saat formatı şeklinde göster + Tahmini varış süresini 24 saat formatı şeklinde göster Use 24h format instead of am/pm - 24 saat formatını kullan + 24 saat formatını kullan Show Map on Left Side of UI - Haritayı arayüzün sol tarafında göster + Haritayı arayüzün sol tarafında göster Show map on left side when in split screen view. - Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. + Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. openpilot Longitudinal Control (Alpha) @@ -2695,244 +1165,6 @@ Reboot Required. - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Aktifleştirilirse eğer gaz pedalına basınca openpilot devre dışı kalır. - - - Enable Lane Departure Warnings - Şerit ihlali uyarı alın - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - 50 km/s (31 mph) hızın üzerinde sürüş sırasında aracınız dönüş sinyali vermeden algılanan bir sonraki başlatılışında çekilir. şerit çizgisi ihlalinde şeride geri dönmek için uyarılar alın. - - - Always-On Driver Monitoring - - - - Enable driver monitoring even when openpilot is not engaged. - - - - Record and Upload Driver Camera - Sürücü kamerasını kayıt et. - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Sürücüye bakan kamera verisini yükleyin ve Cihazın algoritmasını geliştirmemize yardımcı olun. - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - Metrik sistemi kullan - - - Display speed in km/h instead of mph. - Hızı mph yerine km/h şeklinde görüntüleyin. - - - Show ETA in 24h Format - Tahmini varış süresini 24 saat formatı şeklinde göster - - - Use 24h format instead of am/pm - 24 saat formatını kullan - - - Show Map on Left Side of UI - Haritayı arayüzün sol tarafında göster - - - Show map on left side when in split screen view. - Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. - - - Aggressive - - - - Moderate - - - - Standard - - - - Relaxed - - - - Driving Personality - - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - - - - End-to-End Longitudinal Control - - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - - - - New Driving Visualization - - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - - - - openpilot longitudinal control may come in a future update. - - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2968,165 +1200,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. Güncelleme başarız oldu - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 9718fb6fa8..32119ee10f 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -86,18 +86,6 @@ for "%1" 网络名称:"%1" - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - SPEED + SPEED LIMIT - LIMIT - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mph - - - MAX - 最高定速 - - - SPEED - SPEED - - - LIMIT - LIMIT - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + LIMIT @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an 取消 - - CustomOffsetsSettings - - Back - 返回 - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - 您必须接受条款和条件以使用openpilot。 + 您必须接受条款和条件以使用openpilot。 Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 拒绝并卸载%1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - 查看 openpilot 的使用规则,以及其功能和限制 + 查看 openpilot 的使用规则,以及其功能和限制 Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot要求设备安装的偏航角在左4°和右4°之间,俯仰角在上5°和下9°之间。一般来说,openpilot会持续更新校准,很少需要重置。 + openpilot要求设备安装的偏航角在左4°和右4°之间,俯仰角在上5°和下9°之间。一般来说,openpilot会持续更新校准,很少需要重置。 Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR 配对 - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -613,79 +344,6 @@ Please use caution when using this feature. Only use the blinker when traffic an 正在安装…… - - LaneChangeSettings - - Back - 返回 - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -727,48 +385,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.等待路线 - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - m - - - hr - 小时 - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -798,14 +414,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password 密码错误 - - Scan - - - - Scanning... - - OffroadAlert @@ -859,16 +467,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Device temperature too high. System cooling down before starting. Current internal component temperature: %1 设备温度过高。系统正在冷却中,等冷却完毕后才会启动。目前内部组件温度:%1 - - 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. - -%1 - - - - 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. - - OffroadHome @@ -908,186 +506,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.重启设备 - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - 分钟 - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - 查看 - - - Country - - - - SELECT - 选择 - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - 更新 - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1118,51 +536,6 @@ Warning: You are on a metered connection! 启用 - - ParamControlSP - - Enable - 启用 - - - Cancel - 取消 - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mph - - PrimeAdWidget @@ -1217,7 +590,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1257,30 +630,6 @@ Warning: You are on a metered connection! now 现在 - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1323,147 +672,6 @@ This may take up to a minute. 系统重置已触发。按下“确认”以清除所有内容和设置,按下“取消”以继续启动。 - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1487,61 +695,6 @@ Reboot Required. 软件 - - SettingsWindowSP - - × - × - - - Device - 设备 - - - Network - 网络 - - - sunnylink - - - - Toggles - 设定 - - - Software - 软件 - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1734,69 +887,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1873,233 +963,6 @@ Reboot Required. 从未更新 - - SoftwarePanelSP - - Driving Model - - - - SELECT - 选择 - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - 重置设备校准 - - - Warning: You are on a metered connection! - - - - Continue - 继续 - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mph - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - N/A - - - km/h - km/h - - - mph - mph - - SshControl @@ -2146,399 +1009,6 @@ Reboot Required. 启用SSH - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - N/A - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - 配对 - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2602,19 +1072,19 @@ Reboot Required. Show ETA in 24h Format - 以24小时格式显示预计到达时间 + 以24小时格式显示预计到达时间 Use 24h format instead of am/pm - 使用24小时制代替am/pm + 使用24小时制代替am/pm Show Map on Left Side of UI - 在介面左侧显示地图 + 在介面左侧显示地图 Show map on left side when in split screen view. - 在分屏模式中,将地图置于屏幕左侧。 + 在分屏模式中,将地图置于屏幕左侧。 Experimental Mode @@ -2697,244 +1167,6 @@ Reboot Required. 即使在openpilot未激活时也启用驾驶员监控。 - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - openpilot纵向控制(Alpha 版) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 警告:此车辆的 openpilot 纵向控制功能目前处于Alpha版本,使用此功能将会停用自动紧急制动(AEB)功能。 - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 在这辆车上,openpilot 默认使用车辆内建的主动巡航控制(ACC),而非 openpilot 的纵向控制。启用此项功能可切换至 openpilot 的纵向控制。当启用 openpilot 纵向控制 Alpha 版本时,建议同时启用实验性模式(Experimental mode)。 - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - 测试模式 - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - 踩油门时取消控制 - - - When enabled, pressing the accelerator pedal will disengage openpilot. - 启用后,踩下油门踏板将取消openpilot。 - - - Enable Lane Departure Warnings - 启用车道偏离警告 - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - 车速超过31mph(50km/h)时,若检测到车辆越过车道线且未打转向灯,系统将发出警告以提醒您返回车道。 - - - Always-On Driver Monitoring - 驾驶员监控常开 - - - Enable driver monitoring even when openpilot is not engaged. - 即使在openpilot未激活时也启用驾驶员监控。 - - - Record and Upload Driver Camera - 录制并上传驾驶员摄像头 - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - 使用公制单位 - - - Display speed in km/h instead of mph. - 显示车速时,以km/h代替mph。 - - - Show ETA in 24h Format - 以24小时格式显示预计到达时间 - - - Use 24h format instead of am/pm - 使用24小时制代替am/pm - - - Show Map on Left Side of UI - 在介面左侧显示地图 - - - Show map on left side when in split screen view. - 在分屏模式中,将地图置于屏幕左侧。 - - - Aggressive - 积极 - - - Moderate - - - - Standard - 标准 - - - Relaxed - 舒适 - - - Driving Personality - 驾驶风格 - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: - - - End-to-End Longitudinal Control - 端到端纵向控制 - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - 允许驾驶模型控制加速和制动,openpilot将模仿人类驾驶车辆,包括在红灯和停车让行标识前停车。鉴于驾驶模型确定行驶车速,所设定的车速仅作为上限。此功能尚处于早期测试状态,有可能会出现操作错误。 - - - New Driving Visualization - 新驾驶视角 - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - 在低速时,驾驶可视化将转换为道路朝向的广角摄像头,以更好地展示某些转弯。测试模式标志也将显示在右上角。 - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 - - - openpilot longitudinal control may come in a future update. - openpilot纵向控制可能会在未来的更新中提供。 - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 在正式(release)版本以外的分支上,可以测试 openpilot 纵向控制的 Alpha 版本以及实验模式。 - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - 启用 openpilot 纵向控制(alpha)开关以允许实验模式。 - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2970,165 +1202,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. 更新失败 - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index b511a2b6b4..9d1c16db9f 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -86,18 +86,6 @@ for "%1" 給 "%1" - - Retain hotspot/tethering state - - - - Enabling this toggle will retain the hotspot/tethering toggle state across reboots. - - - - Ngrok Service - - AnnotatedCameraWidget @@ -115,119 +103,11 @@ SPEED - 速度 + 速度 LIMIT - 速限 - - - - AnnotatedCameraWidgetSP - - km/h - km/h - - - mph - mph - - - MAX - 最高 - - - SPEED - 速度 - - - LIMIT - 速限 - - - - AutoLaneChangeTimer - - Auto Lane Change by Blinker - - - - Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. -Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. - - - - s - - - - Off - - - - Nudge - - - - Nudgeless - - - - - BackupSettings - - Settings backed up for sunnylink Device ID: - - - - Settings updated successfully, but no additional data was returned by the server. - - - - OOPS! We made a booboo. - - - - Please try again later. - - - - Settings restored. Confirm to restart the interface. - - - - No settings found to restore. - - - - - BrightnessControl - - Brightness - - - - Manually adjusts the global brightness of the screen. - - - - Auto - - - - - CameraOffset - - Camera Offset - Laneful Only - - - - Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm - - - - cm - + 速限 @@ -241,18 +121,11 @@ Please use caution when using this feature. Only use the blinker when traffic an 取消 - - CustomOffsetsSettings - - Back - 回上頁 - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - 您必須先接受條款和條件才能使用 openpilot。 + 您必須先接受條款和條件才能使用 openpilot。 Back @@ -262,10 +135,6 @@ Please use caution when using this feature. Only use the blinker when traffic an Decline, uninstall %1 拒絕並解除安裝 %1 - - You must accept the Terms and Conditions in order to use sunnypilot. - - DestinationWidget @@ -342,7 +211,7 @@ Please use caution when using this feature. Only use the blinker when traffic an Review the rules, features, and limitations of openpilot - 觀看 openpilot 的使用規則、功能和限制 + 觀看 openpilot 的使用規則、功能和限制 Are you sure you want to review the training guide? @@ -378,7 +247,7 @@ Please use caution when using this feature. Only use the blinker when traffic an openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以內或朝下偏差 9° 以內。鏡頭在後台會持續自動校準,很少有需要重置的情況。 + openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以內或朝下偏差 9° 以內。鏡頭在後台會持續自動校準,很少有需要重置的情況。 Your device is pointed %1° %2 and %3° %4. @@ -436,144 +305,6 @@ Please use caution when using this feature. Only use the blinker when traffic an PAIR 配對 - - TOGGLE - - - - Enable or disable PIN requirement for Fleet Manager access. - - - - Are you sure you want to turn off PIN requirement? - - - - Turn Off - - - - Error Troubleshoot - - - - Display error from the tmux session when an error has occurred from a system process. - - - - Reset Access Tokens for Map Services - - - - Reset self-service access tokens for Mapbox, Amap, and Google Maps. - - - - Are you sure you want to reset access tokens for all map services? - - - - Reset sunnypilot Settings - - - - Are you sure you want to reset all sunnypilot settings? - - - - Review the rules, features, and limitations of sunnypilot - - - - Toggle Onroad/Offroad - - - - OFF - - - - sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. - - - - Are you sure you want to unforce offroad? - - - - Unforce - - - - Are you sure you want to force offroad? - - - - Force - - - - Disengage to Force Offroad - - - - Unforce Offroad - - - - Force Offroad - - - - Fleet Manager PIN: - - - - - DisplayPanel - - Driving Screen Off: Non-Critical Events - - - - When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: - - - - Enabled: Wake the brightness of the screen to display all events. - - - - Disabled: Wake the brightness of the screen to display critical events. - - - - - DriveStats - - Drives - - - - Hours - - - - ALL TIME - - - - PAST WEEK - - - - KM - - - - Miles - - DriverViewWindow @@ -613,79 +344,6 @@ Please use caution when using this feature. Only use the blinker when traffic an 安裝中… - - LaneChangeSettings - - Back - 回上頁 - - - Pause Lateral Below Speed with Blinker - - - - Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. - - - - Auto Lane Change: Delay with Blind Spot - - - - Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. - - - - Block Lane Change: Road Edge Detection - - - - Enable this toggle to block lane change when road edge is detected on the stalk actuated side. - - - - - MadsSettings - - Enable ACC+MADS with RES+/SET- - - - - Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. - - - - Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. - - - - Toggle M.A.D.S. with Cruise Main - - - - Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. - - - - Remain Active - - - - Pause Steering - - - - Steering Mode After Braking - - - - Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. - -Remain Active: ALC will remain active even after the brake pedal is pressed. -Pause Steering: ALC will be paused after the brake pedal is manually pressed. - - - MapETA @@ -727,48 +385,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.等待路線 - - MaxTimeOffroad - - Max Time Offroad - - - - Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). - - - - s - - - - m - m - - - hr - 小時 - - - Always On - - - - Immediate - - - - - MonitoringPanel - - Enable Hands on Wheel Monitoring - - - - Monitor and alert when driver is not keeping the hands on the steering wheel. - - - MultiOptionDialog @@ -798,14 +414,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Wrong password 密碼錯誤 - - Scan - - - - Scanning... - - OffroadAlert @@ -859,16 +467,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.Device temperature too high. System cooling down before starting. Current internal component temperature: %1 裝置溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 - - 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. - -%1 - - - - 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. - - OffroadHome @@ -908,186 +506,6 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.請重新啟裝置 - - OnroadScreenOff - - Driving Screen Off Timer - - - - Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. - - - - s - - - - min - 分鐘 - - - Always On - - - - - OnroadScreenOffBrightness - - Driving Screen Off Brightness (%) - - - - When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. - - - - Dark - - - - - OnroadSettings - - ONROAD OPTIONS - - - - <b>ONROAD SETTINGS | SUNNYPILOT</b> - - - - - OsmPanel - - Mapd Version - - - - Offline Maps ETA - - - - Time Elapsed - - - - Downloaded Maps - - - - DELETE - - - - This will delete ALL downloaded maps - -Are you sure you want to delete all the maps? - - - - Yes, delete all the maps. - - - - Database Update - - - - CHECK - 檢查 - - - Country - - - - SELECT - 選取 - - - Fetching Country list... - - - - State - - - - Fetching State list... - - - - All - - - - REFRESH - - - - UPDATE - 更新 - - - Download starting... - - - - Error: Invalid download. Retry. - - - - Download complete! - - - - - -Warning: You are on a metered connection! - - - - This will start the download process and it might take a while to complete. - - - - Continue on Metered - - - - Start Download - - - - m - - - - s - - - - Calculating... - - - - Downloaded - - - - Calculating ETA... - - - - Ready - - - - Time remaining: - - - PairingPopup @@ -1118,51 +536,6 @@ Warning: You are on a metered connection! 啟用 - - ParamControlSP - - Enable - 啟用 - - - Cancel - 取消 - - - - PathOffset - - Path Offset - - - - Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. - - - - cm - - - - - PauseLateralSpeed - - Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. - - - - Default - - - - km/h - km/h - - - mph - mph - - PrimeAdWidget @@ -1217,7 +590,7 @@ Warning: You are on a metered connection! openpilot - openpilot + openpilot %n minute(s) ago @@ -1257,30 +630,6 @@ Warning: You are on a metered connection! now 現在 - - Update downloaded. Ready to reboot. - - - - Update: Check and Download Update - - - - Reboot: Reboot Device - - - - Update - - - - Updating... - - - - sunnypilot - - Reset @@ -1323,147 +672,6 @@ This may take up to a minute. 系統重設已啟動。按下「確認」以清除所有內容和設定,或按下「取消」以繼續開機。 - - SPVehiclesTogglesPanel - - Hyundai/Kia/Genesis - - - - HKG CAN: Smoother Stopping Performance (Beta) - - - - Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. - - - - Subaru - - - - Manual Parking Brake: Stop and Go (Beta) - - - - Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! - - - - Toyota/Lexus - - - - Enable Stock Toyota Longitudinal Control - - - - sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. - - - - Allow M.A.D.S. toggling w/ LKAS Button (Beta) - - - - Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. - - - - Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Toyota TSS2 Longitudinal: Custom Tuning - - - - Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. - - - - Enable Automatic Brake Hold (AHB) - - - - WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. - - - - When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. - - - - Changing this setting takes effect when the car is powered off. - - - - Enable Enhanced Blind Spot Monitor - - - - Enable Toyota Stop and Go Hack - - - - sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. - - - - Enable Toyota Door Auto Locking - - - - sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Toyota Door Auto Unlocking - - - - sunnypilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - Volkswagen - - - - Enable CC Only support - - - - sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. - - - - Start the car to check car compatibility - - - - This platform is already supported, therefore no need to enable this toggle - - - - This platform is not supported - - - - This platform can be supported - - - - sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. - - - - Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. - - - SettingsWindow @@ -1487,61 +695,6 @@ Reboot Required. 軟體 - - SettingsWindowSP - - × - × - - - Device - 裝置 - - - Network - 網路 - - - sunnylink - - - - Toggles - 設定 - - - Software - 軟體 - - - sunnypilot - - - - OSM - - - - Monitoring - - - - Visuals - - - - Display - - - - Trips - - - - Vehicle - - - Setup @@ -1734,69 +887,6 @@ Reboot Required. 5G 5G - - DISABLED - - - - REGIST... - - - - SUNNYLINK - - - - - SlcSettings - - Auto - - - - User Confirm - - - - Engage Mode - - - - Default - - - - Fixed - - - - Percentage - - - - Limit Offset - - - - Set speed limit slightly higher than actual speed limit for a more natural drive. - - - - This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. - - - - Select the desired mode to set the cruising speed to the speed limit: - - - - Auto: Automatic speed adjustment on motorways based on speed limit data. - - - - User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. - - SoftwarePanel @@ -1873,233 +963,6 @@ Reboot Required. 從未更新 - - SoftwarePanelSP - - Driving Model - - - - SELECT - 選取 - - - PENDING - - - - Downloading Driving model - - - - (CACHED) - - - - Driving model - - - - downloaded - - - - Downloading Navigation model - - - - Navigation model - - - - Downloading Metadata model - - - - Metadata model - - - - Downloads have failed, please try swapping the model! - - - - Failed: - - - - Fetching models... - - - - Select a Driving Model - - - - Download has started in the background. - - - - We STRONGLY suggest you to reset calibration. Would you like to do that now? - - - - Reset Calibration - 重設校準 - - - Warning: You are on a metered connection! - - - - Continue - 繼續 - - - on Metered - - - - - SpeedLimitPolicySettings - - Speed Limit Source Policy - - - - Nav - - - - Only - - - - Map - - - - Car - - - - First - - - - Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning - - - - Nav Only: Data from Mapbox active navigation only. - - - - Map Only: Data from OpenStreetMap only. - - - - Car Only: Data from the car's built-in sources (if available). - - - - Nav First: Nav -> Map -> Car - - - - Map First: Map -> Nav -> Car - - - - Car First: Car -> Nav -> Map - - - - - SpeedLimitValueOffset - - km/h - km/h - - - mph - mph - - - - SpeedLimitWarningSettings - - Off - - - - Display - - - - Chime - - - - Speed Limit Warning - - - - Warning with speed limit flash - - - - When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. - - - - Default - - - - Fixed - - - - Percentage - - - - Warning Offset - - - - Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. - - - - Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. - - - - Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. - - - - - SpeedLimitWarningValueOffset - - N/A - 無法使用 - - - km/h - km/h - - - mph - mph - - SshControl @@ -2146,399 +1009,6 @@ Reboot Required. 啟用 SSH 服務 - - SunnylinkPanel - - Enable sunnylink - - - - Device ID - - - - N/A - 無法使用 - - - This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. - - - - 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 - - - - 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. - - - - Sponsor Status - - - - SPONSOR - - - - Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. - - - - Pair GitHub Account - - - - PAIR - 配對 - - - Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. - - - - sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. - - - - Manage Settings - - - - Backup Settings - - - - Are you sure you want to backup sunnypilot settings? - - - - Back Up - - - - Restore Settings - - - - Are you sure you want to restore the last backed up sunnypilot settings? - - - - Restore - - - - THANKS - - - - Not Sponsor - - - - Paired - - - - Not Paired - - - - Backing up... - - - - Restoring... - - - - - SunnylinkSponsorPopup - - Scan the QR code to login to your GitHub account - - - - Follow the prompts to complete the pairing process - - - - Re-enter the "sunnylink" panel to verify sponsorship status - - - - If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot - - - - Scan the QR code to visit sunnyhaibin's GitHub Sponsors page - - - - Choose your sponsorship tier and confirm your support - - - - Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status - - - - Pair your GitHub account - - - - Early Access: Become a sunnypilot Sponsor - - - - - SunnypilotPanel - - Enable M.A.D.S. - - - - Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. - - - - Laneless for Curves in "Auto" Mode - - - - While in Auto Lane, switch to Laneless for current/future curves. - - - - Speed Limit Control (SLC) - - - - When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. - - - - Enable Vision-based Turn Speed Control (V-TSC) - - - - Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - - - - Enable Map Data Turn Speed Control (M-TSC) (Beta) - - - - Use curvature information from map data to define speed limits to take turns ahead. - - - - ACC +/-: Long Press Reverse - - - - Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. - - - - Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) - - - - Enabled: Short = 5 (imperial) / 10 (metric), Long=1 - - - - Custom Offsets - - - - Neural Network Lateral Control (NNLC) - - - - Enforce Torque Lateral Control - - - - Enable this to enforce sunnypilot to steer with Torque lateral control. - - - - Enable Self-Tune - - - - Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - - - - Less Restrict Settings for Self-Tune (Beta) - - - - Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - - - - Enable Custom Tuning - - - - Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. - - - - Manual Real-Time Tuning - - - - Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. - - - - Quiet Drive 🤫 - - - - sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. - - - - Green Traffic Light Chime (Beta) - - - - A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. - - - - Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. - - - - Lead Vehicle Departure Alert - - - - Enable this will notify when the leading vehicle drives away. - - - - Customize M.A.D.S. - - - - Customize Lane Change - - - - Customize Offsets - - - - Customize Speed Limit Control - - - - Customize Warning - - - - Customize Source - - - - Laneful - - - - Laneless - - - - Auto - - - - Dynamic Lane Profile - - - - Speed Limit Assist - - - - Real-time and Offline - - - - Offline Only - - - - Dynamic Lane Profile is not available with the current Driving Model - - - - Custom Offsets is not available with the current Driving Model - - - - NNLC is currently not available on this platform. - - - - Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: - - - - Start the car to check car compatibility - - - - NNLC Not Loaded - - - - NNLC Loaded - - - - Fuzzy - - - - Exact - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: - - - - Match - - - - Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. - - - - Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: - - - - Add custom offsets to Camera and Path in sunnypilot. - - - - Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. - - - TermsPage @@ -2602,19 +1072,19 @@ Reboot Required. Show ETA in 24h Format - 預計到達時間單位改用 24 小時制 + 預計到達時間單位改用 24 小時制 Use 24h format instead of am/pm - 使用 24 小時制。(預設值為 12 小時制) + 使用 24 小時制。(預設值為 12 小時制) Show Map on Left Side of UI - 將地圖顯示在畫面的左側 + 將地圖顯示在畫面的左側 Show map on left side when in split screen view. - 進入分割畫面後,地圖將會顯示在畫面的左側。 + 進入分割畫面後,地圖將會顯示在畫面的左側。 Experimental Mode @@ -2697,244 +1167,6 @@ Reboot Required. 即使在openpilot未激活時也啟用駕駛監控。 - - TogglesPanelSP - - Enable sunnypilot - - - - Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - - - - openpilot Longitudinal Control (Alpha) - openpilot 縱向控制 (Alpha 版) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 警告:此車輛的 openpilot 縱向控制功能目前處於 Alpha 版本,使用此功能將會停用自動緊急煞車(AEB)功能。 - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 在這輛車上,openpilot 預設使用車輛內建的主動巡航控制(ACC),而非 openpilot 的縱向控制。啟用此項功能可切換至 openpilot 的縱向控制。當啟用 openpilot 縱向控制 Alpha 版本時,建議同時啟用實驗性模式(Experimental mode)。 - - - Custom Stock Longitudinal Control - - - - When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. -This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. - - - - Experimental Mode - 實驗模式 - - - Enable Dynamic Experimental Control - - - - Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal. - - - - Enable Dynamic Personality - - - - Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. - - - - Disengage on Accelerator Pedal - 油門取消控車 - - - When enabled, pressing the accelerator pedal will disengage openpilot. - 啟用後,踩踏油門將會取消 openpilot 控制。 - - - Enable Lane Departure Warnings - 啟用車道偏離警告 - - - Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - 車速在時速 50 公里 (31 英里) 以上且未打方向燈的情況下,如果偵測到車輛駛出目前車道線時,發出車道偏離警告。 - - - Always-On Driver Monitoring - 駕駛監控常開 - - - Enable driver monitoring even when openpilot is not engaged. - 即使在openpilot未激活時也啟用駕駛監控。 - - - Record and Upload Driver Camera - 記錄並上傳駕駛監控影像 - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 上傳駕駛監控的錄影來協助我們提升駕駛監控的準確率。 - - - Disable Onroad Uploads - - - - Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). - - - - Use Metric System - 使用公制單位 - - - Display speed in km/h instead of mph. - 啟用後,速度單位顯示將從 mp/h 改為 km/h。 - - - Show ETA in 24h Format - 預計到達時間單位改用 24 小時制 - - - Use 24h format instead of am/pm - 使用 24 小時制。(預設值為 12 小時制) - - - Show Map on Left Side of UI - 將地圖顯示在畫面的左側 - - - Show map on left side when in split screen view. - 進入分割畫面後,地圖將會顯示在畫面的左側。 - - - Aggressive - 積極 - - - Moderate - - - - Standard - 標準 - - - Relaxed - 舒適 - - - Driving Personality - 駕駛風格 - - - Standard is recommended. In moderate/aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - Sport - - - - Normal - - - - Eco - - - - Stock - - - - Acceleration Personality - - - - Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: - - - End-to-End Longitudinal Control - 端到端縱向控制 - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - 讓駕駛模型來控制油門及煞車。openpilot將會模擬人類的駕駛行為,包含在看見紅燈及停止標示時停車。由於車速將由駕駛模型決定,因此您設定的時速將成為速度上限。本功能仍在早期實驗階段,請預期模型有犯錯的可能性。 - - - New Driving Visualization - 新的駕駛視覺介面 - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - 在低速時,駕駛可視化將切換至道路朝向的廣角攝影機,以更好地顯示某些彎道。在右上角還會顯示「實驗模式」的標誌。 - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - 因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。 - - - openpilot longitudinal control may come in a future update. - openpilot 縱向控制可能會在未來的更新中提供。 - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 在正式 (release) 版以外的分支上可以測試 openpilot 縱向控制的 Alpha 版本以及實驗模式。 - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - 啟用 openpilot 縱向控制(alpha)切換以允許實驗模式。 - - - - TorqueFriction - - FRICTION - - - - Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - - - TorqueMaxLatAccel - - LAT_ACCEL_FACTOR - - - - Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. - - - - Real-time and Offline - - - - Offline Only - - - Updater @@ -2970,165 +1202,6 @@ This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. 更新失敗 - - VehiclePanel - - Updating this setting takes effect when the car is powered off. - - - - Select your car - - - - - VisualsPanel - - Display Braking Status - - - - Enable this will turn the current speed value to red while the brake is used. - - - - Display Stand Still Timer - - - - Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). - - - - Display DM Camera in Reverse Gear - - - - Show Driver Monitoring camera while the car is in reverse gear. - - - - OSM: Show debug UI elements - - - - OSM: Show UI elements that aid debugging. - - - - Display Feature Status - - - - Display the statuses of certain features on the driving screen. - - - - Enable Onroad Settings - - - - Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. - - - - Speedometer: Display True Speed - - - - Display the true vehicle current speed from wheel speed sensors. - - - - Speedometer: Hide from Onroad Screen - - - - Display End-to-end Longitudinal Status (Beta) - - - - Enable this will display an icon that appears when the End-to-end model decides to start or stop. - - - - Navigation: Display in Full Screen - - - - Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. - - - - Map: Display 3D Buildings - - - - Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. - - - - Off - - - - 5 Metrics - - - - 10 Metrics - - - - Developer UI - - - - Display real-time parameters and metrics from various sources. - - - - Distance - - - - Speed - - - - Distance -Speed - - - - Display Metrics Below Chevron - - - - Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). - - - - RAM - - - - CPU - - - - GPU - - - - Max - - - - Display Temperature on Sidebar - - - WiFiPromptWidget From 1b291893bb9bdf9a37cd6da24938c9ec90f27e7f Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Tue, 16 Jul 2024 15:05:01 -0400 Subject: [PATCH 425/575] qt/network/sunnylink: rename --- selfdrive/ui/sunnypilot/SConscript | 8 ++++---- .../models/{role_model.h => sp_priv_role_model.h} | 5 +---- ...or_role_model.h => sp_priv_sponsor_role_model.h} | 7 ++----- .../models/{user_model.h => sp_priv_user_model.h} | 5 +---- ...ce_service.cc => sp_priv_base_device_service.cc} | 4 ++-- ...vice_service.h => sp_priv_base_device_service.h} | 8 ++------ .../{role_service.cc => sp_priv_role_service.cc} | 2 +- .../{role_service.h => sp_priv_role_service.h} | 13 ++++++------- .../{user_service.cc => sp_priv_user_service.cc} | 2 +- .../{user_service.h => sp_priv_user_service.h} | 11 +++++------ ...nylink_client.cc => sp_priv_sunnylink_client.cc} | 4 ++-- ...unnylink_client.h => sp_priv_sunnylink_client.h} | 9 +++------ .../ui/sunnypilot/qt/offroad/sunnylink_settings.h | 2 +- selfdrive/ui/ui.cc | 2 +- selfdrive/ui/ui.h | 9 +++++---- 15 files changed, 37 insertions(+), 54 deletions(-) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/models/{role_model.h => sp_priv_role_model.h} (96%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/models/{sponsor_role_model.h => sp_priv_sponsor_role_model.h} (97%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/models/{user_model.h => sp_priv_user_model.h} (93%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{base_device_service.cc => sp_priv_base_device_service.cc} (92%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{base_device_service.h => sp_priv_base_device_service.h} (90%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{role_service.cc => sp_priv_role_service.cc} (96%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{role_service.h => sp_priv_role_service.h} (75%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{user_service.cc => sp_priv_user_service.cc} (96%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{user_service.h => sp_priv_user_service.h} (76%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/{sunnylink_client.cc => sp_priv_sunnylink_client.cc} (67%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/{sunnylink_client.h => sp_priv_sunnylink_client.h} (77%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index bcc296e496..c0cd96dcfd 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -22,10 +22,10 @@ widgets_src = [ ] network_src = [ - "sunnypilot/qt/network/sunnylink/sunnylink_client.cc", - "sunnypilot/qt/network/sunnylink/services/base_device_service.cc", - "sunnypilot/qt/network/sunnylink/services/role_service.cc", - "sunnypilot/qt/network/sunnylink/services/user_service.cc"] + "sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc", + "sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc", + "sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc", + "sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc"] qt_src = [ "sunnypilot/qt/sp_priv_home.cc", diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h similarity index 96% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h index d7f3a98281..a5c40e5057 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h @@ -1,5 +1,4 @@ -#ifndef ROLE_MODEL_H -#define ROLE_MODEL_H +#pragma once #include @@ -56,5 +55,3 @@ public: return T(m_raw_json_object); } }; - -#endif diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h similarity index 97% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h index 64aad1ca46..e964c66c28 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h @@ -1,5 +1,4 @@ -#ifndef SPONSORROLE_MODEL_H -#define SPONSORROLE_MODEL_H +#pragma once #include @@ -24,7 +23,7 @@ public: QJsonObject json = RoleModel::toJson(); json["role_tier"] = sponsorTierToString(roleTier); return json; - }; + } static SponsorTier stringToSponsorTier(const QString &sponsorTierString) { const auto sponsorTierStringLower = sponsorTierString.toLower(); @@ -80,5 +79,3 @@ public: } [[nodiscard]] auto getSponsorTierColor() const { return sponsorTierToColor(roleTier); } }; - -#endif diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h similarity index 93% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h index 72baa3983d..1f83af9be2 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h @@ -1,5 +1,4 @@ -#ifndef USER_MODEL_H -#define USER_MODEL_H +#pragma once #include @@ -29,5 +28,3 @@ public: return json; } }; - -#endif diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc similarity index 92% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc index 46752b38a4..c2d4e6f87a 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc @@ -1,4 +1,4 @@ -#include "base_device_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink//services/sp_priv_base_device_service.h" #include "common/swaglog.h" #include "selfdrive/ui/qt/util.h" @@ -31,7 +31,7 @@ void BaseDeviceService::loadDeviceData(const QString &url, bool poll) { LOGD("Cache key: SunnylinkCache_%s", qPrintable(QString(getCacheKey()))); repeater = new RequestRepeater(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true); connect(repeater, &RequestRepeater::requestDone, this, &BaseDeviceService::handleResponse); - } else if(isCurrentyPolling()){ + } else if (isCurrentyPolling()) { repeater->ForceUpdate(); } else { LOGD("Sending one-time %s", qPrintable(fullUrl)); diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h similarity index 90% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h index cd7aa9d9b9..9b8900a328 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h @@ -1,6 +1,4 @@ -#ifndef BASESERVICE_H -#define BASESERVICE_H - +#pragma once #include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/qt/request_repeater.h" @@ -14,7 +12,7 @@ protected: void loadDeviceData(const QString &url, bool poll = false); virtual void handleResponse(const QString &response, bool success) = 0; - static bool is_sunnylink_enabled() { return Params().getBool("SunnylinkEnabled");}; + static bool is_sunnylink_enabled() { return Params().getBool("SunnylinkEnabled");} ParamWatcher* param_watcher; HttpRequest* initial_request = nullptr; RequestRepeater* repeater = nullptr; @@ -25,5 +23,3 @@ public: bool isCurrentyPolling() {return repeater != nullptr;} void stopPolling(); }; - -#endif diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc index e31d2af5fe..64b19d3a19 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h similarity index 75% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h index 13b5d0505b..9c7d951ef7 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h @@ -1,8 +1,9 @@ -#ifndef ROLESERVICE_H -#define ROLESERVICE_H +#pragma once -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" +#include + +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" class RoleService : public BaseDeviceService { Q_OBJECT @@ -11,7 +12,7 @@ public: explicit RoleService(QObject* parent = nullptr); void load(); void startPolling(); - [[nodiscard]] QString getCacheKey() const final { return "Roles"; }; + [[nodiscard]] QString getCacheKey() const final { return "Roles"; } signals: void rolesReady(const std::vector &roles); @@ -22,5 +23,3 @@ protected: private: QString url = "/roles"; }; - -#endif diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc index 9076dc6a5c..f43df2861a 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h similarity index 76% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h index 53f490722c..dba8dd444c 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h @@ -1,8 +1,9 @@ -#ifndef USERSERVICE_H -#define USERSERVICE_H +#pragma once -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h" +#include + +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h" class UserService : public BaseDeviceService { Q_OBJECT @@ -22,5 +23,3 @@ protected: private: QString url = "/users"; }; - -#endif diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc similarity index 67% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc index b56d9bd582..98bc734d95 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc @@ -1,5 +1,5 @@ -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h" SunnylinkClient::SunnylinkClient(QObject* parent) : QObject(parent) { role_service = new RoleService(parent); diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h similarity index 77% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h index cd802b590e..f63a0f44dc 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h @@ -1,10 +1,9 @@ -#ifndef SUNNYLINK_CLIENT_H -#define SUNNYLINK_CLIENT_H +#pragma once #include -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h" class SunnylinkClient : public QObject { Q_OBJECT @@ -14,5 +13,3 @@ public: RoleService* role_service; UserService* user_service; }; - -#endif diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h index 1a3cb630d9..4027147975 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 72571c4b85..3a3741a27b 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -11,7 +11,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" -#include "sunnypilot/qt/network/sunnylink/models/role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index eb02b8d2d8..b28aa6a74d 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -15,9 +16,9 @@ #include "common/mat.h" #include "common/params.h" #include "common/timing.h" -#include "sunnypilot/qt/network/sunnylink/models/role_model.h" -#include "sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h" -#include "sunnypilot/qt/network/sunnylink/models/user_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h" #include "system/hardware/hw.h" const int UI_BORDER_SIZE = 30; @@ -242,7 +243,7 @@ public: inline SponsorRoleModel sunnylinkSponsorRole() const { std::optional sponsorRoleWithHighestTier = std::nullopt; for (const auto &role : sunnylinkRoles) { - if(role.roleType != RoleType::Sponsor) + if (role.roleType != RoleType::Sponsor) continue; if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { From c001c1e93c03e24ec2b5f72a38222c215ee0fc33 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Tue, 16 Jul 2024 15:39:01 -0400 Subject: [PATCH 426/575] CustomOffsetsSettings: rename --- selfdrive/ui/sunnypilot/SConscript | 2 +- .../sp_priv_custom_offsets_settings.cc} | 6 +++--- .../sp_priv_custom_offsets_settings.h} | 3 +++ .../ui/sunnypilot/qt/offroad/sunnypilot_settings.h | 14 ++++++++++---- 4 files changed, 17 insertions(+), 8 deletions(-) rename selfdrive/ui/sunnypilot/qt/offroad/{custom_offsets_settings.cc => settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc} (91%) rename selfdrive/ui/sunnypilot/qt/offroad/{custom_offsets_settings.h => settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h} (95%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index c0cd96dcfd..d28389b79d 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -9,7 +9,7 @@ widgets_src = [ "sunnypilot/qt/offroad/speed_limit_control_settings.cc", "sunnypilot/qt/offroad/monitoring_settings.cc", "sunnypilot/qt/offroad/osm_settings.cc", - "sunnypilot/qt/offroad/custom_offsets_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc", "sunnypilot/qt/widgets/drive_stats.cc", "sunnypilot/qt/offroad/software_settings_sp.cc", "sunnypilot/qt/offroad/models_fetcher.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc similarity index 91% rename from selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc index 5b762f3230..c257dae7d7 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h" CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -28,7 +28,7 @@ CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) } // Camera Offset Value -CameraOffset::CameraOffset() : OptionControlSP ( +CameraOffset::CameraOffset() : OptionControlSP( "CameraOffset", tr("Camera Offset - Laneful Only"), tr("Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm"), @@ -48,7 +48,7 @@ void CameraOffset::refresh() { } // Path Offset Value -PathOffset::PathOffset() : OptionControlSP ( +PathOffset::PathOffset() : OptionControlSP( "PathOffset", tr("Path Offset"), tr("Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h similarity index 95% rename from selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h index 7463aa365e..094b69dc22 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index c42c0b6c66..470a7a14b7 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -1,8 +1,11 @@ #pragma once +#include +#include + #include "common/model.h" #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h" @@ -71,8 +74,10 @@ private: const QString nnff_description = QString("%1

" "%2") - .arg(tr("Formerly known as \"NNFF\", this replaces the lateral \"torque\" controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy.")) - .arg(tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: ") + "#tuning-nnlc"); + .arg(tr("Formerly known as \"NNFF\", this replaces the lateral \"torque\" controller, " + "with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy.")) + .arg(tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, " + "or to provide log data for your car if your car is currently unsupported: ") + "#tuning-nnlc"); QString nnffDescriptionBuilder(const QString &custom_description) { QString description = "" + custom_description + "

" + nnff_description; @@ -80,5 +85,6 @@ private: } const QString custom_offsets_description = QString(tr("Add custom offsets to Camera and Path in sunnypilot.")); - const QString dlp_description = QString(tr("Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions.")); + const QString dlp_description = QString( + tr("Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions.")); }; From 9377e9a56c21e5c73d979aff14e915ac18ae800c Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Tue, 16 Jul 2024 15:53:34 -0400 Subject: [PATCH 427/575] LaneChangeSettings: rename --- selfdrive/ui/sunnypilot/SConscript | 2 +- .../sp_priv_lane_change_settings.cc} | 15 +++++++++++---- .../sp_priv_lane_change_settings.h} | 3 +++ .../sunnypilot/qt/offroad/sunnypilot_settings.h | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) rename selfdrive/ui/sunnypilot/qt/offroad/{lane_change_settings.cc => settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc} (90%) rename selfdrive/ui/sunnypilot/qt/offroad/{lane_change_settings.h => settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h} (96%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index d28389b79d..cd5cae2d42 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -5,7 +5,7 @@ widgets_src = [ "sunnypilot/qt/offroad/visuals_settings.cc", "sunnypilot/qt/offroad/trips_settings.cc", "sunnypilot/qt/offroad/mads_settings.cc", - "sunnypilot/qt/offroad/lane_change_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc", "sunnypilot/qt/offroad/speed_limit_control_settings.cc", "sunnypilot/qt/offroad/monitoring_settings.cc", "sunnypilot/qt/offroad/osm_settings.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc similarity index 90% rename from selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc index f2f0321c1e..d6165d99d8 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc @@ -1,4 +1,9 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h" + +#include +#include +#include +#include LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -97,10 +102,12 @@ void LaneChangeSettings::updateToggles() { } // Auto Lane Change Timer (ALCT) -AutoLaneChangeTimer::AutoLaneChangeTimer() : OptionControlSP ( +AutoLaneChangeTimer::AutoLaneChangeTimer() : OptionControlSP( "AutoLaneChangeTimer", tr("Auto Lane Change by Blinker"), - tr("Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.\nPlease use caution when using this feature. Only use the blinker when traffic and road conditions permit."), + tr("Set a timer to delay the auto lane change operation when the blinker is used. " + "No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.\n" + "Please use caution when using this feature. Only use the blinker when traffic and road conditions permit."), "../assets/offroad/icon_blank.png", {-1, 5}) { @@ -127,7 +134,7 @@ void AutoLaneChangeTimer::refresh() { } } -PauseLateralSpeed::PauseLateralSpeed() : OptionControlSP ( +PauseLateralSpeed::PauseLateralSpeed() : OptionControlSP( "PauseLateralSpeed", "", tr("Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h index dcc9a457d0..26129c9f6e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index 470a7a14b7..ed309fc034 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -6,7 +6,7 @@ #include "common/model.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h" From 12151efa7808931f24a83b4e7c3f119b0584ec57 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Tue, 16 Jul 2024 16:01:13 -0400 Subject: [PATCH 428/575] MadsSettings: rename --- selfdrive/ui/sunnypilot/SConscript | 2 +- .../sunnypilot_sub_panel/sp_priv_mads_settings.cc} | 11 +++++++---- .../sunnypilot_sub_panel/sp_priv_mads_settings.h} | 3 +++ .../ui/sunnypilot/qt/offroad/sunnypilot_settings.h | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) rename selfdrive/ui/sunnypilot/qt/offroad/{mads_settings.cc => settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc} (87%) rename selfdrive/ui/sunnypilot/qt/offroad/{mads_settings.h => settings/sunnypilot_sub_panel/sp_priv_mads_settings.h} (93%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index cd5cae2d42..c1c1d2d8e8 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -4,7 +4,7 @@ widgets_src = [ "sunnypilot/qt/offroad/vehicle_settings.cc", "sunnypilot/qt/offroad/visuals_settings.cc", "sunnypilot/qt/offroad/trips_settings.cc", - "sunnypilot/qt/offroad/mads_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc", "sunnypilot/qt/offroad/speed_limit_control_settings.cc", "sunnypilot/qt/offroad/monitoring_settings.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc similarity index 87% rename from selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc index 02f32c00f7..7f331a37da 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc @@ -1,4 +1,7 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h" + +#include +#include MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -35,11 +38,11 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { dlob_settings = new ButtonParamControlSP( "DisengageLateralOnBrake", tr("Steering Mode After Braking"), - tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\nRemain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."), + tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\n" + "Remain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."), "../assets/offroad/icon_blank.png", dlob_settings_texts, - 500 - ); + 500); dlob_settings->showDescription(); list->addItem(dlob_settings); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h similarity index 93% rename from selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h index 374e404f29..9dee408ecc 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index ed309fc034..e7d5e74720 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -7,7 +7,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" From a4926ef332aa6102c2bfb581873d7e7a01d0bd1d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 16 Jul 2024 23:14:57 +0200 Subject: [PATCH 429/575] Rename and remove custom abstracts for map support Refactor class names by renaming AbstractControl to AbstractControlSP_TITLED and LayoutWidget to LayoutWidgetSP. Removed conditional compilation definitions for map support to streamline the code and avoid multiple definition errors. --- selfdrive/ui/qt/offroad_home.h | 1 + .../sunnypilot/qt/widgets/sp_priv_controls.cc | 6 ++--- .../sunnypilot/qt/widgets/sp_priv_controls.h | 27 +++++++------------ 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h index f277d852c4..be905861e1 100644 --- a/selfdrive/ui/qt/offroad_home.h +++ b/selfdrive/ui/qt/offroad_home.h @@ -21,6 +21,7 @@ #include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" #define OnroadWindow OnroadWindowSP #define OffroadHomeImp OffroadHomeSP +#define LayoutWidget LayoutWidgetSP #define Sidebar SidebarSP #else #include "selfdrive/ui/qt/widgets/controls.h" diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc index 4ba1f31c9e..7f9985a70c 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc @@ -15,7 +15,7 @@ QFrame *horizontal_line(QWidget *parent) { return line; } -AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { +AbstractControlSP_TITLED::AbstractControlSP_TITLED(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setMargin(0); @@ -68,7 +68,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons main_layout->addStretch(); } -void AbstractControl::hideEvent(QHideEvent *e) { +void AbstractControlSP_TITLED::hideEvent(QHideEvent *e) { if (description != nullptr) { description->hide(); } @@ -121,7 +121,7 @@ void AbstractControlSP::hideEvent(QHideEvent *e) { // controls -ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControl(title, desc, "", parent) { +ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControlSP_TITLED(title, desc, "", parent) { btn.setText(text); btn.setStyleSheet(R"( QPushButton { diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 2549384e3e..44255b2d99 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -25,15 +25,6 @@ #define Toggle ToggleSP #define ToggleControl ToggleControlSP -// To avoid multiple definition errors for AbstractControl and LayoutWidget when ENABLE_MAPS is defined, -// we redefine these classes with custom names (AbstractControlCustomSP and LayoutWidgetSP). -// This ensures that the linker does not encounter duplicate symbols for these classes from different sources. -// This redefinition is only necessary when building with map support; it is not needed if map functionality is not required. -#ifdef ENABLE_MAPS -#define AbstractControl AbstractControlCustomSP -#define LayoutWidget LayoutWidgetSP -#endif - QFrame *horizontal_line(QWidget *parent = nullptr); class ElidedLabelSP : public QLabel { @@ -61,7 +52,7 @@ protected: QString lastText_, elidedText_; }; -class AbstractControl : public QFrame { +class AbstractControlSP_TITLED : public QFrame { Q_OBJECT public: @@ -100,7 +91,7 @@ public: void showDescriptionEvent(); protected: - AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); + AbstractControlSP_TITLED(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); void hideEvent(QHideEvent *e) override; QHBoxLayout *hlayout; @@ -153,11 +144,11 @@ private: // widget to display a value -class LabelControlSP : public AbstractControl { +class LabelControlSP : public AbstractControlSP_TITLED { Q_OBJECT public: - LabelControlSP(const QString &title, const QString &text = "", const QString &desc = "", QWidget *parent = nullptr) : AbstractControl(title, desc, "", parent) { + LabelControlSP(const QString &title, const QString &text = "", const QString &desc = "", QWidget *parent = nullptr) : AbstractControlSP_TITLED(title, desc, "", parent) { label.setText(text); label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); hlayout->addWidget(&label); @@ -169,7 +160,7 @@ private: }; // widget for a button with a label -class ButtonControlSP : public AbstractControl { +class ButtonControlSP : public AbstractControlSP_TITLED { Q_OBJECT public: @@ -188,11 +179,11 @@ private: QPushButton btn; }; -class ToggleControlSP : public AbstractControl { +class ToggleControlSP : public AbstractControlSP_TITLED { Q_OBJECT public: - ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControl(title, desc, icon, parent) { + ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControlSP_TITLED(title, desc, icon, parent) { toggle.setFixedSize(150, 100); if (state) { toggle.togglePosition(); @@ -442,11 +433,11 @@ private: }; // convenience class for wrapping layouts -class LayoutWidget : public QWidget { +class LayoutWidgetSP : public QWidget { Q_OBJECT public: - LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { + LayoutWidgetSP(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { setLayout(l); } }; From c0b732081b0aff3fd8f1173909c47b1723abbd89 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Tue, 16 Jul 2024 23:22:04 -0400 Subject: [PATCH 430/575] sunnypilot: Rename all sub panels --- selfdrive/ui/sunnypilot/SConscript | 6 +++--- .../sp_priv_speed_limit_control_settings.cc} | 12 ++++++------ .../sp_priv_speed_limit_control_settings.h} | 4 ++++ .../sp_priv_speed_limit_policy_settings.cc} | 5 ++--- .../sp_priv_speed_limit_policy_settings.h} | 3 +++ .../sp_priv_speed_limit_warning_settings.cc} | 13 +++++-------- .../sp_priv_speed_limit_warning_settings.h} | 4 ++++ .../sunnypilot/qt/offroad/sunnypilot_settings.h | 6 +++--- .../ui/sunnypilot/qt/onroad/onroad_settings.cc | 15 +++++++-------- 9 files changed, 37 insertions(+), 31 deletions(-) rename selfdrive/ui/sunnypilot/qt/offroad/{speed_limit_control_settings.cc => settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc} (96%) rename selfdrive/ui/sunnypilot/qt/offroad/{speed_limit_control_settings.h => settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h} (97%) rename selfdrive/ui/sunnypilot/qt/offroad/{speed_limit_policy_settings.cc => settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc} (92%) rename selfdrive/ui/sunnypilot/qt/offroad/{speed_limit_policy_settings.h => settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/{speed_limit_warning_settings.cc => settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc} (96%) rename selfdrive/ui/sunnypilot/qt/offroad/{speed_limit_warning_settings.h => settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h} (97%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index c1c1d2d8e8..db81944ecd 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -6,15 +6,15 @@ widgets_src = [ "sunnypilot/qt/offroad/trips_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc", - "sunnypilot/qt/offroad/speed_limit_control_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc", "sunnypilot/qt/offroad/monitoring_settings.cc", "sunnypilot/qt/offroad/osm_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc", "sunnypilot/qt/widgets/drive_stats.cc", "sunnypilot/qt/offroad/software_settings_sp.cc", "sunnypilot/qt/offroad/models_fetcher.cc", - "sunnypilot/qt/offroad/speed_limit_warning_settings.cc", - "sunnypilot/qt/offroad/speed_limit_policy_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc", "sunnypilot/qt/offroad/sunnylink_settings.cc", "sunnypilot/qt/widgets/sp_priv_controls.cc", "sunnypilot/qt/widgets/sp_priv_scrollview.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc index e88d757004..57d3bc1b21 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc @@ -1,4 +1,6 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h" + +#include SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -18,8 +20,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { "", "../assets/offroad/icon_blank.png", speed_limit_engage_texts, - 440 - ); + 440); speed_limit_engage_settings->showDescription(); list->addItem(speed_limit_engage_settings); @@ -28,8 +29,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { "SpeedLimitOffsetType", tr("Limit Offset"), tr("Set speed limit slightly higher than actual speed limit for a more natural drive."), "../assets/offroad/icon_blank.png", speed_limit_offset_settings_texts, - 380 - ); + 380); list->addItem(speed_limit_offset_settings); slvo = new SpeedLimitValueOffset(); @@ -101,7 +101,7 @@ void SlcSettings::updateToggles() { } // Speed Limit Control Custom Offset -SpeedLimitValueOffset::SpeedLimitValueOffset() : OptionControlSP ( +SpeedLimitValueOffset::SpeedLimitValueOffset() : OptionControlSP( "SpeedLimitValueOffset", "", "", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h similarity index 97% rename from selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h index 91ad4842db..e004795aa6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc similarity index 92% rename from selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc index cd1d5f7772..a93164f284 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h" SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -18,8 +18,7 @@ SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(pa "", "../assets/offroad/icon_blank.png", speed_limit_policy_texts, - 250 - ); + 250); speed_limit_policy->showDescription(); connect(speed_limit_policy, &ButtonParamControlSP::buttonToggled, this, &SpeedLimitPolicySettings::updateToggles); list->addItem(speed_limit_policy); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h index a5e8d3e393..572f91d3d2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc index 7b1c12a536..b913d514a1 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h" +#include "../../settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h" SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); @@ -18,8 +18,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( "", "../assets/offroad/icon_blank.png", speed_limit_warning_texts, - 380 - ); + 380); speed_limit_warning_settings->showDescription(); list->addItem(speed_limit_warning_settings); @@ -27,8 +26,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( "SpeedLimitWarningFlash", tr("Warning with speed limit flash"), tr("When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); list->addItem(speed_limit_warning_flash); std::vector speed_limit_warning_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; @@ -37,8 +35,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( tr("Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit."), "../assets/offroad/icon_blank.png", speed_limit_warning_offset_settings_texts, - 380 - ); + 380); list->addItem(speed_limit_warning_offset_settings); slwvo = new SpeedLimitWarningValueOffset(); @@ -86,7 +83,7 @@ void SpeedLimitWarningSettings::updateToggles() { } // Speed Limit Control Custom Offset -SpeedLimitWarningValueOffset::SpeedLimitWarningValueOffset() : OptionControlSP ( +SpeedLimitWarningValueOffset::SpeedLimitWarningValueOffset() : OptionControlSP( "SpeedLimitWarningValueOffset", "", "", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h similarity index 97% rename from selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h index 07ee7d18fa..a50fa790a1 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h index e7d5e74720..0403f43d38 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h @@ -8,9 +8,9 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_control_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_warning_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc index 1db8c654b5..ba8ce720d9 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc @@ -1,5 +1,7 @@ #include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" +#include +#include #include #include @@ -7,7 +9,7 @@ #include "common/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h" OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) { setContentsMargins(0, 0, 0, 0); @@ -185,7 +187,7 @@ void OnroadSettings::changeSpeedLimitControl() { // If EnableSlc is OFF, set it to ON and reset SpeedLimitControlPolicy scene.speed_limit_control_enabled = true; scene.speed_limit_control_policy = 0; - } else if(scene.speed_limit_control_policy < max_policy) { + } else if (scene.speed_limit_control_policy < max_policy) { // If EnableSlc is already ON then increase SpeedLimitControlPolicy till it reaches 6 scene.speed_limit_control_policy++; } else { @@ -304,12 +306,10 @@ void OptionWidget::updateDynamicLaneProfile(QString param) { if (dlp == 0) { title_text = "Laneful"; icon_color = "#2020f8"; - } - else if (dlp == 1) { + } else if (dlp == 1) { title_text = "Laneless"; icon_color = "#0df87a"; - } - else if (dlp == 2) { + } else if (dlp == 2) { title_text = "Auto"; icon_color = "#0df8f8"; } @@ -451,8 +451,7 @@ void OptionWidget::updateSpeedLimitControl(QString param) { if (enable_slc_status == 0) { title_text = "Disabled"; icon_color = "#3B4356"; // Color for "Disabled status" - } - else if (enable_slc_status == 1) { + } else if (enable_slc_status == 1) { title_text = title_text; icon_color = color_map[speed_limit_control_policy_status]; // Color for "Enabled status" } From a4b57f4178bc06a27e54953fc1f9b3bfe5738a24 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Tue, 16 Jul 2024 23:32:25 -0400 Subject: [PATCH 431/575] sunnypilot_main --- selfdrive/ui/qt/offroad/settings.cc | 2 +- selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc | 8 ++++++-- selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc | 2 +- .../{sunnypilot_main.h => sp_priv_sunnypilot_main.h} | 0 4 files changed, 8 insertions(+), 4 deletions(-) rename selfdrive/ui/sunnypilot/{sunnypilot_main.h => sp_priv_sunnypilot_main.h} (100%) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index fa43623c67..e147444e7f 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -18,7 +18,7 @@ #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/widgets/ssh_keys.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" +#include "selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h" #endif TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc index ae86a92c06..51e14c7732 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc @@ -1,8 +1,11 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" +#include +#include + #include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h" #include "selfdrive/ui/qt/network/networking.h" -#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" +#include "selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h" TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { // param, title, desc, icon @@ -74,7 +77,8 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { { "DisableOnroadUploads", tr("Disable Onroad Uploads"), - tr("Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC)."), + tr("Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. " + "Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC)."), "../assets/offroad/icon_blank.png", }, { diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc index 914e6a43c4..5221c5431e 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc @@ -2,7 +2,7 @@ #include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/widgets/prime.h" #include -#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" +#include "selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h" #ifdef ENABLE_MAPS #define LEFT_WIDGET MapSettings diff --git a/selfdrive/ui/sunnypilot/sunnypilot_main.h b/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h similarity index 100% rename from selfdrive/ui/sunnypilot/sunnypilot_main.h rename to selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h From 1fd5140d29abf56f1f6c076dcb3ddcb413f2215c Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 00:14:44 -0400 Subject: [PATCH 432/575] settings: Rename --- selfdrive/ui/qt/window.h | 2 +- selfdrive/ui/sunnypilot/SConscript | 22 ++++++------ .../services/sp_priv_base_device_service.cc | 2 +- .../sp_priv_device_panel.cc} | 6 +++- .../sp_priv_device_panel.h} | 2 +- .../sp_priv_display_settings.cc} | 13 ++++--- .../sp_priv_display_settings.h} | 3 ++ .../sp_priv_monitoring_settings.cc} | 2 +- .../sp_priv_monitoring_settings.h} | 5 +++ .../sp_priv_osm_settings.cc} | 14 +++++--- .../sp_priv_osm_settings.h} | 4 +++ .../{ => settings}/sp_priv_settings.cc | 4 +-- .../offroad/{ => settings}/sp_priv_settings.h | 3 ++ .../sp_priv_software_settings.cc} | 10 +++--- .../sp_priv_software_settings.h} | 6 ++-- .../sp_priv_sunnylink_settings.cc} | 19 +++++----- .../sp_priv_sunnylink_settings.h} | 0 .../sp_priv_sunnypilot_settings.cc} | 32 ++++++++++------- .../sp_priv_sunnypilot_settings.h} | 0 .../sp_priv_trips_settings.cc} | 2 +- .../sp_priv_trips_settings.h} | 0 .../sp_priv_vehicle_settings.cc} | 35 +++++++------------ .../sp_priv_vehicle_settings.h} | 0 .../sp_priv_visuals_settings.cc} | 14 ++++---- .../sp_priv_visuals_settings.h} | 3 ++ .../ui/sunnypilot/sp_priv_sunnypilot_main.h | 18 +++++----- 26 files changed, 125 insertions(+), 96 deletions(-) rename selfdrive/ui/sunnypilot/qt/offroad/{sp_priv_settings_device_panel.cc => settings/sp_priv_device_panel.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/{sp_priv_settings_device_panel.h => settings/sp_priv_device_panel.h} (86%) rename selfdrive/ui/sunnypilot/qt/offroad/{display_settings.cc => settings/sp_priv_display_settings.cc} (95%) rename selfdrive/ui/sunnypilot/qt/offroad/{display_settings.h => settings/sp_priv_display_settings.h} (96%) rename selfdrive/ui/sunnypilot/qt/offroad/{monitoring_settings.cc => settings/sp_priv_monitoring_settings.cc} (91%) rename selfdrive/ui/sunnypilot/qt/offroad/{monitoring_settings.h => settings/sp_priv_monitoring_settings.h} (85%) rename selfdrive/ui/sunnypilot/qt/offroad/{osm_settings.cc => settings/sp_priv_osm_settings.cc} (97%) rename selfdrive/ui/sunnypilot/qt/offroad/{osm_settings.h => settings/sp_priv_osm_settings.h} (99%) rename selfdrive/ui/sunnypilot/qt/offroad/{ => settings}/sp_priv_settings.cc (99%) rename selfdrive/ui/sunnypilot/qt/offroad/{ => settings}/sp_priv_settings.h (96%) rename selfdrive/ui/sunnypilot/qt/offroad/{software_settings_sp.cc => settings/sp_priv_software_settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/{software_settings_sp.h => settings/sp_priv_software_settings.h} (88%) rename selfdrive/ui/sunnypilot/qt/offroad/{sunnylink_settings.cc => settings/sp_priv_sunnylink_settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/{sunnylink_settings.h => settings/sp_priv_sunnylink_settings.h} (100%) rename selfdrive/ui/sunnypilot/qt/offroad/{sunnypilot_settings.cc => settings/sp_priv_sunnypilot_settings.cc} (94%) rename selfdrive/ui/sunnypilot/qt/offroad/{sunnypilot_settings.h => settings/sp_priv_sunnypilot_settings.h} (100%) rename selfdrive/ui/sunnypilot/qt/offroad/{trips_settings.cc => settings/sp_priv_trips_settings.cc} (90%) rename selfdrive/ui/sunnypilot/qt/offroad/{trips_settings.h => settings/sp_priv_trips_settings.h} (100%) rename selfdrive/ui/sunnypilot/qt/offroad/{vehicle_settings.cc => settings/sp_priv_vehicle_settings.cc} (94%) rename selfdrive/ui/sunnypilot/qt/offroad/{vehicle_settings.h => settings/sp_priv_vehicle_settings.h} (100%) rename selfdrive/ui/sunnypilot/qt/offroad/{visuals_settings.cc => settings/sp_priv_visuals_settings.cc} (96%) rename selfdrive/ui/sunnypilot/qt/offroad/{visuals_settings.h => settings/sp_priv_visuals_settings.h} (92%) diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 016f0fd23d..af67644a34 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -6,7 +6,7 @@ #include "selfdrive/ui/qt/offroad/onboarding.h" #ifdef SUNNYPILOT - #include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" + #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" #define HomeWindow HomeWindowSP #define SettingsWindow SettingsWindowSP diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index db81944ecd..d823bcbcbc 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -1,21 +1,21 @@ widgets_src = [ - "sunnypilot/qt/offroad/display_settings.cc", - "sunnypilot/qt/offroad/sunnypilot_settings.cc", - "sunnypilot/qt/offroad/vehicle_settings.cc", - "sunnypilot/qt/offroad/visuals_settings.cc", - "sunnypilot/qt/offroad/trips_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc", - "sunnypilot/qt/offroad/monitoring_settings.cc", - "sunnypilot/qt/offroad/osm_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc", "sunnypilot/qt/widgets/drive_stats.cc", - "sunnypilot/qt/offroad/software_settings_sp.cc", + "sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc", "sunnypilot/qt/offroad/models_fetcher.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc", - "sunnypilot/qt/offroad/sunnylink_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc", "sunnypilot/qt/widgets/sp_priv_controls.cc", "sunnypilot/qt/widgets/sp_priv_scrollview.cc", "sunnypilot/qt/widgets/sp_priv_toggle.cc" @@ -31,8 +31,8 @@ qt_src = [ "sunnypilot/qt/sp_priv_home.cc", "sunnypilot/qt/sp_priv_offroad_home.cc", "sunnypilot/qt/sp_priv_sidebar.cc", - "sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc", - "sunnypilot/qt/offroad/sp_priv_settings.cc", + "sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc", + "sunnypilot/qt/offroad/settings/sp_priv_settings.cc", "sunnypilot/qt/onroad/onroad_settings.cc", "sunnypilot/qt/onroad/onroad_settings_panel.cc", "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc index c2d4e6f87a..5caf67a631 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc @@ -2,7 +2,7 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent), initial_request(nullptr), repeater(nullptr) { param_watcher = new ParamWatcher(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc index 06b1d96589..9ffeb79633 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc @@ -1,5 +1,9 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h" + +#include +#include #include + #include "common/watchdog.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/widgets/prime.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h similarity index 86% rename from selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h index e78b319ba4..b07b0a8d50 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" class DevicePanelSP : public DevicePanel { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc similarity index 95% rename from selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc index 7121ba31c2..20f4e7a274 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc @@ -1,4 +1,7 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/display_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h" + +#include +#include DisplayPanel::DisplayPanel(QWidget *parent) : ListWidgetSP(parent, false) { // param, title, desc, icon @@ -65,7 +68,7 @@ void DisplayPanel::updateToggles() { } // Max Time Offroad (Shutdown timer) -MaxTimeOffroad::MaxTimeOffroad() : OptionControlSP ( +MaxTimeOffroad::MaxTimeOffroad() : OptionControlSP( "MaxTimeOffroad", tr("Max Time Offroad"), tr("Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road)."), @@ -110,7 +113,7 @@ void MaxTimeOffroad::refresh() { } // Onroad Screen Off (Auto Onroad Screen Timer) -OnroadScreenOff::OnroadScreenOff() : OptionControlSP ( +OnroadScreenOff::OnroadScreenOff() : OptionControlSP( "OnroadScreenOff", tr("Driving Screen Off Timer"), tr("Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs."), @@ -136,7 +139,7 @@ void OnroadScreenOff::refresh() { } // Onroad Screen Off Brightness -OnroadScreenOffBrightness::OnroadScreenOffBrightness() : OptionControlSP ( +OnroadScreenOffBrightness::OnroadScreenOffBrightness() : OptionControlSP( "OnroadScreenOffBrightness", tr("Driving Screen Off Brightness (%)"), tr("When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio."), @@ -157,7 +160,7 @@ void OnroadScreenOffBrightness::refresh() { } // Brightness Control (Global) -BrightnessControl::BrightnessControl() : OptionControlSP ( +BrightnessControl::BrightnessControl() : OptionControlSP( "BrightnessControl", tr("Brightness"), tr("Manually adjusts the global brightness of the screen."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/display_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h index a68c58a463..a076f0039a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/display_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" class OnroadScreenOff : public OptionControlSP { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc similarity index 91% rename from selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc index 5102d34167..d8c1458247 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h" MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h similarity index 85% rename from selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h index 542caf208f..3d30a4f830 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h @@ -1,5 +1,10 @@ #pragma once +#include +#include +#include +#include + #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc index cf169cd995..94e489b293 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc @@ -1,4 +1,8 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h" + +#include +#include +#include OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); @@ -73,7 +77,7 @@ ButtonControl *OsmPanel::setupOsmDownloadButton(QWidget *parent) { const QString currentTitle = ((initTitle == "== None ==") || (initTitle.length() == 0)) ? "== None ==" : initTitle; QStringList locationTitles; - for (auto &loc: locations) { + for (auto &loc : locations) { locationTitles.push_back(std::get<0>(loc)); } @@ -81,7 +85,7 @@ ButtonControl *OsmPanel::setupOsmDownloadButton(QWidget *parent) { if (!selection.isEmpty()) { params.put("OsmLocal", "1"); params.put("OsmLocationTitle", selection.toStdString()); - for (auto &loc: locations) { + for (auto &loc : locations) { if (std::get<0>(loc) == selection) { params.put("OsmLocationName", std::get<1>(loc).toStdString()); break; @@ -116,14 +120,14 @@ ButtonControl *OsmPanel::setupUsStatesButton(QWidget *parent) { const QString currentTitle = ((initTitle == std::get<0>(allStatesOption)) || (initTitle.length() == 0)) ? tr("All") : initTitle; QStringList locationTitles; - for (auto &loc: locations) { + for (auto &loc : locations) { locationTitles.push_back(std::get<0>(loc)); } const QString selection = MultiOptionDialog::getSelection(tr("State"), locationTitles, currentTitle, this); if (!selection.isEmpty()) { params.put("OsmStateTitle", selection.toStdString()); - for (auto &loc: locations) { + for (auto &loc : locations) { if (std::get<0>(loc) == selection) { params.put("OsmStateName", std::get<1>(loc).toStdString()); break; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h index 853033be7c..96b61eac42 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h @@ -2,7 +2,11 @@ #include #include +#include #include +#include +#include +#include #include #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc index 51e14c7732..a8427f0647 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc @@ -1,9 +1,9 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" #include #include -#include "selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings_device_panel.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h" #include "selfdrive/ui/qt/network/networking.h" #include "selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h index 718aeaa1a4..04f9db54c5 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sp_priv_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "selfdrive/ui/qt/offroad/settings.h" class TogglesPanelSP : public TogglesPanel { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc index d6ac446a07..65069aecfc 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc @@ -1,4 +1,6 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h" + +#include SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { // Get current model name and create new ButtonControl @@ -185,7 +187,7 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { QMap index_to_model; // Collecting indices with display names - for (const auto &model: models) { + for (const auto &model : models) { if ((is_release_sp && model.environment == "release") || !is_release_sp) { index_to_model.insert(model.index, model.displayName); } @@ -197,7 +199,7 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { return index1.toInt() > index2.toInt(); }); - for (const QString &index: indices) { + for (const QString &index : indices) { modelNames.push_back(index_to_model[index]); } @@ -213,7 +215,7 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { } // Finding and setting the selected model - for (auto &model: models) { + for (auto &model : models) { if (model.displayName == selectedModelName) { selectedModelToDownload = model; selectedNavModelToDownload = model; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h similarity index 88% rename from selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h index 4f8fc0d22a..21ded6e473 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h @@ -19,17 +19,17 @@ private: void checkNetwork(); bool isDownloadingModel() const { - LOGD("isDownloadingModel: selectedModelToDownload.has_value() [%s] && modelDownloadProgress [%f]",selectedModelToDownload.has_value() ?"true": "false", modelDownloadProgress.value_or(0.0)); + LOGD("isDownloadingModel: selectedModelToDownload.has_value() [%s] && modelDownloadProgress [%f]", selectedModelToDownload.has_value() ? "true": "false", modelDownloadProgress.value_or(0.0)); return selectedModelToDownload.has_value() && modelDownloadProgress.value_or(0.0) > 0.0 && modelDownloadProgress.value_or(0.0) < 100.0; } bool isDownloadingNavModel() const { - LOGD("isDownloadingNavModel: selectedNavModelToDownload.has_value() [%s] && navModelDownloadProgress [%f]",selectedNavModelToDownload.has_value() ?"true": "false", navModelDownloadProgress.value_or(0.0)); + LOGD("isDownloadingNavModel: selectedNavModelToDownload.has_value() [%s] && navModelDownloadProgress [%f]", selectedNavModelToDownload.has_value() ? "true": "false", navModelDownloadProgress.value_or(0.0)); return selectedNavModelToDownload.has_value() && navModelDownloadProgress.value_or(0.0) > 0.0 && navModelDownloadProgress.value_or(0.0) < 100.0; } bool isDownloadingMetadata() const { - LOGD("isDownloadingMetadata: selectedMetadataToDownload.has_value() [%s] && metadataDownloadProgress [%f]",selectedMetadataToDownload.has_value() ?"true": "false", metadataDownloadProgress.value_or(0.0)); + LOGD("isDownloadingMetadata: selectedMetadataToDownload.has_value() [%s] && metadataDownloadProgress [%f]", selectedMetadataToDownload.has_value() ? "true": "false", metadataDownloadProgress.value_or(0.0)); return selectedMetadataToDownload.has_value() && metadataDownloadProgress.value_or(0.0) > 0.0 && metadataDownloadProgress.value_or(0.0) < 100.0; } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc index 634e3efe5d..364022d6e2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc @@ -1,7 +1,11 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" + +#include +#include + +#include #include -#include SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { main_layout = new QStackedLayout(this); @@ -21,8 +25,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { "SunnylinkEnabled", tr("Enable sunnylink"), sunnylinkBtnDescription, - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); sunnylinkEnabledBtn->setValue(tr("Device ID ")+ getSunnylinkDongleId().value_or(tr("N/A"))); list->addItem(sunnylinkEnabledBtn); @@ -51,8 +54,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { status_popup = new SunnylinkSponsorPopup(false, this); sponsorBtn = new ButtonControl( tr("Sponsor Status"), tr("SPONSOR"), - tr("Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.") - ); + tr("Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.")); list->addItem(sponsorBtn); connect(sponsorBtn, &ButtonControl::clicked, [=]() { status_popup->exec(); @@ -62,8 +64,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { pair_popup = new SunnylinkSponsorPopup(true, this); pairSponsorBtn = new ButtonControl( tr("Pair GitHub Account"), tr("PAIR"), - tr("Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.") + "🌟" - ); + tr("Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.") + "🌟"); list->addItem(pairSponsorBtn); connect(pairSponsorBtn, &ButtonControl::clicked, [=]() { if (getSunnylinkDongleId().value_or(tr("N/A")) == "N/A") { @@ -194,7 +195,7 @@ void SunnylinkPanel::updateLabels() { auto paired_users = uiState()->sunnylinkDeviceUsers(); //little easter egg for Panda :D - if(sunnylinkDongleId == "d689627422cefcbc") { + if (sunnylinkDongleId == "d689627422cefcbc") { role_name = "Panda 🐼"; } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc similarity index 94% rename from selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc index 36643fd708..0936e9c13e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc @@ -1,4 +1,7 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h" + +#include +#include SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); @@ -21,7 +24,8 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { { "EnableSlc", tr("Speed Limit Control (SLC)"), - tr("When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed."), + tr("When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. " + "The maximum cruising speed will always be the MAX set speed."), "../assets/offroad/icon_blank.png", }, { @@ -80,7 +84,8 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { { "CustomTorqueLateral", tr("Enable Custom Tuning"), - tr("Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within \"selfdrive/torque_data\". The values will also be used live when \"Override Self-Tune\" toggle is enabled."), + tr("Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within \"selfdrive/torque_data\". " + "The values will also be used live when \"Override Self-Tune\" toggle is enabled."), "../assets/offroad/icon_blank.png", }, { @@ -100,7 +105,8 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { tr("Green Traffic Light Chime (Beta)"), QString("%1
" "

%2


") - .arg(tr("A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged.")) + .arg(tr("A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. " + "If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged.")) .arg(tr("Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly.")), "../assets/offroad/icon_blank.png", }, @@ -246,8 +252,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { "DynamicLaneProfile", tr("Dynamic Lane Profile"), "", "../assets/offroad/icon_blank.png", dlp_settings_texts, - 340 - ); + 340); dlp_settings->showDescription(); for (auto &[param, title, desc, icon] : toggle_defs) { @@ -467,7 +472,8 @@ void SunnypilotPanel::updateToggles() { // NNLC/NNFF QString nnff_available_desc = tr("NNLC is currently not available on this platform."); - QString nnff_fuzzy_desc = tr("Match: \"Exact\" is ideal, but \"Fuzzy\" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: ") + "#tuning-nnlc"; + QString nnff_fuzzy_desc = tr("Match: \"Exact\" is ideal, but \"Fuzzy\" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: ") + + "#tuning-nnlc"; QString nnff_status_init = "⚠️ " + tr("Start the car to check car compatibility") + ""; QString nnff_not_loaded = "⚠️ " + tr("NNLC Not Loaded") + ""; QString nnff_loaded = "✅ " + tr("NNLC Loaded") + ""; @@ -493,9 +499,11 @@ void SunnypilotPanel::updateToggles() { QString nn_model_name = QString::fromStdString(CP.getLateralTuning().getTorque().getNnModelName()); QString nn_fuzzy = CP.getLateralTuning().getTorque().getNnModelFuzzyMatch() ? tr("Fuzzy") : tr("Exact"); - nnff_toggle->setDescription(nnffDescriptionBuilder((nn_model_name == "") ? nnff_status_init : - (nn_model_name == "mock") ? (nnff_not_loaded + "
" + tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: ") + "#tuning-nnlc") : - (nnff_loaded + " | " + tr("Match") + " = " + nn_fuzzy + " | " + _car_model + "

" + nnff_fuzzy_desc))); + nnff_toggle->setDescription(nnffDescriptionBuilder( + (nn_model_name == "") ? nnff_status_init : + (nn_model_name == "mock") ? (nnff_not_loaded + "
" + tr("Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: ") + + "#tuning-nnlc") : + (nnff_loaded + " | " + tr("Match") + " = " + nn_fuzzy + " | " + _car_model + "

" + nnff_fuzzy_desc))); enforce_torque_lateral->setEnabled(false); } else { nnff_toggle->setDescription(nnffDescriptionBuilder(nnff_status_init)); @@ -581,7 +589,7 @@ void SunnypilotPanel::updateToggles() { dlp_settings->setDescription((model_use_lateral_planner ? "" : dlp_incompatible_desc + "

") + dlp_description); } -TorqueFriction::TorqueFriction() : OptionControlSP ( +TorqueFriction::TorqueFriction() : OptionControlSP( "TorqueFriction", tr("FRICTION"), tr("Adjust Friction for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), @@ -597,7 +605,7 @@ void TorqueFriction::refresh() { setLabel(QString::number(torqueFrictionVal)); } -TorqueMaxLatAccel::TorqueMaxLatAccel() : OptionControlSP ( +TorqueMaxLatAccel::TorqueMaxLatAccel() : OptionControlSP( "TorqueMaxLatAccel", tr("LAT_ACCEL_FACTOR"), tr("Adjust Max Lateral Acceleration for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc similarity index 90% rename from selfdrive/ui/sunnypilot/qt/offroad/trips_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc index 0f080926b4..413ea5a016 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h" TripsPanel::TripsPanel(QWidget* parent) : QFrame(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc similarity index 94% rename from selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc index 845b223e69..d961c3c3ab 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h" VehiclePanel::VehiclePanel(QWidget *parent) : QWidget(parent) { main_layout = new QStackedLayout(this); @@ -76,8 +76,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "HkgSmoothStop", tr("HKG CAN: Smoother Stopping Performance (Beta)"), tr("Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); hkgSmoothStop->setConfirmation(true, false); addItem(hkgSmoothStop); @@ -87,8 +86,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "SubaruManualParkingBrakeSng", tr("Manual Parking Brake: Stop and Go (Beta)"), tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!"), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); subaruManualParkingBrakeSng->setConfirmation(true, false); addItem(subaruManualParkingBrakeSng); @@ -98,8 +96,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "StockLongToyota", tr("Enable Stock Toyota Longitudinal Control"), tr("sunnypilot will not take over control of gas and brakes. Stock Toyota longitudinal control will be used."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); stockLongToyota->setConfirmation(true, false); addItem(stockLongToyota); @@ -110,8 +107,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "

%2


") .arg(tr("Allows M.A.D.S. engagement/disengagement with \"LKAS\" button from the steering wheel.")) .arg(tr("Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly.")), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); lkasToggle->setConfirmation(true, false); addItem(lkasToggle); @@ -119,8 +115,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "ToyotaTSS2Long", tr("Toyota TSS2 Longitudinal: Custom Tuning"), tr("Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaTss2LongTune->setConfirmation(true, false); addItem(toyotaTss2LongTune); @@ -131,8 +126,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge .arg(tr("WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK.")) .arg(tr("When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold.")) .arg(tr("Changing this setting takes effect when the car is powered off.")), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaAbh->setConfirmation(true, false); addItem(toyotaAbh); @@ -140,8 +134,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "ToyotaEnhancedBsm", tr("Enable Enhanced Blind Spot Monitor"), "", - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaEnhancedBsm->setConfirmation(true, false); addItem(toyotaEnhancedBsm); @@ -149,8 +142,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "ToyotaSnG", tr("Enable Toyota Stop and Go Hack"), tr("sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaSngHack->setConfirmation(true, false); addItem(toyotaSngHack); @@ -158,8 +150,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "ToyotaAutoLock", tr("Enable Toyota Door Auto Locking"), tr("sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph).\nReboot Required."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaAutoLock->setConfirmation(true, false); addItem(toyotaAutoLock); @@ -167,8 +158,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "ToyotaAutoUnlockByShifter", tr("Enable Toyota Door Auto Unlocking"), tr("sunnypilot will attempt to unlock the doors when shift to gear P.\nReboot Required."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); toyotaAutoUnlock->setConfirmation(true, false); addItem(toyotaAutoUnlock); @@ -178,8 +168,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge "VwCCOnly", tr("Enable CC Only support"), tr("sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory."), - "../assets/offroad/icon_blank.png" - ); + "../assets/offroad/icon_blank.png"); volkswagenCCOnly->setConfirmation(true, false); addItem(volkswagenCCOnly); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc index d96f4d8ec2..9ce2ad9993 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc @@ -1,4 +1,7 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h" + +#include +#include VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { // param, title, desc, icon @@ -77,8 +80,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { "DevUIInfo", tr("Developer UI"), tr("Display real-time parameters and metrics from various sources."), "../assets/offroad/icon_blank.png", dev_ui_settings_texts, - 380 - ); + 380); dev_ui_settings->showDescription(); // Visuals: Display Metrics above Chevron @@ -87,8 +89,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { "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 - ); + 340); chevron_info_settings->showDescription(); for (auto &[param, title, desc, icon] : toggle_defs) { @@ -111,8 +112,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { "SidebarTemperatureOptions", tr("Display Temperature on Sidebar"), "", "../assets/offroad/icon_blank.png", sidebar_temp_texts, - 255 - ); + 255); sidebar_temp_setting->showDescription(); addItem(sidebar_temp_setting); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h similarity index 92% rename from selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h index 244bc09b7e..bf1f12d7a6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "selfdrive/ui/ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" diff --git a/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h b/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h index 16224a0780..a3fb37e59b 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h +++ b/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h @@ -1,14 +1,14 @@ #pragma once -#include "selfdrive/ui/sunnypilot/qt/offroad/display_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/sunnypilot_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/vehicle_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/visuals_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/trips_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/monitoring_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/osm_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/software_settings_sp.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" inline void ReplaceWidget(QWidget* old_widget, QWidget* new_widget) From 6e962afe1a7a7451cd692b209fb5597e7c657040 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 00:46:55 -0400 Subject: [PATCH 433/575] settings: more renaming --- selfdrive/ui/sunnypilot/SConscript | 14 +++++++------- .../sp_priv_json_fetcher.h} | 0 .../osm/sp_priv_locations_fetcher.h} | 9 ++++++--- .../osm/sp_priv_models_fetcher.cc} | 5 +++-- .../osm/sp_priv_models_fetcher.h} | 4 +++- .../qt/offroad/settings/sp_priv_osm_settings.h | 2 +- .../offroad/settings/sp_priv_software_settings.h | 2 +- .../offroad/settings/sp_priv_sunnypilot_settings.h | 12 ++++++------ .../sp_priv_custom_offsets_settings.cc | 2 +- .../sp_priv_custom_offsets_settings.h | 0 .../sp_priv_lane_change_settings.cc | 2 +- .../sp_priv_lane_change_settings.h | 0 .../sp_priv_mads_settings.cc | 2 +- .../sp_priv_mads_settings.h | 0 .../sp_priv_speed_limit_control_settings.cc | 2 +- .../sp_priv_speed_limit_control_settings.h | 0 .../sp_priv_speed_limit_policy_settings.cc | 2 +- .../sp_priv_speed_limit_policy_settings.h | 0 .../sp_priv_speed_limit_warning_settings.cc | 2 +- .../sp_priv_speed_limit_warning_settings.h | 0 .../ui/sunnypilot/qt/onroad/onroad_settings.cc | 2 +- 21 files changed, 34 insertions(+), 28 deletions(-) rename selfdrive/ui/sunnypilot/qt/{offroad/json_fetcher.h => common/sp_priv_json_fetcher.h} (100%) rename selfdrive/ui/sunnypilot/qt/offroad/{locations_fetcher.h => settings/osm/sp_priv_locations_fetcher.h} (95%) rename selfdrive/ui/sunnypilot/qt/offroad/{models_fetcher.cc => settings/osm/sp_priv_models_fetcher.cc} (97%) rename selfdrive/ui/sunnypilot/qt/offroad/{models_fetcher.h => settings/osm/sp_priv_models_fetcher.h} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_custom_offsets_settings.cc (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_custom_offsets_settings.h (100%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_lane_change_settings.cc (99%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_lane_change_settings.h (100%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_mads_settings.cc (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_mads_settings.h (100%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_speed_limit_control_settings.cc (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_speed_limit_control_settings.h (100%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_speed_limit_policy_settings.cc (97%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_speed_limit_policy_settings.h (100%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_speed_limit_warning_settings.cc (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sunnypilot_sub_panel => sunnypilot}/sp_priv_speed_limit_warning_settings.h (100%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index d823bcbcbc..7544962f9d 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -4,17 +4,17 @@ widgets_src = [ "sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc", "sunnypilot/qt/widgets/drive_stats.cc", "sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc", - "sunnypilot/qt/offroad/models_fetcher.cc", - "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc", + "sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc", "sunnypilot/qt/widgets/sp_priv_controls.cc", "sunnypilot/qt/widgets/sp_priv_scrollview.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h b/selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h rename to selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h similarity index 95% rename from selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h index e79c6f2e94..38e34544a7 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h @@ -1,11 +1,14 @@ #pragma once -#include #include // for std::sort #include -#include +#include +#include -#include "selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h" +#include +#include + +#include "selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h" static const std::tuple defaultLocation = std::make_tuple("== None ==", ""); // New class LocationsFetcher that handles web requests and JSON parsing diff --git a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc index a46e75706e..296ea6cf33 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc @@ -1,4 +1,5 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h" + #include ModelsFetcher::ModelsFetcher(QObject* parent) : QObject(parent) { @@ -88,7 +89,7 @@ void ModelsFetcher::onFinished(QNetworkReply* reply, const QString& destinationP QFile file(finalPath); //ensure if the path exists and if not create it - if(!QDir().mkpath(destinationPath)) + if (!QDir().mkpath(destinationPath)) { LOGE("Unable to create directory: %s", destinationPath.toStdString().c_str()); emit downloadFailed(filename); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h index 8780c8028a..c8ca43c2ff 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h @@ -3,6 +3,8 @@ #include // for std::sort #include #include +#include + #include #include #include @@ -11,7 +13,7 @@ #include "common/util.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/json_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #else diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h index 96b61eac42..1b76f68126 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h @@ -14,7 +14,7 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/network/wifi_manager.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/locations_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/ui.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h index 21ded6e473..d1d83b6790 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h @@ -3,7 +3,7 @@ #include "common/model.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/offroad/settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/models_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h" class SoftwarePanelSP final : public SoftwarePanel { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h index 0403f43d38..615898e5e5 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h @@ -5,12 +5,12 @@ #include "common/model.h" #include "selfdrive/ui/ui.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc index c257dae7d7..28c9690e8f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h" CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_custom_offsets_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc index d6165d99d8..6e8c8c69f6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_lane_change_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc index 7f331a37da..ee600795c0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_mads_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc index 57d3bc1b21..c3507dcf50 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_control_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc index a93164f284..6041dbb639 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h" SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc index b913d514a1..1da6aa89b5 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc @@ -1,4 +1,4 @@ -#include "../../settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h" +#include "../../settings/sunnypilot/sp_priv_speed_limit_warning_settings.h" SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_warning_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc index ba8ce720d9..6f0904d784 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc @@ -9,7 +9,7 @@ #include "common/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_sub_panel/sp_priv_speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h" OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) { setContentsMargins(0, 0, 0, 0); From 4be255ab6174ef77f76b6ac1cce63421866bc090 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 00:51:48 -0400 Subject: [PATCH 434/575] onroad settings: rename --- selfdrive/ui/sunnypilot/SConscript | 2 +- selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc | 2 +- .../onroad/{onroad_settings.cc => sp_priv_onroad_settings.cc} | 2 +- .../qt/onroad/{onroad_settings.h => sp_priv_onroad_settings.h} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename selfdrive/ui/sunnypilot/qt/onroad/{onroad_settings.cc => sp_priv_onroad_settings.cc} (99%) rename selfdrive/ui/sunnypilot/qt/onroad/{onroad_settings.h => sp_priv_onroad_settings.h} (100%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 7544962f9d..2a9433e67c 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -33,7 +33,7 @@ qt_src = [ "sunnypilot/qt/sp_priv_sidebar.cc", "sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc", "sunnypilot/qt/offroad/settings/sp_priv_settings.cc", - "sunnypilot/qt/onroad/onroad_settings.cc", + "sunnypilot/qt/onroad/sp_priv_onroad_settings.cc", "sunnypilot/qt/onroad/onroad_settings_panel.cc", "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", "sunnypilot/qt/onroad/sp_priv_annotated_camera.cc", diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc index 8a8283fad3..166acaaa2f 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h" #include "../../../qt/util.h" #include "../../../ui.h" diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc rename to selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc index 6f0904d784..efe5aa7766 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h rename to selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h From d111bff3cfd5db90e2c25b8ea3abcddcb10ce6cb Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 00:54:21 -0400 Subject: [PATCH 435/575] onroad settings: rename --- selfdrive/ui/sunnypilot/SConscript | 2 +- selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc | 2 +- ...d_settings_panel.cc => sp_priv_onroad_settings_panel.cc} | 6 +++--- ...oad_settings_panel.h => sp_priv_onroad_settings_panel.h} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename selfdrive/ui/sunnypilot/qt/onroad/{onroad_settings_panel.cc => sp_priv_onroad_settings_panel.cc} (82%) rename selfdrive/ui/sunnypilot/qt/onroad/{onroad_settings_panel.h => sp_priv_onroad_settings_panel.h} (100%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 2a9433e67c..171d0fc3e9 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -34,7 +34,7 @@ qt_src = [ "sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc", "sunnypilot/qt/offroad/settings/sp_priv_settings.cc", "sunnypilot/qt/onroad/sp_priv_onroad_settings.cc", - "sunnypilot/qt/onroad/onroad_settings_panel.cc", + "sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc", "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", "sunnypilot/qt/onroad/sp_priv_annotated_camera.cc", "sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc", diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc index 8f2d6ae57b..a6d2c2f71b 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc @@ -8,7 +8,7 @@ #include "selfdrive/ui/qt/maps/map_panel.h" #endif #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h" #endif #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc similarity index 82% rename from selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc rename to selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc index 166acaaa2f..8c7336c28f 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc @@ -1,11 +1,11 @@ -#include "../../../sunnypilot/qt/onroad/onroad_settings_panel.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h" #include #include #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h" -#include "../../../qt/util.h" -#include "../../../ui.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/ui.h" OnroadSettingsPanel::OnroadSettingsPanel(QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h rename to selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h From ad01e930c6287c1206872730c8d0fbbe21c4dbc5 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 00:56:17 -0400 Subject: [PATCH 436/575] widgets: rename --- selfdrive/ui/sunnypilot/SConscript | 2 +- .../ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h | 2 +- .../qt/widgets/{drive_stats.cc => sp_priv_drive_stats.cc} | 2 +- .../qt/widgets/{drive_stats.h => sp_priv_drive_stats.h} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename selfdrive/ui/sunnypilot/qt/widgets/{drive_stats.cc => sp_priv_drive_stats.cc} (98%) rename selfdrive/ui/sunnypilot/qt/widgets/{drive_stats.h => sp_priv_drive_stats.h} (100%) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 171d0fc3e9..5e1e381484 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -10,7 +10,7 @@ widgets_src = [ "sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc", "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc", - "sunnypilot/qt/widgets/drive_stats.cc", + "sunnypilot/qt/widgets/sp_priv_drive_stats.cc", "sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc", "sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc", "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h index c4955400c9..91044f2b4e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h @@ -5,7 +5,7 @@ #else #include "selfdrive/ui/qt/widgets/controls.h" #endif -#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.h" class TripsPanel : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc rename to selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.cc index 2ac3f59893..58ced84ad0 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h rename to selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.h From fa71f0667c26415d780beb47dbd8144807225bfd Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 00:59:32 -0400 Subject: [PATCH 437/575] cleanup --- selfdrive/ui/sunnypilot/SConscript | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 5e1e381484..54f7d69950 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -25,7 +25,8 @@ network_src = [ "sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc", "sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc", "sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc", - "sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc"] + "sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc" +] qt_src = [ "sunnypilot/qt/sp_priv_home.cc", @@ -37,7 +38,7 @@ qt_src = [ "sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc", "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", "sunnypilot/qt/onroad/sp_priv_annotated_camera.cc", - "sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc", + "sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc" ] sp_widgets_src = widgets_src + network_src From 90e829b8a23e84d8ea47a248295bf54181cc3c19 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 11:22:05 -0400 Subject: [PATCH 438/575] MADS: HKG CAN-FD: Disallow cruise buttons to engage when pcmCruiseSpeed is off --- selfdrive/car/hyundai/carstate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index d8329c5ba7..5261fc8477 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -282,6 +282,8 @@ class CarState(CarStateBase): ret.cruiseState.standstill = False else: cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp + ret.cruiseState.available = cp_cruise_info.vl["SCC_CONTROL"]["MainMode_ACC"] == 1 if not self.CP.pcmCruiseSpeed else \ + ret.cruiseState.available ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor From 093980b767cdd7825eacb357d61c9d661b7a6972 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 18:52:00 -0400 Subject: [PATCH 439/575] SP Controls: Icons not needed for ButtonParamControlSP --- .../ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc | 4 ++-- .../qt/offroad/settings/sp_priv_sunnypilot_settings.cc | 2 +- .../qt/offroad/settings/sp_priv_visuals_settings.cc | 6 +++--- .../qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc | 2 +- .../sunnypilot/sp_priv_speed_limit_control_settings.cc | 4 ++-- .../sunnypilot/sp_priv_speed_limit_policy_settings.cc | 2 +- .../sunnypilot/sp_priv_speed_limit_warning_settings.cc | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc index a8427f0647..d9c1012cc9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc @@ -109,7 +109,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { tr("Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. " "In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " "your steering wheel distance button."), - "../assets/offroad/icon_blank.png", + "", longi_button_texts, 380); long_personality_setting->showDescription(); @@ -120,7 +120,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. " "In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these " "acceleration personality within Onroad Settings on the driving screen."), - "../assets/offroad/icon_blank.png", + "", accel_personality_texts); accel_personality_setting->showDescription(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc index 0936e9c13e..29bc5c8793 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc @@ -250,7 +250,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { std::vector dlp_settings_texts{tr("Laneful"), tr("Laneless"), tr("Auto")}; dlp_settings = new ButtonParamControlSP( "DynamicLaneProfile", tr("Dynamic Lane Profile"), "", - "../assets/offroad/icon_blank.png", + "", dlp_settings_texts, 340); dlp_settings->showDescription(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc index 9ce2ad9993..496f47697d 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc @@ -78,7 +78,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { std::vector dev_ui_settings_texts{tr("Off"), tr("5 Metrics"), tr("10 Metrics")}; dev_ui_settings = new ButtonParamControlSP( "DevUIInfo", tr("Developer UI"), tr("Display real-time parameters and metrics from various sources."), - "../assets/offroad/icon_blank.png", + "", dev_ui_settings_texts, 380); dev_ui_settings->showDescription(); @@ -87,7 +87,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Distance\nSpeed")}; chevron_info_settings = new ButtonParamControlSP( "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); chevron_info_settings->showDescription(); @@ -110,7 +110,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { std::vector sidebar_temp_texts{tr("Off"), tr("RAM"), tr("CPU"), tr("GPU"), tr("Max")}; sidebar_temp_setting = new ButtonParamControlSP( "SidebarTemperatureOptions", tr("Display Temperature on Sidebar"), "", - "../assets/offroad/icon_blank.png", + "", sidebar_temp_texts, 255); sidebar_temp_setting->showDescription(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc index ee600795c0..4640bbd21d 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc @@ -40,7 +40,7 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { tr("Steering Mode After Braking"), tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\n" "Remain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."), - "../assets/offroad/icon_blank.png", + "", dlob_settings_texts, 500); dlob_settings->showDescription(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc index c3507dcf50..0c0a90aeb2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc @@ -18,7 +18,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { speed_limit_engage_settings = new ButtonParamControlSP( "SpeedLimitEngageType", tr("Engage Mode"), "", - "../assets/offroad/icon_blank.png", + "", speed_limit_engage_texts, 440); speed_limit_engage_settings->showDescription(); @@ -27,7 +27,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { std::vector speed_limit_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; speed_limit_offset_settings = new ButtonParamControlSP( "SpeedLimitOffsetType", tr("Limit Offset"), tr("Set speed limit slightly higher than actual speed limit for a more natural drive."), - "../assets/offroad/icon_blank.png", + "", speed_limit_offset_settings_texts, 380); list->addItem(speed_limit_offset_settings); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc index 6041dbb639..e3dc87a0c7 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc @@ -16,7 +16,7 @@ SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(pa "SpeedLimitControlPolicy", tr("Speed Limit Source Policy"), "", - "../assets/offroad/icon_blank.png", + "", speed_limit_policy_texts, 250); speed_limit_policy->showDescription(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc index 1da6aa89b5..c35876a18e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc @@ -16,7 +16,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( speed_limit_warning_settings = new ButtonParamControlSP( "SpeedLimitWarningType", tr("Speed Limit Warning"), "", - "../assets/offroad/icon_blank.png", + "", speed_limit_warning_texts, 380); speed_limit_warning_settings->showDescription(); @@ -33,7 +33,7 @@ SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget( speed_limit_warning_offset_settings = new ButtonParamControlSP( "SpeedLimitWarningOffsetType", tr("Warning Offset"), tr("Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit."), - "../assets/offroad/icon_blank.png", + "", speed_limit_warning_offset_settings_texts, 380); list->addItem(speed_limit_warning_offset_settings); From 124a230296586b1d8462a65e4f80f19dd0d6dfd2 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 20:23:24 -0400 Subject: [PATCH 440/575] AbstractControlSP: Inherit from stock AbstractControl --- selfdrive/ui/qt/widgets/controls.cc | 2 + selfdrive/ui/qt/widgets/controls.h | 8 +- .../settings/sp_priv_sunnypilot_settings.cc | 2 +- .../sunnypilot/qt/widgets/sp_priv_controls.cc | 32 +++++-- .../sunnypilot/qt/widgets/sp_priv_controls.h | 96 +++++-------------- 5 files changed, 55 insertions(+), 85 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 3fbf506bb4..ba7ab5df40 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -2,8 +2,10 @@ #include #include +#include AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { + RETURN_IF_SUNNYPILOT QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setMargin(0); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 98ee30389a..8eb83d1705 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -40,7 +40,7 @@ class AbstractControl : public QFrame { Q_OBJECT public: - void setDescription(const QString &desc) { + virtual void setDescription(const QString &desc) { if (description) description->setText(desc); } @@ -48,11 +48,11 @@ public: title_label->setText(title); } - void setValue(const QString &val) { + virtual void setValue(const QString &val) { value->setText(val); } - const QString getDescription() { + virtual const QString getDescription() { return description->text(); } @@ -60,7 +60,7 @@ public: QPixmap icon_pixmap; public slots: - void showDescription() { + virtual void showDescription() { description->setVisible(true); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc index 29bc5c8793..4a49488aba 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc @@ -575,7 +575,7 @@ void SunnypilotPanel::updateToggles() { } // toggle names to update when CustomTorqueLateral is flipped - std::vector customTorqueGroup{friction, lat_accel_factor}; + std::vector customTorqueGroup{friction, lat_accel_factor}; for (const auto& customTorqueControl : customTorqueGroup) { customTorqueControl->setVisible(!(nnff_toggle->isToggled() || !custom_torque_lateral->isToggled())); customTorqueControl->setEnabled(!(nnff_toggle->isToggled() || !custom_torque_lateral->isToggled())); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc index 7f9985a70c..dc0974eee5 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc @@ -2,6 +2,7 @@ #include #include +#include QFrame *horizontal_line(QWidget *parent) { QFrame *line = new QFrame(parent); @@ -15,8 +16,10 @@ QFrame *horizontal_line(QWidget *parent) { return line; } -AbstractControlSP_TITLED::AbstractControlSP_TITLED(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); +AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent) + : AbstractControl(title, desc, icon, parent) { + + main_layout = new QVBoxLayout(this); main_layout->setMargin(0); hlayout = new QHBoxLayout; @@ -68,14 +71,28 @@ AbstractControlSP_TITLED::AbstractControlSP_TITLED(const QString &title, const Q main_layout->addStretch(); } -void AbstractControlSP_TITLED::hideEvent(QHideEvent *e) { +void AbstractControlSP::hideEvent(QHideEvent *e) { if (description != nullptr) { description->hide(); } } -AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); +AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent) + : AbstractControlSP(title, desc, icon, parent) { + + if (value != nullptr) { + ReplaceWidget(value, new QWidget()); + value = nullptr; + } + + QLayoutItem* item; + while ((item = main_layout->takeAt(0)) != nullptr) { + if (item->widget()) { + delete item->widget(); + } + delete item; + } + main_layout->setMargin(0); hlayout = new QHBoxLayout; @@ -116,12 +133,9 @@ AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, main_layout->addStretch(); } -void AbstractControlSP::hideEvent(QHideEvent *e) { -} - // controls -ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControlSP_TITLED(title, desc, "", parent) { +ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControlSP(title, desc, "", parent) { btn.setText(text); btn.setStyleSheet(R"( QPushButton { diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 44255b2d99..c11d489bb4 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -12,8 +13,8 @@ #include #include "common/params.h" +#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/input.h" -//#include "selfdrive/ui/qt/widgets/toggle.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" // This is for compatibility purposes, until we properly do inheritance splitting @@ -52,18 +53,14 @@ protected: QString lastText_, elidedText_; }; -class AbstractControlSP_TITLED : public QFrame { +class AbstractControlSP : public AbstractControl { Q_OBJECT public: - void setDescription(const QString &desc) { + void setDescription(const QString &desc) override { if (description) description->setText(desc); } - void setTitle(const QString &title) { - title_label->setText(title); - } - void setValue(const QString &val, std::optional color = std::nullopt) { value->setText(val); if (color.has_value()) { @@ -71,84 +68,41 @@ public: } } - const QString getDescription() { + const QString getDescription() override { return description->text(); } - QLabel *icon_label; - QPixmap icon_pixmap; - - public slots: - void showDescription() { - description->setVisible(true); - } - void hideDescription() { description->setVisible(false); } - signals: - void showDescriptionEvent(); - -protected: - AbstractControlSP_TITLED(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); - void hideEvent(QHideEvent *e) override; - - QHBoxLayout *hlayout; - QPushButton *title_label; - -private: - ElidedLabel *value; - QLabel *description = nullptr; -}; - - -class AbstractControlSP : public QFrame { - Q_OBJECT - -public: - void setDescription(const QString &desc) { - if (description) description->setText(desc); - } - - void setTitle(const QString &title) { - title_label->setText(title); - } - - const QString getDescription() { - return description->text(); - } - - public slots: - void showDescription() { +public slots: + void showDescription() override { description->setVisible(true); } - void hideDescription() { - description->setVisible(false); - } - - signals: - void showDescriptionEvent(); - protected: AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); void hideEvent(QHideEvent *e) override; - QHBoxLayout *hlayout; - QPushButton *title_label; - -private: + QVBoxLayout *main_layout; + ElidedLabel *value; QLabel *description = nullptr; }; +class AbstractControlSP_SELECTOR : public AbstractControlSP { + Q_OBJECT + +protected: + AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); +}; // widget to display a value -class LabelControlSP : public AbstractControlSP_TITLED { +class LabelControlSP : public AbstractControlSP { Q_OBJECT public: - LabelControlSP(const QString &title, const QString &text = "", const QString &desc = "", QWidget *parent = nullptr) : AbstractControlSP_TITLED(title, desc, "", parent) { + LabelControlSP(const QString &title, const QString &text = "", const QString &desc = "", QWidget *parent = nullptr) : AbstractControlSP(title, desc, "", parent) { label.setText(text); label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); hlayout->addWidget(&label); @@ -160,7 +114,7 @@ private: }; // widget for a button with a label -class ButtonControlSP : public AbstractControlSP_TITLED { +class ButtonControlSP : public AbstractControlSP { Q_OBJECT public: @@ -179,11 +133,11 @@ private: QPushButton btn; }; -class ToggleControlSP : public AbstractControlSP_TITLED { +class ToggleControlSP : public AbstractControlSP { Q_OBJECT public: - ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControlSP_TITLED(title, desc, icon, parent) { + ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControlSP(title, desc, icon, parent) { toggle.setFixedSize(150, 100); if (state) { toggle.togglePosition(); @@ -250,11 +204,11 @@ private: bool store_confirm = false; }; -class ButtonParamControlSP : public AbstractControlSP { +class ButtonParamControlSP : public AbstractControlSP_SELECTOR { Q_OBJECT public: ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const std::vector &button_texts, const int minimum_button_width = 300) : AbstractControlSP(title, desc, icon), button_texts(button_texts) { + const std::vector &button_texts, const int minimum_button_width = 300) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts) { const QString style = R"( QPushButton { border-radius: 20px; @@ -399,7 +353,7 @@ class ListWidgetSP : public QWidget { inline void AddWidgetAt(const int index, QWidget *new_widget) { inner_layout.insertWidget(index, new_widget); } inline void RemoveWidgetAt(const int index) { if (QLayoutItem* item; (item = inner_layout.takeAt(index)) != nullptr) { - if(item->widget()) delete item->widget(); + if (item->widget()) delete item->widget(); delete item; } } @@ -442,7 +396,7 @@ public: } }; -class OptionControlSP : public AbstractControlSP { +class OptionControlSP : public AbstractControlSP_SELECTOR { Q_OBJECT private: @@ -453,7 +407,7 @@ private: public: OptionControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const MinMaxValue &range, const int per_value_change = 1) : _title(title), AbstractControlSP(title, desc, icon) { + const MinMaxValue &range, const int per_value_change = 1) : _title(title), AbstractControlSP_SELECTOR(title, desc, icon) { const QString style = R"( QPushButton { border-radius: 20px; From e07dd2fd9c87490755fbfd2ee70b71c0a968980b Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 20:23:36 -0400 Subject: [PATCH 441/575] ReplaceWidget: more safety --- selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h b/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h index a3fb37e59b..5327b0ad86 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h +++ b/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h @@ -11,8 +11,10 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" -inline void ReplaceWidget(QWidget* old_widget, QWidget* new_widget) -{ +inline void ReplaceWidget(QWidget* old_widget, QWidget* new_widget) { + if (old_widget && old_widget->parentWidget() && old_widget->parentWidget()->layout()) { old_widget->parentWidget()->layout()->replaceWidget(old_widget, new_widget); - delete old_widget; + old_widget->hide(); + old_widget->deleteLater(); + } } From b12e82adc7bc33135fad911a606b1d791943a4fd Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 20:39:00 -0400 Subject: [PATCH 442/575] controls: Keep stock as stock as possible --- selfdrive/ui/qt/widgets/controls.cc | 2 +- selfdrive/ui/qt/widgets/controls.h | 10 +++++----- selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index ba7ab5df40..8af48185aa 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -140,4 +140,4 @@ void ParamControl::toggleClicked(bool state) { } else { toggle.togglePosition(); } -} \ No newline at end of file +} diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 8eb83d1705..aa304e0df6 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -40,7 +40,7 @@ class AbstractControl : public QFrame { Q_OBJECT public: - virtual void setDescription(const QString &desc) { + void setDescription(const QString &desc) { if (description) description->setText(desc); } @@ -48,11 +48,11 @@ public: title_label->setText(title); } - virtual void setValue(const QString &val) { + void setValue(const QString &val) { value->setText(val); } - virtual const QString getDescription() { + const QString getDescription() { return description->text(); } @@ -60,7 +60,7 @@ public: QPixmap icon_pixmap; public slots: - virtual void showDescription() { + void showDescription() { description->setVisible(true); } @@ -293,4 +293,4 @@ public: LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { setLayout(l); } -}; \ No newline at end of file +}; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index c11d489bb4..ca5101b079 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -57,7 +57,7 @@ class AbstractControlSP : public AbstractControl { Q_OBJECT public: - void setDescription(const QString &desc) override { + void setDescription(const QString &desc) { if (description) description->setText(desc); } @@ -68,7 +68,7 @@ public: } } - const QString getDescription() override { + const QString getDescription() { return description->text(); } @@ -77,7 +77,7 @@ public: } public slots: - void showDescription() override { + void showDescription() { description->setVisible(true); } From fd64c79d354f8edb713804e1075994c702c4ada3 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 22:48:54 -0400 Subject: [PATCH 443/575] ui.cc|h: init --- selfdrive/ui/sunnypilot/SConscript | 1 + selfdrive/ui/sunnypilot/sp_priv_ui.cc | 328 ++++++++++++++++++++++++++ selfdrive/ui/sunnypilot/sp_priv_ui.h | 226 ++++++++++++++++++ selfdrive/ui/ui.cc | 182 +------------- selfdrive/ui/ui.h | 166 +------------ 5 files changed, 571 insertions(+), 332 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/sp_priv_ui.cc create mode 100644 selfdrive/ui/sunnypilot/sp_priv_ui.h diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 54f7d69950..9e28fc1e21 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -1,4 +1,5 @@ widgets_src = [ + "sunnypilot/sp_priv_ui.cc", "sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc", diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.cc b/selfdrive/ui/sunnypilot/sp_priv_ui.cc new file mode 100644 index 0000000000..5470a1ceee --- /dev/null +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.cc @@ -0,0 +1,328 @@ +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" + +#include +#include + +#include + +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" + +// Projects a point in car to space to the corresponding point in full frame +// image space. +static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { + const float margin = 1000.0f; + const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; + + const vec3 pt = (vec3){{in_x, in_y, in_z}}; + const vec3 Ep = matvecmul3(s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib, pt); + const vec3 KEp = matvecmul3(s->scene.wide_cam ? ECAM_INTRINSIC_MATRIX : FCAM_INTRINSIC_MATRIX, Ep); + + // Project. + QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]}); + if (clip_region.contains(point)) { + *out = point; + return true; + } + return false; +} + +void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, + float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); + QPointF left, right; + pvd->clear(); + for (int i = 0; i <= max_idx; i++) { + // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera + if (line_x[i] < 0) continue; + + bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); + bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); + if (l && r) { + // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts + if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { + continue; + } + pvd->push_back(left); + pvd->push_front(right); + } + } +} + +void update_model(UIStateSP *s, + const cereal::ModelDataV2::Reader &model) { + UISceneSP &scene = s->scene_sp; + auto model_position = model.getPosition(); + float max_distance = std::clamp(*(model_position.getX().end() - 1), + MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); + + // update lane lines + const auto lane_lines = model.getLaneLines(); + const auto lane_line_probs = model.getLaneLineProbs(); + int max_idx = get_path_length_idx(lane_lines[0], max_distance); + for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { + scene.lane_line_probs[i] = lane_line_probs[i]; + update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, 0, &scene.lane_line_vertices[i], max_idx); + } + + // lane barriers for blind spot + int max_distance_barrier = 40; + int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); + update_line_data(s, lane_lines[1], 0, -0.05, -0.6, &scene.lane_barrier_vertices[0], max_idx_barrier, false); + update_line_data(s, lane_lines[2], 0, -0.05, -0.6, &scene.lane_barrier_vertices[1], max_idx_barrier, false); + + // update road edges + const auto road_edges = model.getRoadEdges(); + const auto road_edge_stds = model.getRoadEdgeStds(); + for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { + scene.road_edge_stds[i] = road_edge_stds[i]; + update_line_data(s, road_edges[i], 0.025, 0, 0, &scene.road_edge_vertices[i], max_idx); + } + + // update path + auto lead_one = (*s->sm)["radarState"].getRadarState().getLeadOne(); + if (lead_one.getStatus()) { + const float lead_d = lead_one.getDRel() * 2.; + max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); + } + max_idx = get_path_length_idx(model_position, max_distance); + update_line_data(s, model_position, 0.9 - scene.mads_path_range * scene.mads_path_scale, 1.22, 1.22, &scene.track_vertices, max_idx, false); + update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); +} + +static void update_state(UIStateSP *s) { + SubMaster &sm = *(s->sm); + UISceneSP &scene = s->scene_sp; + auto params = Params(); + + scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); + + // TODO: SP - Set this dynamically on init with manual toggle or driving model selection + if (sm.updated("lateralPlanSPDEPRECATED")) { + scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); + } + if (sm.updated("controlsState")) { + scene.controlsState = sm["controlsState"].getControlsState(); + } + if (sm.updated("longitudinalPlanSP")) { + for (int i = 0; i < std::size(scene.e2eX); i++) { + scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; + } + } + if (sm.updated("modelV2SP")) { + auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); + scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); + scene.driving_model_generation = model_v2_sp.getModelGeneration(); + scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); + } +} + +void ui_update_params(UIStateSP *s) { + UISceneSP &scene = s->scene_sp; + auto params = Params(); + scene.map_on_left = params.getBool("NavSettingLeftSide"); + + scene.visual_brake_lights = params.getBool("BrakeLights"); + scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); + scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); + scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); + scene.stand_still_timer = params.getBool("StandStillTimer"); + scene.show_debug_ui = params.getBool("ShowDebugUI"); + scene.hide_vego_ui = params.getBool("HideVEgoUi"); + scene.true_vego_ui = params.getBool("TrueVEgoUi"); + scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); + scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); + scene.button_auto_hide = params.getBool("ButtonAutoHide"); + scene.reverse_dm_cam = params.getBool("ReverseDmCam"); + scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); + scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); + scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); + scene.map_3d_buildings = params.getBool("Map3DBuildings"); + scene.live_torque_toggle = params.getBool("LiveTorque"); + scene.torqued_override = params.getBool("TorquedOverride"); + scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); + scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); + scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); + scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); + scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); + scene.speed_limit_control_enabled = params.getBool("EnableSlc"); + scene.feature_status_toggle = params.getBool("FeatureStatus"); + scene.onroad_settings_toggle = params.getBool("OnroadSettings"); + + // Handle Onroad Screen Off params + if (scene.onroadScreenOff > 0) { + scene.osoTimer = scene.onroadScreenOff * 60 * UI_FREQ; + } else if (scene.onroadScreenOff == 0) { + scene.osoTimer = 30 * UI_FREQ; + } else if (scene.onroadScreenOff == -1) { + scene.osoTimer = 15 * UI_FREQ; + } else { + scene.osoTimer = -1; + } +} + +void UIStateSP::updateStatus() { + auto params = Params(); + if (scene_sp.started && sm->updated("controlsState")) { + auto controls_state = (*sm)["controlsState"].getControlsState(); + auto car_control = (*sm)["carControl"].getCarControl(); + auto car_state = (*sm)["carState"].getCarState(); + auto mads_enabled = car_state.getMadsEnabled(); + auto state = controls_state.getState(); + if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { + status_sp = STATUS_OVERRIDE_SP; + } else { + status_sp = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED_SP : STATUS_MADS_SP : STATUS_DISENGAGED_SP; + } + + if (mads_enabled != last_mads_enabled) { + mads_path_state = true; + } + last_mads_enabled = mads_enabled; + if (mads_path_state) { + if (mads_enabled) { + mads_path_count = fmax(mads_path_count - 1, 0); + if (mads_path_count == 0) { + mads_path_state = false; + } + } else { + mads_path_count = fmin(mads_path_count + 1, mads_path_timestep); + if (mads_path_count == mads_path_timestep) { + mads_path_state = false; + } + } + } + scene_sp.mads_path_scale = mads_path_count * (1 / mads_path_timestep); + } + + if (scene_sp.started) { + // Auto hide UI button state machine + { + if (scene_sp.button_auto_hide) { + if (scene_sp.touch_to_wake) { + scene_sp.sleep_btn = 30 * UI_FREQ; + } else if (scene_sp.sleep_btn > 0) { + scene_sp.sleep_btn--; + } else if (scene_sp.sleep_btn == -1) { + scene_sp.sleep_btn = 30 * UI_FREQ; + } + // Check if the sleep button should be fading in + if (scene_sp.sleep_btn_fading_in) { + // Increase the opacity of the sleep button by a small amount + if (scene_sp.sleep_btn_opacity < 20) { + scene_sp.sleep_btn_opacity+= 10; + } + if (scene_sp.sleep_btn_opacity >= 20) { + // If the opacity has reached its maximum value, stop fading in + scene_sp.sleep_btn_fading_in = false; + scene_sp.sleep_btn_opacity = 20; + } + } else if (scene_sp.sleep_btn == 0) { + // Fade out the sleep button as before + if (scene_sp.sleep_btn_opacity > 0) { + scene_sp.sleep_btn_opacity-= 2; + } + } else { + // Set the opacity of the sleep button to its maximum value + scene_sp.sleep_btn_opacity = 20; + } + } else { + scene_sp.sleep_btn_opacity = 20; + } + } + + // Onroad Screen Off Brightness + Timer + Global Brightness + { + if (scene_sp.onroadScreenOff != -2 && scene_sp.touched2) { + scene_sp.sleep_time = scene_sp.osoTimer; + } else if (scene_sp.onroadScreenOff != -2 && + ((scene_sp.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && + ((scene_sp.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene_sp.onroadScreenOffEvent) || + (scene_sp.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { + scene_sp.sleep_time = scene_sp.osoTimer; + } else if (scene_sp.sleep_time > 0 && scene_sp.onroadScreenOff != -2) { + scene_sp.sleep_time--; + } else if (scene_sp.sleep_time == -1 && scene_sp.onroadScreenOff != -2) { + scene_sp.sleep_time = scene_sp.osoTimer; + } + } + } + + if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz + scene_sp.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); + } + + scene_sp.brightness = std::atoi(params.get("BrightnessControl").c_str()); +} + +UIStateSP::UIStateSP(QObject *parent) : UIState(parent) { +} + +void UIStateSP::update() { + update_state(this); + updateStatus(); + UIState::update(); + emit uiUpdate(*this); +} + +void UIStateSP::setSunnylinkRoles(const std::vector& roles) { + sunnylinkRoles = roles; + emit sunnylinkRolesChanged(roles); +} + +void UIStateSP::setSunnylinkDeviceUsers(const std::vector& users) { + sunnylinkUsers = users; + emit sunnylinkDeviceUsersChanged(users); +} + +DeviceSP::DeviceSP(QObject *parent) : Device(parent) { + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &DeviceSP::update); +} + +void DeviceSP::update(const UIStateSP &s) { + updateBrightness(s); + updateWakefulness(s); +} + +void DeviceSP::updateBrightness(const UIStateSP &s) { + UISceneSP scene = s.scene_sp; + float clipped_brightness = offroad_brightness; + if (scene.started && scene.light_sensor > 0) { + clipped_brightness = scene.light_sensor; + + // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm + if (clipped_brightness <= 8) { + clipped_brightness = (clipped_brightness / 903.3); + } else { + clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0); + } + + // Scale back to 10% to 100% + clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); + } + + int brightness = brightness_filter.update(clipped_brightness); + if (!awake) { + brightness = 0; + } else if (scene.started && scene.sleep_time == 0 && scene.onroadScreenOff != -2) { + brightness = scene.onroadScreenOffBrightness * 0.01 * brightness; + } else if (scene.brightness) { + brightness = scene.brightness * 0.99; + } + + if (brightness != last_brightness) { + if (!brightness_future.isRunning()) { + brightness_future = QtConcurrent::run(Hardware::set_brightness, brightness); + last_brightness = brightness; + } + } +} + +UIStateSP *uiStateSP() { + static UIStateSP ui_state_sp; + return &ui_state_sp; +} + +DeviceSP *deviceSP() { + static DeviceSP _device_sp; + return &_device_sp; +} diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.h b/selfdrive/ui/sunnypilot/sp_priv_ui.h new file mode 100644 index 0000000000..6f6afdde6e --- /dev/null +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.h @@ -0,0 +1,226 @@ +#pragma once + +#include "selfdrive/ui/ui.h" + +#include +#include + +#include + +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h" + +const int UI_ROAD_NAME_MARGIN_X = 14; + +struct FeatureStatusText { + const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto"}; + const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed"}; + const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active"}; +}; + +struct FeatureStatusColor { + const QStringList dlp_list_color = { "#2020f8", "#0df87a", "#0df8f8" }; + const QStringList gac_list_color = { "#ff4b4b", "#fcff4b", "#4bff66", "#6a0ac9" }; + const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; +}; + +const float DRIVING_PATH_WIDE = 0.9; +const float DRIVING_PATH_NARROW = 0.25; + +typedef enum UIStatusSP { + STATUS_DISENGAGED_SP = STATUS_DISENGAGED, + STATUS_OVERRIDE_SP = STATUS_OVERRIDE, + STATUS_ENGAGED_SP = STATUS_ENGAGED, + STATUS_MADS_SP, +} UIStatusSP; + +const QColor bg_colors_sp [] = { + [STATUS_DISENGAGED_SP] = QColor(0x17, 0x33, 0x49, 0xc8), + [STATUS_OVERRIDE_SP] = QColor(0x91, 0x9b, 0x95, 0xf1), + [STATUS_ENGAGED_SP] = QColor(0x00, 0xc8, 0x00, 0xf1), + [STATUS_MADS_SP] = QColor(0x00, 0xc8, 0xc8, 0xf1), +}; + +const QColor tcs_colors [] = { + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), + [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), +}; + +typedef struct UISceneSP : UIScene { + cereal::ControlsState::Reader controlsState; + + // Debug UI + bool show_debug_ui; + + // Speed limit control + bool speed_limit_control_enabled; + int speed_limit_control_policy; + double last_speed_limit_sign_tap; + + // modelV2 + QPolygonF track_edge_vertices; + QPolygonF lane_barrier_vertices[2]; + + bool navigate_on_openpilot_deprecated = false; + cereal::AccelerationPersonality accel_personality; + + bool map_on_left; + + int dynamic_lane_profile; + bool dynamic_lane_profile_status = true; + + bool visual_brake_lights; + + int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; + bool onroadScreenOffEvent; + int sleep_time = -1; + bool touched2 = false; + + bool stand_still_timer; + + bool hide_vego_ui, true_vego_ui; + + int chevron_data; + + bool gac; + int longitudinal_personality; + int longitudinal_accel_personality; + + bool map_visible; + int dev_ui_info; + bool live_torque_toggle; + + bool touch_to_wake = false; + int sleep_btn = -1; + bool sleep_btn_fading_in = false; + int sleep_btn_opacity = 20; + bool button_auto_hide; + + bool reverse_dm_cam; + + bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; + float e2eX[13] = {0}; + + int sidebar_temp_options; + + float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; + float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 + + bool onroad_settings_visible; + + bool map_3d_buildings; + + bool torqued_override; + + bool dynamic_experimental_control; + + int speed_limit_control_engage_type; + + bool mapbox_fullscreen; + + bool speed_limit_warning_flash; + int speed_limit_warning_type; + int speed_limit_warning_value_offset; + + bool custom_driving_model_valid; + cereal::ModelGeneration driving_model_generation; + uint32_t driving_model_capabilities; + + bool feature_status_toggle; + bool onroad_settings_toggle; + + bool dynamic_personality; +} UISceneSP; + +class UIStateSP : public UIState { + Q_OBJECT + +public: + UIStateSP(QObject* parent = nullptr); + void updateStatus(); + void setSunnylinkRoles(const std::vector &roles); + void setSunnylinkDeviceUsers(const std::vector &users); + + inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } + inline bool isSunnylinkAdmin() const { + return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { + return role.roleType == RoleType::Admin; + }); + } + inline bool isSunnylinkSponsor() const { + return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { + return role.roleType == RoleType::Sponsor && role.as().roleTier != SponsorTier::Free; + }); + } + inline SponsorRoleModel sunnylinkSponsorRole() const { + std::optional sponsorRoleWithHighestTier = std::nullopt; + for (const auto &role : sunnylinkRoles) { + if (role.roleType != RoleType::Sponsor) + continue; + + if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { + sponsorRoleWithHighestTier = sponsorRole; + } + } + return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free)); + } + inline SponsorTier sunnylinkSponsorTier() const { + return sunnylinkSponsorRole().roleTier; + } + inline std::vector sunnylinkDeviceUsers() const { return sunnylinkUsers; } + inline bool isSunnylinkPaired() const { + return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) { + return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; + }); + } + + UIStatusSP status_sp; + UISceneSP scene_sp = {}; + +signals: + void uiUpdate(const UIStateSP &s); + void sunnylinkRoleChanged(bool subscriber); + void sunnylinkRolesChanged(std::vector roles); + void sunnylinkDeviceUsersChanged(std::vector users); + +private slots: + void update(); + +private: + std::vector sunnylinkRoles = {}; + std::vector sunnylinkUsers = {}; + + bool last_mads_enabled = false; + bool mads_path_state = false; + float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] + float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] + +public slots: + void update(const UIStateSP &s); +}; + +UIStateSP *uiStateSP(); + +class DeviceSP : public Device { + Q_OBJECT + +public: + DeviceSP(QObject *parent = nullptr); + void updateBrightness(const UIStateSP &s); + +protected: + void updateBrightness(const UIState &s); + +public slots: + void update(const UIStateSP &s); +}; + +DeviceSP *deviceSP(); + +void update_model(UIState *s, + const cereal::ModelDataV2::Reader &model); +void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, + float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 3a3741a27b..7559087e86 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -11,7 +11,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" +#include "qt/util.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 @@ -20,7 +20,7 @@ // Projects a point in car to space to the corresponding point in full frame // image space. static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { - const float margin = 1000.0f; + const float margin = 500.0f; const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; const vec3 pt = (vec3){{in_x, in_y, in_z}}; @@ -56,7 +56,7 @@ void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, con } void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); QPointF left, right; pvd->clear(); @@ -64,8 +64,8 @@ void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera if (line_x[i] < 0) continue; - bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); - bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); + bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); + bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right); if (l && r) { // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { @@ -90,21 +90,15 @@ void update_model(UIState *s, int max_idx = get_path_length_idx(lane_lines[0], max_distance); for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { scene.lane_line_probs[i] = lane_line_probs[i]; - update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, 0, &scene.lane_line_vertices[i], max_idx); + update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, &scene.lane_line_vertices[i], max_idx); } - // lane barriers for blind spot - int max_distance_barrier = 40; - int max_idx_barrier = std::min(max_idx, get_path_length_idx(lane_lines[0], max_distance_barrier)); - update_line_data(s, lane_lines[1], 0, -0.05, -0.6, &scene.lane_barrier_vertices[0], max_idx_barrier, false); - update_line_data(s, lane_lines[2], 0, -0.05, -0.6, &scene.lane_barrier_vertices[1], max_idx_barrier, false); - // update road edges const auto road_edges = model.getRoadEdges(); const auto road_edge_stds = model.getRoadEdgeStds(); for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { scene.road_edge_stds[i] = road_edge_stds[i]; - update_line_data(s, road_edges[i], 0.025, 0, 0, &scene.road_edge_vertices[i], max_idx); + update_line_data(s, road_edges[i], 0.025, 0, &scene.road_edge_vertices[i], max_idx); } // update path @@ -114,8 +108,7 @@ void update_model(UIState *s, max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); } max_idx = get_path_length_idx(model_position, max_distance); - update_line_data(s, model_position, 0.9 - scene.mads_path_range * scene.mads_path_scale, 1.22, 1.22, &scene.track_vertices, max_idx, false); - update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); + update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); } void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { @@ -160,7 +153,6 @@ 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(); @@ -212,108 +204,28 @@ static void update_state(UIState *s) { } else if (!sm.allAliveAndValid({"wideRoadCameraState"})) { scene.light_sensor = -1; } - scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); + scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; scene.world_objects_visible = scene.world_objects_visible || (scene.started && sm.rcv_frame("liveCalibration") > scene.started_frame && sm.rcv_frame("modelV2") > scene.started_frame); - // TODO: SP - Set this dynamically on init with manual toggle or driving model selection - if (sm.updated("lateralPlanSPDEPRECATED")) { - scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); - } - if (sm.updated("controlsState")) { - scene.controlsState = sm["controlsState"].getControlsState(); - } - if (sm.updated("longitudinalPlanSP")) { - for (int i = 0; i < std::size(scene.e2eX); i++) { - scene.e2eX[i] = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getE2eX()[i]; - } - } - if (sm.updated("modelV2SP")) { - auto model_v2_sp = sm["modelV2SP"].getModelV2SP(); - scene.custom_driving_model_valid = model_v2_sp.getCustomModel(); - scene.driving_model_generation = model_v2_sp.getModelGeneration(); - scene.driving_model_capabilities = model_v2_sp.getModelCapabilities(); - } } void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); - s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - - s->scene.visual_brake_lights = params.getBool("BrakeLights"); - s->scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); - s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); - s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); - s->scene.stand_still_timer = params.getBool("StandStillTimer"); - s->scene.show_debug_ui = params.getBool("ShowDebugUI"); - s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); - s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); - s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); - s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); - s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); - s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); - s->scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); - s->scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); - s->scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); - s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); - s->scene.live_torque_toggle = params.getBool("LiveTorque"); - s->scene.torqued_override = params.getBool("TorquedOverride"); - s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); - s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); - s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); - s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); - s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); - s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); - s->scene.feature_status_toggle = params.getBool("FeatureStatus"); - s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); - - // Handle Onroad Screen Off params - if (s->scene.onroadScreenOff > 0) { - s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ; - } else if (s->scene.onroadScreenOff == 0) { - s->scene.osoTimer = 30 * UI_FREQ; - } else if (s->scene.onroadScreenOff == -1) { - s->scene.osoTimer = 15 * UI_FREQ; - } else { - s->scene.osoTimer = -1; - } } void UIState::updateStatus() { - auto params = Params(); if (scene.started && sm->updated("controlsState")) { auto controls_state = (*sm)["controlsState"].getControlsState(); - auto car_control = (*sm)["carControl"].getCarControl(); - auto car_state = (*sm)["carState"].getCarState(); - auto mads_enabled = car_state.getMadsEnabled(); auto state = controls_state.getState(); if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { status = STATUS_OVERRIDE; } else { - status = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED : STATUS_MADS : STATUS_DISENGAGED; + status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; } - - if (mads_enabled != last_mads_enabled) { - mads_path_state = true; - } - last_mads_enabled = mads_enabled; - if (mads_path_state) { - if (mads_enabled) { - mads_path_count = fmax(mads_path_count - 1, 0); - if (mads_path_count == 0) { - mads_path_state = false; - } - } else { - mads_path_count = fmin(mads_path_count + 1, mads_path_timestep); - if (mads_path_count == mads_path_timestep) { - mads_path_state = false; - } - } - } - scene.mads_path_scale = mads_path_count * (1 / mads_path_timestep); } // Handle onroad/offroad transition @@ -326,65 +238,6 @@ void UIState::updateStatus() { scene.world_objects_visible = false; emit offroadTransition(!scene.started); } - - if (scene.started) { - // Auto hide UI button state machine - { - if (scene.button_auto_hide) { - if (scene.touch_to_wake) { - scene.sleep_btn = 30 * UI_FREQ; - } else if (scene.sleep_btn > 0) { - scene.sleep_btn--; - } else if (scene.sleep_btn == -1) { - scene.sleep_btn = 30 * UI_FREQ; - } - // Check if the sleep button should be fading in - if (scene.sleep_btn_fading_in) { - // Increase the opacity of the sleep button by a small amount - if (scene.sleep_btn_opacity < 20) { - scene.sleep_btn_opacity+= 10; - } - if (scene.sleep_btn_opacity >= 20) { - // If the opacity has reached its maximum value, stop fading in - scene.sleep_btn_fading_in = false; - scene.sleep_btn_opacity = 20; - } - } else if (scene.sleep_btn == 0) { - // Fade out the sleep button as before - if (scene.sleep_btn_opacity > 0) { - scene.sleep_btn_opacity-= 2; - } - } else { - // Set the opacity of the sleep button to its maximum value - scene.sleep_btn_opacity = 20; - } - } else { - scene.sleep_btn_opacity = 20; - } - } - - // Onroad Screen Off Brightness + Timer + Global Brightness - { - if (scene.onroadScreenOff != -2 && scene.touched2) { - scene.sleep_time = scene.osoTimer; - } else if (scene.onroadScreenOff != -2 && - ((scene.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && - ((scene.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene.onroadScreenOffEvent) || - (scene.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { - scene.sleep_time = scene.osoTimer; - } else if (scene.sleep_time > 0 && scene.onroadScreenOff != -2) { - scene.sleep_time--; - } else if (scene.sleep_time == -1 && scene.onroadScreenOff != -2) { - scene.sleep_time = scene.osoTimer; - } - } - } - - if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz - scene.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); - } - - scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); } UIState::UIState(QObject *parent) : QObject(parent) { @@ -435,20 +288,11 @@ void UIState::setPrimeType(PrimeType type) { } } -void UIState::setSunnylinkRoles(const std::vector& roles) { - sunnylinkRoles = roles; - emit sunnylinkRolesChanged(roles); -} - -void UIState::setSunnylinkDeviceUsers(const std::vector& users) { - sunnylinkUsers = users; - emit sunnylinkDeviceUsersChanged(users); -} - Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { setAwake(true); resetInteractiveTimeout(); + RETURN_IF_SUNNYPILOT QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); } @@ -492,10 +336,6 @@ void Device::updateBrightness(const UIState &s) { int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; - } else if (s.scene.started && s.scene.sleep_time == 0 && s.scene.onroadScreenOff != -2) { - brightness = s.scene.onroadScreenOffBrightness * 0.01 * brightness; - } else if (s.scene.brightness) { - brightness = s.scene.brightness * 0.99; } if (brightness != last_brightness) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index b28aa6a74d..e3e9756200 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -1,9 +1,7 @@ #pragma once #include -#include #include -#include #include #include @@ -16,31 +14,11 @@ #include "common/mat.h" #include "common/params.h" #include "common/timing.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h" #include "system/hardware/hw.h" const int UI_BORDER_SIZE = 30; const int UI_HEADER_HEIGHT = 420; -const int UI_ROAD_NAME_MARGIN_X = 14; - -struct FeatureStatusText { - const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto"}; - const QStringList gac_list_text = { "Aggressive", "Moderate", "Standard", "Relaxed"}; - const QStringList slc_list_text = { "Inactive", "Temp Off", "Adapting", "Active", "Pre Active"}; -}; - -struct FeatureStatusColor { - const QStringList dlp_list_color = { "#2020f8", "#0df87a", "#0df8f8" }; - const QStringList gac_list_color = { "#ff4b4b", "#fcff4b", "#4bff66", "#6a0ac9" }; - const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; -}; - -const float DRIVING_PATH_WIDE = 0.9; -const float DRIVING_PATH_NARROW = 0.25; - const int UI_FREQ = 20; // Hz const int BACKLIGHT_OFFROAD = 50; @@ -72,7 +50,6 @@ typedef enum UIStatus { STATUS_DISENGAGED, STATUS_OVERRIDE, STATUS_ENGAGED, - STATUS_MADS, } UIStatus; enum PrimeType { @@ -89,18 +66,10 @@ enum PrimeType { const QColor bg_colors [] = { [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), - [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), - [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), + [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), }; -const QColor tcs_colors [] = { - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1), - [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), -}; - typedef struct UIScene { bool calibration_valid = false; bool calibration_wide_valid = false; @@ -108,24 +77,13 @@ typedef struct UIScene { mat3 view_from_calib = DEFAULT_CALIBRATION; mat3 view_from_wide_calib = DEFAULT_CALIBRATION; cereal::PandaState::PandaType pandaType; - cereal::ControlsState::Reader controlsState; - - // Debug UI - bool show_debug_ui; - - // Speed limit control - bool speed_limit_control_enabled; - int speed_limit_control_policy; - double last_speed_limit_sign_tap; // modelV2 float lane_line_probs[4]; float road_edge_stds[2]; QPolygonF track_vertices; - QPolygonF track_edge_vertices; QPolygonF lane_line_vertices[4]; QPolygonF road_edge_vertices[2]; - QPolygonF lane_barrier_vertices[2]; // lead QPointF lead_vertices[2]; @@ -137,79 +95,12 @@ typedef struct UIScene { float driver_pose_coss[3]; vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; - bool navigate_on_openpilot_deprecated = false; cereal::LongitudinalPersonality personality; - cereal::AccelerationPersonality accel_personality; float light_sensor = -1; - bool started, ignition, is_metric, map_on_left, longitudinal_control; + bool started, ignition, is_metric, longitudinal_control; bool world_objects_visible = false; uint64_t started_frame; - - int dynamic_lane_profile; - bool dynamic_lane_profile_status = true; - - bool visual_brake_lights; - - int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; - bool onroadScreenOffEvent; - int sleep_time = -1; - bool touched2 = false; - - bool stand_still_timer; - - bool hide_vego_ui, true_vego_ui; - - int chevron_data; - - bool gac; - int longitudinal_personality; - int longitudinal_accel_personality; - - bool map_visible; - int dev_ui_info; - bool live_torque_toggle; - - bool touch_to_wake = false; - int sleep_btn = -1; - bool sleep_btn_fading_in = false; - int sleep_btn_opacity = 20; - bool button_auto_hide; - - bool reverse_dm_cam; - - bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; - float e2eX[13] = {0}; - - int sidebar_temp_options; - - float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; - float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 - - bool onroad_settings_visible; - - bool map_3d_buildings; - - bool torqued_override; - - bool dynamic_experimental_control; - - int speed_limit_control_engage_type; - - bool mapbox_fullscreen; - - bool speed_limit_warning_flash; - int speed_limit_warning_type; - int speed_limit_warning_value_offset; - - bool custom_driving_model_valid; - cereal::ModelGeneration driving_model_generation; - uint32_t driving_model_capabilities; - - bool feature_status_toggle; - bool onroad_settings_toggle; - - bool dynamic_personality; } UIScene; class UIState : public QObject { @@ -226,42 +117,6 @@ public: inline PrimeType primeType() const { return prime_type; } inline bool hasPrime() const { return prime_type > PrimeType::NONE; } - void setSunnylinkRoles(const std::vector &roles); - void setSunnylinkDeviceUsers(const std::vector &users); - - inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } - inline bool isSunnylinkAdmin() const { - return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { - return role.roleType == RoleType::Admin; - }); - } - inline bool isSunnylinkSponsor() const { - return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { - return role.roleType == RoleType::Sponsor && role.as().roleTier != SponsorTier::Free; - }); - } - inline SponsorRoleModel sunnylinkSponsorRole() const { - std::optional sponsorRoleWithHighestTier = std::nullopt; - for (const auto &role : sunnylinkRoles) { - if (role.roleType != RoleType::Sponsor) - continue; - - if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { - sponsorRoleWithHighestTier = sponsorRole; - } - } - return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free)); - } - inline SponsorTier sunnylinkSponsorTier() const { - return sunnylinkSponsorRole().roleTier; - } - inline std::vector sunnylinkDeviceUsers() const { return sunnylinkUsers; } - inline bool isSunnylinkPaired() const { - return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) { - return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; - }); - } - int fb_w = 0, fb_h = 0; std::unique_ptr sm; @@ -279,24 +134,13 @@ signals: void primeChanged(bool prime); void primeTypeChanged(PrimeType prime_type); - void sunnylinkRoleChanged(bool subscriber); - void sunnylinkRolesChanged(std::vector roles); - void sunnylinkDeviceUsersChanged(std::vector users); - -private slots: +protected slots: void update(); private: QTimer *timer; bool started_prev = false; PrimeType prime_type = PrimeType::UNKNOWN; - std::vector sunnylinkRoles = {}; - std::vector sunnylinkUsers = {}; - - bool last_mads_enabled = false; - bool mads_path_state = false; - float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] - float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] }; UIState *uiState(); @@ -312,7 +156,7 @@ public: offroad_brightness = std::clamp(brightness, 0, 100); } -private: +protected: bool awake = false; int interactive_timeout = 0; bool ignition_on = false; @@ -344,4 +188,4 @@ void update_model(UIState *s, void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, - float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); From f6ee3d69f4825e0269d86bf612ed5b1eb4c77bc4 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 22:53:42 -0400 Subject: [PATCH 444/575] ui.h rename (WIP) --- .../sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h | 2 +- .../ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h | 2 +- .../sunnypilot/qt/offroad/settings/sp_priv_software_settings.h | 2 +- .../sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h | 2 +- .../qt/offroad/settings/sp_priv_sunnypilot_settings.h | 2 +- .../sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h | 2 +- .../sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h | 2 +- .../offroad/settings/sunnypilot/sp_priv_lane_change_settings.h | 2 +- .../qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h | 2 +- .../settings/sunnypilot/sp_priv_speed_limit_control_settings.h | 2 +- .../settings/sunnypilot/sp_priv_speed_limit_policy_settings.h | 2 +- .../settings/sunnypilot/sp_priv_speed_limit_warning_settings.h | 2 +- selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h | 2 +- .../ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc | 2 +- selfdrive/ui/sunnypilot/qt/sp_priv_home.h | 2 +- selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h index c8ca43c2ff..628ee68687 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h @@ -11,7 +11,7 @@ #include "common/swaglog.h" #include "common/util.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h" #ifdef SUNNYPILOT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h index 1b76f68126..a655d4db35 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h @@ -17,7 +17,7 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "system/hardware/hw.h" constexpr int FAST_REFRESH_INTERVAL = 1000; // ms diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h index d1d83b6790..6d04d07817 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h @@ -1,7 +1,7 @@ #pragma once #include "common/model.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/qt/offroad/settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h index 4027147975..76ec1417b3 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h @@ -5,7 +5,7 @@ #include #include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h index 615898e5e5..b669932cfb 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h @@ -4,7 +4,7 @@ #include #include "common/model.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h index b66d29dcd6..f30810358c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h @@ -3,7 +3,7 @@ #include #include "common/watchdog.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h index bf1f12d7a6..9b386b6b35 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" class VisualsPanel : public ListWidgetSP { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h index 26129c9f6e..e135e50a1e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h index 9dee408ecc..86ba749db9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h index e004795aa6..59237e0a1c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h @@ -4,7 +4,7 @@ #include #include -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h index 572f91d3d2..5124f3c109 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h index a50fa790a1..732e623d31 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h @@ -4,7 +4,7 @@ #include #include -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h index 17d0f461d7..d8df42b96e 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h @@ -6,7 +6,7 @@ #include #include "common/params.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc index 8c7336c28f..ee722a696f 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc @@ -5,7 +5,7 @@ #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" OnroadSettingsPanel::OnroadSettingsPanel(QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h index 5f49ee4794..cc01a05bd5 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.h @@ -11,7 +11,7 @@ #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/qt/home.h" #ifdef SUNNYPILOT diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h index 158f1bd96c..9bd7a55f58 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h @@ -6,7 +6,7 @@ #include #include "selfdrive/ui/qt/sidebar.h" -#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" class SidebarSP : public Sidebar { Q_OBJECT From eb0ced8040bfbd18c26724b0efad46b6c3d883d4 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 23:07:32 -0400 Subject: [PATCH 445/575] ui: header imports --- selfdrive/ui/qt/offroad/settings.h | 3 +- selfdrive/ui/qt/request_repeater.h | 4 + selfdrive/ui/qt/widgets/cameraview.h | 4 + selfdrive/ui/sunnypilot/qt/sp_priv_home.cc | 111 --------------------- 4 files changed, 10 insertions(+), 112 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 734f47e01e..96fc04bbca 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -11,15 +11,16 @@ #include #include -#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #define ListWidget ListWidgetSP #define ParamControl ParamControlSP #define ButtonParamControl ButtonParamControlSP #else +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h index 45cb643b13..43ce6492c9 100644 --- a/selfdrive/ui/qt/request_repeater.h +++ b/selfdrive/ui/qt/request_repeater.h @@ -3,7 +3,11 @@ #include "common/swaglog.h" #include "common/util.h" #include "selfdrive/ui/qt/api.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class RequestRepeater : public HttpRequest { diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 526c17e77c..09a1479bf6 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -24,7 +24,11 @@ #include "msgq/visionipc/visionipc_client.h" #include "system/camerad/cameras/camera_common.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const int FRAME_BUFFER_SIZE = 5; static_assert(FRAME_BUFFER_SIZE <= YUV_BUFFER_COUNT); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc index c8caade101..77a779484d 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc @@ -50,114 +50,3 @@ void HomeWindowSP::mousePressEvent(QMouseEvent* e) { } } } - -// OffroadHome: the offroad home page -// -// OffroadHomeSP::OffroadHomePS(QWidget* parent) : QFrame(parent) { -// QVBoxLayout* main_layout = new QVBoxLayout(this); -// main_layout->setContentsMargins(40, 40, 40, 40); -// -// // top header -// QHBoxLayout* header_layout = new QHBoxLayout(); -// header_layout->setContentsMargins(0, 0, 0, 0); -// header_layout->setSpacing(16); -// -// update_notif = new QPushButton(tr("UPDATE")); -// update_notif->setVisible(false); -// update_notif->setStyleSheet("background-color: #364DEF;"); -// QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); -// header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); -// -// alert_notif = new QPushButton(); -// alert_notif->setVisible(false); -// alert_notif->setStyleSheet("background-color: #E22C2C;"); -// QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); }); -// header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); -// -// version = new ElidedLabel(); -// header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight); -// -// main_layout->addLayout(header_layout); -// -// // main content -// main_layout->addSpacing(25); -// center_layout = new QStackedLayout(); -// -// QWidget *home_widget = new QWidget(this); -// { -// QHBoxLayout *home_layout = new QHBoxLayout(home_widget); -// home_layout->setContentsMargins(0, 0, 0, 0); -// home_layout->setSpacing(30); -// -// // left: PrimeAdWidget -// QStackedWidget *left_widget = new QStackedWidget(this); -// QVBoxLayout *left_prime_layout = new QVBoxLayout(); -// QWidget *prime_user = new PrimeUserWidget(); -// prime_user->setStyleSheet(R"( -// border-radius: 10px; -// background-color: #333333; -// )"); -// left_prime_layout->addWidget(prime_user); -// left_prime_layout->addStretch(); -// left_widget->addWidget(new LayoutWidget(left_prime_layout)); -// left_widget->addWidget(new PrimeAdWidget); -// left_widget->setStyleSheet("border-radius: 10px;"); -// -// left_widget->setCurrentIndex(uiState()->hasPrime() ? 0 : 1); -// connect(uiState(), &UIState::primeChanged, [=](bool prime) { -// left_widget->setCurrentIndex(prime ? 0 : 1); -// }); -// -// home_layout->addWidget(left_widget, 1); -// -// // right: ExperimentalModeButton, SetupWidget -// QWidget* right_widget = new QWidget(this); -// QVBoxLayout* right_column = new QVBoxLayout(right_widget); -// right_column->setContentsMargins(0, 0, 0, 0); -// right_widget->setFixedWidth(750); -// right_column->setSpacing(30); -// -// ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); -// QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); -// right_column->addWidget(experimental_mode, 1); -// -// SetupWidget *setup_widget = new SetupWidget; -// QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings); -// right_column->addWidget(setup_widget, 1); -// -// home_layout->addWidget(right_widget, 1); -// } -// center_layout->addWidget(home_widget); -// -// // add update & alerts widgets -// update_widget = new UpdateAlert(); -// QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); -// center_layout->addWidget(update_widget); -// alerts_widget = new OffroadAlert(); -// QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); -// center_layout->addWidget(alerts_widget); -// -// main_layout->addLayout(center_layout, 1); -// -// // set up refresh timer -// timer = new QTimer(this); -// timer->callOnTimeout(this, &OffroadHome::refresh); -// -// setStyleSheet(R"( -// * { -// color: white; -// } -// OffroadHome { -// background-color: black; -// } -// OffroadHome > QPushButton { -// padding: 15px 30px; -// border-radius: 5px; -// font-size: 40px; -// font-weight: 500; -// } -// OffroadHome > QLabel { -// font-size: 55px; -// } -// )"); -// } \ No newline at end of file From f4dac4b4624686b6e78621bd466a54e848d22518 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 18 Jul 2024 09:08:52 +0200 Subject: [PATCH 446/575] Add git-crypt unlock attempt in provision script This update tries to unlock the repository with git-crypt at the start of the provision script. If the unlock is successful, it exits early, confirming access. Check for /TICI file and install git-crypt if found Previously, the script would exit if git-crypt was not found. Now, the script checks for the presence of the /TICI file and attempts to install git-crypt automatically if the file is detected. This change helps automate setup on systems where /TICI is present. Remove GPG key file after import in CI script Ensure GPG key files are removed after importing to improve security. This change mitigates the risk of accidental exposure of key files in subsequent steps. Refactor key export path handling Simplify and improve clarity in the key export process by defining a separate variable for the key installation path. This ensures consistency and reduces potential errors in directory management. Additionally, it automates committing and pushing the new public key to the repository. Add provision script for git-crypt setup This script checks for gpg and git-crypt installations and sets up a GPG key for git-crypt. It supports user input for name, email, and passphrase, with options for non-interactive execution and displays help information when requested. Updated .gitlab-ci.yml to support automation of git-crypt provisioning and improved CI configuration. This update adds a new stage to the pipeline for automatic provisioning of git-crypt when a change is detected in the git-crypt path. It introduces shared configurations to manage SSH key configurations, Git configurations, and git-crypt unlocking across multiple jobs. Unnecessary repetition of scripts has been minimized through reusability of these shared configurations. This ultimately enhances modularity and readability of the pipeline script. --- .git-crypt/provision.sh | 110 ++++++++++++++++++++++++++++++++++++++++ .gitlab-ci.yml | 110 +++++++++++++++++++++++++++++++--------- 2 files changed, 197 insertions(+), 23 deletions(-) create mode 100755 .git-crypt/provision.sh diff --git a/.git-crypt/provision.sh b/.git-crypt/provision.sh new file mode 100755 index 0000000000..2fb9bc08d9 --- /dev/null +++ b/.git-crypt/provision.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +# Check if gpg is installed +if ! command -v gpg &> /dev/null; then + echo "gpg could not be found. Please install gpg to continue." + exit 1 +fi + +# Check if git-crypt is installed +if ! command -v git-crypt &> /dev/null; then + # Check if /TICI file exists + if [ -f "/TICI" ]; then + echo "/TICI file detected. Attempting to install git-crypt..." + sudo apt update && sudo apt install git-crypt -y + else + echo "git-crypt could not be found. Please install git-crypt to continue." + exit 1 + fi +fi + +# Attempt to unlock with git-crypt +if git-crypt unlock &> /dev/null; then + echo "git-crypt unlock successful. You already have access." + exit 0 +fi + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +no_prompt=0 +# Attempt to retrieve name and email from git config, default to "undefined" +name=$(git config --get user.name) +email=$(git config --get user.email) +name=${name:-"undefined"} +email=${email:-"undefined"} +passphrase="" + +# Function to display help message +show_help() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " --name Set the user's name. Default is git config user.name or 'undefined'." + echo " --email Set the user's email. Default is git config user.email or 'undefined'." + echo " --passphrase Set the passphrase for the GPG key." + echo " --no-prompt Run without interactive prompts, using defaults or provided values." + echo " -h, --help Display this help message and exit." + echo "" + exit 0 +} + +# Check for help flag before parsing other arguments +for arg in "$@"; do + case $arg in + -h|--help) + show_help + ;; + esac +done + +# Parse named arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --name) name="$2"; shift ;; + --email) email="$2"; shift ;; + --passphrase) passphrase="$2"; shift ;; + --no-prompt) no_prompt=1 ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +# No need to prompt for missing information if no-prompt is enabled or defaults are used + +# Set protection line based on passphrase +if [[ -z "$passphrase" ]]; then + protection_line="%no-protection" +else + protection_line="Passphrase: $passphrase" +fi + +# Generate and process GPG key +GPG_OUTPUT=$(echo " +%echo Generating a basic OpenPGP key +Key-Type: RSA +Key-Length: 4096 +Subkey-Type: RSA +Subkey-Length: 4096 +Name-Real: $name +Name-Comment: Generated from git config or default +Name-Email: $email +Expire-Date: 0 +$protection_line +%commit +%echo done +" | gpg --batch --generate-key - 2>&1) + +# Extract and export the key ID +KEY_ID=$(echo "$GPG_OUTPUT" | grep -o '[0-9A-F]\{40\}' | head -n 1) +if [ -z "$KEY_ID" ]; then + echo "Failed to generate GPG key or extract key ID." + exit 1 +fi + +INSTALL_KEY_PATH=${DIR}/keys/install_new_key_sp +mkdir -p ${INSTALL_KEY_PATH} +gpg --export $KEY_ID > ${INSTALL_KEY_PATH}/$KEY_ID.gpg +git add ${INSTALL_KEY_PATH}/$KEY_ID.gpg +git commit -m "Add new public key to install." +git push + +echo "Public key exported to ${INSTALL_KEY_PATH}/${KEY_ID}.gpg" \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1775e4ab7a..9544efbfc4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,8 +9,11 @@ variables: GIT_CONFIG_USER_EMAIL: "gitlab@pipeline.com" GIT_CONFIG_USER_NAME: "Gitlab Pipeline" PUBLIC_REPO_URL: "https://github.com/sunnyhaibin/sunnypilot" + GIT_CRYPT_INSTALL_PATH: ".git-crypt/keys/install_new_key_sp" + GIT_CRYPT_KEY_PATH: ".git-crypt/keys/default/0" stages: + - git-crypt-auto-provision - build - sanity - publish @@ -22,18 +25,43 @@ default: - sunnypilot - x86 +.configure_ci_deploy_key: &configure_ci_deploy_key + - 'eval $(ssh-agent -s)' + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - + - 'mkdir -p ~/.ssh/' + - 'chmod 700 ~/.ssh' + - export HOST=$(echo ${GIT_ORIGIN} | cut -d'@' -f2 | cut -d':' -f1) + - 'if [ -z "$HOST" ]; then export HOST="gitlab.com"; fi' # Fallback to gitlab.com if HOST is not defined + - echo fetching ssh pub keys for ${HOST} + - 'ssh-keyscan -H ${HOST} >> ~/.ssh/known_hosts' # Adding gitlab to trusted + - 'chmod 644 ~/.ssh/known_hosts' + +.configure_git: &configure_git + - 'git config --global user.email "${GIT_CONFIG_USER_EMAIL}"' + - 'git config --global user.name "${GIT_CONFIG_USER_NAME}"' + # Replace the URL with the SSH version + - export NEW_URL=$(echo ${CI_REPOSITORY_URL} | sed -E "s#^(https?://)?[^@]+@([^/]+)/(.*)#git@\2:\3#") + - git remote set-url --push origin ${NEW_URL} + - git remote -v + .default_before_script: &default_before_script - 'export VERSION=$(eval $VERSION)${EXTRA_VERSION_IDENTIFIER}' - 'mkdir -p "${BUILD_DIR}/"' - - 'git config --global user.email "${GIT_CONFIG_USER_EMAIL}"' - - 'git config --global user.name "${GIT_CONFIG_USER_NAME}"' - + - *configure_git workflow: # If running on any branch other than main, use the `aws-datacontracts-dev` account; otherwise use `aws-datacontracts` 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 have changes in the git-crypt path, we need to provision the key first before proceeding + - changes: + compare_to: $CI_DEFAULT_BRANCH + paths: + - "${GIT_CRYPT_INSTALL_PATH}/**" + when: always + # We are a merge request - if: $CI_MERGE_REQUEST_IID #|| $CI_COMMIT_REF_NAME == "gitlab-pipelines" # TBD once merged variables: @@ -63,6 +91,53 @@ workflow: # If running on any branch other than main, use the `aws-datacontracts EXTRA_VERSION_IDENTIFIER: "-release" - when: always +.git-crypt privision & unlock: &git-crypt-provision-and-unlock + - echo "Decoding and importing GPG key..." + - gpg -v --import <(echo "$GPG_PRIVATE_KEY_BASE64" | base64 -d) + - echo "Unlocking git-crypt..." + - git-crypt unlock + +GPG key change detected: + stage: git-crypt-auto-provision + image: alpine + before_script: + - apk --update add gpg gpg-agent git-crypt git grep git-lfs openssh + - *configure_ci_deploy_key + - *configure_git + - *git-crypt-provision-and-unlock + script: + - echo "Checking for changes in $GIT_CRYPT_INSTALL_PATH" + - git fetch origin $CI_DEFAULT_BRANCH + - export LAST_TARGET_COMMIT_SHA=$(git rev-parse FETCH_HEAD) + - | + echo CI_COMMIT_SHA: $CI_COMMIT_SHA + echo GIT_CRYPT_INSTALL_PATH: $GIT_CRYPT_INSTALL_PATH + echo LAST_TARGET_COMMIT_SHA: $LAST_TARGET_COMMIT_SHA + - CHANGES=$(git diff-tree --name-only -r $LAST_TARGET_COMMIT_SHA $CI_COMMIT_SHA | grep -i "${GIT_CRYPT_INSTALL_PATH}") + - echo "$CHANGES" + - ls -la $GIT_CRYPT_KEY_PATH + - git-crypt unlock + - | + for key_path in $CHANGES; do + export key_id=$(echo $(gpg --import $key_path 2>&1) | grep -oE 'key [A-F0-9]{8,}' | awk '{print $2}') + git-crypt add-gpg-user --trusted $key_id + rm $key_path + done + - ls -la $GIT_CRYPT_KEY_PATH + - git-crypt lock + - git branch -r + - git fetch origin $CI_COMMIT_REF_NAME + - git branch -D $CI_COMMIT_REF_NAME; git checkout -b $CI_COMMIT_REF_NAME + - git push -u origin $CI_COMMIT_REF_NAME + allow_failure: false + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + changes: + compare_to: $CI_DEFAULT_BRANCH + paths: + - "${GIT_CRYPT_INSTALL_PATH}/**" + when: manual + - when: never # This ensures the job does not run if none of the above conditions are met build: stage: build @@ -79,12 +154,7 @@ build: - "echo VERSION: ${VERSION}" - "echo CI_COMMIT_REF_NAME: ${CI_COMMIT_REF_NAME}" - git config --global --add safe.directory ${CI_PROJECT_DIR} - - | - echo "Decoding and importing GPG key..." - gpg -v --import <(echo "$GPG_PRIVATE_KEY_BASE64" | base64 -d) - - | - echo "Unlocking git-crypt..." - git-crypt unlock + - *git-crypt-provision-and-unlock script: - export PYTHONPATH="$BUILD_DIR" - "echo Building Panda..." @@ -98,7 +168,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 @@ -148,10 +218,11 @@ build: - ${OUTPUT_DIR}/ tags: [ 'sunnypilot', 'tici' ] rules: - - if: $CI_MERGE_REQUEST_IID + - if: $NEW_BRANCH && $CI_MERGE_REQUEST_IID when: manual - if: $NEW_BRANCH when: always + - when: never check no source code sent: image: alpine @@ -189,14 +260,7 @@ check no source code sent: before_script: - 'apk update && apk upgrade' - 'apk add git bash openssh' - - 'eval $(ssh-agent -s)' - - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - - 'mkdir -p ~/.ssh/' - - 'chmod 700 ~/.ssh' - - export HOST=$(echo ${GIT_ORIGIN} | cut -d'@' -f2 | cut -d':' -f1) - - echo fetching ssh pub keys for ${HOST} - - 'ssh-keyscan -H ${HOST} >> ~/.ssh/known_hosts' # Adding gitlab to trusted - - 'chmod 644 ~/.ssh/known_hosts' + - *configure_ci_deploy_key - *default_before_script script: - echo "${GIT_ORIGIN}" @@ -231,13 +295,13 @@ publish to public github prebuilt: needs: ["build"] variables: DISCORD_HOOK: "${DISCORD_MANUAL_BUILD_WEBHOOK_URL}" # Default hook if not overriden by children - before_script: ¬ify_discord_before_script + before_script: - 'apk add curl jq envsubst' script: - echo using [${TEMPLATE}] - cat release/ci/${TEMPLATE} | envsubst | tee payload.json - 'curl -X POST -H "Content-Type: application/json" -d "$(cat payload.json)" ${DISCORD_HOOK} | jq .' - rules: ¬ify_discord_rules + rules: - if: $NEW_BRANCH when: on_success - when: never @@ -248,7 +312,7 @@ notify pending action: variables: TEMPLATE: "discord_template_notify_dev_private.json" before_script: - - *notify_discord_before_script + - !reference [".notify_discord", "before_script"] - export AVATAR_URL=$(curl -s -X GET "https://gitlab.com/api/v4/avatar?email=${GITLAB_USER_EMAIL}" | jq -r '.avatar_url') notify new dev build: @@ -261,4 +325,4 @@ notify new dev build: - if: $NEW_BRANCH == "dev-c3" variables: DISCORD_HOOK: "${DISCORD_NEW_BUILD_WEBHOOK_URL}" # Overriding hook because we know we are dev-c3 - - *notify_discord_rules + - !reference [".notify_discord", "rules"] From f51a6f5462a8371931f2d1ec2f4361df978e3725 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 18 Jul 2024 16:15:55 +0200 Subject: [PATCH 447/575] Add .git-crypt and .venv to the blacklist This commit updates the release_files.py script to include .git-crypt and .venv in the list of blacklisted files. This ensures that these directories are excluded from the release process, maintaining cleaner and more secure releases. --- release/release_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release/release_files.py b/release/release_files.py index 31d1518d74..cf244bb72f 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 From 522d42500e833fa8a3466aab9db4a608b6ef9686 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Thu, 18 Jul 2024 11:17:07 -0400 Subject: [PATCH 448/575] maps: split --- selfdrive/navd/map_renderer.cc | 4 + selfdrive/ui/SConscript | 5 +- selfdrive/ui/qt/maps/map.cc | 64 +------------ selfdrive/ui/qt/maps/map.h | 13 ++- selfdrive/ui/qt/maps/map_helpers.h | 3 +- selfdrive/ui/sunnypilot/SConscript | 6 +- .../ui/sunnypilot/qt/maps/sp_priv_map.cc | 95 +++++++++++++++++++ selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h | 25 +++++ .../sunnypilot/qt/maps/sp_priv_map_helpers.h | 12 +++ 9 files changed, 154 insertions(+), 73 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc create mode 100644 selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h create mode 100644 selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index d52ee162bd..ecbff25768 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -8,7 +8,11 @@ #include "common/util.h" #include "common/timing.h" #include "common/swaglog.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#else #include "selfdrive/ui/qt/maps/map_helpers.h" +#endif const float DEFAULT_ZOOM = 13.5; // Don't go below 13 or features will start to disappear const int HEIGHT = 256, WIDTH = 256; diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index f62e204136..961f263dd3 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -18,10 +18,11 @@ if arch == "Darwin": qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] sp_widgets_src = [] +sp_maps_widgets_src = [] sp_qt_src = [] if GetOption('sunnypilot'): SConscript(['sunnypilot/SConscript']) - Import('sp_widgets_src', 'sp_qt_src') + Import('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src') qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", @@ -34,7 +35,7 @@ qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if GetOption('sunnypilot') else [] if maps: base_libs += ['QMapLibre'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc", - "qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"] + "qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"] + sp_maps_widgets_src qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index b5b731ef66..d165ae1edc 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -75,7 +75,7 @@ void MapWindow::initLayers() { QVariantMap transition; transition["duration"] = 400; // ms - m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiState()->scene.navigate_on_openpilot_deprecated)); + m_map->setPaintProperty("navLayer", "line-color", QColor("#31a1ee")); m_map->setPaintProperty("navLayer", "line-color-transition", transition); m_map->setPaintProperty("navLayer", "line-width", 7.5); m_map->setLayoutProperty("navLayer", "line-cap", "round"); @@ -110,52 +110,10 @@ void MapWindow::initLayers() { // TODO: remove, symbol-sort-key does not seem to matter outside of each layer m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0); } - if ((!m_map->layerExists("buildingsLayer")) && uiState()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage - qDebug() << "Initializing buildingsLayer"; - QVariantMap buildings; - buildings["id"] = "buildingsLayer"; - buildings["source"] = "composite"; - buildings["source-layer"] = "building"; - buildings["type"] = "fill-extrusion"; - buildings["minzoom"] = 15; - m_map->addLayer("buildingsLayer", buildings); - m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"})); - - QVariantList fillExtrusionHeight = { // scale buildings as you zoom in - "interpolate", - QVariantList{"linear"}, - QVariantList{"zoom"}, - 15, 0, - 15.05, QVariantList{"get", "height"} - }; - - QVariantList fillExtrusionBase = { - "interpolate", - QVariantList{"linear"}, - QVariantList{"zoom"}, - 15, 0, - 15.05, QVariantList{"get", "min_height"} - }; - - QVariantList fillExtrusionOpacity = { - "interpolate", - QVariantList{"linear"}, - QVariantList{"zoom"}, - 15, 0, // transparent at zoom level 15 - 15.5, .6, // fade in - 17, .6, // begin fading out - 20, 0 // fade out when zoomed in - }; - - m_map->setPaintProperty("buildingsLayer", "fill-extrusion-color", QColor("grey")); - m_map->setPaintProperty("buildingsLayer", "fill-extrusion-opacity", fillExtrusionOpacity); - m_map->setPaintProperty("buildingsLayer", "fill-extrusion-height", fillExtrusionHeight); - m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase); - m_map->setLayoutProperty("buildingsLayer", "visibility", "visible"); - } } void MapWindow::updateState(const UIState &s) { +#ifdef SUNNYPILOT if (!uiState()->scene.started) { return; } @@ -169,22 +127,7 @@ void MapWindow::updateState(const UIState &s) { initializeGL(); } prev_time_valid = sm.valid("clocks"); - - if (sm.updated("modelV2")) { - // set path color on change, and show map on rising edge of navigate on openpilot - auto car_control = sm["carControl"].getCarControl(); - bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() && - (sm["controlsState"].getControlsState().getEnabled() || car_control.getLatActive() || car_control.getLongActive()); - if (nav_enabled != uiState()->scene.navigate_on_openpilot_deprecated) { - if (loaded_once) { - m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled)); - } - if (nav_enabled) { - emit requestVisible(true); - } - } - uiState()->scene.navigate_on_openpilot_deprecated = nav_enabled; - } +#endif if (sm.updated("liveLocationKalman")) { auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman(); @@ -425,7 +368,6 @@ void MapWindow::pinchTriggered(QPinchGesture *gesture) { void MapWindow::offroadTransition(bool offroad) { if (offroad) { clearRoute(); - uiState()->scene.navigate_on_openpilot_deprecated = false; routing_problem = false; } else { auto dest = coordinate_from_param("NavDestination"); diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 62871e79d2..3c78ef39d9 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -32,15 +32,17 @@ public: ~MapWindow(); private: - void initializeGL() final; void paintGL() final; void resizeGL(int w, int h) override; +protected: + void initializeGL() final; QMapLibre::Settings m_settings; QScopedPointer m_map; void initLayers(); +private: void mousePressEvent(QMouseEvent *ev) final; void mouseDoubleClickEvent(QMouseEvent *ev) final; void mouseMoveEvent(QMouseEvent *ev) final; @@ -50,9 +52,11 @@ private: void pinchTriggered(QPinchGesture *gesture); void setError(const QString &err_str); +protected: bool loaded_once = false; bool prev_time_valid = true; +private: // Panning QPointF m_lastPos; int interaction_counter = 0; @@ -70,16 +74,11 @@ private: MapInstructions* map_instructions; MapETA* map_eta; - // Blue with normal nav, green when nav is input into the model - QColor getNavPathColor(bool nav_enabled) { - return nav_enabled ? QColor("#31ee73") : QColor("#31a1ee"); - } - void clearRoute(); void updateDestinationMarker(); uint64_t route_rcv_frame = 0; -private slots: +protected slots: void updateState(const UIState &s); public slots: diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index b9cb1f9469..0f4be674f0 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -12,9 +12,8 @@ #include "common/transformations/coordinates.hpp" #include "common/transformations/orientation.hpp" #include "cereal/messaging/messaging.h" -#include "common/params.h" -const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("CustomMapboxTokenSk")) != "" ? QString::fromStdString(Params().get("CustomMapboxTokenSk")) : util::getenv("MAPBOX_TOKEN").c_str(); +const QString MAPBOX_TOKEN = util::getenv("MAPBOX_TOKEN").c_str(); const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str(); const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db"; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 9e28fc1e21..e8953d5409 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -22,6 +22,10 @@ widgets_src = [ "sunnypilot/qt/widgets/sp_priv_toggle.cc" ] +sp_maps_widgets_src = [ + "sunnypilot/qt/maps/sp_priv_map.cc" +] + network_src = [ "sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc", "sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc", @@ -45,4 +49,4 @@ qt_src = [ sp_widgets_src = widgets_src + network_src sp_qt_src = qt_src -Export('sp_widgets_src', 'sp_qt_src') +Export('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src') diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc new file mode 100644 index 0000000000..de2ebbd3fd --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc @@ -0,0 +1,95 @@ +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h" + +#include "common/swaglog.h" +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" + +void MapWindowSP::initLayers() { + if (!m_map->layerExists("navLayer")) { + m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiState()->scene.navigate_on_openpilot_deprecated)); + } + if ((!m_map->layerExists("buildingsLayer")) && uiState()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage + qDebug() << "Initializing buildingsLayer"; + QVariantMap buildings; + buildings["id"] = "buildingsLayer"; + buildings["source"] = "composite"; + buildings["source-layer"] = "building"; + buildings["type"] = "fill-extrusion"; + buildings["minzoom"] = 15; + m_map->addLayer("buildingsLayer", buildings); + m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"})); + + QVariantList fillExtrusionHeight = { // scale buildings as you zoom in + "interpolate", + QVariantList{"linear"}, + QVariantList{"zoom"}, + 15, 0, + 15.05, QVariantList{"get", "height"} + }; + + QVariantList fillExtrusionBase = { + "interpolate", + QVariantList{"linear"}, + QVariantList{"zoom"}, + 15, 0, + 15.05, QVariantList{"get", "min_height"} + }; + + QVariantList fillExtrusionOpacity = { + "interpolate", + QVariantList{"linear"}, + QVariantList{"zoom"}, + 15, 0, // transparent at zoom level 15 + 15.5, .6, // fade in + 17, .6, // begin fading out + 20, 0 // fade out when zoomed in + }; + + m_map->setPaintProperty("buildingsLayer", "fill-extrusion-color", QColor("grey")); + m_map->setPaintProperty("buildingsLayer", "fill-extrusion-opacity", fillExtrusionOpacity); + m_map->setPaintProperty("buildingsLayer", "fill-extrusion-height", fillExtrusionHeight); + m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase); + m_map->setLayoutProperty("buildingsLayer", "visibility", "visible"); + } +} + +void MapWindowSP::updateState(const UIState &s) { + if (!uiState()->scene.started) { + return; + } + const SubMaster &sm = *(s.sm); + update(); + + // on rising edge of a valid system time, reinitialize the map to set a new token + if (sm.valid("clocks") && !prev_time_valid) { + LOGW("Time is now valid, reinitializing map"); + m_settings.setApiKey(get_mapbox_token()); + initializeGL(); + } + prev_time_valid = sm.valid("clocks"); + + if (sm.updated("modelV2")) { + // set path color on change, and show map on rising edge of navigate on openpilot + auto car_control = sm["carControl"].getCarControl(); + bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() && + (sm["controlsState"].getControlsState().getEnabled() || car_control.getLatActive() || car_control.getLongActive()); + if (nav_enabled != uiState()->scene.navigate_on_openpilot_deprecated) { + if (loaded_once) { + m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled)); + } + if (nav_enabled) { + emit requestVisible(true); + } + } + uiState()->scene.navigate_on_openpilot_deprecated = nav_enabled; + } + + MapWindow::initLayers(); +} + +void MapWindowSP::offroadTransition(bool offroad) { + if (offroad) { + uiState()->scene.navigate_on_openpilot_deprecated = false; + } + + MapWindow::offroadTransition(offroad); +} diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h new file mode 100644 index 0000000000..9bdafd33ac --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h @@ -0,0 +1,25 @@ +#pragma once + +#include "selfdrive/ui/qt/maps/map.h" + +#include + +class MapWindowSP : public MapWindow { + Q_OBJECT + +public: + explicit MapWindowSP(const QMapLibre::Settings &settings) : MapWindow(settings) {} + +private: + void initLayers(); + // Blue with normal nav, green when nav is input into the model + QColor getNavPathColor(bool nav_enabled) { + return nav_enabled ? QColor("#31ee73") : QColor("#31a1ee"); + } + +protected slots: + void updateState(const UIState &s); + +public slots: + void offroadTransition(bool offroad); +}; diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h new file mode 100644 index 0000000000..7cf335000d --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h @@ -0,0 +1,12 @@ +#pragma once + +#include "selfdrive/ui/qt/maps/map_helpers.h" + +#define MAPBOX_TOKEN MAPBOX_TOKEN_SP +#define MAPS_HOST MAPS_HOST_SP + +#include "common/params.h" + +const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("CustomMapboxTokenSk")) != "" ? + QString::fromStdString(Params().get("CustomMapboxTokenSk")) : util::getenv("MAPBOX_TOKEN").c_str(); +const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str(); From fcd0dc2874685e72f6044cabd1f3239446322ee9 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 19 Jul 2024 12:17:18 +0200 Subject: [PATCH 449/575] Refactor and enhance UI state and device classes for both base and sunny pilot Various improvements have been made to UI state and device classes in both base and sunny pilot. This includes better organization of code, addition of new functionalities and removal of unused code and libraries. Each UI state update now also updates the sockets and the state itself. Conditions for brightness updates in deviceSP's methods are also simplified. This results in a more streamlined update process. Changes have been made to update the device brightness according to the specified parameters. If the screen is expected to be off, the brightness is set to 0. Otherwise, the brightness is set to a level according to the onroadScreenOffBrightness parameter. The rest of the changes involve cleaning up redundant code, updating includes, and removing unnecessary typecasts and conversions. The parameters updating are now handled more succinctly. Overall, the updated code provides a cleaner, more efficient implementation of the UI and devices. --- selfdrive/ui/qt/onroad/buttons.cc | 59 +--- selfdrive/ui/qt/onroad/buttons.h | 26 -- selfdrive/ui/qt/onroad/onroad_home.h | 3 +- selfdrive/ui/qt/sidebar.h | 2 +- selfdrive/ui/qt/window.cc | 3 +- selfdrive/ui/sunnypilot/SConscript | 7 +- .../ui/sunnypilot/qt/maps/sp_priv_map.cc | 12 +- .../services/sp_priv_role_service.cc | 2 +- .../services/sp_priv_user_service.cc | 2 +- .../offroad/settings/sp_priv_device_panel.cc | 8 +- .../offroad/settings/sp_priv_osm_settings.cc | 2 +- .../offroad/settings/sp_priv_osm_settings.h | 2 +- .../qt/offroad/settings/sp_priv_settings.cc | 8 +- .../qt/offroad/settings/sp_priv_settings.h | 2 +- .../settings/sp_priv_software_settings.cc | 2 +- .../settings/sp_priv_sunnylink_settings.cc | 14 +- .../settings/sp_priv_sunnypilot_settings.cc | 4 +- .../settings/sp_priv_vehicle_settings.cc | 2 +- .../settings/sp_priv_visuals_settings.cc | 2 +- .../sunnypilot/sp_priv_mads_settings.cc | 6 +- .../qt/onroad/sp_priv_annotated_camera.cc | 20 +- .../qt/onroad/sp_priv_annotated_camera.h | 7 +- .../sunnypilot/qt/onroad/sp_priv_buttons.cc | 59 ++++ .../ui/sunnypilot/qt/onroad/sp_priv_buttons.h | 32 ++ .../qt/onroad/sp_priv_onroad_home.cc | 14 +- .../qt/onroad/sp_priv_onroad_home.h | 10 +- .../qt/onroad/sp_priv_onroad_settings.cc | 26 +- selfdrive/ui/sunnypilot/qt/sp_priv_home.cc | 20 +- .../ui/sunnypilot/qt/sp_priv_offroad_home.cc | 4 +- selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc | 8 +- selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h | 2 +- selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h | 90 ++++++ selfdrive/ui/sunnypilot/sp_priv_ui.cc | 277 +++++++++--------- selfdrive/ui/sunnypilot/sp_priv_ui.h | 165 +++-------- selfdrive/ui/ui.cc | 21 +- selfdrive/ui/ui.h | 26 +- 36 files changed, 485 insertions(+), 464 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc create mode 100644 selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index fbca42da7b..76f83104b5 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -15,18 +15,6 @@ void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrus p.setOpacity(1.0); } -static void drawCustomButtonIcon(QPainter &p, const int btn_size_x, const int btn_size_y, const QPixmap &img, const QBrush &bg, float opacity) { - QPoint center(btn_size_x / 2, btn_size_y / 2); - p.setRenderHint(QPainter::Antialiasing); - p.setOpacity(1.0); // bg dictates opacity of ellipse - p.setPen(Qt::NoPen); - p.setBrush(bg); - p.drawEllipse(center, btn_size_x / 2, btn_size_y / 2); - p.setOpacity(opacity); - p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img); - p.setOpacity(1.0); -} - // ExperimentalButton ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) { setFixedSize(btn_size, btn_size); @@ -59,49 +47,4 @@ void ExperimentalButton::paintEvent(QPaintEvent *event) { QPainter p(this); QPixmap img = experimental_mode ? experimental_img : engage_img; drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0); -} - - -// MapSettingsButton -MapSettingsButton::MapSettingsButton(QWidget *parent) : QPushButton(parent) { - // btn_size: 192 * 80% ~= 152 - // img_size: (152 / 4) * 3 = 114 - setFixedSize(152, 152); - settings_img = loadPixmap("../assets/navigation/icon_directions_outlined.svg", {114, 114}); - - // hidden by default, made visible if map is created (has prime or mapbox token) - setVisible(false); - setEnabled(false); -} - -void MapSettingsButton::paintEvent(QPaintEvent *event) { - QPainter p(this); - drawCustomButtonIcon(p, 152, 152, settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0); -} - - -// OnroadSettingsButton -OnroadSettingsButton::OnroadSettingsButton(QWidget *parent) : QPushButton(parent) { - // btn_size: 192 * 80% ~= 152 - // img_size: (152 / 4) * 3 = 114 - setFixedSize(152, 152); - settings_img = loadPixmap("../assets/navigation/icon_settings.svg", {114, 114}); - - // hidden by default, made visible if Driving Personality / GAC, DLP, DEC, or SLC is enabled - setVisible(false); - setEnabled(false); -} - -void OnroadSettingsButton::paintEvent(QPaintEvent *event) { - QPainter p(this); - drawCustomButtonIcon(p, 152, 152, settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0); -} - -void OnroadSettingsButton::updateState(const UIState &s) { - const auto cp = (*s.sm)["carParams"].getCarParams(); - auto dlp_enabled = s.scene.driving_model_generation == cereal::ModelGeneration::ONE; - bool allow_btn = s.scene.onroad_settings_toggle && (dlp_enabled || hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed()); - - setVisible(allow_btn); - setEnabled(allow_btn); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h index feee1ba639..cfb9f1c5fd 100644 --- a/selfdrive/ui/qt/onroad/buttons.h +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -26,30 +26,4 @@ private: }; -class MapSettingsButton : public QPushButton { - Q_OBJECT - -public: - explicit MapSettingsButton(QWidget *parent = 0); - -private: - void paintEvent(QPaintEvent *event) override; - - QPixmap settings_img; -}; - - -class OnroadSettingsButton : public QPushButton { - Q_OBJECT - -public: - explicit OnroadSettingsButton(QWidget *parent = 0); - void updateState(const UIState &s); - -private: - void paintEvent(QPaintEvent *event) override; - - QPixmap settings_img; -}; - void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity); diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h index 40292dfa9a..046ef62545 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.h +++ b/selfdrive/ui/qt/onroad/onroad_home.h @@ -1,10 +1,11 @@ #pragma once +#include "selfdrive/ui/qt/onroad/annotated_camera.h" #include "selfdrive/ui/qt/onroad/alerts.h" #if SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h" -#define AnnotatedCameraWidget AnnotatedCameraWidgetSP +// #define AnnotatedCameraWidget AnnotatedCameraWidgetSP #else #include "selfdrive/ui/qt/onroad/annotated_camera.h" #endif diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 741b4214fa..40b515434b 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -27,7 +27,7 @@ signals: public slots: void offroadTransition(bool offroad); - virtual void updateState(const UIState &s); + void updateState(const UIState &s); protected: void paintEvent(QPaintEvent *event) override; diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 323befb81f..e821fec60c 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -76,8 +76,9 @@ void MainWindow::closeSettings() { if (uiState()->scene.started) { homeWindow->showSidebar(false); #ifdef SUNNYPILOT + // TODO: validate this and move it away if valid to move // Map is always shown when using navigate on openpilot - if (uiState()->scene.navigate_on_openpilot_deprecated) { + if (uiStateSP()->scene.navigate_on_openpilot_deprecated) { homeWindow->showMapPanel(true); } #endif diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index e8953d5409..b8e1d96946 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -39,11 +39,12 @@ qt_src = [ "sunnypilot/qt/sp_priv_sidebar.cc", "sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc", "sunnypilot/qt/offroad/settings/sp_priv_settings.cc", - "sunnypilot/qt/onroad/sp_priv_onroad_settings.cc", - "sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc", + "sunnypilot/qt/onroad/sp_priv_buttons.cc", "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", + "sunnypilot/qt/onroad/sp_priv_onroad_settings.cc", "sunnypilot/qt/onroad/sp_priv_annotated_camera.cc", - "sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc" + "sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc", + "sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc", ] sp_widgets_src = widgets_src + network_src diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc index de2ebbd3fd..7b438850eb 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc +++ b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc @@ -5,9 +5,9 @@ void MapWindowSP::initLayers() { if (!m_map->layerExists("navLayer")) { - m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiState()->scene.navigate_on_openpilot_deprecated)); + m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiStateSP()->scene.navigate_on_openpilot_deprecated)); } - if ((!m_map->layerExists("buildingsLayer")) && uiState()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage + if ((!m_map->layerExists("buildingsLayer")) && uiStateSP()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage qDebug() << "Initializing buildingsLayer"; QVariantMap buildings; buildings["id"] = "buildingsLayer"; @@ -53,7 +53,7 @@ void MapWindowSP::initLayers() { } void MapWindowSP::updateState(const UIState &s) { - if (!uiState()->scene.started) { + if (!uiStateSP()->scene.started) { return; } const SubMaster &sm = *(s.sm); @@ -72,7 +72,7 @@ void MapWindowSP::updateState(const UIState &s) { auto car_control = sm["carControl"].getCarControl(); bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() && (sm["controlsState"].getControlsState().getEnabled() || car_control.getLatActive() || car_control.getLongActive()); - if (nav_enabled != uiState()->scene.navigate_on_openpilot_deprecated) { + if (nav_enabled != uiStateSP()->scene.navigate_on_openpilot_deprecated) { if (loaded_once) { m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled)); } @@ -80,7 +80,7 @@ void MapWindowSP::updateState(const UIState &s) { emit requestVisible(true); } } - uiState()->scene.navigate_on_openpilot_deprecated = nav_enabled; + uiStateSP()->scene.navigate_on_openpilot_deprecated = nav_enabled; } MapWindow::initLayers(); @@ -88,7 +88,7 @@ void MapWindowSP::updateState(const UIState &s) { void MapWindowSP::offroadTransition(bool offroad) { if (offroad) { - uiState()->scene.navigate_on_openpilot_deprecated = false; + uiStateSP()->scene.navigate_on_openpilot_deprecated = false; } MapWindow::offroadTransition(offroad); diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc index 64b19d3a19..d9acba4a40 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc @@ -25,5 +25,5 @@ void RoleService::handleResponse(const QString &response, bool success) { } emit rolesReady(roles); - uiState()->setSunnylinkRoles(roles); + uiStateSP()->setSunnylinkRoles(roles); } diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc index f43df2861a..f230e59e7c 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc @@ -27,5 +27,5 @@ void UserService::handleResponse(const QString &response, bool success) { } emit usersReady(users); - uiState()->setSunnylinkDeviceUsers(users); + uiStateSP()->setSunnylinkDeviceUsers(users); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc index 9ffeb79633..ca262d4b16 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc @@ -74,7 +74,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindow *parent) : DevicePanel(parent) { }); AddWidgetAt(6, resetParamsBtn); - QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + QObject::connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { if (btn != pair_device && btn != errorBtn) { btn->setEnabled(offroad); @@ -118,18 +118,18 @@ void DevicePanelSP::refreshPin() { } void DevicePanelSP::forceoffroad() { - if (!uiState()->engaged()) { + if (!uiStateSP()->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()) { + if (!uiStateSP()->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()) { + if (!uiStateSP()->engaged()) { params.putBool("ForceOffroad", true); } } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc index 94e489b293..397a97b993 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc @@ -16,7 +16,7 @@ OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { list->addItem(setupOsmDownloadButton(parent)); list->addItem(setupUsStatesButton(parent)); - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { updateLabels(); }); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h index a655d4db35..b40362d82f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h @@ -37,7 +37,7 @@ private: Params mem_params{ Hardware::PC() ? "": "/dev/shm/params"}; std::map toggles; std::optional> mapSizeFuture; - const SubMaster &sm = *uiState()->sm; + const SubMaster &sm = *uiStateSP()->sm; bool is_onroad = false; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc index d9c1012cc9..1e86d9050c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc @@ -125,7 +125,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { accel_personality_setting->showDescription(); // set up uiState update for personality setting - QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanelSP::updateState); + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &TogglesPanelSP::updateState); for (auto &[param, title, desc, icon] : toggle_defs) { auto toggle = new ParamControlSP(param, title, desc, icon, this); @@ -163,7 +163,7 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { }); } -void TogglesPanelSP::updateState(const UIState &s) { +void TogglesPanelSP::updateState(const UIStateSP &s) { const SubMaster &sm = *(s.sm); if (sm.updated("controlsState")) { @@ -171,7 +171,7 @@ void TogglesPanelSP::updateState(const UIState &s) { if (personality != s.scene.personality && s.scene.started && isVisible()) { long_personality_setting->setCheckedButton(static_cast(personality)); } - uiState()->scene.personality = personality; + uiStateSP()->scene.personality = personality; } if (sm.updated("controlsStateSP")) { @@ -179,7 +179,7 @@ void TogglesPanelSP::updateState(const UIState &s) { if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) { accel_personality_setting->setCheckedButton(static_cast(accel_personality)); } - uiState()->scene.accel_personality = accel_personality; + uiStateSP()->scene.accel_personality = accel_personality; } } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h index 04f9db54c5..16e16dc549 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h @@ -13,7 +13,7 @@ public: void showEvent(QShowEvent *event) override; private slots: - void updateState(const UIState &s) override; + void updateState(const UIStateSP &s); private: std::map toggles; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc index 65069aecfc..30d0c2539d 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc @@ -256,7 +256,7 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { } void SoftwarePanelSP::checkNetwork() { - const SubMaster &sm = *(uiState()->sm); + const SubMaster &sm = *(uiStateSP()->sm); const auto device_state = sm["deviceState"].getDeviceState(); const auto network_type = device_state.getNetworkType(); is_wifi = network_type == cereal::DeviceState::NetworkType::WIFI; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc index 364022d6e2..61fd2823ce 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc @@ -17,8 +17,8 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { }); is_sunnylink_enabled = Params().getBool("SunnylinkEnabled"); - connect(uiState(), &UIState::sunnylinkRolesChanged, this, &SunnylinkPanel::updateLabels); - connect(uiState(), &UIState::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updateLabels); + connect(uiStateSP(), &UIStateSP::sunnylinkRolesChanged, this, &SunnylinkPanel::updateLabels); + connect(uiStateSP(), &UIStateSP::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updateLabels); auto list = new ListWidgetSP(this, false); sunnylinkEnabledBtn = new ParamControlSP( @@ -126,7 +126,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { settings_layout->setAlignment(Qt::AlignLeft); list->addItem(settings_layout); - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { is_onroad = !offroad; updateLabels(); }); @@ -187,12 +187,12 @@ void SunnylinkPanel::updateLabels() { is_sunnylink_enabled = Params().getBool("SunnylinkEnabled"); const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A")); - bool is_sub = uiState()->isSunnylinkSponsor() && is_sunnylink_enabled; - auto max_current_sponsor_rule = uiState()->sunnylinkSponsorRole(); + bool is_sub = uiStateSP()->isSunnylinkSponsor() && is_sunnylink_enabled; + auto max_current_sponsor_rule = uiStateSP()->sunnylinkSponsorRole(); auto role_name = max_current_sponsor_rule.getSponsorTierString(); std::optional role_color = max_current_sponsor_rule.getSponsorTierColor(); - bool is_paired = uiState()->isSunnylinkPaired(); - auto paired_users = uiState()->sunnylinkDeviceUsers(); + bool is_paired = uiStateSP()->isSunnylinkPaired(); + auto paired_users = uiStateSP()->sunnylinkDeviceUsers(); //little easter egg for Panda :D if (sunnylinkDongleId == "d689627422cefcbc") { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc index 4a49488aba..467c696e38 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc @@ -329,7 +329,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { // trigger offroadTransition when going onroad/offroad for (const auto& offroadName : toggleOffroad) { if (toggles.find(offroadName) != toggles.end()) { - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { toggles[offroadName]->setEnabled(offroad); }); } @@ -359,7 +359,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { customOffsetsSettings->setEnabled(toggles["CustomOffsets"]->isToggled()); // update "FRICTION" and "LAT_ACCEL_FACTOR" titles when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { friction->setEnabled(offroad || toggles["TorquedOverride"]->isToggled()); lat_accel_factor->setEnabled(offroad || toggles["TorquedOverride"]->isToggled()); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc index d961c3c3ab..ec1ddc6f7e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc @@ -173,7 +173,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge addItem(volkswagenCCOnly); // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { is_onroad = !offroad; hkgSmoothStop->setEnabled(offroad); toyotaTss2LongTune->setEnabled(offroad); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc index 496f47697d..037a806bea 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc @@ -117,7 +117,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { addItem(sidebar_temp_setting); // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { }); QObject::connect(toggles["MapboxFullScreen"], &ToggleControl::toggleFlipped, [=](bool state) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc index 4640bbd21d..75852b9dc4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc @@ -53,11 +53,11 @@ MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { toggles[param.toStdString()] = toggle; // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, toggle, &ParamControlSP::setEnabled); + connect(uiStateSP(), &UIStateSP::offroadTransition, toggle, &ParamControlSP::setEnabled); } // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, dlob_settings, &ButtonParamControlSP::setEnabled); + connect(uiStateSP(), &UIStateSP::offroadTransition, dlob_settings, &ButtonParamControlSP::setEnabled); main_layout->addWidget(new ScrollViewSP(list, this)); } @@ -71,7 +71,7 @@ void MadsSettings::updateToggles() { return; } - const bool is_offroad = !uiState()->scene.started; + const bool is_offroad = !uiStateSP()->scene.started; const bool enable_mads = params.getBool("EnableMads"); const bool enabled = is_offroad && enable_mads; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc index dd4b95dc57..6aa7f61d1f 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc @@ -57,8 +57,8 @@ AnnotatedCameraWidgetSP::AnnotatedCameraWidgetSP(VisionStreamType type, QWidget* void AnnotatedCameraWidgetSP::mousePressEvent(QMouseEvent* e) { bool propagate_event = true; - UIState *s = uiState(); - UIScene &scene = s->scene; + UIStateSP *s = uiStateSP(); + UISceneSP &scene = s->scene; const SubMaster &sm = *(s->sm); const auto longitudinal_plan_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); @@ -103,7 +103,7 @@ void AnnotatedCameraWidgetSP::updateButtonsLayout(bool is_rhd) { } } -void AnnotatedCameraWidgetSP::updateState(const UIState &s) { +void AnnotatedCameraWidgetSP::updateState(const UIStateSP &s) { const int SET_SPEED_NA = 255; const SubMaster &sm = *(s.sm); @@ -1096,7 +1096,7 @@ void AnnotatedCameraWidgetSP::initializeGL() { void AnnotatedCameraWidgetSP::updateFrameMat() { CameraWidget::updateFrameMat(); - UIState *s = uiState(); + UIStateSP *s = uiStateSP(); int w = width(), h = height(); s->fb_w = w; @@ -1112,10 +1112,10 @@ void AnnotatedCameraWidgetSP::updateFrameMat() { .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); } -void AnnotatedCameraWidgetSP::drawLaneLines(QPainter &painter, const UIState *s) { +void AnnotatedCameraWidgetSP::drawLaneLines(QPainter &painter, const UIStateSP *s) { painter.save(); - const UIScene &scene = s->scene; + const UISceneSP &scene = s->scene; SubMaster &sm = *(s->sm); const auto car_state = sm["carState"].getCarState(); @@ -1216,7 +1216,7 @@ void AnnotatedCameraWidgetSP::drawLaneLines(QPainter &painter, const UIState *s) painter.restore(); } -void AnnotatedCameraWidgetSP::drawDriverState(QPainter &painter, const UIState *s) { +void AnnotatedCameraWidgetSP::drawDriverState(QPainter &painter, const UIStateSP *s) { const UIScene &scene = s->scene; painter.save(); @@ -1271,7 +1271,7 @@ void AnnotatedCameraWidgetSP::rocketFuel(QPainter &p) { t[dim_n] = 1.0; t[(int)(ct/ct_n)] = 1.0; - UIState *s = uiState(); + UIStateSP *s = uiStateSP(); float vc_accel0 = (*s->sm)["carState"].getCarState().getAEgo(); static float vc_accel; vc_accel = vc_accel + (vc_accel0 - vc_accel) / 5; @@ -1374,7 +1374,7 @@ void AnnotatedCameraWidgetSP::paintGL() { } void AnnotatedCameraWidgetSP::paintEvent(QPaintEvent *event) { - UIState *s = uiState(); + UIStateSP *s = uiStateSP(); SubMaster &sm = *(s->sm); const double start_draw_t = millis_since_boot(); const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); @@ -1501,6 +1501,6 @@ void AnnotatedCameraWidgetSP::paintEvent(QPaintEvent *event) { void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) { CameraWidget::showEvent(event); - ui_update_params(uiState()); + ui_update_params(uiStateSP()); prev_draw_t = millis_since_boot(); } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h index 0210f259d7..7f778db0da 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h @@ -6,6 +6,7 @@ #include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/widgets/cameraview.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h" #include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h" const int subsign_img_size = 35; @@ -16,7 +17,7 @@ class AnnotatedCameraWidgetSP : public CameraWidget { public: explicit AnnotatedCameraWidgetSP(VisionStreamType type, QWidget* parent = 0); - void updateState(const UIState &s); + void updateState(const UIStateSP &s); MapSettingsButton *map_settings_btn; OnroadSettingsButton *onroad_settings_btn; @@ -187,11 +188,11 @@ protected: void initializeGL() override; void showEvent(QShowEvent *event) override; void updateFrameMat() override; - void drawLaneLines(QPainter &painter, const UIState *s); + void drawLaneLines(QPainter &painter, const UIStateSP *s); void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, int num, const cereal::CarState::Reader &car_data, int chevron_data); void drawHud(QPainter &p); - void drawDriverState(QPainter &painter, const UIState *s); + void drawDriverState(QPainter &painter, const UIStateSP *s); inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc new file mode 100644 index 0000000000..ff6d57ddf9 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc @@ -0,0 +1,59 @@ +#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h" +#include "selfdrive/ui/qt/util.h" + + +static void drawCustomButtonIcon(QPainter &p, const int btn_size_x, const int btn_size_y, const QPixmap &img, const QBrush &bg, float opacity) { + QPoint center(btn_size_x / 2, btn_size_y / 2); + p.setRenderHint(QPainter::Antialiasing); + p.setOpacity(1.0); // bg dictates opacity of ellipse + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(center, btn_size_x / 2, btn_size_y / 2); + p.setOpacity(opacity); + p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img); + p.setOpacity(1.0); +} + + +// OnroadSettingsButton +OnroadSettingsButton::OnroadSettingsButton(QWidget *parent) : QPushButton(parent) { + // btn_size: 192 * 80% ~= 152 + // img_size: (152 / 4) * 3 = 114 + setFixedSize(152, 152); + settings_img = loadPixmap("../assets/navigation/icon_settings.svg", {114, 114}); + + // hidden by default, made visible if Driving Personality / GAC, DLP, DEC, or SLC is enabled + setVisible(false); + setEnabled(false); +} + +void OnroadSettingsButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + drawCustomButtonIcon(p, 152, 152, settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0); +} + +void OnroadSettingsButton::updateState(const UIStateSP &s) { + const auto cp = (*s.sm)["carParams"].getCarParams(); + auto dlp_enabled = s.scene.driving_model_generation == cereal::ModelGeneration::ONE; + bool allow_btn = s.scene.onroad_settings_toggle && (dlp_enabled || hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed()); + + setVisible(allow_btn); + setEnabled(allow_btn); +} + +// MapSettingsButton +MapSettingsButton::MapSettingsButton(QWidget *parent) : QPushButton(parent) { + // btn_size: 192 * 80% ~= 152 + // img_size: (152 / 4) * 3 = 114 + setFixedSize(152, 152); + settings_img = loadPixmap("../assets/navigation/icon_directions_outlined.svg", {114, 114}); + + // hidden by default, made visible if map is created (has prime or mapbox token) + setVisible(false); + setEnabled(false); +} + +void MapSettingsButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + drawCustomButtonIcon(p, 152, 152, settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h new file mode 100644 index 0000000000..f89a97c60c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" + +#include "selfdrive/ui/ui.h" + +class OnroadSettingsButton : public QPushButton { + Q_OBJECT + + public: + explicit OnroadSettingsButton(QWidget *parent = 0); + void updateState(const UIStateSP &s); + +private: + void paintEvent(QPaintEvent *event) override; + + QPixmap settings_img; +}; + + +class MapSettingsButton : public QPushButton { + Q_OBJECT + + public: + explicit MapSettingsButton(QWidget *parent = 0); + +private: + void paintEvent(QPaintEvent *event) override; + + QPixmap settings_img; +}; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc index a6d2c2f71b..65fd8513f1 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc @@ -19,10 +19,10 @@ OnroadWindowSP::OnroadWindowSP(QWidget *parent) : OnroadWindow(parent) { CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); split->insertWidget(0, map_render); //TODO: We MIGHT to override the split variable because it is added on onroad_home.cc and we need to access it before. I am not sure so we will need to test it before } - QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindowSP::primeChanged); + QObject::connect(uiStateSP(), &UIStateSP::primeChanged, this, &OnroadWindowSP::primeChanged); } -void OnroadWindowSP::updateState(const UIState &s) { +void OnroadWindowSP::updateState(const UIStateSP &s) { if (!s.scene.started) { return; } @@ -39,13 +39,13 @@ void OnroadWindowSP::updateState(const UIState &s) { void OnroadWindowSP::mousePressEvent(QMouseEvent* e) { #ifdef ENABLE_MAPS - UIState *s = uiState(); + UIState *s = uiStateSP(); UIScene &scene = s->scene; if (map != nullptr && !isOnroadSettingsVisible()) { if (wakeScreenTimeout()) { // Switch between map and sidebar when using navigate on openpilot bool sidebarVisible = geometry().x() > 0; - bool show_map = uiState()->scene.navigate_on_openpilot_deprecated ? sidebarVisible : !sidebarVisible; + bool show_map = uiStateSP()->scene.navigate_on_openpilot_deprecated ? sidebarVisible : !sidebarVisible; updateMapSize(scene); map->setVisible(show_map && !map->isVisible()); } @@ -72,7 +72,7 @@ void OnroadWindowSP::createMapWidget() { QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings); nvg->map_settings_btn->setEnabled(true); - m->setFixedWidth(uiState()->scene.mapbox_fullscreen ? topWidget(this)->width() : + m->setFixedWidth(uiStateSP()->scene.mapbox_fullscreen ? topWidget(this)->width() : topWidget(this)->width() / 2 - UI_BORDER_SIZE); split->insertWidget(0, m); @@ -102,7 +102,7 @@ void OnroadWindowSP::createOnroadSettingsWidget() { void OnroadWindowSP::offroadTransition(bool offroad) { if (!offroad) { #ifdef ENABLE_MAPS - if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { + if (map == nullptr && (uiStateSP()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { createMapWidget(); } #endif @@ -114,7 +114,7 @@ void OnroadWindowSP::offroadTransition(bool offroad) { OnroadWindow::offroadTransition(offroad); // Carry on with the parent class offroadTransition method } -void OnroadWindowSP::updateMapSize(const UIScene &scene) { +void OnroadWindowSP::updateMapSize(const UISceneSP &scene) { map->setFixedWidth(scene.mapbox_fullscreen ? topWidget(this)->width() : topWidget(this)->width() / 2 - UI_BORDER_SIZE); split->insertWidget(0, map); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h index 6bf05d3d5d..0ef9e25701 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h @@ -21,8 +21,8 @@ public: void onroadSettingsPanelNotRequested() { if (onroad_settings) onroad_settings->setVisible(false); } bool wakeScreenTimeout() { - if ((uiState()->scene.sleep_btn != 0 && uiState()->scene.sleep_btn_opacity != 0) || - (uiState()->scene.sleep_time != 0 && uiState()->scene.onroadScreenOff != -2)) { + if ((uiStateSP()->scene.sleep_btn != 0 && uiStateSP()->scene.sleep_btn_opacity != 0) || + (uiStateSP()->scene.sleep_time != 0 && uiStateSP()->scene.onroadScreenOff != -2)) { return true; } return false; @@ -36,7 +36,7 @@ private: void createMapWidget(); void createOnroadSettingsWidget(); void mousePressEvent(QMouseEvent* e) override; - // AnnotatedCameraWidget *nvg; //TODO: override? this is defined on onroad_home.h + AnnotatedCameraWidgetSP *nvg; QWidget *map = nullptr; QWidget *onroad_settings = nullptr; @@ -44,7 +44,7 @@ private: protected slots: void offroadTransition(bool offroad) override; - void updateState(const UIState &s) override; + void updateState(const UIStateSP &s); void primeChanged(bool prime); - void updateMapSize(const UIScene &scene); + void updateMapSize(const UISceneSP &scene); }; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc index efe5aa7766..e25586f770 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc @@ -120,7 +120,7 @@ OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) } void OnroadSettings::changeDynamicLaneProfile() { - UIScene &scene = uiState()->scene; + UISceneSP &scene = uiStateSP()->scene; bool can_change = scene.driving_model_generation == cereal::ModelGeneration::ONE; if (can_change) { scene.dynamic_lane_profile++; @@ -131,8 +131,8 @@ void OnroadSettings::changeDynamicLaneProfile() { } void OnroadSettings::changeGapAdjustCruise() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp); if (can_change) { scene.longitudinal_personality--; @@ -143,8 +143,8 @@ void OnroadSettings::changeGapAdjustCruise() { } void OnroadSettings::changeAccelerationPersonality() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp); if (can_change) { scene.longitudinal_accel_personality--; @@ -155,8 +155,8 @@ void OnroadSettings::changeAccelerationPersonality() { } void OnroadSettings::changeDynamicPersonality() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp); if (can_change) { scene.dynamic_personality = !scene.dynamic_personality; @@ -166,8 +166,8 @@ void OnroadSettings::changeDynamicPersonality() { } void OnroadSettings::changeDynamicExperimentalControl() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp); if (can_change) { scene.dynamic_experimental_control = !scene.dynamic_experimental_control; @@ -177,8 +177,8 @@ void OnroadSettings::changeDynamicExperimentalControl() { } void OnroadSettings::changeSpeedLimitControl() { - UIScene &scene = uiState()->scene; - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + UISceneSP &scene = uiStateSP()->scene; + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed(); int max_policy = SpeedLimitPolicySettings::speed_limit_policy_texts.size() - 1; @@ -213,7 +213,7 @@ void OnroadSettings::refresh() { param_watcher->addParam("DynamicExperimentalControl"); param_watcher->addParam("EnableSlc"); - UIScene &scene = uiState()->scene; + UISceneSP &scene = uiStateSP()->scene; // Update live params on Feature Status on camera view scene.dynamic_lane_profile = std::atoi(params.get("DynamicLaneProfile").c_str()); scene.longitudinal_personality = std::atoi(params.get("LongitudinalPersonality").c_str()); @@ -227,7 +227,7 @@ void OnroadSettings::refresh() { setUpdatesEnabled(false); - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + const auto cp = (*uiStateSP()->sm)["carParams"].getCarParams(); // Dynamic Lane Profile dlp_widget->updateDynamicLaneProfile("DynamicLaneProfile"); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc index 77a779484d..18f43cdb1b 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc @@ -22,22 +22,22 @@ void HomeWindowSP::showMapPanel(bool show) { void HomeWindowSP::updateState(const UIState &s) { //OVERRIDE HomeWindow::updateState(s); - uiState()->scene.map_visible = onroad->isMapVisible(); - uiState()->scene.onroad_settings_visible = onroad->isOnroadSettingsVisible(); + uiStateSP()->scene.map_visible = onroad->isMapVisible(); + uiStateSP()->scene.onroad_settings_visible = onroad->isOnroadSettingsVisible(); } void HomeWindowSP::mousePressEvent(QMouseEvent* e) { HomeWindow::mousePressEvent(e); // We call it first so that we could potentially override whatever was done by parent - if (uiState()->scene.started) { - if (uiState()->scene.onroadScreenOff != -2) { - uiState()->scene.touched2 = true; - QTimer::singleShot(500, []() { uiState()->scene.touched2 = false; }); + if (uiStateSP()->scene.started) { + if (uiStateSP()->scene.onroadScreenOff != -2) { + uiStateSP()->scene.touched2 = true; + QTimer::singleShot(500, []() { uiStateSP()->scene.touched2 = false; }); } - if (uiState()->scene.button_auto_hide) { - uiState()->scene.touch_to_wake = true; - uiState()->scene.sleep_btn_fading_in = true; - QTimer::singleShot(500, []() { uiState()->scene.touch_to_wake = false; }); + if (uiStateSP()->scene.button_auto_hide) { + uiStateSP()->scene.touch_to_wake = true; + uiStateSP()->scene.sleep_btn_fading_in = true; + QTimer::singleShot(500, []() { uiStateSP()->scene.touch_to_wake = false; }); } } diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc index 5221c5431e..ea643e36d8 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc @@ -17,8 +17,8 @@ void OffroadHomeSP::replaceLeftWidget(){ new_left_widget->addWidget(new PrimeAdWidget); new_left_widget->setStyleSheet("border-radius: 10px;"); - new_left_widget->setCurrentIndex((uiState()->hasPrime() || custom_mapbox) ? 0 : 1); - connect(uiState(), &UIState::primeChanged, [=](bool prime) { + new_left_widget->setCurrentIndex((uiStateSP()->hasPrime() || custom_mapbox) ? 0 : 1); + connect(uiStateSP(), &UIStateSP::primeChanged, [=](bool prime) { new_left_widget->setCurrentIndex((prime || custom_mapbox) ? 0 : 1); }); ReplaceWidget(left_widget, new_left_widget); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc index f9a391b09f..e682d87b69 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc @@ -8,9 +8,13 @@ #include "common/params.h" -SidebarSP::SidebarSP(QWidget *parent) : Sidebar(parent) {} +SidebarSP::SidebarSP(QWidget *parent) : Sidebar(parent) { + // Because I know that stock sidebar makes this connection, I will disconnect it and connect it to the new updateState function + QObject::disconnect(uiState(), &UIState::uiUpdate, this, &Sidebar::updateState); + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &SidebarSP::updateState); +} -void SidebarSP::updateState(const UIState &s) { +void SidebarSP::updateState(const UIStateSP &s) { if (!isVisible()) return; Sidebar::updateState(s); auto &sm = *(s.sm); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h index 9bd7a55f58..46207ff791 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h @@ -17,7 +17,7 @@ public: explicit SidebarSP(QWidget* parent = 0); public slots: - void updateState(const UIState &s) override; + void updateState(const UIStateSP &s); protected: const QColor progress_color = QColor(3, 132, 252); diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h b/selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h new file mode 100644 index 0000000000..1dd25f31f8 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h @@ -0,0 +1,90 @@ +#pragma once +#include "selfdrive/ui/ui.h" + +const float DRIVING_PATH_WIDE = 0.9; +const float DRIVING_PATH_NARROW = 0.25; + +typedef struct UISceneSP : public UIScene { + cereal::ControlsState::Reader controlsState; + + // Debug UI + bool show_debug_ui; + + // Speed limit control + bool speed_limit_control_enabled; + int speed_limit_control_policy; + double last_speed_limit_sign_tap; + + QPolygonF track_edge_vertices; + QPolygonF lane_barrier_vertices[2]; + + bool navigate_on_openpilot_deprecated = false; + cereal::AccelerationPersonality accel_personality; + + bool map_on_left; + + int dynamic_lane_profile; + bool dynamic_lane_profile_status = true; + + bool visual_brake_lights; + + int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; + bool onroadScreenOffEvent; + int sleep_time = -1; + bool touched2 = false; + + bool stand_still_timer; + + bool hide_vego_ui, true_vego_ui; + + int chevron_data; + + bool gac; + int longitudinal_personality; + int longitudinal_accel_personality; + + bool map_visible; + int dev_ui_info; + bool live_torque_toggle; + + bool touch_to_wake = false; + int sleep_btn = -1; + bool sleep_btn_fading_in = false; + int sleep_btn_opacity = 20; + bool button_auto_hide; + + bool reverse_dm_cam; + + bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; + float e2eX[13] = {0}; + + int sidebar_temp_options; + + float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; + float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 + + bool onroad_settings_visible; + + bool map_3d_buildings; + + bool torqued_override; + + bool dynamic_experimental_control; + + int speed_limit_control_engage_type; + + bool mapbox_fullscreen; + + bool speed_limit_warning_flash; + int speed_limit_warning_type; + int speed_limit_warning_value_offset; + + bool custom_driving_model_valid; + cereal::ModelGeneration driving_model_generation; + uint32_t driving_model_capabilities; + + bool feature_status_toggle; + bool onroad_settings_toggle; + + bool dynamic_personality; +} UISceneSP; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.cc b/selfdrive/ui/sunnypilot/sp_priv_ui.cc index 5470a1ceee..5c96ea52a8 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.cc +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.cc @@ -1,32 +1,30 @@ #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include +#include #include #include +#include "common/transformations/orientation.hpp" +#include "common/params.h" +#include "common/swaglog.h" +#include "common/util.h" +#include "common/watchdog.h" #include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" +#include "system/hardware/hw.h" + +#define BACKLIGHT_DT 0.05 +#define BACKLIGHT_TS 10.00 // Projects a point in car to space to the corresponding point in full frame // image space. -static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { - const float margin = 1000.0f; - const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; - - const vec3 pt = (vec3){{in_x, in_y, in_z}}; - const vec3 Ep = matvecmul3(s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib, pt); - const vec3 KEp = matvecmul3(s->scene.wide_cam ? ECAM_INTRINSIC_MATRIX : FCAM_INTRINSIC_MATRIX, Ep); - - // Project. - QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]}); - if (clip_region.contains(point)) { - *out = point; - return true; - } - return false; +static bool sp_calib_frame_to_full_frame(const UIStateSP *s, float in_x, float in_y, float in_z, QPointF *out) { + return calib_frame_to_full_frame(s, in_x, in_y, in_z, out, 1000.0f); } -void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, +//todo: revisit, we could reuse from OG update_model. But this is an overload in this case. +void update_line_data(const UIStateSP *s, const cereal::XYZTData::Reader &line, float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); QPointF left, right; @@ -35,8 +33,8 @@ void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera if (line_x[i] < 0) continue; - bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); - bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); + bool l = sp_calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off_left, &left); + bool r = sp_calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off_right, &right); if (l && r) { // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { @@ -48,9 +46,9 @@ void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, } } -void update_model(UIStateSP *s, - const cereal::ModelDataV2::Reader &model) { - UISceneSP &scene = s->scene_sp; +//todo: revisit, we could reuse from OG update_model +void sp_update_model(UIStateSP *s, const cereal::ModelDataV2::Reader &model) { + UISceneSP &scene = s->scene; auto model_position = model.getPosition(); float max_distance = std::clamp(*(model_position.getX().end() - 1), MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); @@ -89,13 +87,12 @@ void update_model(UIStateSP *s, update_line_data(s, model_position, 1.0 - scene.mads_path_range * scene.mads_path_scale - 0.1 * scene.mads_path_scale, 1.22, 1.22, &scene.track_edge_vertices, max_idx, false); } -static void update_state(UIStateSP *s) { +static void sp_update_state(UIStateSP *s) { + update_state(s); SubMaster &sm = *(s->sm); - UISceneSP &scene = s->scene_sp; + UISceneSP &scene = s->scene; auto params = Params(); - scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition && !params.getBool("ForceOffroad"); - // TODO: SP - Set this dynamically on init with manual toggle or driving model selection if (sm.updated("lateralPlanSPDEPRECATED")) { scene.dynamic_lane_profile_status = sm["lateralPlanSPDEPRECATED"].getLateralPlanSPDEPRECATED().getDynamicLaneProfileStatus(); @@ -116,64 +113,61 @@ static void update_state(UIStateSP *s) { } } -void ui_update_params(UIStateSP *s) { - UISceneSP &scene = s->scene_sp; +void sp_ui_update_params(UIStateSP *s) { + ui_update_params(s); auto params = Params(); - scene.map_on_left = params.getBool("NavSettingLeftSide"); + s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - scene.visual_brake_lights = params.getBool("BrakeLights"); - scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); - scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); - scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); - scene.stand_still_timer = params.getBool("StandStillTimer"); - scene.show_debug_ui = params.getBool("ShowDebugUI"); - scene.hide_vego_ui = params.getBool("HideVEgoUi"); - scene.true_vego_ui = params.getBool("TrueVEgoUi"); - scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); - scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); - scene.button_auto_hide = params.getBool("ButtonAutoHide"); - scene.reverse_dm_cam = params.getBool("ReverseDmCam"); - scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); - scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); - scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); - scene.map_3d_buildings = params.getBool("Map3DBuildings"); - scene.live_torque_toggle = params.getBool("LiveTorque"); - scene.torqued_override = params.getBool("TorquedOverride"); - scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); - scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); - scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); - scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); - scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); - scene.speed_limit_control_enabled = params.getBool("EnableSlc"); - scene.feature_status_toggle = params.getBool("FeatureStatus"); - scene.onroad_settings_toggle = params.getBool("OnroadSettings"); + s->scene.visual_brake_lights = params.getBool("BrakeLights"); + s->scene.onroadScreenOff = std::atoi(params.get("OnroadScreenOff").c_str()); + s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); + s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); + s->scene.stand_still_timer = params.getBool("StandStillTimer"); + s->scene.show_debug_ui = params.getBool("ShowDebugUI"); + s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); + s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); + s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); + s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); + s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); + s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); + s->scene.e2e_long_alert_light = params.getBool("EndToEndLongAlertLight"); + s->scene.e2e_long_alert_lead = params.getBool("EndToEndLongAlertLead"); + s->scene.e2e_long_alert_ui = params.getBool("EndToEndLongAlertUI"); + s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); + s->scene.live_torque_toggle = params.getBool("LiveTorque"); + s->scene.torqued_override = params.getBool("TorquedOverride"); + s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); + s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); + s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); + s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); + s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); + s->scene.speed_limit_control_enabled = params.getBool("EnableSlc"); + s->scene.feature_status_toggle = params.getBool("FeatureStatus"); + s->scene.onroad_settings_toggle = params.getBool("OnroadSettings"); // Handle Onroad Screen Off params - if (scene.onroadScreenOff > 0) { - scene.osoTimer = scene.onroadScreenOff * 60 * UI_FREQ; - } else if (scene.onroadScreenOff == 0) { - scene.osoTimer = 30 * UI_FREQ; - } else if (scene.onroadScreenOff == -1) { - scene.osoTimer = 15 * UI_FREQ; + if (s->scene.onroadScreenOff > 0) { + s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ; + } else if (s->scene.onroadScreenOff == 0) { + s->scene.osoTimer = 30 * UI_FREQ; + } else if (s->scene.onroadScreenOff == -1) { + s->scene.osoTimer = 15 * UI_FREQ; } else { - scene.osoTimer = -1; + s->scene.osoTimer = -1; } } void UIStateSP::updateStatus() { + UIState::updateStatus(); auto params = Params(); - if (scene_sp.started && sm->updated("controlsState")) { - auto controls_state = (*sm)["controlsState"].getControlsState(); - auto car_control = (*sm)["carControl"].getCarControl(); - auto car_state = (*sm)["carState"].getCarState(); - auto mads_enabled = car_state.getMadsEnabled(); - auto state = controls_state.getState(); - if (state == cereal::ControlsState::OpenpilotState::PRE_ENABLED || state == cereal::ControlsState::OpenpilotState::OVERRIDING) { - status_sp = STATUS_OVERRIDE_SP; - } else { - status_sp = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED_SP : STATUS_MADS_SP : STATUS_DISENGAGED_SP; + auto car_control = (*sm)["carControl"].getCarControl(); + auto car_state = (*sm)["carState"].getCarState(); + auto mads_enabled = car_state.getMadsEnabled(); + if (scene.started && sm->updated("controlsState")) { + if (status != STATUS_OVERRIDE) { + status = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED : STATUS_MADS : STATUS_DISENGAGED; } - + if (mads_enabled != last_mads_enabled) { mads_path_state = true; } @@ -191,79 +185,104 @@ void UIStateSP::updateStatus() { } } } - scene_sp.mads_path_scale = mads_path_count * (1 / mads_path_timestep); + scene.mads_path_scale = mads_path_count * (1 / mads_path_timestep); } - if (scene_sp.started) { + if (scene.started) { // Auto hide UI button state machine { - if (scene_sp.button_auto_hide) { - if (scene_sp.touch_to_wake) { - scene_sp.sleep_btn = 30 * UI_FREQ; - } else if (scene_sp.sleep_btn > 0) { - scene_sp.sleep_btn--; - } else if (scene_sp.sleep_btn == -1) { - scene_sp.sleep_btn = 30 * UI_FREQ; + if (scene.button_auto_hide) { + if (scene.touch_to_wake) { + scene.sleep_btn = 30 * UI_FREQ; + } else if (scene.sleep_btn > 0) { + scene.sleep_btn--; + } else if (scene.sleep_btn == -1) { + scene.sleep_btn = 30 * UI_FREQ; } // Check if the sleep button should be fading in - if (scene_sp.sleep_btn_fading_in) { + if (scene.sleep_btn_fading_in) { // Increase the opacity of the sleep button by a small amount - if (scene_sp.sleep_btn_opacity < 20) { - scene_sp.sleep_btn_opacity+= 10; + if (scene.sleep_btn_opacity < 20) { + scene.sleep_btn_opacity+= 10; } - if (scene_sp.sleep_btn_opacity >= 20) { + if (scene.sleep_btn_opacity >= 20) { // If the opacity has reached its maximum value, stop fading in - scene_sp.sleep_btn_fading_in = false; - scene_sp.sleep_btn_opacity = 20; + scene.sleep_btn_fading_in = false; + scene.sleep_btn_opacity = 20; } - } else if (scene_sp.sleep_btn == 0) { + } else if (scene.sleep_btn == 0) { // Fade out the sleep button as before - if (scene_sp.sleep_btn_opacity > 0) { - scene_sp.sleep_btn_opacity-= 2; + if (scene.sleep_btn_opacity > 0) { + scene.sleep_btn_opacity-= 2; } } else { // Set the opacity of the sleep button to its maximum value - scene_sp.sleep_btn_opacity = 20; + scene.sleep_btn_opacity = 20; } } else { - scene_sp.sleep_btn_opacity = 20; + scene.sleep_btn_opacity = 20; } } // Onroad Screen Off Brightness + Timer + Global Brightness { - if (scene_sp.onroadScreenOff != -2 && scene_sp.touched2) { - scene_sp.sleep_time = scene_sp.osoTimer; - } else if (scene_sp.onroadScreenOff != -2 && - ((scene_sp.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && - ((scene_sp.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene_sp.onroadScreenOffEvent) || - (scene_sp.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { - scene_sp.sleep_time = scene_sp.osoTimer; - } else if (scene_sp.sleep_time > 0 && scene_sp.onroadScreenOff != -2) { - scene_sp.sleep_time--; - } else if (scene_sp.sleep_time == -1 && scene_sp.onroadScreenOff != -2) { - scene_sp.sleep_time = scene_sp.osoTimer; + if (scene.onroadScreenOff != -2 && scene.touched2) { + scene.sleep_time = scene.osoTimer; + } else if (scene.onroadScreenOff != -2 && + ((scene.controlsState.getAlertSize() != cereal::ControlsState::AlertSize::NONE) && + ((scene.controlsState.getAlertStatus() == cereal::ControlsState::AlertStatus::NORMAL && scene.onroadScreenOffEvent) || + (scene.controlsState.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL)))) { + scene.sleep_time = scene.osoTimer; + } else if (scene.sleep_time > 0 && scene.onroadScreenOff != -2) { + scene.sleep_time--; + } else if (scene.sleep_time == -1 && scene.onroadScreenOff != -2) { + scene.sleep_time = scene.osoTimer; } } } if (sm->frame % UI_FREQ == 0) { // Update every 1 Hz - scene_sp.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); + scene.sidebar_temp_options = std::atoi(params.get("SidebarTemperatureOptions").c_str()); } - scene_sp.brightness = std::atoi(params.get("BrightnessControl").c_str()); + scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); } UIStateSP::UIStateSP(QObject *parent) : UIState(parent) { +// todo: Can we simply append to SM? + sm = std::make_unique>({ + "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", + "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", + "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", + "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", + "controlsStateSP", "modelV2SP" + }); + + Params params; + language = QString::fromStdString(params.get("LanguageSetting")); + auto prime_value = params.get("PrimeType"); + if (!prime_value.empty()) { + prime_type = static_cast(std::atoi(prime_value.c_str())); + } + + // update timer + timer = new QTimer(this); + QObject::connect(timer, &QTimer::timeout, this, &UIStateSP::update); + timer->start(1000 / UI_FREQ); } void UIStateSP::update() { - update_state(this); + update_sockets(this); + sp_update_state(this); updateStatus(); - UIState::update(); + + if (sm->frame % UI_FREQ == 0) { + watchdog_kick(nanos_since_boot()); + } emit uiUpdate(*this); } + void UIStateSP::setSunnylinkRoles(const std::vector& roles) { sunnylinkRoles = roles; emit sunnylinkRolesChanged(roles); @@ -274,39 +293,19 @@ void UIStateSP::setSunnylinkDeviceUsers(const std::vector& users) { emit sunnylinkDeviceUsersChanged(users); } -DeviceSP::DeviceSP(QObject *parent) : Device(parent) { - QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &DeviceSP::update); -} - -void DeviceSP::update(const UIStateSP &s) { - updateBrightness(s); - updateWakefulness(s); +DeviceSP::DeviceSP(QObject *parent) : Device(parent){ } +//todo: revisit this void DeviceSP::updateBrightness(const UIStateSP &s) { - UISceneSP scene = s.scene_sp; - float clipped_brightness = offroad_brightness; - if (scene.started && scene.light_sensor > 0) { - clipped_brightness = scene.light_sensor; - - // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm - if (clipped_brightness <= 8) { - clipped_brightness = (clipped_brightness / 903.3); - } else { - clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0); - } - - // Scale back to 10% to 100% - clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); - } - + Device::updateBrightness(s); int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; - } else if (scene.started && scene.sleep_time == 0 && scene.onroadScreenOff != -2) { - brightness = scene.onroadScreenOffBrightness * 0.01 * brightness; - } else if (scene.brightness) { - brightness = scene.brightness * 0.99; + } else if (s.scene.started && s.scene.sleep_time == 0 && s.scene.onroadScreenOff != -2) { + brightness = s.scene.onroadScreenOffBrightness * 0.01 * brightness; + } else if (s.scene.brightness) { + brightness = s.scene.brightness * 0.99; } if (brightness != last_brightness) { @@ -318,11 +317,11 @@ void DeviceSP::updateBrightness(const UIStateSP &s) { } UIStateSP *uiStateSP() { - static UIStateSP ui_state_sp; - return &ui_state_sp; + static UIStateSP ui_state; + return &ui_state; } DeviceSP *deviceSP() { - static DeviceSP _device_sp; - return &_device_sp; -} + static DeviceSP _device; + return &_device; +} \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.h b/selfdrive/ui/sunnypilot/sp_priv_ui.h index 6f6afdde6e..fcb9940d97 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.h +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.h @@ -1,15 +1,18 @@ #pragma once +#include +#include +#include + #include "selfdrive/ui/ui.h" -#include -#include - -#include - -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h" +#include "cereal/messaging/messaging.h" +#include "common/timing.h" +#include "qt/network/sunnylink/models/sp_priv_role_model.h" +#include "qt/network/sunnylink/models/sp_priv_sponsor_role_model.h" +#include "qt/network/sunnylink/models/sp_priv_user_model.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h" +#include "system/hardware/hw.h" const int UI_ROAD_NAME_MARGIN_X = 14; @@ -25,23 +28,15 @@ struct FeatureStatusColor { const QStringList slc_list_color = { "#ffffff", "#ffffff", "#fcff4b", "#4bff66", "#fcff4b" }; }; -const float DRIVING_PATH_WIDE = 0.9; -const float DRIVING_PATH_NARROW = 0.25; -typedef enum UIStatusSP { - STATUS_DISENGAGED_SP = STATUS_DISENGAGED, - STATUS_OVERRIDE_SP = STATUS_OVERRIDE, - STATUS_ENGAGED_SP = STATUS_ENGAGED, - STATUS_MADS_SP, -} UIStatusSP; - -const QColor bg_colors_sp [] = { - [STATUS_DISENGAGED_SP] = QColor(0x17, 0x33, 0x49, 0xc8), - [STATUS_OVERRIDE_SP] = QColor(0x91, 0x9b, 0x95, 0xf1), - [STATUS_ENGAGED_SP] = QColor(0x00, 0xc8, 0x00, 0xf1), - [STATUS_MADS_SP] = QColor(0x00, 0xc8, 0xc8, 0xf1), +const QColor sp_bg_colors [] = { + [STATUS_DISENGAGED] = bg_colors[STATUS_DISENGAGED], + [STATUS_OVERRIDE] = bg_colors[STATUS_OVERRIDE], + [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), + [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), }; + const QColor tcs_colors [] = { [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1), @@ -49,101 +44,18 @@ const QColor tcs_colors [] = { [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1), }; -typedef struct UISceneSP : UIScene { - cereal::ControlsState::Reader controlsState; - - // Debug UI - bool show_debug_ui; - - // Speed limit control - bool speed_limit_control_enabled; - int speed_limit_control_policy; - double last_speed_limit_sign_tap; - - // modelV2 - QPolygonF track_edge_vertices; - QPolygonF lane_barrier_vertices[2]; - - bool navigate_on_openpilot_deprecated = false; - cereal::AccelerationPersonality accel_personality; - - bool map_on_left; - - int dynamic_lane_profile; - bool dynamic_lane_profile_status = true; - - bool visual_brake_lights; - - int onroadScreenOff, osoTimer, brightness, onroadScreenOffBrightness, awake; - bool onroadScreenOffEvent; - int sleep_time = -1; - bool touched2 = false; - - bool stand_still_timer; - - bool hide_vego_ui, true_vego_ui; - - int chevron_data; - - bool gac; - int longitudinal_personality; - int longitudinal_accel_personality; - - bool map_visible; - int dev_ui_info; - bool live_torque_toggle; - - bool touch_to_wake = false; - int sleep_btn = -1; - bool sleep_btn_fading_in = false; - int sleep_btn_opacity = 20; - bool button_auto_hide; - - bool reverse_dm_cam; - - bool e2e_long_alert_light, e2e_long_alert_lead, e2e_long_alert_ui; - float e2eX[13] = {0}; - - int sidebar_temp_options; - - float mads_path_scale = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; - float mads_path_range = DRIVING_PATH_WIDE - DRIVING_PATH_NARROW; // 0.9 - 0.25 = 0.65 - - bool onroad_settings_visible; - - bool map_3d_buildings; - - bool torqued_override; - - bool dynamic_experimental_control; - - int speed_limit_control_engage_type; - - bool mapbox_fullscreen; - - bool speed_limit_warning_flash; - int speed_limit_warning_type; - int speed_limit_warning_value_offset; - - bool custom_driving_model_valid; - cereal::ModelGeneration driving_model_generation; - uint32_t driving_model_capabilities; - - bool feature_status_toggle; - bool onroad_settings_toggle; - - bool dynamic_personality; -} UISceneSP; - class UIStateSP : public UIState { Q_OBJECT public: - UIStateSP(QObject* parent = nullptr); - void updateStatus(); + UIStateSP(QObject* parent = 0); + void updateStatus() override; + void setSunnylinkRoles(const std::vector &roles); void setSunnylinkDeviceUsers(const std::vector &users); + UISceneSP scene = {}; + inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } inline bool isSunnylinkAdmin() const { return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { @@ -158,9 +70,9 @@ public: inline SponsorRoleModel sunnylinkSponsorRole() const { std::optional sponsorRoleWithHighestTier = std::nullopt; for (const auto &role : sunnylinkRoles) { - if (role.roleType != RoleType::Sponsor) + if(role.roleType != RoleType::Sponsor) continue; - + if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { sponsorRoleWithHighestTier = sponsorRole; } @@ -177,17 +89,15 @@ public: }); } - UIStatusSP status_sp; - UISceneSP scene_sp = {}; - signals: - void uiUpdate(const UIStateSP &s); void sunnylinkRoleChanged(bool subscriber); void sunnylinkRolesChanged(std::vector roles); void sunnylinkDeviceUsersChanged(std::vector users); + void uiUpdate(const UIStateSP &s); private slots: - void update(); + void update() override; + private: std::vector sunnylinkRoles = {}; @@ -197,30 +107,21 @@ private: bool mads_path_state = false; float mads_path_timestep = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] - -public slots: - void update(const UIStateSP &s); }; +//TODO: maybe need to redeclare but the sp? UIStateSP *uiStateSP(); +// device management class class DeviceSP : public Device { Q_OBJECT public: - DeviceSP(QObject *parent = nullptr); - void updateBrightness(const UIStateSP &s); - + DeviceSP(QObject *parent = 0); protected: - void updateBrightness(const UIState &s); - -public slots: - void update(const UIStateSP &s); + void updateBrightness(const UIStateSP &s); }; -DeviceSP *deviceSP(); - -void update_model(UIState *s, - const cereal::ModelDataV2::Reader &model); -void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, +void sp_update_model(UIStateSP *s, const cereal::ModelDataV2::Reader &model); +void update_line_data(const UIStateSP *s, const cereal::XYZTData::Reader &line, float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 7559087e86..89bd4e2efd 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -11,7 +11,6 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" -#include "qt/util.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 @@ -19,8 +18,7 @@ // Projects a point in car to space to the corresponding point in full frame // image space. -static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { - const float margin = 500.0f; +bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out, const float margin) { const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; const vec3 pt = (vec3){{in_x, in_y, in_z}}; @@ -146,11 +144,11 @@ void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &drivers } } -static void update_sockets(UIState *s) { +void update_sockets(UIState *s) { s->sm->update(0); } -static void update_state(UIState *s) { +void update_state(UIState *s) { SubMaster &sm = *(s->sm); UIScene &scene = s->scene; @@ -244,9 +242,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", - "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "clocks", "longitudinalPlanSP", "liveMapDataSP", - "carControl", "lateralPlanSPDEPRECATED", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", - "controlsStateSP", "modelV2SP" + "wideRoadCameraState", "managerState", "clocks", }); Params params; @@ -267,6 +263,10 @@ void UIState::update() { update_state(this); updateStatus(); + if (std::getenv("PRIME_TYPE")) { + setPrimeType((PrimeType)atoi(std::getenv("PRIME_TYPE"))); + } + if (sm->frame % UI_FREQ == 0) { watchdog_kick(nanos_since_boot()); } @@ -292,7 +292,6 @@ Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT setAwake(true); resetInteractiveTimeout(); - RETURN_IF_SUNNYPILOT QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); } @@ -318,7 +317,7 @@ void Device::resetInteractiveTimeout(int timeout) { } void Device::updateBrightness(const UIState &s) { - float clipped_brightness = offroad_brightness; + clipped_brightness = offroad_brightness; if (s.scene.started && s.scene.light_sensor > 0) { clipped_brightness = s.scene.light_sensor; @@ -367,4 +366,4 @@ UIState *uiState() { Device *device() { static Device _device; return &_device; -} +} \ No newline at end of file diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index e3e9756200..9f3a082b2e 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -45,13 +45,22 @@ constexpr vec3 default_face_kpts_3d[] = { {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, }; +//Example of a macro +#ifdef SUNNYPILOT +#define EXTRA_UI_STATES STATUS_MADS, STATUS_LA +#else +#define EXTRA_UI_STATES +#endif typedef enum UIStatus { STATUS_DISENGAGED, STATUS_OVERRIDE, STATUS_ENGAGED, + EXTRA_UI_STATES } UIStatus; + + enum PrimeType { UNKNOWN = -2, UNPAIRED = -1, @@ -108,7 +117,7 @@ class UIState : public QObject { public: UIState(QObject* parent = 0); - void updateStatus(); + virtual void updateStatus(); inline bool engaged() const { return scene.started && (*sm)["controlsState"].getControlsState().getEnabled(); } @@ -135,12 +144,14 @@ signals: void primeTypeChanged(PrimeType prime_type); protected slots: - void update(); + virtual void update(); -private: +protected: QTimer *timer; - bool started_prev = false; PrimeType prime_type = PrimeType::UNKNOWN; + +private: + bool started_prev = false; }; UIState *uiState(); @@ -166,9 +177,10 @@ protected: FirstOrderFilter brightness_filter; QFuture brightness_future; - void updateBrightness(const UIState &s); + virtual void updateBrightness(const UIState &s); void updateWakefulness(const UIState &s); void setAwake(bool on); + float clipped_brightness; signals: void displayPowerChanged(bool on); @@ -189,3 +201,7 @@ void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &drivers void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); + +bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out, float margin=500.0f); +void update_state(UIState *s); +void update_sockets(UIState *s); \ No newline at end of file From 62e8791b1e8705b3f1aeafa5501a87122ecc28f6 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 19 Jul 2024 18:51:42 +0200 Subject: [PATCH 450/575] Update UIState references in Sunnypilot code Updated to use new UIStateSP class in Sunnypilot's map and onroad functionality. The previous UIState class has been replaced, to fall in line with other changes in the Sunnypilot system, adapting the function arguments and variable assignments accordingly. --- selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h | 3 ++- selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h index 9bdafd33ac..3b2049de7b 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h +++ b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h @@ -1,6 +1,7 @@ #pragma once #include "selfdrive/ui/qt/maps/map.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include @@ -18,7 +19,7 @@ private: } protected slots: - void updateState(const UIState &s); + void updateState(const UIStateSP &s); public slots: void offroadTransition(bool offroad); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc index 65fd8513f1..9a91d34264 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc @@ -39,8 +39,8 @@ void OnroadWindowSP::updateState(const UIStateSP &s) { void OnroadWindowSP::mousePressEvent(QMouseEvent* e) { #ifdef ENABLE_MAPS - UIState *s = uiStateSP(); - UIScene &scene = s->scene; + UIStateSP *s = uiStateSP(); + UISceneSP &scene = s->scene; if (map != nullptr && !isOnroadSettingsVisible()) { if (wakeScreenTimeout()) { // Switch between map and sidebar when using navigate on openpilot From ee229c2b709b37e6a49a5c4266bf6dd435ed639b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 19 Jul 2024 15:52:19 -0400 Subject: [PATCH 451/575] maps: Use UIStateSP and disconnect --- selfdrive/ui/qt/maps/map.cc | 2 -- selfdrive/ui/qt/maps/map.h | 2 +- selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc | 9 +++++++-- selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h | 3 ++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index d165ae1edc..490eb118ca 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -113,7 +113,6 @@ void MapWindow::initLayers() { } void MapWindow::updateState(const UIState &s) { -#ifdef SUNNYPILOT if (!uiState()->scene.started) { return; } @@ -127,7 +126,6 @@ void MapWindow::updateState(const UIState &s) { initializeGL(); } prev_time_valid = sm.valid("clocks"); -#endif if (sm.updated("liveLocationKalman")) { auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman(); diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 3c78ef39d9..36b385d42d 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -78,7 +78,7 @@ private: void updateDestinationMarker(); uint64_t route_rcv_frame = 0; -protected slots: +public slots: void updateState(const UIState &s); public slots: diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc index 7b438850eb..926151cae7 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc +++ b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc @@ -3,6 +3,11 @@ #include "common/swaglog.h" #include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +MapWindowSP::MapWindowSP(const QMapLibre::Settings &settings) : MapWindow(settings) { + QObject::disconnect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState); + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &MapWindowSP::updateState); +} + void MapWindowSP::initLayers() { if (!m_map->layerExists("navLayer")) { m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiStateSP()->scene.navigate_on_openpilot_deprecated)); @@ -52,7 +57,7 @@ void MapWindowSP::initLayers() { } } -void MapWindowSP::updateState(const UIState &s) { +void MapWindowSP::updateState(const UIStateSP &s) { if (!uiStateSP()->scene.started) { return; } @@ -83,7 +88,7 @@ void MapWindowSP::updateState(const UIState &s) { uiStateSP()->scene.navigate_on_openpilot_deprecated = nav_enabled; } - MapWindow::initLayers(); + MapWindow::updateState(s); } void MapWindowSP::offroadTransition(bool offroad) { diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h index 3b2049de7b..6c97d23756 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h +++ b/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h @@ -9,7 +9,8 @@ class MapWindowSP : public MapWindow { Q_OBJECT public: - explicit MapWindowSP(const QMapLibre::Settings &settings) : MapWindow(settings) {} + explicit MapWindowSP(const QMapLibre::Settings &); + ~MapWindowSP(); private: void initLayers(); From 8735a0a776f592a3418841e1742aca1c8b414441 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 20 Jul 2024 11:57:56 +0200 Subject: [PATCH 452/575] Refactor MainWindow and add support for SunnyPilot The MainWindow constructor now accepts optional parameters to allow for more flexibility in window management. This enabled us to add support for a variant of the UI called SunnyPilot. Separate header and source files `sp_priv_window.h` and `sp_priv_window.cc` have been created for this variant, which has its own logic but inherits from the same MainWindow class. --- selfdrive/ui/main.cc | 7 +++++- selfdrive/ui/qt/window.cc | 16 ++++--------- selfdrive/ui/qt/window.h | 25 ++++++++------------ selfdrive/ui/sunnypilot/SConscript | 1 + selfdrive/ui/sunnypilot/qt/sp_priv_window.cc | 18 ++++++++++++++ selfdrive/ui/sunnypilot/qt/sp_priv_window.h | 17 +++++++++++++ 6 files changed, 57 insertions(+), 27 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_window.cc create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_window.h diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc index 4903a3db3d..c47b6d7920 100644 --- a/selfdrive/ui/main.cc +++ b/selfdrive/ui/main.cc @@ -6,7 +6,13 @@ #include "system/hardware/hw.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" + +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/sp_priv_window.h" +#define MainWindow MainWindowSP +#else #include "selfdrive/ui/qt/window.h" +#endif int main(int argc, char *argv[]) { setpriority(PRIO_PROCESS, 0, -20); @@ -22,7 +28,6 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); a.installTranslator(&translator); - MainWindow w; setMainWindow(&w); a.installEventFilter(&w); diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index e821fec60c..d9ff51763b 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -4,16 +4,18 @@ #include "system/hardware/hw.h" -MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { +MainWindow::MainWindow(QWidget* parent, HomeWindow* hw, SettingsWindow* sw, OnboardingWindow* ow) + : QWidget(parent), + homeWindow(hw ? hw : new HomeWindow(this)), + settingsWindow(sw ? sw : new SettingsWindow(this)), + onboardingWindow(ow ? ow : new OnboardingWindow(this)) { main_layout = new QStackedLayout(this); main_layout->setMargin(0); - homeWindow = new HomeWindow(this); main_layout->addWidget(homeWindow); QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings); QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings); - settingsWindow = new SettingsWindow(this); main_layout->addWidget(settingsWindow); QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings); QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() { @@ -24,7 +26,6 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { homeWindow->showDriverView(true); }); - onboardingWindow = new OnboardingWindow(this); main_layout->addWidget(onboardingWindow); QObject::connect(onboardingWindow, &OnboardingWindow::onboardingDone, [=]() { main_layout->setCurrentWidget(homeWindow); @@ -75,13 +76,6 @@ void MainWindow::closeSettings() { if (uiState()->scene.started) { homeWindow->showSidebar(false); -#ifdef SUNNYPILOT - // TODO: validate this and move it away if valid to move - // Map is always shown when using navigate on openpilot - if (uiStateSP()->scene.navigate_on_openpilot_deprecated) { - homeWindow->showMapPanel(true); - } -#endif } } diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index af67644a34..7bd3bb9f0a 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -3,31 +3,26 @@ #include #include +#include "selfdrive/ui/qt/home.h" #include "selfdrive/ui/qt/offroad/onboarding.h" - -#ifdef SUNNYPILOT - #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" - #include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" - #define HomeWindow HomeWindowSP - #define SettingsWindow SettingsWindowSP -#else - #include "selfdrive/ui/qt/offroad/settings.h" - #include "selfdrive/ui/qt/home.h" -#endif +#include "selfdrive/ui/qt/offroad/settings.h" class MainWindow : public QWidget { Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(QWidget *parent = nullptr) : MainWindow(parent, nullptr, nullptr, nullptr) {} + +protected: + explicit MainWindow(QWidget* parent, HomeWindow* hw = nullptr, SettingsWindow* sw = nullptr, OnboardingWindow* ow = nullptr); + HomeWindow *homeWindow; + SettingsWindow *settingsWindow; + virtual void closeSettings(); private: bool eventFilter(QObject *obj, QEvent *event) override; void openSettings(int index = 0, const QString ¶m = ""); - void closeSettings(); QStackedLayout *main_layout; - HomeWindow *homeWindow; - SettingsWindow *settingsWindow; OnboardingWindow *onboardingWindow; -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index b8e1d96946..6ef7344337 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -1,5 +1,6 @@ widgets_src = [ "sunnypilot/sp_priv_ui.cc", + "sunnypilot/qt/sp_priv_window.cc", "sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc", diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc new file mode 100644 index 0000000000..a6b7fac15a --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc @@ -0,0 +1,18 @@ +#include "sp_priv_window.h" + +MainWindowSP::MainWindowSP(QWidget *parent) : MainWindow(parent, new HomeWindowSP(parent), + new SettingsWindowSP(parent), new OnboardingWindow(parent)) { + homeWindow = dynamic_cast(MainWindow::homeWindow); + settingsWindow = dynamic_cast(MainWindow::settingsWindow); +} + +void MainWindowSP::closeSettings() { + MainWindow::closeSettings(); + if (uiState()->scene.started) { + homeWindow->showSidebar(false); + if (uiStateSP()->scene.navigate_on_openpilot_deprecated) { + homeWindow->showMapPanel(true); + } + } +} + diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_window.h b/selfdrive/ui/sunnypilot/qt/sp_priv_window.h new file mode 100644 index 0000000000..cf67270cf3 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_window.h @@ -0,0 +1,17 @@ +#pragma once + +#include "selfdrive/ui/qt/window.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" + +class MainWindowSP : public MainWindow { + Q_OBJECT + +public: +explicit MainWindowSP(QWidget *parent = 0); + +private: + HomeWindowSP *homeWindow; + SettingsWindowSP *settingsWindow; + void closeSettings() override; +}; From 3366bda92aa878348bee786c3be48072441cd5de Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 07:39:46 -0400 Subject: [PATCH 453/575] handle states properly --- README.md | 2 +- selfdrive/car/hyundai/carstate.py | 2 -- selfdrive/car/hyundai/interface.py | 17 +++++++++-------- selfdrive/car/interfaces.py | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c78052ad6b..fef54c6e9c 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ The goal of Modified Assistive Driving Safety (MADS) is to enhance the user driv * `SET-` button enables ACC/SCC * `CANCEL` button only disables ACC/SCC * `CRUISE (MAIN)` must be `ON` to use ACC/SCC -* `CRUISE (MAIN)` button disables sunnypilot completely when `OFF` **(strictly enforced in panda safety code)** +* `CRUISE (MAIN)` button disables ACC/SCC completely when `OFF` **(strictly enforced in panda safety code)** ### Disengage Lateral ALC on Brake Press Mode toggle Dedicated toggle to handle Lateral state on brake pedal press and release: diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 5261fc8477..d6e9279035 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -216,8 +216,6 @@ class CarState(CarStateBase): if self.CP.spFlags & HyundaiFlagsSP.SP_CAN_LFA_BTN: self.lfa_enabled = cp.vl["BCM_PO_11"]["LFA_Pressed"] - self.mads_enabled = False if not self.control_initialized else ret.cruiseState.available - if self.CP.spFlags & HyundaiFlagsSP.SP_NAV_MSG or self.CP.spFlags & HyundaiFlagsSP.SP_LKAS12: self._update_traffic_signals(cp, cp_cam) ret.cruiseState.speedLimit = self._calculate_speed_limit() * speed_conv diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index e801b47771..d59541fa66 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -207,11 +207,11 @@ class CarInterface(CarInterfaceBase): buttonEvents = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT) + 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.mads_enabled = False if not self.mads_main_toggle else self.CS.mads_enabled - if ret.cruiseState.available: if not self.CP.pcmCruiseSpeed: if self.CS.prev_main_buttons == 1: @@ -220,15 +220,16 @@ class CarInterface(CarInterfaceBase): elif self.CS.prev_cruise_buttons == 4: if self.CS.cruise_buttons[-1] != 4: self.accEnabled = True - if self.enable_mads: - if not self.CS.prev_mads_enabled and self.CS.mads_enabled: - self.CS.madsEnabled = True - if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: - self.CS.madsEnabled = not self.CS.madsEnabled - self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: self.CS.madsEnabled = False + if self.enable_mads: + if not self.CS.prev_mads_enabled and self.CS.mads_enabled: + self.CS.madsEnabled = True + if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: + self.CS.madsEnabled = not self.CS.madsEnabled + self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) + 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): diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 5fe64832c8..55b1adfccf 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -472,7 +472,7 @@ class CarInterfaceBase(ABC): events.add(EventName.spReverseGear) elif cs_out.vEgo >= 5: events.add(EventName.reverseGear) - if not cs_out.cruiseState.available: + if not cs_out.cruiseState.available and cs_out.cruiseState.enabled: events.add(EventName.wrongCarMode) if cs_out.espDisabled: events.add(EventName.espDisabled) From 3232d4d01ed4830b6e9ba1ebe195fbbd51d8084f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 15:14:50 -0400 Subject: [PATCH 454/575] Revert "more, and comfort band" This reverts commit 91dadaafd1071575ed5cf084a917d0bcafbcd666. --- selfdrive/car/hyundai/carcontroller.py | 13 ++++--------- selfdrive/car/hyundai/hyundaican.py | 6 +++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 290d32852c..a2d240ffcd 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -109,9 +109,6 @@ class CarController(CarControllerBase): self.jerk_l = 0.0 self.jerk_u = 0.0 self.jerkStartLimit = 2.0 - self.cb_upper = 0.0 - self.cb_lower = 0.0 - self.jerk_count = 0.0 def calculate_lead_distance(self, hud_control: car.CarControl.HUDControl) -> float: lead_one = self.sm["radarState"].leadOne @@ -297,8 +294,6 @@ class CarController(CarControllerBase): 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)] * 25) - else: - self.make_jerk(CS, accel, actuators) # 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: @@ -313,7 +308,7 @@ class CarController(CarControllerBase): use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled and CS.out.cruiseState.enabled, accel, self.jerk_l, self.jerk_u, int(self.frame / 2), hud_control, set_speed_in_units, stopping, - CC.cruiseControl.override, use_fca, CS, escc, self.CP, self.lead_distance, self.cb_lower, self.cb_upper)) + CC.cruiseControl.override, use_fca, CS, escc, self.CP, self.lead_distance)) # 20 Hz LFA MFA message if self.frame % 5 == 0 and self.CP.flags & HyundaiFlags.SEND_LFA.value: @@ -502,7 +497,7 @@ class CarController(CarControllerBase): self.jerk = self.jerk * 0.9 + accel_diff * 0.1 return self.jerk - def make_jerk(self, CS, accel, actuators): + def make_jerk(self, CS, accel, actuators, hud_control): jerk = self.cal_jerk(accel, actuators) a_error = accel - CS.out.aEgo jerk = jerk + (a_error * 2.0) @@ -516,7 +511,7 @@ class CarController(CarControllerBase): self.jerk_u = jerkLimit self.jerk_l = jerkLimit self.jerk_count = 0 - elif actuators.longControlState == LongCtrlState.stopping: + elif actuators.longControlState == LongCtrlState.stopping or hud_control.softHold > 0: self.jerk_u += 0.1 if self.jerk_u < 1.5 else -0.1 self.jerk_l += 0.1 if self.jerk_l < 1.0 else -0.1 self.jerk_count = 0 @@ -535,7 +530,7 @@ class CarController(CarControllerBase): self.jerk_u = jerkLimit self.jerk_l = jerkLimit self.jerk_count = 0 - elif actuators.longControlState == LongCtrlState.stopping: + elif actuators.longControlState == LongCtrlState.stopping or hud_control.softHold > 0: self.jerk_u += 0.1 if self.jerk_u < 0.5 else -0.1 self.jerk_l += 0.1 if self.jerk_l < 1.0 else -0.1 self.jerk_count = 0 diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index f3776c46ef..b28a94e055 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -131,7 +131,7 @@ def create_lfahda_mfc(packer, enabled, lat_active, lateral_paused, blinking_icon return packer.make_can_msg("LFAHDA_MFC", 0, values) def create_acc_commands(packer, enabled, accel, lower_jerk, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, - CS, escc, CP, lead_distance, cb_lower, cb_upper): + CS, escc, CP, lead_distance): commands = [] scc11_values = { @@ -172,8 +172,8 @@ def create_acc_commands(packer, enabled, accel, lower_jerk, upper_jerk, idx, hud commands.append(packer.make_can_msg("SCC12", 0, scc12_values)) scc14_values = { - "ComfortBandUpper": cb_upper, # stock usually is 0 but sometimes uses higher values - "ComfortBandLower": cb_lower, # stock usually is 0 but sometimes uses higher values + "ComfortBandUpper": 0.0, # stock usually is 0 but sometimes uses higher values + "ComfortBandLower": 0.0, # stock usually is 0 but sometimes uses higher values "JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values "JerkLowerLimit": lower_jerk, # stock usually is 0.5 but sometimes uses higher values "ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage From 84274e2fc09c1329cdee052b7fa186c467a56027 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 15:14:51 -0400 Subject: [PATCH 455/575] Revert "add lower" This reverts commit dac5e529b6b737567fb4e5e4113c042b2faf77fe. --- selfdrive/car/hyundai/hyundaican.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index b28a94e055..83ceb38659 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -175,7 +175,7 @@ def create_acc_commands(packer, enabled, accel, lower_jerk, upper_jerk, idx, hud "ComfortBandUpper": 0.0, # stock usually is 0 but sometimes uses higher values "ComfortBandLower": 0.0, # stock usually is 0 but sometimes uses higher values "JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values - "JerkLowerLimit": lower_jerk, # stock usually is 0.5 but sometimes uses higher values + "JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values "ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage "ObjGap": get_object_gap(lead_distance), # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead } From e923d773f8b1189207c5fdb4f79bf626b6772af8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 15:14:51 -0400 Subject: [PATCH 456/575] Revert "HKG Longitudinal: Jerk limits" This reverts commit 4821f1bbc81293ecc64f67e35dc0827502de8c5f. --- selfdrive/car/hyundai/carcontroller.py | 65 +------------------------- selfdrive/car/hyundai/hyundaican.py | 2 +- 2 files changed, 3 insertions(+), 64 deletions(-) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index a2d240ffcd..b42f61f1b5 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -1,7 +1,7 @@ from cereal import car import cereal.messaging as messaging from openpilot.common.conversions import Conversions as CV -from openpilot.common.numpy_fast import clip, interp +from openpilot.common.numpy_fast import clip from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL from opendbc.can.packer import CANPacker @@ -105,11 +105,6 @@ class CarController(CarControllerBase): self.custom_stock_planner_speed = self.param_s.get_bool("CustomStockLongPlanner") self.lead_distance = 0 - self.jerk = 0.0 - self.jerk_l = 0.0 - self.jerk_u = 0.0 - self.jerkStartLimit = 2.0 - def calculate_lead_distance(self, hud_control: car.CarControl.HUDControl) -> float: lead_one = self.sm["radarState"].leadOne lead_two = self.sm["radarState"].leadTwo @@ -306,7 +301,7 @@ class CarController(CarControllerBase): # TODO: unclear if this is needed jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0 use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value - can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled and CS.out.cruiseState.enabled, accel, self.jerk_l, self.jerk_u, int(self.frame / 2), + can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled and CS.out.cruiseState.enabled, accel, jerk, int(self.frame / 2), hud_control, set_speed_in_units, stopping, CC.cruiseControl.override, use_fca, CS, escc, self.CP, self.lead_distance)) @@ -484,59 +479,3 @@ class CarController(CarControllerBase): cruise_button = self.get_button_control(CS, self.final_speed_kph, v_cruise_kph_prev) # MPH/KPH based button presses return cruise_button - - def cal_jerk(self, accel, actuators): - if actuators.longControlState == LongCtrlState.off: - accel_diff = 0.0 - elif actuators.longControlState == LongCtrlState.stopping:# or hud_control.softHold > 0: - accel_diff = 0.0 - else: - accel_diff = accel - self.accel_last - - accel_diff /= DT_CTRL - self.jerk = self.jerk * 0.9 + accel_diff * 0.1 - return self.jerk - - def make_jerk(self, CS, accel, actuators, hud_control): - jerk = self.cal_jerk(accel, actuators) - a_error = accel - CS.out.aEgo - jerk = jerk + (a_error * 2.0) - - if self.CP.carFingerprint in CANFD_CAR: - startingJerk = 0.5 #self.jerkStartLimit - jerkLimit = 5.0 - self.jerk_count += DT_CTRL - jerk_max = interp(self.jerk_count, [0, 1.5, 2.5], [startingJerk, startingJerk, jerkLimit]) - if actuators.longControlState == LongCtrlState.off: - self.jerk_u = jerkLimit - self.jerk_l = jerkLimit - self.jerk_count = 0 - elif actuators.longControlState == LongCtrlState.stopping or hud_control.softHold > 0: - self.jerk_u += 0.1 if self.jerk_u < 1.5 else -0.1 - self.jerk_l += 0.1 if self.jerk_l < 1.0 else -0.1 - self.jerk_count = 0 - else: - #self.jerk_u = min(max(2.5, jerk * 2.0), jerk_max) - #self.jerk_l = min(max(2.0, -jerk * 3.0), jerkLimit) - self.jerk_u = min(max(0.5, jerk * 2.0), jerk_max) - self.jerk_l = min(max(1.0, -jerk * 3.0), jerkLimit) - else: - startingJerk = self.jerkStartLimit - jerkLimit = 5.0 - self.jerk_count += DT_CTRL - jerk_max = interp(self.jerk_count, [0, 1.5, 2.5], [startingJerk, startingJerk, jerkLimit]) - self.cb_upper = self.cb_lower = 0 - if actuators.longControlState == LongCtrlState.off: - self.jerk_u = jerkLimit - self.jerk_l = jerkLimit - self.jerk_count = 0 - elif actuators.longControlState == LongCtrlState.stopping or hud_control.softHold > 0: - self.jerk_u += 0.1 if self.jerk_u < 0.5 else -0.1 - self.jerk_l += 0.1 if self.jerk_l < 1.0 else -0.1 - self.jerk_count = 0 - else: - self.jerk_u = self.jerk_u * 0.8 + min(max(0.5, jerk * 2.0), jerk_max) * 0.2 - self.jerk_l = self.jerk_l * 0.8 + min(max(0.5, -jerk * 2.0), jerkLimit) * 0.2 - #self.jerk_l = min(max(1.2, -jerk * 2.0), jerkLimit) ## 1.0으로 하니 덜감속, 1.5로하니 너무감속, 1.2로 한번해보자(231228) - self.cb_upper = clip(0.9 + accel * 0.2, 0, 1.2) - self.cb_lower = clip(0.8 + accel * 0.2, 0, 1.2) diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 83ceb38659..a2f44716ef 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -130,7 +130,7 @@ def create_lfahda_mfc(packer, enabled, lat_active, lateral_paused, blinking_icon } return packer.make_can_msg("LFAHDA_MFC", 0, values) -def create_acc_commands(packer, enabled, accel, lower_jerk, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, +def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CS, escc, CP, lead_distance): commands = [] From fb06ddf85b593ded9e687883756a5fe6fb3b4e13 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 17:24:11 -0400 Subject: [PATCH 457/575] do this universally --- selfdrive/car/chrysler/carstate.py | 4 +--- selfdrive/car/ford/carstate.py | 4 +--- selfdrive/car/gm/carstate.py | 1 - selfdrive/car/honda/carstate.py | 1 - selfdrive/car/hyundai/carstate.py | 1 - selfdrive/car/interfaces.py | 2 ++ selfdrive/car/mazda/carstate.py | 4 +--- selfdrive/car/nissan/carstate.py | 1 - selfdrive/car/subaru/carstate.py | 1 - selfdrive/car/toyota/carstate.py | 1 - 10 files changed, 5 insertions(+), 15 deletions(-) diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index 6d949e9f9a..a58681175e 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -75,16 +75,14 @@ class CarState(CarStateBase): ) # Buttons - button_events = [] 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 - button_events.append(event) + self.button_events.append(event) self.button_states[button.event_type] = state - self.button_events = button_events # button presses ret.leftBlinker, ret.rightBlinker = ret.leftBlinkerOn, ret.rightBlinkerOn = self.update_blinker_from_stalk(200, cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1, diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index fb06bf5f5f..e52454f81d 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -87,16 +87,14 @@ class CarState(CarStateBase): ret.gearShifter = GearShifter.drive # Buttons - button_events = [] 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 - button_events.append(event) + self.button_events.append(event) self.button_states[button.event_type] = state - self.button_events = button_events # safety ret.stockFcw = bool(cp_cam.vl["ACCDATA_3"]["FcwVisblWarn_B_Rq"]) diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 78092c23bd..2fc1ac024b 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -33,7 +33,6 @@ class CarState(CarStateBase): self.prev_lkas_enabled = False def update(self, pt_cp, cam_cp, loopback_cp): - self.button_events = [] ret = car.CarState.new_message() self.prev_cruise_buttons = self.cruise_buttons diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index c50c6d08fb..ae8746d958 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -111,7 +111,6 @@ class CarState(CarStateBase): self.dash_speed_seen = False def update(self, cp, cp_cam, cp_body): - self.button_events = [] ret = car.CarState.new_message() # car params diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 4066a988ec..d8329c5ba7 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -63,7 +63,6 @@ class CarState(CarStateBase): self._speed_limit_clu = 0 def update(self, cp, cp_cam): - self.button_events = [] if self.CP.carFingerprint in CANFD_CAR: return self.update_canfd(cp, cp_cam) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index f2d921e108..c55c292934 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -426,6 +426,8 @@ class CarInterfaceBase(ABC): if cp is not None: cp.update_strings(can_strings) + self.CS.button_events = [] + # get CarState ret = self._update(c) diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index 6fd287fa7c..e5808238f8 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -55,16 +55,14 @@ class CarState(CarStateBase): ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) # Buttons - button_events = [] 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 - button_events.append(event) + self.button_events.append(event) self.button_states[button.event_type] = state - self.button_events = button_events ret.genericToggle = bool(cp.vl["BLINK_INFO"]["HIGH_BEAMS"]) ret.leftBlindspot = cp.vl["BSM"]["LEFT_BS_STATUS"] != 0 diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index 8dba6dcdf6..17c6abf681 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -24,7 +24,6 @@ class CarState(CarStateBase): self.distance_button = 0 def update(self, cp, cp_adas, cp_cam): - self.button_events = [] ret = car.CarState.new_message() self.prev_distance_button = self.distance_button diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 1a48901dde..5cff425efe 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -20,7 +20,6 @@ class CarState(CarStateBase): self.prev_lkas_enabled = None def update(self, cp, cp_cam, cp_body): - self.button_events = [] ret = car.CarState.new_message() self.prev_mads_enabled = self.mads_enabled diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 53bf7da4c2..456282982b 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -85,7 +85,6 @@ class CarState(CarStateBase): self.frame = 0 def update(self, cp, cp_cam): - self.button_events = [] ret = car.CarState.new_message() self.prev_mads_enabled = self.mads_enabled From 5facba229a431f6f7fe227401c833d4b181a9526 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 18:23:38 -0400 Subject: [PATCH 458/575] encapsulate in method --- selfdrive/car/__init__.py | 14 ++++++++++---- selfdrive/car/chrysler/interface.py | 22 +++++++--------------- selfdrive/car/ford/interface.py | 21 +++++++-------------- selfdrive/car/gm/interface.py | 24 ++++++++---------------- selfdrive/car/honda/interface.py | 14 ++++++++------ selfdrive/car/hyundai/interface.py | 22 +++++++--------------- selfdrive/car/interfaces.py | 4 ++-- selfdrive/car/mazda/interface.py | 22 +++++++--------------- selfdrive/car/nissan/interface.py | 15 ++++----------- selfdrive/car/subaru/interface.py | 15 ++++----------- selfdrive/car/toyota/interface.py | 15 ++++----------- selfdrive/car/volkswagen/carstate.py | 4 ++-- selfdrive/car/volkswagen/interface.py | 22 +++++++--------------- 13 files changed, 77 insertions(+), 137 deletions(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 6aebd2bd90..293ee2387c 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -43,10 +43,16 @@ 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 +def create_mads_event(mads_enabled: bool, prev_mads_enabled: bool, + mads_event_lock: bool) -> tuple[list[capnp.lib.capnp._DynamicStructBuilder], bool]: + events: list[capnp.lib.capnp._DynamicStructBuilder] = [] + + mads_changed = prev_mads_enabled != mads_enabled + if (mads_changed and mads_event_lock) or (not mads_changed and not mads_event_lock): + events.append(car.CarState.ButtonEvent(pressed=mads_event_lock, type=ButtonType.altButton1)) + mads_event_lock = not mads_event_lock + + return events, mads_event_lock def gen_empty_fingerprint(): diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index bc80530011..056128dc2e 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -93,15 +93,14 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events = [ + ret.buttonEvents = [ *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, - self.CS.button_events, c.vCruise, + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, 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 @@ -120,7 +119,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 self.CS.button_events): + if any(b.type == ButtonType.cancel for b in ret.buttonEvents): 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) @@ -138,17 +137,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: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *ret.buttonEvents, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + ] # events events = self.create_common_events(ret, c, extra_gears=[car.CarState.GearShifter.low], pcm_enable=False) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 68db0f096d..b2413ed14e 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -74,15 +74,14 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events = [ + ret.buttonEvents = [ *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, - self.CS.button_events, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -95,7 +94,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 self.CS.button_events): + if any(b.type == ButtonType.cancel for b in ret.buttonEvents): 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) @@ -108,16 +107,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: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *ret.buttonEvents, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.manumatic], pcm_enable=False) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 2e2f63fd44..c445e90118 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -210,7 +210,7 @@ class CarInterface(CarInterfaceBase): # 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: - self.CS.button_events = [ + ret.buttonEvents = [ *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, @@ -221,11 +221,10 @@ 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 self.CS.button_events): + if any(b.type == ButtonType.accelCruise and b.pressed for b in ret.buttonEvents): self.CS.accEnabled = True - self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, - self.CS.button_events, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -238,7 +237,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 self.CS.button_events): + if any(b.type == ButtonType.cancel for b in ret.buttonEvents): 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) @@ -251,17 +250,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: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *ret.buttonEvents, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # 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 52984035c4..2036a0f4a9 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -6,7 +6,7 @@ from openpilot.common.numpy_fast import interp from openpilot.selfdrive.car.honda.hondacan import CanBus from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, CruiseSettings, HondaFlags, CAR, HONDA_BOSCH, \ HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS -from openpilot.selfdrive.car import create_button_events, get_safety_config +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.disable_ecu import disable_ecu @@ -268,15 +268,14 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) self.sp_update_params() - self.CS.button_events = [ + ret.buttonEvents = [ *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), ] 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, - self.CS.button_events, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -289,7 +288,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 self.CS.button_events): + if any(b.type == ButtonType.cancel for b in ret.buttonEvents): 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 +303,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 = self.CS.button_events + ret.buttonEvents = [ + *ret.buttonEvents, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # 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 60411abc39..3610b2a744 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -205,10 +205,9 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT) + ret.buttonEvents = 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, - self.CS.button_events, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, c.vCruise) self.CS.mads_enabled = False if not self.mads_main_toggle else self.CS.mads_enabled @@ -231,7 +230,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 self.CS.button_events): + if any(b.type == ButtonType.cancel for b in ret.buttonEvents): 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: @@ -242,17 +241,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: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *ret.buttonEvents, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # 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 c55c292934..2fe8a982ac 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -560,12 +560,12 @@ class CarInterfaceBase(ABC): return mads_enabled - def get_sp_v_cruise_non_pcm_state(self, cs_out, acc_enabled, button_events, vCruise, + def get_sp_v_cruise_non_pcm_state(self, cs_out, acc_enabled, vCruise, enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise), resume_button=(ButtonType.accelCruise, ButtonType.resumeCruise)): if cs_out.cruiseState.available: - for b in button_events: + for b in cs_out.buttonEvents: if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed: if b.type in enable_buttons and not b.pressed: acc_enabled = True diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index f8919d13e7..6481c021c4 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -40,15 +40,14 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() # TODO: add button types for inc and dec - self.CS.button_events = [ + ret.buttonEvents = [ *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, - self.CS.button_events, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -61,7 +60,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 self.CS.button_events): + if any(b.type == ButtonType.cancel for b in ret.buttonEvents): 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) @@ -74,17 +73,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: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *ret.buttonEvents, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + ] # events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 707eab06c0..26467d9f80 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -60,17 +60,10 @@ class CarInterface(CarInterfaceBase): be.type = ButtonType.cancel self.CS.button_events.append(be) - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *self.CS.button_events, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # 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 967d426fe4..d12aa44e18 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -150,17 +150,10 @@ class CarInterface(CarInterfaceBase): be.type = ButtonType.cancel self.CS.button_events.append(be) - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *self.CS.button_events, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # 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 7b808f5d61..85b895b3fd 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -251,17 +251,10 @@ class CarInterface(CarInterfaceBase): be.type = ButtonType.cancel self.CS.button_events.append(be) - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *ret.buttonEvents, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # 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 1dc4c0e430..2c183aabde 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -150,7 +150,7 @@ class CarState(CarStateBase): # 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"]) - self.button_events = self.create_button_events(pt_cp, self.CCP.BUTTONS) + ret.buttonEvents = 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. @@ -256,7 +256,7 @@ class CarState(CarStateBase): # 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"]) - self.button_events = self.create_button_events(pt_cp, self.CCP.BUTTONS) + ret.buttonEvents = 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 fe042984e2..505ea7db31 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -117,8 +117,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, - self.CS.button_events, c.vCruise, + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, c.vCruise, enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise)) if ret.cruiseState.available: @@ -131,7 +130,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 self.CS.button_events): + if any(b.type == ButtonType.cancel for b in ret.buttonEvents): 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,19 +142,12 @@ class CarInterface(CarInterfaceBase): self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled 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)) + gap_button=any(b.type == ButtonType.gapAdjustCruise and b.pressed for b in ret.buttonEvents)) - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + ret.buttonEvents = [ + *ret.buttonEvents, + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], pcm_enable=False, From 93226506fa79efb7aacd50567d4e98094ebdc2f9 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 18:32:50 -0400 Subject: [PATCH 459/575] encapsulate cancel event in method --- selfdrive/car/__init__.py | 9 +++++++++ selfdrive/car/nissan/interface.py | 10 ++-------- selfdrive/car/subaru/interface.py | 10 ++-------- selfdrive/car/toyota/interface.py | 10 ++-------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 293ee2387c..06fa120eee 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -43,6 +43,15 @@ def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, ca return events +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(mads_enabled: bool, prev_mads_enabled: bool, mads_event_lock: bool) -> tuple[list[capnp.lib.capnp._DynamicStructBuilder], bool]: events: list[capnp.lib.capnp._DynamicStructBuilder] = [] diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 26467d9f80..2176a4a107 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, create_mads_event, create_cancel_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.nissan.values import CAR @@ -53,15 +53,9 @@ 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 - self.CS.button_events.append(be) - ret.buttonEvents = [ *self.CS.button_events, + *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled) *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index d12aa44e18..f6b6fad9e9 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, create_mads_event, create_cancel_event 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 @@ -143,15 +143,9 @@ 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 - self.CS.button_events.append(be) - ret.buttonEvents = [ *self.CS.button_events, + *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled), *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 85b895b3fd..35db87e1f9 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, create_mads_event, create_cancel_event from openpilot.selfdrive.car.disable_ecu import disable_ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase @@ -244,15 +244,9 @@ 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 - self.CS.button_events.append(be) - ret.buttonEvents = [ *ret.buttonEvents, + *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled) *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] From b95079bf7aa29c878daab974737008e4a1f6c1ea Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 18:48:07 -0400 Subject: [PATCH 460/575] encapsulate in a class --- selfdrive/car/__init__.py | 31 +++++++++++++++------------ selfdrive/car/chrysler/interface.py | 4 ++-- selfdrive/car/ford/interface.py | 4 ++-- selfdrive/car/gm/interface.py | 4 ++-- selfdrive/car/honda/interface.py | 4 ++-- selfdrive/car/hyundai/interface.py | 4 ++-- selfdrive/car/interfaces.py | 3 ++- selfdrive/car/mazda/interface.py | 4 ++-- selfdrive/car/nissan/interface.py | 6 +++--- selfdrive/car/subaru/interface.py | 6 +++--- selfdrive/car/toyota/interface.py | 6 +++--- selfdrive/car/volkswagen/interface.py | 4 ++-- 12 files changed, 42 insertions(+), 38 deletions(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 06fa120eee..b26607d24f 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -43,25 +43,28 @@ def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, ca return events -def create_cancel_event(long_enabled: bool, prev_long_enabled: bool) -> list[capnp.lib.capnp._DynamicStructBuilder]: - events: list[capnp.lib.capnp._DynamicStructBuilder] = [] +class ButtonEvents: + def __init__(self) -> None: + self.mads_event_lock: bool = True - if not long_enabled and prev_long_enabled: - events.append(car.CarState.ButtonEvent(pressed=True, - type=ButtonType.cancel)) - return events + @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(mads_enabled: bool, prev_mads_enabled: bool, - mads_event_lock: bool) -> tuple[list[capnp.lib.capnp._DynamicStructBuilder], bool]: - events: list[capnp.lib.capnp._DynamicStructBuilder] = [] + 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 mads_event_lock) or (not mads_changed and not mads_event_lock): - events.append(car.CarState.ButtonEvent(pressed=mads_event_lock, type=ButtonType.altButton1)) - mads_event_lock = not mads_event_lock + mads_changed = prev_mads_enabled != mads_enabled + if (mads_changed and self.mads_event_lock) or (not mads_changed and not self.mads_event_lock): + events.append(car.CarState.ButtonEvent(pressed=self.mads_event_lock, type=ButtonType.altButton1)) + self.mads_event_lock = not self.mads_event_lock - return events, mads_event_lock + return events def gen_empty_fingerprint(): diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 056128dc2e..6673ab5347 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,7 +1,7 @@ #!/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 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 @@ -139,7 +139,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index b2413ed14e..2a44accbab 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,7 +1,7 @@ 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 from openpilot.selfdrive.car.interfaces import CarInterfaceBase @@ -109,7 +109,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.manumatic], pcm_enable=False) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index c445e90118..60cf9c422b 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 @@ -252,7 +252,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # The ECM allows enabling on falling edge of set, but only rising edge of resume diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 2036a0f4a9..631b111a5d 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -6,7 +6,7 @@ from openpilot.common.numpy_fast import interp from openpilot.selfdrive.car.honda.hondacan import CanBus from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, CruiseSettings, HondaFlags, CAR, HONDA_BOSCH, \ HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS -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 @@ -305,7 +305,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 3610b2a744..591dc894e1 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 @@ -243,7 +243,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 2fe8a982ac..d7399db9f1 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -18,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 @@ -254,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"), "")) diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 6481c021c4..e23637aef3 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -2,7 +2,7 @@ from cereal import car from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS -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 ButtonType = car.CarState.ButtonEvent.Type @@ -75,7 +75,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 2176a4a107..84b5de35f3 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, create_cancel_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 @@ -55,8 +55,8 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *self.CS.button_events, - *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled) - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *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, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index f6b6fad9e9..058ecb0def 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, create_cancel_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 @@ -145,8 +145,8 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *self.CS.button_events, - *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled), - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *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, self.mads_event_lock) # 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 35db87e1f9..d0ec0eabb1 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, create_cancel_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 @@ -246,8 +246,8 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled) - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *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, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 505ea7db31..5f431c457a 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,7 +1,7 @@ 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, \ VolkswagenFlagsSP @@ -146,7 +146,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], From 17b8c6ab4e02d6392428a477ccbeb9d47893cb82 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 19:48:19 -0400 Subject: [PATCH 461/575] pre-commit: Apply mypy and ruff check fixes --- selfdrive/car/car_helpers.py | 4 +-- selfdrive/car/interfaces.py | 4 +-- selfdrive/controls/controlsd.py | 15 ++++++---- selfdrive/controls/plannerd.py | 2 -- selfdrive/modeld/custom_model_metadata.py | 11 +++---- selfdrive/modeld/fill_model_msg.py | 6 ++-- selfdrive/modeld/modeld.py | 16 +++++----- selfdrive/modeld/navmodeld.py | 5 ++-- selfdrive/navd/otisserv.py | 36 ++++++++++++----------- selfdrive/ui/ui.h | 3 +- system/mapd_manager.py | 4 +-- system/sentry.py | 8 ++--- 12 files changed, 59 insertions(+), 55 deletions(-) mode change 100644 => 100755 selfdrive/navd/otisserv.py diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 5fcfe34a4f..225eebdafb 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -219,7 +219,7 @@ def crash_log(candidate): while True: if is_connected_to_internet(): sentry.get_init() - sentry.capture_info("fingerprinted %s" % candidate) + sentry.capture_info(f"fingerprinted {candidate}") break else: no_internet += 1 @@ -233,7 +233,7 @@ def crash_log2(fingerprints, fw): while True: if is_connected_to_internet(): sentry.get_init() - sentry.capture_warning("car doesn't match any fingerprints: %s" % repr(fingerprints)) + sentry.capture_warning(f"car doesn't match any fingerprints: {repr(fingerprints)}") break else: no_internet += 1 diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 5fe64832c8..c5fef5b6b7 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -103,7 +103,7 @@ def get_torque_params(): # lateral neural network feedforward class FluxModel: def __init__(self, params_file, zero_bias=False): - with open(params_file, "r") as f: + with open(params_file) as f: params = json.load(f) self.input_size = params["input_size"] @@ -162,7 +162,7 @@ class FluxModel: return float(output_array[0, 0]) def validate_layers(self): - for W, b, activation in self.layers: + for _, _, activation in self.layers: if not hasattr(self, activation): raise ValueError(f"Unknown activation: {activation}") diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 2af32f8a0c..968eb72c05 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -41,7 +41,7 @@ CAMERA_OFFSET = 0.04 REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ TESTING_CLOSET = "TESTING_CLOSET" in os.environ -IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd", "mapd", "gpxd", "gpxd_uploader", "mapd", "otisserv", "fleet_manager"} +IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd", "gpxd", "gpxd_uploader", "mapd", "otisserv", "fleet_manager"} ThermalStatus = log.DeviceState.ThermalStatus State = log.ControlsState.OpenpilotState @@ -298,7 +298,8 @@ class Controls: self.events.add(EventName.calibrationInvalid) # Handle lane change - lane_change_edge_block = self.sm['lateralPlanSPDEPRECATED'].laneChangeEdgeBlockDEPRECATED if self.model_use_lateral_planner else self.sm['modelV2SP'].laneChangeEdgeBlock + lane_change_edge_block = self.sm['lateralPlanSPDEPRECATED'].laneChangeEdgeBlockDEPRECATED if self.model_use_lateral_planner else \ + self.sm['modelV2SP'].laneChangeEdgeBlock lane_change_svs = self.sm['lateralPlanDEPRECATED'] if self.model_use_lateral_planner else self.sm['modelV2'].meta if lane_change_svs.laneChangeState == LaneChangeState.preLaneChange and lane_change_edge_block: self.events.add(EventName.laneChangeRoadEdge) @@ -616,9 +617,10 @@ class Controls: CC.latActive = (self.active or self.mads_ndlob) and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ (not standstill or self.joystick_mode) and CS.madsEnabled and (not CS.brakePressed or self.mads_ndlob) and \ (not CS.belowLaneChangeSpeed or (not (((self.sm.frame - self.last_blinker_frame) * DT_CTRL) < 1.0) and - not (CS.leftBlinker or CS.rightBlinker))) and CS.latActive and self.sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.calibrated and \ - not self.process_not_running - CC.longActive = self.enabled_long and not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) + not (CS.leftBlinker or CS.rightBlinker))) and CS.latActive and \ + self.sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.calibrated and not self.process_not_running + CC.longActive = self.enabled_long and not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and \ + not self.events.contains(ET.OVERRIDE_LONGITUDINAL) actuators = CC.actuators actuators.longControlState = self.LoC.long_control_state @@ -825,7 +827,8 @@ class Controls: controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan'] controlsState.lateralPlanMonoTime = self.sm.logMonoTime[lp_mono_time_svs] - controlsState.enabled = not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and (self.enabled or CS.cruiseState.enabled) and CS.gearShifter not in [GearShifter.park, GearShifter.reverse] + controlsState.enabled = not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and \ + (self.enabled or CS.cruiseState.enabled) and CS.gearShifter not in [GearShifter.park, GearShifter.reverse] controlsState.active = not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and (self.active or CS.cruiseState.enabled) controlsState.curvature = curvature controlsState.desiredCurvature = self.desired_curvature diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 0d1eaeb075..37b0fd4060 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -1,11 +1,9 @@ #!/usr/bin/env python3 import os -import numpy as np from cereal import car from openpilot.common.params import Params from openpilot.common.realtime import Priority, config_realtime_process from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner from openpilot.selfdrive.modeld.custom_model_metadata import CustomModelMetadata, ModelCapabilities diff --git a/selfdrive/modeld/custom_model_metadata.py b/selfdrive/modeld/custom_model_metadata.py index 0469bd189c..d5f31feee9 100644 --- a/selfdrive/modeld/custom_model_metadata.py +++ b/selfdrive/modeld/custom_model_metadata.py @@ -1,12 +1,13 @@ from enum import IntFlag import os +from typing import TypeAlias from cereal import custom from openpilot.common.params import Params SIMULATION = "SIMULATION" in os.environ -ModelGeneration = custom.ModelGeneration +ModelGeneration: TypeAlias = custom.ModelGeneration class ModelCapabilities(IntFlag): @@ -15,7 +16,7 @@ class ModelCapabilities(IntFlag): Default = 1 """Default capability, used for the prebuilt model.""" - NoO = 2 + NavigateOnOpenpilot = 2 """Navigation on Openpilot capability, used for models support navigation.""" LateralPlannerSolution = 2 ** 2 @@ -56,11 +57,11 @@ class CustomModelMetadata: elif self.generation == ModelGeneration.four: return ModelCapabilities.DesiredCurvatureV2 elif self.generation == ModelGeneration.three: - return ModelCapabilities.DesiredCurvatureV2 | ModelCapabilities.NoO + return ModelCapabilities.DesiredCurvatureV2 | ModelCapabilities.NavigateOnOpenpilot elif self.generation == ModelGeneration.two: - return ModelCapabilities.DesiredCurvatureV1 | ModelCapabilities.NoO + return ModelCapabilities.DesiredCurvatureV1 | ModelCapabilities.NavigateOnOpenpilot elif self.generation == ModelGeneration.one: - return ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO + return ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NavigateOnOpenpilot else: # Default model is meant to represent the capabilities of the prebuilt model return ModelCapabilities.Default diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 8d2bcc0ef4..f94f773b8a 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -78,7 +78,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D modelV2.frameAge = frame_age modelV2.frameDropPerc = frame_drop_perc modelV2.timestampEof = timestamp_eof - model_use_nav = custom_model_valid and custom_model_capabilities & ModelCapabilities.NoO + model_use_nav = custom_model_valid and custom_model_capabilities & ModelCapabilities.NavigateOnOpenpilot if model_use_nav: modelV2.locationMonoTimeDEPRECATED = timestamp_llk modelV2.modelExecutionTime = model_execution_time @@ -104,8 +104,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D # lateral planning if model_use_lateral_planner: solution = modelV2.lateralPlannerSolutionDEPRECATED - solution.x, solution.y, solution.yaw, solution.yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)] - solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = [net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)] + solution.x, solution.y, solution.yaw, solution.yawRate = (net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)) + solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = (net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)) else: action = modelV2.action action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index ff3d9ec633..174f61e896 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -81,7 +81,7 @@ class ModelState: 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), 'prev_desired_curvs': np.zeros(ModelConstants.PREV_DESIRED_CURVS_LEN, dtype=np.float32), } - if self.custom_model_metadata.capabilities & ModelCapabilities.NoO: + if self.custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: _inputs_2 = { 'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32), @@ -101,10 +101,10 @@ class ModelState: _metadata_name = self.param_s.get("DrivingModelMetadataText", encoding="utf8") _metadata_path = f"{CUSTOM_MODEL_PATH}/supercombo_metadata_{_metadata_name}.pkl" if _model_name else METADATA_PATH else: - _model_paths = MODEL_PATHS + _model_paths = MODEL_PATHS # type: ignore _metadata_path = METADATA_PATH - with open(_metadata_path, 'rb') as f: + with open(_metadata_path, 'rb') as f: # type: ignore model_metadata = pickle.load(f) self.output_slices = model_metadata['output_slices'] @@ -135,7 +135,7 @@ class ModelState: self.inputs['traffic_convention'][:] = inputs['traffic_convention'] if not (self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.LateralPlannerSolution): self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] - if self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.NoO: + if self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: self.inputs['nav_features'][:] = inputs['nav_features'] self.inputs['nav_instructions'][:] = inputs['nav_instructions'] @@ -207,7 +207,7 @@ def main(demo=False): # messaging extended_svs = ["lateralPlanDEPRECATED", "lateralPlanSPDEPRECATED"] - if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO: + if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: extended_svs += ["navModelDEPRECATED", "navInstruction"] pm = PubMaster(["modelV2", "modelV2SP", "drivingModelData", "cameraOdometry"]) sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"] + extended_svs) @@ -225,7 +225,7 @@ def main(demo=False): live_calib_seen = False if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.LateralPlannerSolution: driving_style = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32) - if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO: + if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) buf_main, buf_extra = None, None @@ -303,7 +303,7 @@ def main(demo=False): timestamp_llk = 0 nav_enabled = False - if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO: + if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: # Enable/disable nav features timestamp_llk = sm["navModelDEPRECATED"].locationMonoTime nav_valid = sm.valid["navModelDEPRECATED"] # and (nanos_since_boot() - timestamp_llk < 1e9) @@ -349,7 +349,7 @@ def main(demo=False): _inputs = { 'lateral_control_params': lateral_control_params } - if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO: + if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: _inputs_2 = { 'nav_features': nav_features, 'nav_instructions': nav_instructions diff --git a/selfdrive/modeld/navmodeld.py b/selfdrive/modeld/navmodeld.py index 4eadddc713..ad3744f69c 100755 --- a/selfdrive/modeld/navmodeld.py +++ b/selfdrive/modeld/navmodeld.py @@ -4,7 +4,6 @@ import math import time import ctypes import numpy as np -from pathlib import Path from cereal import messaging from cereal.messaging import PubMaster, SubMaster @@ -21,7 +20,7 @@ NAV_INPUT_SIZE = 256*256 NAV_FEATURE_LEN = 256 NAV_DESIRE_LEN = 32 NAV_OUTPUT_SIZE = 2*2*ModelConstants.IDX_N + NAV_DESIRE_LEN + NAV_FEATURE_LEN -MODEL_PATHS = {} +MODEL_PATHS = {} # type: ignore CUSTOM_MODEL_PATH = Paths.model_root() @@ -51,7 +50,7 @@ class ModelState: self.output = np.zeros(NAV_OUTPUT_SIZE, dtype=np.float32) self.param_s = Params() self.custom_model_metadata = CustomModelMetadata(params=self.param_s, init_only=True) - if self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.NoO: + if self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: _model_name = self.param_s.get("NavModelText", encoding="utf8") _model_paths = { ModelRunner.SNPE: f"{CUSTOM_MODEL_PATH}/navmodel_q_{_model_name}.dlc"} diff --git a/selfdrive/navd/otisserv.py b/selfdrive/navd/otisserv.py old mode 100644 new mode 100755 index f5110e2598..0337cdbe64 --- a/selfdrive/navd/otisserv.py +++ b/selfdrive/navd/otisserv.py @@ -22,7 +22,7 @@ # THE SOFTWARE. from http.server import BaseHTTPRequestHandler, HTTPServer -from cgi import parse_header, parse_multipart +from cgi import parse_header from urllib.parse import parse_qs, unquote import json import requests @@ -184,7 +184,7 @@ class OtisServ(BaseHTTPRequestHandler): name = postvars.get("name")[0] if postvars.get("name") is not None else "" if use_amap: lng, lat = self.gcj02towgs84(lng, lat) - params.put('NavDestination', "{\"latitude\": %f, \"longitude\": %f, \"place_name\": \"%s\"}" % (lat, lng, name)) + params.put('NavDestination', f'{{"latitude": {lat:.6f}, "longitude": {lng:.6f}, "place_name": "{name}"}}') self.to_json(lat, lng, save_type, name) if postvars is not None: latitude_value = postvars.get("latitude") @@ -194,7 +194,7 @@ class OtisServ(BaseHTTPRequestHandler): lng = float(longitude_value) save_type = "recent" name = postvars.get("place_name", [""]) - params.put('NavDestination', "{\"latitude\": %f, \"longitude\": %f, \"place_name\": \"%s\"}" % (lat, lng, name)) + params.put('NavDestination', f'{{"latitude": {lat:.6f}, "longitude": {lng:.6f}, "place_name": "{name}"}}') self.to_json(lat, lng, save_type, name) # favorites if not use_gmap and "fav_val" in postvars: @@ -244,7 +244,7 @@ class OtisServ(BaseHTTPRequestHandler): self.send_response(200) self.send_header('Content-type','image/png') self.end_headers() - f = open("%s/selfdrive/assets/img_spinner_comma.png" % BASEDIR, "rb") + f = open(f"{BASEDIR}/selfdrive/assets/img_spinner_comma.png", "rb") self.wfile.write(f.read()) f.close() @@ -323,17 +323,19 @@ class OtisServ(BaseHTTPRequestHandler): self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": self.get_parsed_template("addr_input", {"{{msg}}": msg})}), "utf-8")) def display_page_nav_confirmation(self, addr, lon, lat): - content = self.get_parsed_template("addr_input", {"{{msg}}": ""}) + self.get_parsed_template("nav_confirmation", {"{{token}}": self.get_public_token(), "{{lon}}": lon, "{{lat}}": lat, "{{addr}}": addr}) + content = self.get_parsed_template("addr_input", {"{{msg}}": ""}) + self.get_parsed_template("nav_confirmation", {"{{token}}": self.get_public_token(), "{{lon}}": lon, "{{lat}}": lat, "{{addr}}": addr}) # noqa: E501 self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": content }), "utf-8")) def display_page_gmap(self): self.wfile.write(bytes(self.get_parsed_template("gmap/index.html", {"{{gmap_key}}": self.get_gmap_key()}), "utf-8")) def display_page_amap(self): - self.wfile.write(bytes(self.get_parsed_template("amap/index.html", {"{{amap_key}}": self.get_amap_key(), "{{amap_key_2}}": self.get_amap_key_2()}), "utf-8")) + self.wfile.write(bytes(self.get_parsed_template("amap/index.html", {"{{amap_key}}": self.get_amap_key(), "{{amap_key_2}}": self.get_amap_key_2()}), "utf-8")) # noqa: E501 - def get_parsed_template(self, name, replace = {}): - f = open('%s/selfdrive/navd/tpl/%s.tpl' % (BASEDIR, name), mode='r', encoding='utf-8') + def get_parsed_template(self, name, replace=None): + if replace is None: + replace = {} + f = open(f'{BASEDIR}/selfdrive/navd/tpl/{name}.tpl', encoding='utf-8') content = f.read() for key in replace: content = content.replace(key, str(replace[key])) @@ -348,7 +350,7 @@ class OtisServ(BaseHTTPRequestHandler): last_pos = Params().get("LastGPSPosition") if last_pos is not None and last_pos != "": l = json.loads(last_pos) - query += "&proximity=%s,%s" % (l["longitude"], l["latitude"]) + query += f"&proximity={l['longitude']},{l['latitude']}" r = requests.get(query) if r.status_code != 200: @@ -407,16 +409,16 @@ class OtisServ(BaseHTTPRequestHandler): ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0 return ret - def to_json(self, lat, lng, type = "recent", name = ""): + def to_json(self, lat, lng, _type="recent", name=""): if name == "": name = str(lat) + "," + str(lng) new_dest = {"latitude": float(lat), "longitude": float(lng), "place_name": name} - if type == "recent": + if _type == "recent": new_dest["save_type"] = "recent" else: new_dest["save_type"] = "favorite" - new_dest["label"] = type + new_dest["label"] = _type val = params.get("ApiCache_NavDestinations", encoding='utf8') if val is not None: @@ -433,17 +435,17 @@ class OtisServ(BaseHTTPRequestHandler): type_label_ids["recent"].append(idx) idx += 1 - if type == "recent": - id = None + if _type == "recent": + _id = None if len(type_label_ids["recent"]) > 10: dests.pop(type_label_ids["recent"][-1]) else: - id = type_label_ids[type] + _id = type_label_ids[_type] - if id is None: + if _id is None: dests.insert(0, new_dest) else: - dests[id] = new_dest + dests[_id] = new_dest params.put("ApiCache_NavDestinations", json.dumps(dests).rstrip("\n\r")) diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index a9fb6f0671..bb6e4c71b0 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -242,7 +243,7 @@ public: inline SponsorRoleModel sunnylinkSponsorRole() const { std::optional sponsorRoleWithHighestTier = std::nullopt; for (const auto &role : sunnylinkRoles) { - if(role.roleType != RoleType::Sponsor) + if (role.roleType != RoleType::Sponsor) continue; if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { diff --git a/system/mapd_manager.py b/system/mapd_manager.py index 3f389de6f8..b892fc9224 100644 --- a/system/mapd_manager.py +++ b/system/mapd_manager.py @@ -80,7 +80,7 @@ def _request_refresh_osm_bounds_data(self): mem_params.put("OSMDownloadBounds", json.dumps(current_bounding_box)) -def request_refresh_osm_location_data(nations: [str], states: [str] = None): +def request_refresh_osm_location_data(nations: list[str], states: list[str]=None): params.put("OsmDownloadedDate", str(time.time())) params.put_bool("OsmDbUpdatesCheck", False) @@ -93,7 +93,7 @@ def request_refresh_osm_location_data(nations: [str], states: [str] = None): mem_params.put("OSMDownloadLocations", osm_download_locations) -def filter_nations_and_states(nations: [str], states: [str] = None): +def filter_nations_and_states(nations: list[str], states: list[str]=None): """Filters and prepares nation and state data for OSM map download. If the nation is 'US' and a specific state is provided, the nation 'US' is removed from the list. diff --git a/system/sentry.py b/system/sentry.py index 32772742cc..222b4cb43a 100644 --- a/system/sentry.py +++ b/system/sentry.py @@ -10,8 +10,8 @@ from sentry_sdk.integrations.threading import ThreadingIntegration from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params -from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID, is_registered_device -from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID +from openpilot.system.hardware import HARDWARE from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_build_metadata, get_version @@ -21,7 +21,7 @@ class SentryProject(Enum): # python project SELFDRIVE = "https://7e3be9bfcfe04c9abe58bd25fe290d1a@o1138119.ingest.sentry.io/6191481" # native project - SELFDRIVE_NATIVE = "https://7e3be9bfcfe04c9abe58bd25fe290d1a@o1138119.ingest.sentry.io/6191481" + SELFDRIVE_NATIVE = "https://7e3be9bfcfe04c9abe58bd25fe290d1a@o1138119.ingest.sentry.io/6191481" # noqa: PIE796 CRASHES_DIR = Paths.community_crash_root() @@ -68,7 +68,7 @@ def save_exception(exc_text: str) -> None: else: f.write(exc_text) - print('Logged current crash to {}'.format(files)) + print(f'Logged current crash to {files}') def bind_user() -> None: From b62682f108a5e917262c477a9358ff6bc6ccd338 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 20:53:50 -0400 Subject: [PATCH 462/575] one too much --- selfdrive/car/chrysler/interface.py | 2 +- selfdrive/car/ford/interface.py | 2 +- selfdrive/car/gm/interface.py | 2 +- selfdrive/car/honda/interface.py | 2 +- selfdrive/car/hyundai/interface.py | 2 +- selfdrive/car/mazda/interface.py | 2 +- selfdrive/car/nissan/interface.py | 2 +- selfdrive/car/subaru/interface.py | 2 +- selfdrive/car/toyota/interface.py | 2 +- selfdrive/car/volkswagen/interface.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 6673ab5347..0741dfc7d1 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -139,7 +139,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] # events diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 2a44accbab..2ec1ec070a 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -109,7 +109,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *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) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 60cf9c422b..560d44f6e0 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -252,7 +252,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *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 diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 631b111a5d..9c8b505a03 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -305,7 +305,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] # events diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 591dc894e1..4d601ef224 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -243,7 +243,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *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 diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index e23637aef3..86ce548a12 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -75,7 +75,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] # events diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 84b5de35f3..4a9e5a9125 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -56,7 +56,7 @@ class CarInterface(CarInterfaceBase): 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, self.mads_event_lock) # MADS BUTTON + *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], diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 058ecb0def..7eefeb66eb 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -146,7 +146,7 @@ class CarInterface(CarInterfaceBase): 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, self.mads_event_lock) # MADS BUTTON + *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 d0ec0eabb1..26f0bbd001 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -247,7 +247,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, *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, self.mads_event_lock) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] # events diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 5f431c457a..b02c5f1aa8 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -146,7 +146,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *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], From d51d95426ab8fa576947106d72d3fa043cc91902 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 21:15:37 -0400 Subject: [PATCH 463/575] this is better --- selfdrive/car/hyundai/interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index d59541fa66..e8e1dba1a3 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -220,8 +220,6 @@ class CarInterface(CarInterfaceBase): elif self.CS.prev_cruise_buttons == 4: if self.CS.cruise_buttons[-1] != 4: self.accEnabled = True - else: - self.CS.madsEnabled = False if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: @@ -230,6 +228,9 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) + if not ret.cruiseState.available and self.CS.out.cruiseState.available: + self.CS.madsEnabled = False + 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): From f62c14c7616981464237348d3975c36ef194f050 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 22:40:19 -0400 Subject: [PATCH 464/575] Revert "one too much" This reverts commit b62682f108a5e917262c477a9358ff6bc6ccd338. --- selfdrive/car/chrysler/interface.py | 2 +- selfdrive/car/ford/interface.py | 2 +- selfdrive/car/gm/interface.py | 2 +- selfdrive/car/honda/interface.py | 2 +- selfdrive/car/hyundai/interface.py | 2 +- selfdrive/car/mazda/interface.py | 2 +- selfdrive/car/nissan/interface.py | 2 +- selfdrive/car/subaru/interface.py | 2 +- selfdrive/car/toyota/interface.py | 2 +- selfdrive/car/volkswagen/interface.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 0741dfc7d1..6673ab5347 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -139,7 +139,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 79e1adaa39..1b76c92790 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -119,7 +119,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.manumatic], pcm_enable=False) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 701a039662..19431fb2fc 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -253,7 +253,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # The ECM allows enabling on falling edge of set, but only rising edge of resume diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 9c8b505a03..631b111a5d 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -305,7 +305,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index f99cf509ae..99233b8163 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -250,7 +250,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 86ce548a12..e23637aef3 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -75,7 +75,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 4a9e5a9125..84b5de35f3 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -56,7 +56,7 @@ class CarInterface(CarInterfaceBase): 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 + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 7eefeb66eb..058ecb0def 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -146,7 +146,7 @@ class CarInterface(CarInterfaceBase): 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 + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # 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 2ec4d12251..5f60a6a3a6 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -247,7 +247,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, *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 + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index b02c5f1aa8..5f431c457a 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -146,7 +146,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON + *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], From cd09b479362e290f96ad62c61dd99277552f8791 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 22:40:20 -0400 Subject: [PATCH 465/575] Revert "pre-commit: Apply mypy and ruff check fixes" This reverts commit 17b8c6ab4e02d6392428a477ccbeb9d47893cb82. --- selfdrive/car/car_helpers.py | 4 +-- selfdrive/car/interfaces.py | 4 +-- selfdrive/controls/controlsd.py | 15 ++++------ selfdrive/controls/plannerd.py | 2 ++ selfdrive/modeld/custom_model_metadata.py | 11 ++++--- selfdrive/modeld/fill_model_msg.py | 6 ++-- selfdrive/modeld/modeld.py | 16 +++++----- selfdrive/modeld/navmodeld.py | 5 ++-- selfdrive/navd/otisserv.py | 36 +++++++++++------------ selfdrive/ui/ui.h | 3 +- system/mapd_manager.py | 4 +-- system/sentry.py | 8 ++--- 12 files changed, 55 insertions(+), 59 deletions(-) mode change 100755 => 100644 selfdrive/navd/otisserv.py diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 225eebdafb..5fcfe34a4f 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -219,7 +219,7 @@ def crash_log(candidate): while True: if is_connected_to_internet(): sentry.get_init() - sentry.capture_info(f"fingerprinted {candidate}") + sentry.capture_info("fingerprinted %s" % candidate) break else: no_internet += 1 @@ -233,7 +233,7 @@ def crash_log2(fingerprints, fw): while True: if is_connected_to_internet(): sentry.get_init() - sentry.capture_warning(f"car doesn't match any fingerprints: {repr(fingerprints)}") + sentry.capture_warning("car doesn't match any fingerprints: %s" % repr(fingerprints)) break else: no_internet += 1 diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index c8ebe1bd61..46991f7dde 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -104,7 +104,7 @@ def get_torque_params(): # lateral neural network feedforward class FluxModel: def __init__(self, params_file, zero_bias=False): - with open(params_file) as f: + with open(params_file, "r") as f: params = json.load(f) self.input_size = params["input_size"] @@ -163,7 +163,7 @@ class FluxModel: return float(output_array[0, 0]) def validate_layers(self): - for _, _, activation in self.layers: + for W, b, activation in self.layers: if not hasattr(self, activation): raise ValueError(f"Unknown activation: {activation}") diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 460baa68ba..3770dd9622 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -41,7 +41,7 @@ CAMERA_OFFSET = 0.04 REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ TESTING_CLOSET = "TESTING_CLOSET" in os.environ -IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd", "gpxd", "gpxd_uploader", "mapd", "otisserv", "fleet_manager"} +IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd", "mapd", "gpxd", "gpxd_uploader", "mapd", "otisserv", "fleet_manager"} ThermalStatus = log.DeviceState.ThermalStatus State = log.ControlsState.OpenpilotState @@ -300,8 +300,7 @@ class Controls: self.events.add(EventName.calibrationInvalid) # Handle lane change - lane_change_edge_block = self.sm['lateralPlanSPDEPRECATED'].laneChangeEdgeBlockDEPRECATED if self.model_use_lateral_planner else \ - self.sm['modelV2SP'].laneChangeEdgeBlock + lane_change_edge_block = self.sm['lateralPlanSPDEPRECATED'].laneChangeEdgeBlockDEPRECATED if self.model_use_lateral_planner else self.sm['modelV2SP'].laneChangeEdgeBlock lane_change_svs = self.sm['lateralPlanDEPRECATED'] if self.model_use_lateral_planner else self.sm['modelV2'].meta if lane_change_svs.laneChangeState == LaneChangeState.preLaneChange and lane_change_edge_block: self.events.add(EventName.laneChangeRoadEdge) @@ -621,10 +620,9 @@ class Controls: CC.latActive = (self.active or self.mads_ndlob) and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ (not standstill or self.joystick_mode) and CS.madsEnabled and (not CS.brakePressed or self.mads_ndlob) and \ (not CS.belowLaneChangeSpeed or (not (((self.sm.frame - self.last_blinker_frame) * DT_CTRL) < 1.0) and - not (CS.leftBlinker or CS.rightBlinker))) and CS.latActive and \ - self.sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.calibrated and not self.process_not_running - CC.longActive = self.enabled_long and not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and \ - not self.events.contains(ET.OVERRIDE_LONGITUDINAL) + not (CS.leftBlinker or CS.rightBlinker))) and CS.latActive and self.sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.calibrated and \ + not self.process_not_running + CC.longActive = self.enabled_long and not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) actuators = CC.actuators actuators.longControlState = self.LoC.long_control_state @@ -831,8 +829,7 @@ class Controls: controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan'] controlsState.lateralPlanMonoTime = self.sm.logMonoTime[lp_mono_time_svs] - controlsState.enabled = not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and \ - (self.enabled or CS.cruiseState.enabled) and CS.gearShifter not in [GearShifter.park, GearShifter.reverse] + controlsState.enabled = not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and (self.enabled or CS.cruiseState.enabled) and CS.gearShifter not in [GearShifter.park, GearShifter.reverse] controlsState.active = not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and (self.active or CS.cruiseState.enabled) controlsState.curvature = curvature controlsState.desiredCurvature = self.desired_curvature diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 2cb8bcc0cc..6c51b38b13 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 import os +import numpy as np from cereal import car from openpilot.common.params import Params from openpilot.common.realtime import Priority, config_realtime_process from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner from openpilot.selfdrive.modeld.custom_model_metadata import CustomModelMetadata, ModelCapabilities diff --git a/selfdrive/modeld/custom_model_metadata.py b/selfdrive/modeld/custom_model_metadata.py index d5f31feee9..0469bd189c 100644 --- a/selfdrive/modeld/custom_model_metadata.py +++ b/selfdrive/modeld/custom_model_metadata.py @@ -1,13 +1,12 @@ from enum import IntFlag import os -from typing import TypeAlias from cereal import custom from openpilot.common.params import Params SIMULATION = "SIMULATION" in os.environ -ModelGeneration: TypeAlias = custom.ModelGeneration +ModelGeneration = custom.ModelGeneration class ModelCapabilities(IntFlag): @@ -16,7 +15,7 @@ class ModelCapabilities(IntFlag): Default = 1 """Default capability, used for the prebuilt model.""" - NavigateOnOpenpilot = 2 + NoO = 2 """Navigation on Openpilot capability, used for models support navigation.""" LateralPlannerSolution = 2 ** 2 @@ -57,11 +56,11 @@ class CustomModelMetadata: elif self.generation == ModelGeneration.four: return ModelCapabilities.DesiredCurvatureV2 elif self.generation == ModelGeneration.three: - return ModelCapabilities.DesiredCurvatureV2 | ModelCapabilities.NavigateOnOpenpilot + return ModelCapabilities.DesiredCurvatureV2 | ModelCapabilities.NoO elif self.generation == ModelGeneration.two: - return ModelCapabilities.DesiredCurvatureV1 | ModelCapabilities.NavigateOnOpenpilot + return ModelCapabilities.DesiredCurvatureV1 | ModelCapabilities.NoO elif self.generation == ModelGeneration.one: - return ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NavigateOnOpenpilot + return ModelCapabilities.LateralPlannerSolution | ModelCapabilities.NoO else: # Default model is meant to represent the capabilities of the prebuilt model return ModelCapabilities.Default diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index f94f773b8a..8d2bcc0ef4 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -78,7 +78,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D modelV2.frameAge = frame_age modelV2.frameDropPerc = frame_drop_perc modelV2.timestampEof = timestamp_eof - model_use_nav = custom_model_valid and custom_model_capabilities & ModelCapabilities.NavigateOnOpenpilot + model_use_nav = custom_model_valid and custom_model_capabilities & ModelCapabilities.NoO if model_use_nav: modelV2.locationMonoTimeDEPRECATED = timestamp_llk modelV2.modelExecutionTime = model_execution_time @@ -104,8 +104,8 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D # lateral planning if model_use_lateral_planner: solution = modelV2.lateralPlannerSolutionDEPRECATED - solution.x, solution.y, solution.yaw, solution.yawRate = (net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)) - solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = (net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)) + solution.x, solution.y, solution.yaw, solution.yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)] + solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = [net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)] else: action = modelV2.action action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index c6b8b39129..bb883c32cb 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -81,7 +81,7 @@ class ModelState: 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), 'prev_desired_curvs': np.zeros(ModelConstants.PREV_DESIRED_CURVS_LEN, dtype=np.float32), } - if self.custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: + if self.custom_model_metadata.capabilities & ModelCapabilities.NoO: _inputs_2 = { 'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32), @@ -101,10 +101,10 @@ class ModelState: _metadata_name = self.param_s.get("DrivingModelMetadataText", encoding="utf8") _metadata_path = f"{CUSTOM_MODEL_PATH}/supercombo_metadata_{_metadata_name}.pkl" if _model_name else METADATA_PATH else: - _model_paths = MODEL_PATHS # type: ignore + _model_paths = MODEL_PATHS _metadata_path = METADATA_PATH - with open(_metadata_path, 'rb') as f: # type: ignore + with open(_metadata_path, 'rb') as f: model_metadata = pickle.load(f) self.output_slices = model_metadata['output_slices'] @@ -135,7 +135,7 @@ class ModelState: self.inputs['traffic_convention'][:] = inputs['traffic_convention'] if not (self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.LateralPlannerSolution): self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] - if self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: + if self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.NoO: self.inputs['nav_features'][:] = inputs['nav_features'] self.inputs['nav_instructions'][:] = inputs['nav_instructions'] @@ -207,7 +207,7 @@ def main(demo=False): # messaging extended_svs = ["lateralPlanDEPRECATED", "lateralPlanSPDEPRECATED"] - if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: + if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO: extended_svs += ["navModelDEPRECATED", "navInstruction"] pm = PubMaster(["modelV2", "modelV2SP", "drivingModelData", "cameraOdometry"]) sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"] + extended_svs) @@ -225,7 +225,7 @@ def main(demo=False): live_calib_seen = False if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.LateralPlannerSolution: driving_style = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32) - if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: + if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO: nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) buf_main, buf_extra = None, None @@ -303,7 +303,7 @@ def main(demo=False): timestamp_llk = 0 nav_enabled = False - if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: + if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO: # Enable/disable nav features timestamp_llk = sm["navModelDEPRECATED"].locationMonoTime nav_valid = sm.valid["navModelDEPRECATED"] # and (nanos_since_boot() - timestamp_llk < 1e9) @@ -349,7 +349,7 @@ def main(demo=False): _inputs = { 'lateral_control_params': lateral_control_params } - if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: + if custom_model_metadata.valid and custom_model_metadata.capabilities & ModelCapabilities.NoO: _inputs_2 = { 'nav_features': nav_features, 'nav_instructions': nav_instructions diff --git a/selfdrive/modeld/navmodeld.py b/selfdrive/modeld/navmodeld.py index ad3744f69c..4eadddc713 100755 --- a/selfdrive/modeld/navmodeld.py +++ b/selfdrive/modeld/navmodeld.py @@ -4,6 +4,7 @@ import math import time import ctypes import numpy as np +from pathlib import Path from cereal import messaging from cereal.messaging import PubMaster, SubMaster @@ -20,7 +21,7 @@ NAV_INPUT_SIZE = 256*256 NAV_FEATURE_LEN = 256 NAV_DESIRE_LEN = 32 NAV_OUTPUT_SIZE = 2*2*ModelConstants.IDX_N + NAV_DESIRE_LEN + NAV_FEATURE_LEN -MODEL_PATHS = {} # type: ignore +MODEL_PATHS = {} CUSTOM_MODEL_PATH = Paths.model_root() @@ -50,7 +51,7 @@ class ModelState: self.output = np.zeros(NAV_OUTPUT_SIZE, dtype=np.float32) self.param_s = Params() self.custom_model_metadata = CustomModelMetadata(params=self.param_s, init_only=True) - if self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.NavigateOnOpenpilot: + if self.custom_model_metadata.valid and self.custom_model_metadata.capabilities & ModelCapabilities.NoO: _model_name = self.param_s.get("NavModelText", encoding="utf8") _model_paths = { ModelRunner.SNPE: f"{CUSTOM_MODEL_PATH}/navmodel_q_{_model_name}.dlc"} diff --git a/selfdrive/navd/otisserv.py b/selfdrive/navd/otisserv.py old mode 100755 new mode 100644 index 0337cdbe64..f5110e2598 --- a/selfdrive/navd/otisserv.py +++ b/selfdrive/navd/otisserv.py @@ -22,7 +22,7 @@ # THE SOFTWARE. from http.server import BaseHTTPRequestHandler, HTTPServer -from cgi import parse_header +from cgi import parse_header, parse_multipart from urllib.parse import parse_qs, unquote import json import requests @@ -184,7 +184,7 @@ class OtisServ(BaseHTTPRequestHandler): name = postvars.get("name")[0] if postvars.get("name") is not None else "" if use_amap: lng, lat = self.gcj02towgs84(lng, lat) - params.put('NavDestination', f'{{"latitude": {lat:.6f}, "longitude": {lng:.6f}, "place_name": "{name}"}}') + params.put('NavDestination', "{\"latitude\": %f, \"longitude\": %f, \"place_name\": \"%s\"}" % (lat, lng, name)) self.to_json(lat, lng, save_type, name) if postvars is not None: latitude_value = postvars.get("latitude") @@ -194,7 +194,7 @@ class OtisServ(BaseHTTPRequestHandler): lng = float(longitude_value) save_type = "recent" name = postvars.get("place_name", [""]) - params.put('NavDestination', f'{{"latitude": {lat:.6f}, "longitude": {lng:.6f}, "place_name": "{name}"}}') + params.put('NavDestination', "{\"latitude\": %f, \"longitude\": %f, \"place_name\": \"%s\"}" % (lat, lng, name)) self.to_json(lat, lng, save_type, name) # favorites if not use_gmap and "fav_val" in postvars: @@ -244,7 +244,7 @@ class OtisServ(BaseHTTPRequestHandler): self.send_response(200) self.send_header('Content-type','image/png') self.end_headers() - f = open(f"{BASEDIR}/selfdrive/assets/img_spinner_comma.png", "rb") + f = open("%s/selfdrive/assets/img_spinner_comma.png" % BASEDIR, "rb") self.wfile.write(f.read()) f.close() @@ -323,19 +323,17 @@ class OtisServ(BaseHTTPRequestHandler): self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": self.get_parsed_template("addr_input", {"{{msg}}": msg})}), "utf-8")) def display_page_nav_confirmation(self, addr, lon, lat): - content = self.get_parsed_template("addr_input", {"{{msg}}": ""}) + self.get_parsed_template("nav_confirmation", {"{{token}}": self.get_public_token(), "{{lon}}": lon, "{{lat}}": lat, "{{addr}}": addr}) # noqa: E501 + content = self.get_parsed_template("addr_input", {"{{msg}}": ""}) + self.get_parsed_template("nav_confirmation", {"{{token}}": self.get_public_token(), "{{lon}}": lon, "{{lat}}": lat, "{{addr}}": addr}) self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": content }), "utf-8")) def display_page_gmap(self): self.wfile.write(bytes(self.get_parsed_template("gmap/index.html", {"{{gmap_key}}": self.get_gmap_key()}), "utf-8")) def display_page_amap(self): - self.wfile.write(bytes(self.get_parsed_template("amap/index.html", {"{{amap_key}}": self.get_amap_key(), "{{amap_key_2}}": self.get_amap_key_2()}), "utf-8")) # noqa: E501 + self.wfile.write(bytes(self.get_parsed_template("amap/index.html", {"{{amap_key}}": self.get_amap_key(), "{{amap_key_2}}": self.get_amap_key_2()}), "utf-8")) - def get_parsed_template(self, name, replace=None): - if replace is None: - replace = {} - f = open(f'{BASEDIR}/selfdrive/navd/tpl/{name}.tpl', encoding='utf-8') + def get_parsed_template(self, name, replace = {}): + f = open('%s/selfdrive/navd/tpl/%s.tpl' % (BASEDIR, name), mode='r', encoding='utf-8') content = f.read() for key in replace: content = content.replace(key, str(replace[key])) @@ -350,7 +348,7 @@ class OtisServ(BaseHTTPRequestHandler): last_pos = Params().get("LastGPSPosition") if last_pos is not None and last_pos != "": l = json.loads(last_pos) - query += f"&proximity={l['longitude']},{l['latitude']}" + query += "&proximity=%s,%s" % (l["longitude"], l["latitude"]) r = requests.get(query) if r.status_code != 200: @@ -409,16 +407,16 @@ class OtisServ(BaseHTTPRequestHandler): ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0 return ret - def to_json(self, lat, lng, _type="recent", name=""): + def to_json(self, lat, lng, type = "recent", name = ""): if name == "": name = str(lat) + "," + str(lng) new_dest = {"latitude": float(lat), "longitude": float(lng), "place_name": name} - if _type == "recent": + if type == "recent": new_dest["save_type"] = "recent" else: new_dest["save_type"] = "favorite" - new_dest["label"] = _type + new_dest["label"] = type val = params.get("ApiCache_NavDestinations", encoding='utf8') if val is not None: @@ -435,17 +433,17 @@ class OtisServ(BaseHTTPRequestHandler): type_label_ids["recent"].append(idx) idx += 1 - if _type == "recent": - _id = None + if type == "recent": + id = None if len(type_label_ids["recent"]) > 10: dests.pop(type_label_ids["recent"][-1]) else: - _id = type_label_ids[_type] + id = type_label_ids[type] - if _id is None: + if id is None: dests.insert(0, new_dest) else: - dests[_id] = new_dest + dests[id] = new_dest params.put("ApiCache_NavDestinations", json.dumps(dests).rstrip("\n\r")) diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index bb6e4c71b0..a9fb6f0671 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -243,7 +242,7 @@ public: inline SponsorRoleModel sunnylinkSponsorRole() const { std::optional sponsorRoleWithHighestTier = std::nullopt; for (const auto &role : sunnylinkRoles) { - if (role.roleType != RoleType::Sponsor) + if(role.roleType != RoleType::Sponsor) continue; if (auto sponsorRole = role.as(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) { diff --git a/system/mapd_manager.py b/system/mapd_manager.py index b892fc9224..3f389de6f8 100644 --- a/system/mapd_manager.py +++ b/system/mapd_manager.py @@ -80,7 +80,7 @@ def _request_refresh_osm_bounds_data(self): mem_params.put("OSMDownloadBounds", json.dumps(current_bounding_box)) -def request_refresh_osm_location_data(nations: list[str], states: list[str]=None): +def request_refresh_osm_location_data(nations: [str], states: [str] = None): params.put("OsmDownloadedDate", str(time.time())) params.put_bool("OsmDbUpdatesCheck", False) @@ -93,7 +93,7 @@ def request_refresh_osm_location_data(nations: list[str], states: list[str]=None mem_params.put("OSMDownloadLocations", osm_download_locations) -def filter_nations_and_states(nations: list[str], states: list[str]=None): +def filter_nations_and_states(nations: [str], states: [str] = None): """Filters and prepares nation and state data for OSM map download. If the nation is 'US' and a specific state is provided, the nation 'US' is removed from the list. diff --git a/system/sentry.py b/system/sentry.py index 222b4cb43a..32772742cc 100644 --- a/system/sentry.py +++ b/system/sentry.py @@ -10,8 +10,8 @@ from sentry_sdk.integrations.threading import ThreadingIntegration from openpilot.common.api.sunnylink import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params -from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID -from openpilot.system.hardware import HARDWARE +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID, is_registered_device +from openpilot.system.hardware import HARDWARE, PC from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_build_metadata, get_version @@ -21,7 +21,7 @@ class SentryProject(Enum): # python project SELFDRIVE = "https://7e3be9bfcfe04c9abe58bd25fe290d1a@o1138119.ingest.sentry.io/6191481" # native project - SELFDRIVE_NATIVE = "https://7e3be9bfcfe04c9abe58bd25fe290d1a@o1138119.ingest.sentry.io/6191481" # noqa: PIE796 + SELFDRIVE_NATIVE = "https://7e3be9bfcfe04c9abe58bd25fe290d1a@o1138119.ingest.sentry.io/6191481" CRASHES_DIR = Paths.community_crash_root() @@ -68,7 +68,7 @@ def save_exception(exc_text: str) -> None: else: f.write(exc_text) - print(f'Logged current crash to {files}') + print('Logged current crash to {}'.format(files)) def bind_user() -> None: From 38697b1f4c15409279a6cce21afa90afda092db8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 22:40:20 -0400 Subject: [PATCH 466/575] Revert "encapsulate in a class" This reverts commit b95079bf7aa29c878daab974737008e4a1f6c1ea. --- selfdrive/car/__init__.py | 31 ++++++++++++--------------- selfdrive/car/chrysler/interface.py | 4 ++-- selfdrive/car/ford/interface.py | 4 ++-- selfdrive/car/gm/interface.py | 4 ++-- selfdrive/car/honda/interface.py | 4 ++-- selfdrive/car/hyundai/interface.py | 4 ++-- selfdrive/car/interfaces.py | 3 +-- selfdrive/car/mazda/interface.py | 4 ++-- selfdrive/car/nissan/interface.py | 6 +++--- selfdrive/car/subaru/interface.py | 6 +++--- selfdrive/car/toyota/interface.py | 6 +++--- selfdrive/car/volkswagen/interface.py | 4 ++-- 12 files changed, 38 insertions(+), 42 deletions(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index b26607d24f..06fa120eee 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -43,28 +43,25 @@ def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, ca return events -class ButtonEvents: - def __init__(self) -> None: - self.mads_event_lock: bool = True +def create_cancel_event(long_enabled: bool, prev_long_enabled: bool) -> list[capnp.lib.capnp._DynamicStructBuilder]: + events: list[capnp.lib.capnp._DynamicStructBuilder] = [] - @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 - 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] = [] +def create_mads_event(mads_enabled: bool, prev_mads_enabled: bool, + mads_event_lock: bool) -> tuple[list[capnp.lib.capnp._DynamicStructBuilder], bool]: + events: list[capnp.lib.capnp._DynamicStructBuilder] = [] - mads_changed = prev_mads_enabled != mads_enabled - if (mads_changed and self.mads_event_lock) or (not mads_changed and not self.mads_event_lock): - events.append(car.CarState.ButtonEvent(pressed=self.mads_event_lock, type=ButtonType.altButton1)) - self.mads_event_lock = not self.mads_event_lock + mads_changed = prev_mads_enabled != mads_enabled + if (mads_changed and mads_event_lock) or (not mads_changed and not mads_event_lock): + events.append(car.CarState.ButtonEvent(pressed=mads_event_lock, type=ButtonType.altButton1)) + mads_event_lock = not mads_event_lock - return events + return events, mads_event_lock def gen_empty_fingerprint(): diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 6673ab5347..056128dc2e 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from openpilot.selfdrive.car import create_button_events, get_safety_config +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 from openpilot.selfdrive.car.interfaces import CarInterfaceBase @@ -139,7 +139,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 1b76c92790..190196de64 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,7 +1,7 @@ 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 +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.ford.fordcan import CanBus from openpilot.common.params import Params from openpilot.selfdrive.car.ford.values import Ecu, FordFlags, FordFlagsSP @@ -119,7 +119,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.manumatic], pcm_enable=False) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 19431fb2fc..15ebb0fc82 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 +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event 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 @@ -253,7 +253,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # The ECM allows enabling on falling edge of set, but only rising edge of resume diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 631b111a5d..2036a0f4a9 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -6,7 +6,7 @@ from openpilot.common.numpy_fast import interp from openpilot.selfdrive.car.honda.hondacan import CanBus from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, CruiseSettings, HondaFlags, CAR, HONDA_BOSCH, \ HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS -from openpilot.selfdrive.car import create_button_events, get_safety_config +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.disable_ecu import disable_ecu @@ -305,7 +305,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 99233b8163..621a2ae541 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 +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.disable_ecu import disable_ecu @@ -250,7 +250,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 46991f7dde..df7bb5dea1 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -18,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, ButtonEvents +from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG 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 @@ -254,7 +254,6 @@ 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"), "")) diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index e23637aef3..6481c021c4 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -2,7 +2,7 @@ from cereal import car from openpilot.common.conversions import Conversions as CV 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 import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -75,7 +75,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 84b5de35f3..2176a4a107 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 +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event, create_cancel_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.nissan.values import CAR @@ -55,8 +55,8 @@ class CarInterface(CarInterfaceBase): 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, self.mads_event_lock) # MADS BUTTON + *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled) + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 058ecb0def..f6b6fad9e9 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 +from openpilot.selfdrive.car import get_safety_config, create_mads_event, create_cancel_event 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 @@ -145,8 +145,8 @@ class CarInterface(CarInterfaceBase): 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, self.mads_event_lock) # MADS BUTTON + *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled), + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # 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 5f60a6a3a6..91a138372d 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 +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event, create_cancel_event from openpilot.selfdrive.car.disable_ecu import disable_ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase @@ -246,8 +246,8 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *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, self.mads_event_lock) # MADS BUTTON + *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled) + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] # events diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 5f431c457a..505ea7db31 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,7 +1,7 @@ from cereal import car from panda import Panda from openpilot.common.params import Params -from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car import get_safety_config, create_mads_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.volkswagen.values import CAR, CANBUS, CarControllerParams, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags, \ VolkswagenFlagsSP @@ -146,7 +146,7 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [ *ret.buttonEvents, - *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON + *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] events = self.create_common_events(ret, c, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], From 0521f54536e9a203b0e4d53c33f3ec5b092f03e7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 22:40:21 -0400 Subject: [PATCH 467/575] Revert "encapsulate cancel event in method" This reverts commit 93226506fa79efb7aacd50567d4e98094ebdc2f9. --- selfdrive/car/__init__.py | 9 --------- selfdrive/car/nissan/interface.py | 10 ++++++++-- selfdrive/car/subaru/interface.py | 10 ++++++++-- selfdrive/car/toyota/interface.py | 10 ++++++++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 06fa120eee..293ee2387c 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -43,15 +43,6 @@ def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, ca return events -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(mads_enabled: bool, prev_mads_enabled: bool, mads_event_lock: bool) -> tuple[list[capnp.lib.capnp._DynamicStructBuilder], bool]: events: list[capnp.lib.capnp._DynamicStructBuilder] = [] diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 2176a4a107..26467d9f80 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, create_cancel_event +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.nissan.values import CAR @@ -53,9 +53,15 @@ 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 + self.CS.button_events.append(be) + ret.buttonEvents = [ *self.CS.button_events, - *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled) *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index f6b6fad9e9..d12aa44e18 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, create_cancel_event +from openpilot.selfdrive.car import get_safety_config, create_mads_event 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 @@ -143,9 +143,15 @@ 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 + self.CS.button_events.append(be) + ret.buttonEvents = [ *self.CS.button_events, - *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled), *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 91a138372d..7b7592ea64 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, create_cancel_event +from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.disable_ecu import disable_ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase @@ -244,9 +244,15 @@ 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 + self.CS.button_events.append(be) + ret.buttonEvents = [ *ret.buttonEvents, - *create_cancel_event(ret.cruiseState.enabled, self.CS.out.cruiseState.enabled) *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON ] From f5b1b4c73ebae79b518f8c2604c6bb2f21277e3c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 22:40:45 -0400 Subject: [PATCH 468/575] Revert "encapsulate in method" This reverts commit 5facba22 --- selfdrive/car/__init__.py | 14 ++++---------- selfdrive/car/chrysler/interface.py | 22 +++++++++++++++------- selfdrive/car/ford/interface.py | 21 ++++++++++++++------- selfdrive/car/gm/interface.py | 24 ++++++++++++++++-------- selfdrive/car/honda/interface.py | 14 ++++++-------- selfdrive/car/hyundai/interface.py | 22 +++++++++++++++------- selfdrive/car/interfaces.py | 4 ++-- selfdrive/car/mazda/interface.py | 22 +++++++++++++++------- selfdrive/car/nissan/interface.py | 15 +++++++++++---- selfdrive/car/subaru/interface.py | 15 +++++++++++---- selfdrive/car/toyota/interface.py | 15 +++++++++++---- selfdrive/car/volkswagen/carstate.py | 4 ++-- selfdrive/car/volkswagen/interface.py | 22 +++++++++++++++------- 13 files changed, 137 insertions(+), 77 deletions(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 293ee2387c..6aebd2bd90 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -43,16 +43,10 @@ def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, ca return events -def create_mads_event(mads_enabled: bool, prev_mads_enabled: bool, - mads_event_lock: bool) -> tuple[list[capnp.lib.capnp._DynamicStructBuilder], bool]: - events: list[capnp.lib.capnp._DynamicStructBuilder] = [] - - mads_changed = prev_mads_enabled != mads_enabled - if (mads_changed and mads_event_lock) or (not mads_changed and not mads_event_lock): - events.append(car.CarState.ButtonEvent(pressed=mads_event_lock, type=ButtonType.altButton1)) - mads_event_lock = not mads_event_lock - - return events, mads_event_lock +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 def gen_empty_fingerprint(): diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 056128dc2e..bc80530011 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -93,14 +93,15 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - ret.buttonEvents = [ + 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, c.vCruise, + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + 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 @@ -119,7 +120,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 ret.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) @@ -137,10 +138,17 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) - ret.buttonEvents = [ - *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + # MADS BUTTON + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events # events events = self.create_common_events(ret, c, extra_gears=[car.CarState.GearShifter.low], pcm_enable=False) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 190196de64..6045339557 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -84,14 +84,15 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - ret.buttonEvents = [ + 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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -104,7 +105,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 ret.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) @@ -117,10 +118,16 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) - ret.buttonEvents = [ - *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events events = self.create_common_events(ret, c, extra_gears=[GearShifter.manumatic], pcm_enable=False) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 15ebb0fc82..a570c547ff 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -211,7 +211,7 @@ class CarInterface(CarInterfaceBase): # 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: - ret.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,10 +222,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 ret.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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -238,7 +239,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 ret.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) @@ -251,10 +252,17 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(distance_button)) - ret.buttonEvents = [ - *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + # MADS BUTTON + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events # 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 2036a0f4a9..52984035c4 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -6,7 +6,7 @@ from openpilot.common.numpy_fast import interp from openpilot.selfdrive.car.honda.hondacan import CanBus from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, CruiseSettings, HondaFlags, CAR, HONDA_BOSCH, \ HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS -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 @@ -268,14 +268,15 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) self.sp_update_params() - ret.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), ] 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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -288,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 ret.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) @@ -303,10 +304,7 @@ 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 = [ - *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + ret.buttonEvents = self.CS.button_events # 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 621a2ae541..6845f79f7e 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -210,11 +210,12 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - ret.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.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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if not self.CP.pcmCruiseSpeed: @@ -237,7 +238,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 ret.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: @@ -248,10 +249,17 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=(self.CS.cruise_buttons[-1] == 3)) - ret.buttonEvents = [ - *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + # MADS BUTTON + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events # 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 df7bb5dea1..177ad74373 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -560,12 +560,12 @@ class CarInterfaceBase(ABC): return mads_enabled - def get_sp_v_cruise_non_pcm_state(self, cs_out, acc_enabled, vCruise, + def get_sp_v_cruise_non_pcm_state(self, cs_out, acc_enabled, button_events, vCruise, enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise), resume_button=(ButtonType.accelCruise, ButtonType.resumeCruise)): if cs_out.cruiseState.available: - for b in cs_out.buttonEvents: + for b in button_events: if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed: if b.type in enable_buttons and not b.pressed: acc_enabled = True diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 6481c021c4..f8919d13e7 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -40,14 +40,15 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() # TODO: add button types for inc and dec - ret.buttonEvents = [ + 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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -60,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 ret.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) @@ -73,10 +74,17 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) - ret.buttonEvents = [ - *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + # MADS BUTTON + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events # events events = self.create_common_events(ret, c, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.brake], diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 26467d9f80..707eab06c0 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -60,10 +60,17 @@ class CarInterface(CarInterfaceBase): be.type = ButtonType.cancel self.CS.button_events.append(be) - ret.buttonEvents = [ - *self.CS.button_events, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + # MADS BUTTON + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events 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 d12aa44e18..967d426fe4 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -150,10 +150,17 @@ class CarInterface(CarInterfaceBase): be.type = ButtonType.cancel self.CS.button_events.append(be) - ret.buttonEvents = [ - *self.CS.button_events, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + # MADS BUTTON + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events 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 7b7592ea64..71e08e4bea 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -251,10 +251,17 @@ class CarInterface(CarInterfaceBase): be.type = ButtonType.cancel self.CS.button_events.append(be) - ret.buttonEvents = [ - *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + # MADS BUTTON + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events # 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 2c183aabde..1dc4c0e430 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -150,7 +150,7 @@ class CarState(CarStateBase): # 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. @@ -256,7 +256,7 @@ class CarState(CarStateBase): # 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 505ea7db31..fe042984e2 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -117,7 +117,8 @@ 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, c.vCruise, + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise, enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise)) if ret.cruiseState.available: @@ -130,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 ret.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) @@ -142,12 +143,19 @@ class CarInterface(CarInterfaceBase): self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled ret, self.CS = self.get_sp_common_state(ret, self.CS, - gap_button=any(b.type == ButtonType.gapAdjustCruise and b.pressed for b in ret.buttonEvents)) + gap_button=any(b.type == ButtonType.gapAdjustCruise and b.pressed for b in self.CS.button_events)) - ret.buttonEvents = [ - *ret.buttonEvents, - *create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled, self.mads_event_lock) # MADS BUTTON - ] + # MADS BUTTON + if self.CS.out.madsEnabled != self.CS.madsEnabled: + if self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = False + else: + if not self.mads_event_lock: + self.CS.button_events.append(create_mads_event(self.mads_event_lock)) + self.mads_event_lock = True + + ret.buttonEvents = self.CS.button_events events = self.create_common_events(ret, c, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], pcm_enable=False, From 36498041a9585b7d8726178680013bfbb71d8f63 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 00:50:00 -0400 Subject: [PATCH 469/575] add to cereal at the end --- selfdrive/car/chrysler/interface.py | 12 +++++------- selfdrive/car/ford/interface.py | 12 +++++------- selfdrive/car/gm/interface.py | 11 ++++++----- selfdrive/car/honda/interface.py | 9 +++++---- selfdrive/car/hyundai/interface.py | 9 +++++---- selfdrive/car/interfaces.py | 4 ++-- selfdrive/car/mazda/interface.py | 9 +++++---- selfdrive/car/toyota/interface.py | 2 +- selfdrive/car/volkswagen/carstate.py | 4 ++-- selfdrive/car/volkswagen/interface.py | 9 +++++---- 10 files changed, 41 insertions(+), 40 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 0741dfc7d1..d4911375e7 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -93,14 +93,12 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - ret.buttonEvents = [ - *self.CS.button_events, - *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) - ] + 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, c.vCruise, + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + 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 @@ -119,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 ret.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) @@ -138,7 +136,7 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) ret.buttonEvents = [ - *ret.buttonEvents, + *self.CS.button_events, *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 2ec1ec070a..18dd7b4fa3 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -74,14 +74,12 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - ret.buttonEvents = [ - *self.CS.button_events, - *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) - ] + 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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -94,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 ret.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) @@ -108,7 +106,7 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) ret.buttonEvents = [ - *ret.buttonEvents, + *self.CS.button_events, *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 560d44f6e0..0e428bc13b 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -210,7 +210,7 @@ class CarInterface(CarInterfaceBase): # 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: - ret.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, @@ -221,10 +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 ret.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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -237,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 ret.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) @@ -251,7 +252,7 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(distance_button)) ret.buttonEvents = [ - *ret.buttonEvents, + *self.CS.button_events, *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 9c8b505a03..72ab68647f 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -268,14 +268,15 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) self.sp_update_params() - ret.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), ] 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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -288,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 ret.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 +305,7 @@ class CarInterface(CarInterfaceBase): gap_button=(self.CS.cruise_setting == 3)) ret.buttonEvents = [ - *ret.buttonEvents, + *self.CS.button_events, *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 4d601ef224..dbe0b62895 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -205,9 +205,10 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - ret.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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) self.CS.mads_enabled = False if not self.mads_main_toggle else self.CS.mads_enabled @@ -230,7 +231,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 ret.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: @@ -242,7 +243,7 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=(self.CS.cruise_buttons[-1] == 3)) ret.buttonEvents = [ - *ret.buttonEvents, + *self.CS.button_events, *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index d7399db9f1..b8ca13ec86 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -561,12 +561,12 @@ class CarInterfaceBase(ABC): return mads_enabled - def get_sp_v_cruise_non_pcm_state(self, cs_out, acc_enabled, vCruise, + def get_sp_v_cruise_non_pcm_state(self, cs_out, acc_enabled, button_events, vCruise, enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise), resume_button=(ButtonType.accelCruise, ButtonType.resumeCruise)): if cs_out.cruiseState.available: - for b in cs_out.buttonEvents: + for b in button_events: if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed: if b.type in enable_buttons and not b.pressed: acc_enabled = True diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 86ce548a12..9447803ae6 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -40,14 +40,15 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() # TODO: add button types for inc and dec - ret.buttonEvents = [ + 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, c.vCruise) + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise) if ret.cruiseState.available: if self.enable_mads: @@ -60,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 ret.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) @@ -74,7 +75,7 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(self.CS.distance_button)) ret.buttonEvents = [ - *ret.buttonEvents, + *self.CS.button_events, *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 26f0bbd001..d8b2516aba 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -245,7 +245,7 @@ class CarInterface(CarInterfaceBase): ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=bool(distance_button)) ret.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 ] diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 2c183aabde..1dc4c0e430 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -150,7 +150,7 @@ class CarState(CarStateBase): # 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. @@ -256,7 +256,7 @@ class CarState(CarStateBase): # 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 b02c5f1aa8..d965cf2e8b 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -117,7 +117,8 @@ 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, c.vCruise, + self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, + self.CS.button_events, c.vCruise, enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise)) if ret.cruiseState.available: @@ -130,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 ret.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) @@ -142,10 +143,10 @@ class CarInterface(CarInterfaceBase): self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled ret, self.CS = self.get_sp_common_state(ret, self.CS, - gap_button=any(b.type == ButtonType.gapAdjustCruise and b.pressed for b in ret.buttonEvents)) + gap_button=any(b.type == ButtonType.gapAdjustCruise and b.pressed for b in self.CS.button_events)) ret.buttonEvents = [ - *ret.buttonEvents, + *self.CS.button_events, *self.button_events.create_mads_event(self.CS.madsEnabled, self.CS.out.madsEnabled) # MADS BUTTON ] From 1f69ad630d410d6f7920e91eefa249b66e64f75b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 01:13:31 -0400 Subject: [PATCH 470/575] update logic --- selfdrive/car/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index b26607d24f..207c90678d 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -45,7 +45,7 @@ def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, ca class ButtonEvents: def __init__(self) -> None: - self.mads_event_lock: bool = True + self.is_mads: bool = False @staticmethod def create_cancel_event(long_enabled: bool, prev_long_enabled: bool) -> list[capnp.lib.capnp._DynamicStructBuilder]: @@ -60,9 +60,9 @@ class ButtonEvents: events: list[capnp.lib.capnp._DynamicStructBuilder] = [] mads_changed = prev_mads_enabled != mads_enabled - if (mads_changed and self.mads_event_lock) or (not mads_changed and not self.mads_event_lock): - events.append(car.CarState.ButtonEvent(pressed=self.mads_event_lock, type=ButtonType.altButton1)) - self.mads_event_lock = not self.mads_event_lock + 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 From fa668e786cc8175331f04e94b74b420394f76a18 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 20 Jul 2024 11:57:56 +0200 Subject: [PATCH 471/575] Implemented a window update for SunnyPilot mode The code was tailored to work with SunnyPilot mode. It includes various changes, such as adding logic to the main window to include SunnyPilot's window when it's active and making changes to the MainWindow constructor for compatibility with SunnyPilot. Additional changes were made to various UI elements to ensure compatibility with the different mode. This update provides a more seamless user experience when switching between the two modes, SunnyPilot and standard. --- selfdrive/ui/qt/body.h | 4 ++++ selfdrive/ui/qt/network/networking.cc | 4 ++++ selfdrive/ui/qt/network/wifi_manager.cc | 4 ++++ selfdrive/ui/qt/onroad/buttons.cc | 3 +-- selfdrive/ui/qt/onroad/buttons.h | 13 +++++++++---- selfdrive/ui/qt/onroad/onroad_home.cc | 4 ++++ selfdrive/ui/qt/onroad/onroad_home.h | 3 ++- selfdrive/ui/qt/sidebar.h | 5 +++++ selfdrive/ui/qt/widgets/wifi.h | 4 ++++ .../qt/onroad/sp_priv_annotated_camera.cc | 4 ++-- .../sunnypilot/qt/onroad/sp_priv_annotated_camera.h | 2 +- .../ui/sunnypilot/qt/onroad/sp_priv_buttons.cc | 11 +++++++++++ selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h | 10 ++++++++++ .../ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc | 9 ++++++--- .../ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h | 4 ++-- selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h | 2 +- selfdrive/ui/sunnypilot/sp_priv_ui.cc | 2 ++ selfdrive/ui/sunnypilot/sp_priv_ui.h | 8 +++++--- selfdrive/ui/ui.cc | 11 +++++++++-- selfdrive/ui/ui.h | 9 +++++++++ 20 files changed, 95 insertions(+), 21 deletions(-) diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h index 567a54d49b..68495aaf73 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -5,7 +5,11 @@ #include #include "common/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class RecordButton : public QPushButton { Q_OBJECT diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index a84460d5e9..acf6add481 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -6,7 +6,11 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/scrollview.h" diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc index 717da47096..7cfa17f774 100644 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -2,7 +2,11 @@ #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/widgets/prime.h" #include "common/params.h" diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index 76f83104b5..1185e5cdea 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -34,8 +34,7 @@ void ExperimentalButton::changeMode() { void ExperimentalButton::updateState(const UIState &s) { const auto cs = (*s.sm)["controlsState"].getControlsState(); - const auto lp_sp = (*s.sm)["longitudinalPlanSP"].getLongitudinalPlanSP(); - bool eng = (cs.getEngageable() || cs.getEnabled()) && !(lp_sp.getVisionTurnControllerState() > cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED); + bool eng = cs.getEngageable() || cs.getEnabled(); if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { engageable = eng; experimental_mode = cs.getExperimentalMode(); diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h index cfb9f1c5fd..791b87506b 100644 --- a/selfdrive/ui/qt/onroad/buttons.h +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -2,7 +2,11 @@ #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const int btn_size = 192; const int img_size = (btn_size / 4) * 3; @@ -14,6 +18,10 @@ public: explicit ExperimentalButton(QWidget *parent = 0); void updateState(const UIState &s); +protected: + bool experimental_mode; + bool engageable; + private: void paintEvent(QPaintEvent *event) override; void changeMode(); @@ -21,9 +29,6 @@ private: Params params; QPixmap engage_img; QPixmap experimental_img; - bool experimental_mode; - bool engageable; }; - -void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity); +void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity); \ No newline at end of file diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc index acb9cad89d..1b3f9ac8ab 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/qt/onroad/onroad_home.cc @@ -35,8 +35,12 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { alerts->raise(); setAttribute(Qt::WA_OpaquePaintEvent); + + // We handle the connection of the signals on the derived class +#ifndef SUNNYPILOT QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); +#endif } void OnroadWindow::updateState(const UIState &s) { diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h index 046ef62545..3ca4494a75 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.h +++ b/selfdrive/ui/qt/onroad/onroad_home.h @@ -5,7 +5,8 @@ #if SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h" -// #define AnnotatedCameraWidget AnnotatedCameraWidgetSP +#define AnnotatedCameraWidget AnnotatedCameraWidgetSP +#define UIState UIStateSP #else #include "selfdrive/ui/qt/onroad/annotated_camera.h" #endif diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 40b515434b..a427d8210b 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -4,8 +4,13 @@ #include #include +#include "cereal/messaging/messaging.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif typedef QPair, QColor> ItemStatus; Q_DECLARE_METATYPE(ItemStatus); diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h index 60c865f2b8..bdcd9aa2ca 100644 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -4,7 +4,11 @@ #include #include +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif class WiFiPromptWidget : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc index 6aa7f61d1f..3048f00987 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc @@ -36,7 +36,7 @@ AnnotatedCameraWidgetSP::AnnotatedCameraWidgetSP(VisionStreamType type, QWidget* main_layout->setMargin(UI_BORDER_SIZE); main_layout->setSpacing(0); - experimental_btn = new ExperimentalButton(this); + experimental_btn = new ExperimentalButtonSP(this); main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); onroad_settings_btn = new OnroadSettingsButton(this); @@ -1501,6 +1501,6 @@ void AnnotatedCameraWidgetSP::paintEvent(QPaintEvent *event) { void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) { CameraWidget::showEvent(event); - ui_update_params(uiStateSP()); + sp_ui_update_params(uiStateSP()); prev_draw_t = millis_since_boot(); } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h index 7f778db0da..341d684af1 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h @@ -59,7 +59,7 @@ private: void drawRoadNameText(QPainter &p, int x, int y, const QString &text, QColor color); QVBoxLayout *main_layout; - ExperimentalButton *experimental_btn; + ExperimentalButtonSP *experimental_btn; QPixmap dm_img; float speed; QString speedUnit; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc index ff6d57ddf9..3ec138425d 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc @@ -14,6 +14,17 @@ static void drawCustomButtonIcon(QPainter &p, const int btn_size_x, const int bt p.setOpacity(1.0); } +// ExperimentalButtonSP +void ExperimentalButtonSP::updateState(const UIStateSP &s) { + const auto cs = (*s.sm)["controlsState"].getControlsState(); + bool eng = cs.getEngageable() || cs.getEnabled(); + if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { + engageable = eng; + experimental_mode = cs.getExperimentalMode(); + update(); + } +} + // OnroadSettingsButton OnroadSettingsButton::OnroadSettingsButton(QWidget *parent) : QPushButton(parent) { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h index f89a97c60c..a64eede128 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h @@ -1,10 +1,20 @@ #pragma once #include +#include "selfdrive/ui/qt/onroad/buttons.h" + #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" #include "selfdrive/ui/ui.h" +class ExperimentalButtonSP : public ExperimentalButton { + Q_OBJECT + + public: + explicit ExperimentalButtonSP(QWidget *parent = 0) : ExperimentalButton(parent) {}; + void updateState(const UIStateSP &s); +}; + class OnroadSettingsButton : public QPushButton { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc index 9a91d34264..65b42e4fe5 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc @@ -12,14 +12,16 @@ #endif #include "selfdrive/ui/qt/util.h" - OnroadWindowSP::OnroadWindowSP(QWidget *parent) : OnroadWindow(parent) { - + // QObject::disconnect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); + // QObject::disconnect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); + if (getenv("MAP_RENDER_VIEW")) { CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); split->insertWidget(0, map_render); //TODO: We MIGHT to override the split variable because it is added on onroad_home.cc and we need to access it before. I am not sure so we will need to test it before } - QObject::connect(uiStateSP(), &UIStateSP::primeChanged, this, &OnroadWindowSP::primeChanged); + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &OnroadWindowSP::updateState); + QObject::connect(uiStateSP(), &UIStateSP::offroadTransition, this, &OnroadWindowSP::offroadTransition); } void OnroadWindowSP::updateState(const UIStateSP &s) { @@ -100,6 +102,7 @@ void OnroadWindowSP::createOnroadSettingsWidget() { } void OnroadWindowSP::offroadTransition(bool offroad) { + if (!offroad) { #ifdef ENABLE_MAPS if (map == nullptr && (uiStateSP()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h index 0ef9e25701..746239cf3d 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h @@ -36,7 +36,7 @@ private: void createMapWidget(); void createOnroadSettingsWidget(); void mousePressEvent(QMouseEvent* e) override; - AnnotatedCameraWidgetSP *nvg; + // AnnotatedCameraWidgetSP *nvg; QWidget *map = nullptr; QWidget *onroad_settings = nullptr; @@ -44,7 +44,7 @@ private: protected slots: void offroadTransition(bool offroad) override; - void updateState(const UIStateSP &s); + void updateState(const UIStateSP &s) override; void primeChanged(bool prime); void updateMapSize(const UISceneSP &scene); }; diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h b/selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h index 1dd25f31f8..bedd80468e 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h @@ -4,7 +4,7 @@ const float DRIVING_PATH_WIDE = 0.9; const float DRIVING_PATH_NARROW = 0.25; -typedef struct UISceneSP : public UIScene { +typedef struct UISceneSP : UIScene { cereal::ControlsState::Reader controlsState; // Debug UI diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.cc b/selfdrive/ui/sunnypilot/sp_priv_ui.cc index 5c96ea52a8..c11469584d 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.cc +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.cc @@ -294,6 +294,7 @@ void UIStateSP::setSunnylinkDeviceUsers(const std::vector& users) { } DeviceSP::DeviceSP(QObject *parent) : Device(parent){ + QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &DeviceSP::update); } //todo: revisit this @@ -320,6 +321,7 @@ UIStateSP *uiStateSP() { static UIStateSP ui_state; return &ui_state; } +UIStateSP *uiState() { return uiStateSP(); } DeviceSP *deviceSP() { static DeviceSP _device; diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.h b/selfdrive/ui/sunnypilot/sp_priv_ui.h index fcb9940d97..74ec647889 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.h +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.h @@ -54,8 +54,6 @@ public: void setSunnylinkRoles(const std::vector &roles); void setSunnylinkDeviceUsers(const std::vector &users); - UISceneSP scene = {}; - inline std::vector sunnylinkDeviceRoles() const { return sunnylinkRoles; } inline bool isSunnylinkAdmin() const { return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) { @@ -109,8 +107,8 @@ private: float mads_path_count = 4; // UI runs at 20 Hz, therefore 0.2 second is [0.2 second / (1 / 20 Hz) = 4] }; -//TODO: maybe need to redeclare but the sp? UIStateSP *uiStateSP(); +UIStateSP *uiState(); // device management class class DeviceSP : public Device { @@ -122,6 +120,10 @@ protected: void updateBrightness(const UIStateSP &s); }; +DeviceSP *deviceSP(); +inline DeviceSP *device() { return deviceSP(); } + void sp_update_model(UIStateSP *s, const cereal::ModelDataV2::Reader &model); +void sp_ui_update_params(UIStateSP *s); void update_line_data(const UIStateSP *s, const cereal::XYZTData::Reader &line, float y_off, float z_off_left, float z_off_right, QPolygonF *pvd, int max_idx, bool allow_invert); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 89bd4e2efd..951af31cdf 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -238,6 +238,10 @@ void UIState::updateStatus() { } } +// #ifdef SUNNYPILOT +// #include "selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h" +// #define UIScene UISceneSP +// #endif UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", @@ -291,8 +295,9 @@ void UIState::setPrimeType(PrimeType type) { Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { setAwake(true); resetInteractiveTimeout(); - +#ifndef SUNNYPILOT QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); +#endif } void Device::update(const UIState &s) { @@ -358,6 +363,7 @@ void Device::updateWakefulness(const UIState &s) { setAwake(s.scene.ignition || interactive_timeout > 0); } +#ifndef SUNNYPILOT UIState *uiState() { static UIState ui_state; return &ui_state; @@ -366,4 +372,5 @@ UIState *uiState() { Device *device() { static Device _device; return &_device; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 9f3a082b2e..9c91b0e637 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -112,6 +112,10 @@ typedef struct UIScene { uint64_t started_frame; } UIScene; +#ifdef SUNNYPILOT +#include "sunnypilot/qt/sp_priv_ui_scene.h" +#define UIScene UISceneSP +#endif class UIState : public QObject { Q_OBJECT @@ -153,8 +157,11 @@ protected: private: bool started_prev = false; }; +#undef UIScene +#ifndef SUNNYPILOT UIState *uiState(); +#endif // device management class class Device : public QObject { @@ -191,7 +198,9 @@ public slots: void update(const UIState &s); }; +#ifndef SUNNYPILOT Device *device(); +#endif void ui_update_params(UIState *s); int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height); From 69a2408c44ddf492b56a7b04e98612a881930933 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 21 Jul 2024 13:44:48 +0200 Subject: [PATCH 472/575] Update self-driving UI for SunnyPilot behavior Modified the self-driving UI components to better accommodate SunnyPilot's custom behavior. Changes include the reduction of EXTRA_UI_STATES, addition of early returns in updates if in SunnyPilot mode, and revisions in UIStateSP update methods. The refactoring also includes rearrangement of car status updates and removal of duplicate parameters acquisition. --- selfdrive/ui/sunnypilot/sp_priv_ui.cc | 16 +++++----------- selfdrive/ui/sunnypilot/sp_priv_ui.h | 2 +- selfdrive/ui/ui.cc | 3 +++ selfdrive/ui/ui.h | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.cc b/selfdrive/ui/sunnypilot/sp_priv_ui.cc index c11469584d..4a8240bdad 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.cc +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.cc @@ -160,12 +160,12 @@ void sp_ui_update_params(UIStateSP *s) { void UIStateSP::updateStatus() { UIState::updateStatus(); auto params = Params(); - auto car_control = (*sm)["carControl"].getCarControl(); - auto car_state = (*sm)["carState"].getCarState(); - auto mads_enabled = car_state.getMadsEnabled(); if (scene.started && sm->updated("controlsState")) { + auto car_control = (*sm)["carControl"].getCarControl(); + auto car_state = (*sm)["carState"].getCarState(); + auto mads_enabled = car_state.getMadsEnabled(); if (status != STATUS_OVERRIDE) { - status = car_state.getMadsEnabled() ? car_control.getLongActive() ? STATUS_ENGAGED : STATUS_MADS : STATUS_DISENGAGED; + status = mads_enabled && car_control.getLongActive() ? STATUS_ENGAGED : mads_enabled ? STATUS_MADS : STATUS_DISENGAGED; } if (mads_enabled != last_mads_enabled) { @@ -258,19 +258,13 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) { "controlsStateSP", "modelV2SP" }); - Params params; - language = QString::fromStdString(params.get("LanguageSetting")); - auto prime_value = params.get("PrimeType"); - if (!prime_value.empty()) { - prime_type = static_cast(std::atoi(prime_value.c_str())); - } - // update timer timer = new QTimer(this); QObject::connect(timer, &QTimer::timeout, this, &UIStateSP::update); timer->start(1000 / UI_FREQ); } +// Note: This method overrides completely the update method from the parent class intentionally. void UIStateSP::update() { update_sockets(this); sp_update_state(this); diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.h b/selfdrive/ui/sunnypilot/sp_priv_ui.h index 74ec647889..162bc2f294 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.h +++ b/selfdrive/ui/sunnypilot/sp_priv_ui.h @@ -35,7 +35,7 @@ const QColor sp_bg_colors [] = { [STATUS_ENGAGED] = QColor(0x00, 0xc8, 0x00, 0xf1), [STATUS_MADS] = QColor(0x00, 0xc8, 0xc8, 0xf1), }; - +#define bg_colors sp_bg_colors // Override the bg_colors array with the sp_bg_colors array const QColor tcs_colors [] = { [int(cereal::LongitudinalPlanSP::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff), diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 951af31cdf..ff6691a780 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -11,6 +11,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" +#include "qt/util.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 @@ -256,6 +257,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { prime_type = static_cast(std::atoi(prime_value.c_str())); } + RETURN_IF_SUNNYPILOT // update timer timer = new QTimer(this); QObject::connect(timer, &QTimer::timeout, this, &UIState::update); @@ -263,6 +265,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { } void UIState::update() { + RETURN_IF_SUNNYPILOT update_sockets(this); update_state(this); updateStatus(); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 9c91b0e637..8e5fe6da34 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -47,7 +47,7 @@ constexpr vec3 default_face_kpts_3d[] = { //Example of a macro #ifdef SUNNYPILOT -#define EXTRA_UI_STATES STATUS_MADS, STATUS_LA +#define EXTRA_UI_STATES STATUS_MADS #else #define EXTRA_UI_STATES #endif From 72f34a3335d58d03b096ef0aa58bd2d063972ec3 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 08:51:06 -0400 Subject: [PATCH 473/575] should be removed --- selfdrive/car/chrysler/interface.py | 2 +- selfdrive/car/ford/interface.py | 2 +- selfdrive/car/gm/interface.py | 2 +- selfdrive/car/hyundai/interface.py | 2 +- selfdrive/car/mazda/interface.py | 2 +- selfdrive/car/nissan/interface.py | 2 +- selfdrive/car/subaru/interface.py | 2 +- selfdrive/car/toyota/interface.py | 2 +- selfdrive/car/volkswagen/interface.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index ba23d88da9..d4911375e7 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,7 +1,7 @@ #!/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 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 diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index def160af65..c9a95de45b 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,7 +1,7 @@ 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.common.params import Params from openpilot.selfdrive.car.ford.values import Ecu, FordFlags, FordFlagsSP diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 9a71b92937..2696c3d8f1 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 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index a404d74a1b..8dfaa35416 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 diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index f1e11aa306..9447803ae6 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -2,7 +2,7 @@ from cereal import car from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.mazda.values import CAR, LKAS_LIMITS -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 ButtonType = car.CarState.ButtonEvent.Type diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 707eab06c0..a977c6de21 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 diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 967d426fe4..5d83ea4917 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 diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index be6763e2da..d6936ec6c2 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 diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index feeb89ea49..d965cf2e8b 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,7 +1,7 @@ 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, \ VolkswagenFlagsSP From 4aa7d25c9afb5b465e4be54d0068460810051e53 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 08:56:45 -0400 Subject: [PATCH 474/575] add them back --- selfdrive/car/interfaces.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 177ad74373..5fbdcd2e3f 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -18,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 @@ -254,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"), "")) From b7b813c53187e7cb571d07b5cf97813de2f52820 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 21 Jul 2024 15:04:53 +0200 Subject: [PATCH 475/575] Update UI components with new SP controls Replaced several different ButtonControl, ToggleControl, and LabelControl instances with their corresponding SP versions across various sections of the UI. This improves the consistency of the user interface and lays the groundwork for further enhancements. --- selfdrive/ui/qt/network/networking.cc | 1 + selfdrive/ui/qt/network/networking.h | 2 ++ selfdrive/ui/qt/offroad/settings.h | 2 ++ selfdrive/ui/qt/widgets/ssh_keys.h | 4 ++- .../offroad/settings/sp_priv_device_panel.cc | 18 +++++------ .../offroad/settings/sp_priv_device_panel.h | 2 +- .../offroad/settings/sp_priv_osm_settings.cc | 30 +++++++++---------- .../offroad/settings/sp_priv_osm_settings.h | 26 ++++++++-------- .../qt/offroad/settings/sp_priv_settings.cc | 4 +-- .../settings/sp_priv_software_settings.cc | 6 ++-- .../settings/sp_priv_software_settings.h | 2 +- .../settings/sp_priv_sunnylink_settings.cc | 10 +++---- .../settings/sp_priv_sunnylink_settings.h | 4 +-- .../settings/sp_priv_sunnypilot_settings.cc | 18 +++++------ .../settings/sp_priv_vehicle_settings.cc | 8 ++--- .../settings/sp_priv_visuals_settings.cc | 2 +- .../sp_priv_lane_change_settings.cc | 2 +- .../qt/onroad/sp_priv_onroad_settings.cc | 4 +-- .../sunnypilot/qt/widgets/sp_priv_controls.cc | 2 +- .../sunnypilot/qt/widgets/sp_priv_controls.h | 16 +++++----- 20 files changed, 85 insertions(+), 78 deletions(-) diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index acf6add481..1aafb06c48 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -8,6 +8,7 @@ #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#define ToggleControl ToggleControlSP #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index 1709eb4610..a02ddb6702 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -9,6 +9,8 @@ #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #define ListWidget ListWidgetSP +#define ToggleControl ToggleControlSP +#define LabelControl LabelControlSP #else #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 96fc04bbca..66754e516b 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -19,6 +19,8 @@ #define ListWidget ListWidgetSP #define ParamControl ParamControlSP #define ButtonParamControl ButtonParamControlSP +#define ToggleControl ToggleControlSP +#define LabelControl LabelControlSP #else #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/controls.h" diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h index 8b7b732849..944d6890ea 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.h +++ b/selfdrive/ui/qt/widgets/ssh_keys.h @@ -5,6 +5,8 @@ #include "system/hardware/hw.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#define ToggleControl ToggleControlSP +#define ButtonControl ButtonControlSP #else #include "selfdrive/ui/qt/widgets/controls.h" #endif @@ -33,4 +35,4 @@ private: void refresh(); void getUserKeys(const QString &username); -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc index ca262d4b16..161f082985 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc @@ -9,10 +9,10 @@ #include "selfdrive/ui/qt/widgets/prime.h" DevicePanelSP::DevicePanelSP(SettingsWindow *parent) : DevicePanel(parent) { - fleetManagerPin = new ButtonControl( + fleetManagerPin = new ButtonControlSP( pin_title + pin, tr("TOGGLE"), tr("Enable or disable PIN requirement for Fleet Manager access.")); - connect(fleetManagerPin, &ButtonControl::clicked, [=]() { + connect(fleetManagerPin, &ButtonControlSP::clicked, [=]() { if (params.getBool("FleetManagerPin")) { if (ConfirmationDialog::confirm(tr("Are you sure you want to turn off PIN requirement?"), tr("Turn Off"), this)) { params.remove("FleetManagerPin"); @@ -35,21 +35,21 @@ DevicePanelSP::DevicePanelSP(SettingsWindow *parent) : DevicePanel(parent) { refreshPin(); // Error Troubleshoot - auto errorBtn = new ButtonControl( + auto errorBtn = new ButtonControlSP( tr("Error Troubleshoot"), tr("VIEW"), tr("Display error from the tmux session when an error has occurred from a system process.")); QFileInfo file("/data/community/crashes/error.txt"); QDateTime modifiedTime = file.lastModified(); QString modified_time = modifiedTime.toString("yyyy-MM-dd hh:mm:ss "); - connect(errorBtn, &ButtonControl::clicked, [=]() { + connect(errorBtn, &ButtonControlSP::clicked, [=]() { const std::string txt = util::read_file("/data/community/crashes/error.txt"); ConfirmationDialog::rich(modified_time + QString::fromStdString(txt), this); }); AddWidgetAt(3, errorBtn); - auto resetMapboxTokenBtn = new ButtonControl(tr("Reset Access Tokens for Map Services"), tr("RESET"), tr("Reset self-service access tokens for Mapbox, Amap, and Google Maps.")); - connect(resetMapboxTokenBtn, &ButtonControl::clicked, [=]() { + auto resetMapboxTokenBtn = new ButtonControlSP(tr("Reset Access Tokens for Map Services"), tr("RESET"), tr("Reset self-service access tokens for Mapbox, Amap, and Google Maps.")); + connect(resetMapboxTokenBtn, &ButtonControlSP::clicked, [=]() { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset access tokens for all map services?"), tr("Reset"), this)) { std::vector tokens = { "CustomMapboxTokenPk", @@ -65,8 +65,8 @@ DevicePanelSP::DevicePanelSP(SettingsWindow *parent) : DevicePanel(parent) { }); AddWidgetAt(6, resetMapboxTokenBtn); - auto resetParamsBtn = new ButtonControl(tr("Reset sunnypilot Settings"), tr("RESET"), ""); - connect(resetParamsBtn, &ButtonControl::clicked, [=]() { + auto resetParamsBtn = new ButtonControlSP(tr("Reset sunnypilot Settings"), tr("RESET"), ""); + connect(resetParamsBtn, &ButtonControlSP::clicked, [=]() { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all sunnypilot settings?"), tr("Reset"), this)) { std::system("sudo rm -rf /data/params/d/*"); Hardware::reboot(); @@ -75,7 +75,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindow *parent) : DevicePanel(parent) { AddWidgetAt(6, resetParamsBtn); QObject::connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { - for (auto btn : findChildren()) { + for (auto btn : findChildren()) { if (btn != pair_device && btn != errorBtn) { btn->setEnabled(offroad); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h index b07b0a8d50..6543b00636 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h @@ -16,7 +16,7 @@ private slots: void updateLabels(); private: - ButtonControl *fleetManagerPin; + ButtonControlSP *fleetManagerPin; QString pin_title = tr("Fleet Manager PIN:") + " "; QString pin = "OFF"; QFileSystemWatcher *fs_watch; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc index 397a97b993..4fe227e831 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc @@ -8,10 +8,10 @@ OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); const auto list = new ListWidgetSP(this, false); - list->addItem(mapdVersion = new LabelControl(tr("Mapd Version"), "Loading...")); + list->addItem(mapdVersion = new LabelControlSP(tr("Mapd Version"), "Loading...")); list->addItem(setupOsmDeleteMapsButton(parent)); - list->addItem(offlineMapsETA = new LabelControl(tr("Offline Maps ETA"), "")); - list->addItem(offlineMapsElapsed = new LabelControl(tr("Time Elapsed"), "")); + list->addItem(offlineMapsETA = new LabelControlSP(tr("Offline Maps ETA"), "")); + list->addItem(offlineMapsElapsed = new LabelControlSP(tr("Time Elapsed"), "")); list->addItem(setupOsmUpdateButton(parent)); list->addItem(setupOsmDownloadButton(parent)); list->addItem(setupUsStatesButton(parent)); @@ -32,9 +32,9 @@ OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { main_layout->addWidget(osmScreen); } -ButtonControl *OsmPanel::setupOsmDeleteMapsButton(QWidget *parent) { - osmDeleteMapsBtn = new ButtonControl(tr("Downloaded Maps"), tr("DELETE")); // Updated on updateLabels() - connect(osmDeleteMapsBtn, &ButtonControl::clicked, [=]() { +ButtonControlSP *OsmPanel::setupOsmDeleteMapsButton(QWidget *parent) { + osmDeleteMapsBtn = new ButtonControlSP(tr("Downloaded Maps"), tr("DELETE")); // Updated on updateLabels() + connect(osmDeleteMapsBtn, &ButtonControlSP::clicked, [=]() { if (showConfirmationDialog(parent, tr("This will delete ALL downloaded maps\n\nAre you sure you want to delete all the maps?"), tr("Yes, delete all the maps."))) { QtConcurrent::run([=]() { QDir dir(MAP_PATH); @@ -51,9 +51,9 @@ ButtonControl *OsmPanel::setupOsmDeleteMapsButton(QWidget *parent) { return osmDeleteMapsBtn; } -ButtonControl *OsmPanel::setupOsmUpdateButton(QWidget *parent) { - osmUpdateBtn = new ButtonControl(tr("Database Update"), tr("CHECK")); // Updated on updateLabels() - connect(osmUpdateBtn, &ButtonControl::clicked, [=]() { +ButtonControlSP *OsmPanel::setupOsmUpdateButton(QWidget *parent) { + osmUpdateBtn = new ButtonControlSP(tr("Database Update"), tr("CHECK")); // Updated on updateLabels() + connect(osmUpdateBtn, &ButtonControlSP::clicked, [=]() { if (osm_download_in_progress && !download_failed_state) { updateLabels(); } else if (showConfirmationDialog(parent)) { @@ -65,9 +65,9 @@ ButtonControl *OsmPanel::setupOsmUpdateButton(QWidget *parent) { return osmUpdateBtn; } -ButtonControl *OsmPanel::setupOsmDownloadButton(QWidget *parent) { - osmDownloadBtn = new ButtonControl(tr("Country"), tr("SELECT")); - connect(osmDownloadBtn, &ButtonControl::clicked, [=]() { +ButtonControlSP *OsmPanel::setupOsmDownloadButton(QWidget *parent) { + osmDownloadBtn = new ButtonControlSP(tr("Country"), tr("SELECT")); + connect(osmDownloadBtn, &ButtonControlSP::clicked, [=]() { osmDownloadBtn->setEnabled(false); osmDownloadBtn->setValue(tr("Fetching Country list...")); const std::vector> locations = getOsmLocations(); @@ -107,9 +107,9 @@ ButtonControl *OsmPanel::setupOsmDownloadButton(QWidget *parent) { return osmDownloadBtn; } -ButtonControl *OsmPanel::setupUsStatesButton(QWidget *parent) { - usStatesBtn = new ButtonControl(tr("State"), tr("SELECT")); - connect(usStatesBtn, &ButtonControl::clicked, [=]() { +ButtonControlSP *OsmPanel::setupUsStatesButton(QWidget *parent) { + usStatesBtn = new ButtonControlSP(tr("State"), tr("SELECT")); + connect(usStatesBtn, &ButtonControlSP::clicked, [=]() { const std::tuple allStatesOption = std::make_tuple("All States (~4.8 GB)", "All"); usStatesBtn->setEnabled(false); usStatesBtn->setValue(tr("Fetching State list...")); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h index b40362d82f..a83fe9a619 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h @@ -49,17 +49,17 @@ private: quint64 mapsDirSize = 0; QLabel *osmUpdateLbl; - ButtonControl *osmDownloadBtn; - ButtonControl *osmUpdateBtn; - ButtonControl *usStatesBtn; - ButtonControl *osmDeleteMapsBtn; - ButtonControl *setupOsmDeleteMapsButton(QWidget *parent);; - ButtonControl* setupOsmUpdateButton(QWidget *parent); - ButtonControl* setupOsmDownloadButton(QWidget *parent); - ButtonControl* setupUsStatesButton(QWidget *parent); + ButtonControlSP *osmDownloadBtn; + ButtonControlSP *osmUpdateBtn; + ButtonControlSP *usStatesBtn; + ButtonControlSP *osmDeleteMapsBtn; + ButtonControlSP *setupOsmDeleteMapsButton(QWidget *parent);; + ButtonControlSP* setupOsmUpdateButton(QWidget *parent); + ButtonControlSP* setupOsmDownloadButton(QWidget *parent); + ButtonControlSP* setupUsStatesButton(QWidget *parent); QTimer *timer; std::string osm_download_locations; -// void updateButtonControl(ButtonControl *btnControl, QWidget *parent, const QString &initTitle, const QString &allStatesOption); +// void updateButtonControlSP(ButtonControlSP *btnControl, QWidget *parent, const QString &initTitle, const QString &allStatesOption); void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent* event) override; @@ -69,10 +69,10 @@ private: QString processUpdateStatus(bool pending_update_check, int total_files, int downloaded_files, const QJsonObject& json, bool failed_state); ConfirmationDialog* confirmationDialog; - LabelControl *mapdVersion; - LabelControl *offlineMapsStatus; - LabelControl *offlineMapsETA; - LabelControl *offlineMapsElapsed; + LabelControlSP *mapdVersion; + LabelControlSP *offlineMapsStatus; + LabelControlSP *offlineMapsETA; + LabelControlSP *offlineMapsElapsed; std::optional lastDownloadedTimePoint; LocationsFetcher locationsFetcher; void updateMapSize(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc index 1e86d9050c..b826c212b6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc @@ -149,10 +149,10 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); toggles["CustomStockLong"]->setConfirmation(true, false); - connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { + connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControlSP::toggleFlipped, [=]() { updateToggles(); }); - connect(toggles["CustomStockLong"], &ToggleControl::toggleFlipped, [=]() { + connect(toggles["CustomStockLong"], &ToggleControlSP::toggleFlipped, [=]() { updateToggles(); }); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc index 30d0c2539d..eb2d0e2b43 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc @@ -3,9 +3,9 @@ #include SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { - // Get current model name and create new ButtonControl + // Get current model name and create new ButtonControlSP const auto current_model = GetModelName(); - currentModelLblBtn = new ButtonControl(tr("Driving Model"), tr("SELECT"), current_model); + currentModelLblBtn = new ButtonControlSP(tr("Driving Model"), tr("SELECT"), current_model); currentModelLblBtn->setValue(current_model); connect(&models_fetcher, &ModelsFetcher::downloadProgress, this, [this](const double progress) { @@ -63,7 +63,7 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { // Connect click event from currentModelLblBtn to local slot - connect(currentModelLblBtn, &ButtonControl::clicked, this, &SoftwarePanelSP::handleCurrentModelLblBtnClicked); + connect(currentModelLblBtn, &ButtonControlSP::clicked, this, &SoftwarePanelSP::handleCurrentModelLblBtnClicked); AddWidgetAt(0, currentModelLblBtn); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h index 6d04d07817..ab573ea5cf 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h @@ -69,7 +69,7 @@ private: std::optional selectedModelToDownload; std::optional selectedNavModelToDownload; std::optional selectedMetadataToDownload; - ButtonControl *currentModelLblBtn; + ButtonControlSP *currentModelLblBtn; ModelsFetcher models_fetcher; ModelsFetcher nav_models_fetcher; ModelsFetcher metadata_fetcher; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc index 61fd2823ce..1912349eb3 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc @@ -52,21 +52,21 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { }); status_popup = new SunnylinkSponsorPopup(false, this); - sponsorBtn = new ButtonControl( + sponsorBtn = new ButtonControlSP( tr("Sponsor Status"), tr("SPONSOR"), tr("Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.")); list->addItem(sponsorBtn); - connect(sponsorBtn, &ButtonControl::clicked, [=]() { + connect(sponsorBtn, &ButtonControlSP::clicked, [=]() { status_popup->exec(); }); list->addItem(horizontal_line()); pair_popup = new SunnylinkSponsorPopup(true, this); - pairSponsorBtn = new ButtonControl( + pairSponsorBtn = new ButtonControlSP( tr("Pair GitHub Account"), tr("PAIR"), tr("Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.") + "🌟"); list->addItem(pairSponsorBtn); - connect(pairSponsorBtn, &ButtonControl::clicked, [=]() { + connect(pairSponsorBtn, &ButtonControlSP::clicked, [=]() { if (getSunnylinkDongleId().value_or(tr("N/A")) == "N/A") { ConfirmationDialog::alert(tr("sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again."), this); } else { @@ -75,7 +75,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { }); list->addItem(horizontal_line()); - list->addItem(new LabelControl(tr("Manage Settings"))); + list->addItem(new LabelControlSP(tr("Manage Settings"))); backup_settings = new BackupSettings; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h index 76ec1417b3..579514737d 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h @@ -88,8 +88,8 @@ private: SubPanelButton *backupSettings; SunnylinkSponsorPopup *status_popup; SunnylinkSponsorPopup *pair_popup; - ButtonControl* sponsorBtn; - ButtonControl* pairSponsorBtn; + ButtonControlSP* sponsorBtn; + ButtonControlSP* pairSponsorBtn; SunnylinkClient* sunnylink_client; bool is_onroad = false; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc index 467c696e38..ef2f9d56c8 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc @@ -274,7 +274,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { list->addItem(laneChangeSettingsLayout); list->addItem(horizontal_line()); - list->addItem(new LabelControl(tr("Speed Limit Assist"))); + list->addItem(new LabelControlSP(tr("Speed Limit Assist"))); } if (param == "EnableSlc") { @@ -304,7 +304,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { } } - connect(toggles["NNFF"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["NNFF"], &ToggleControlSP::toggleFlipped, [=](bool state) { if (state) { toggles["EnforceTorqueLateral"]->setEnabled(false); params.putBool("EnforceTorqueLateral", false); @@ -320,7 +320,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { // trigger updateToggles() when toggleFlipped for (const auto& updateToggleName : updateTogglesNames) { if (toggles.find(updateToggleName) != toggles.end()) { - connect(toggles[updateToggleName], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles[updateToggleName], &ToggleControlSP::toggleFlipped, [=](bool state) { updateToggles(); }); } @@ -339,21 +339,21 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { toggles["EndToEndLongAlertLight"]->setConfirmation(true, false); toggles["CustomOffsets"]->showDescription(); - connect(toggles["EnableMads"], &ToggleControl::toggleFlipped, mads_settings, &MadsSettings::updateToggles); - connect(toggles["EnableMads"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["EnableMads"], &ToggleControlSP::toggleFlipped, mads_settings, &MadsSettings::updateToggles); + connect(toggles["EnableMads"], &ToggleControlSP::toggleFlipped, [=](bool state) { madsSettings->setEnabled(state); }); madsSettings->setEnabled(toggles["EnableMads"]->isToggled()); - connect(toggles["EnableSlc"], &ToggleControl::toggleFlipped, slc_settings, &SlcSettings::updateToggles); - connect(toggles["EnableSlc"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["EnableSlc"], &ToggleControlSP::toggleFlipped, slc_settings, &SlcSettings::updateToggles); + connect(toggles["EnableSlc"], &ToggleControlSP::toggleFlipped, [=](bool state) { slcSettings->setEnabled(state); slcSettings->setVisible(state); }); slcSettings->setEnabled(toggles["EnableSlc"]->isToggled()); slcSettings->setVisible(toggles["EnableSlc"]->isToggled()); - connect(toggles["CustomOffsets"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["CustomOffsets"], &ToggleControlSP::toggleFlipped, [=](bool state) { customOffsetsSettings->setEnabled(state); }); customOffsetsSettings->setEnabled(toggles["CustomOffsets"]->isToggled()); @@ -368,7 +368,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { }); // update "FRICTION" and "LAT_ACCEL_FACTOR" titles when TorquedOverride is flipped - connect(toggles["TorquedOverride"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["TorquedOverride"], &ToggleControlSP::toggleFlipped, [=](bool state) { friction->setEnabled(params.getBool("IsOffroad") || state); lat_accel_factor->setEnabled(params.getBool("IsOffroad") || state); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc index ec1ddc6f7e..f237e75ee9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc @@ -71,7 +71,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge setSpacing(50); // Hyundai/Kia/Genesis - addItem(new LabelControl(tr("Hyundai/Kia/Genesis"))); + addItem(new LabelControlSP(tr("Hyundai/Kia/Genesis"))); auto hkgSmoothStop = new ParamControlSP( "HkgSmoothStop", tr("HKG CAN: Smoother Stopping Performance (Beta)"), @@ -81,7 +81,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge addItem(hkgSmoothStop); // Subaru - addItem(new LabelControl(tr("Subaru"))); + addItem(new LabelControlSP(tr("Subaru"))); auto subaruManualParkingBrakeSng = new ParamControlSP( "SubaruManualParkingBrakeSng", tr("Manual Parking Brake: Stop and Go (Beta)"), @@ -91,7 +91,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge addItem(subaruManualParkingBrakeSng); // Toyota/Lexus - addItem(new LabelControl(tr("Toyota/Lexus"))); + addItem(new LabelControlSP(tr("Toyota/Lexus"))); stockLongToyota = new ParamControlSP( "StockLongToyota", tr("Enable Stock Toyota Longitudinal Control"), @@ -163,7 +163,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge addItem(toyotaAutoUnlock); // Volkswagen - addItem(new LabelControl(tr("Volkswagen"))); + addItem(new LabelControlSP(tr("Volkswagen"))); auto volkswagenCCOnly = new ParamControlSP( "VwCCOnly", tr("Enable CC Only support"), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc index 037a806bea..0d2e843a27 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc @@ -120,7 +120,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : ListWidgetSP(parent) { connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { }); - QObject::connect(toggles["MapboxFullScreen"], &ToggleControl::toggleFlipped, [=](bool state) { + QObject::connect(toggles["MapboxFullScreen"], &ToggleControlSP::toggleFlipped, [=](bool state) { toggles["MapboxFullScreen"]->showDescription(); }); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc index 6e8c8c69f6..756e5a8192 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc @@ -62,7 +62,7 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { } } - connect(toggles["BelowSpeedPause"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["BelowSpeedPause"], &ToggleControlSP::toggleFlipped, [=](bool state) { pause_lateral_speed->setEnabled(state); pause_lateral_speed->setVisible(state); }); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc index e25586f770..daf6c07ba6 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc @@ -273,11 +273,11 @@ OptionWidget::OptionWidget(QWidget *parent) : QPushButton(parent) { inner_frame->setContentsMargins(0, 0, 0, 0); inner_frame->setSpacing(0); { - title = new ElidedLabel(this); + title = new ElidedLabelSP(this); title->setAttribute(Qt::WA_TransparentForMouseEvents); inner_frame->addWidget(title); - subtitle = new ElidedLabel(this); + subtitle = new ElidedLabelSP(this); subtitle->setAttribute(Qt::WA_TransparentForMouseEvents); subtitle->setObjectName("subtitle"); inner_frame->addWidget(subtitle); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc index dc0974eee5..313a2ba926 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc @@ -43,7 +43,7 @@ AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, hlayout->addWidget(title_label, 1); // value next to control button - value = new ElidedLabel(); + value = new ElidedLabelSP(); value->setAlignment(Qt::AlignRight | Qt::AlignVCenter); value->setStyleSheet("color: #aaaaaa"); hlayout->addWidget(value); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index ca5101b079..1719b6150f 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -20,11 +20,11 @@ // This is for compatibility purposes, until we properly do inheritance splitting // This is because some controls were needing LabelControlSP, but the original code was using LabelControl // which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP -#define LabelControl LabelControlSP -#define ElidedLabel ElidedLabelSP -#define ButtonControl ButtonControlSP -#define Toggle ToggleSP -#define ToggleControl ToggleControlSP +#define LabelControl LabelControlSP //Not used on SP private code +#define ElidedLabel ElidedLabelSP //Not used on SP private code +#define ButtonControl ButtonControlSP //Not used on SP private code +#define Toggle ToggleSP //Not used on SP private code +#define ToggleControl ToggleControlSP //Not used on SP private code QFrame *horizontal_line(QWidget *parent = nullptr); @@ -86,7 +86,7 @@ protected: void hideEvent(QHideEvent *e) override; QVBoxLayout *main_layout; - ElidedLabel *value; + ElidedLabelSP *value; QLabel *description = nullptr; }; @@ -143,7 +143,7 @@ public: toggle.togglePosition(); } hlayout->addWidget(&toggle); - QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControlSP::toggleFlipped); + QObject::connect(&toggle, &ToggleSP::stateChanged, this, &ToggleControlSP::toggleFlipped); } void setEnabled(bool enabled) { @@ -155,7 +155,7 @@ signals: void toggleFlipped(bool state); protected: - Toggle toggle; + ToggleSP toggle; }; // widget to toggle params From 88659504993f892cc84a96f85d6bf5d53d5cb71f Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 21 Jul 2024 15:16:31 +0200 Subject: [PATCH 476/575] Update definitions for compatibility in SunnyPilot UI The commit introduces several definitions under the SUNNYPILOT flag in different files for compatibility. These changes are meant to make sure controls previously relying on other labels now properly use specifically defined SunnyPilot labels. The update reduces confusion and enhances code clarity. --- selfdrive/ui/qt/network/networking.h | 1 + selfdrive/ui/qt/offroad/settings.h | 1 + selfdrive/ui/qt/offroad/software_settings.cc | 1 + selfdrive/ui/qt/offroad_home.h | 1 + selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h | 9 --------- tools/cabana/detailwidget.h | 1 + 6 files changed, 5 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index a02ddb6702..502b47a276 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -11,6 +11,7 @@ #define ListWidget ListWidgetSP #define ToggleControl ToggleControlSP #define LabelControl LabelControlSP +#define ElidedLabel ElidedLabelSP #else #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 66754e516b..dcc2f132c4 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -18,6 +18,7 @@ #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #define ListWidget ListWidgetSP #define ParamControl ParamControlSP +#define ButtonControl ButtonControlSP #define ButtonParamControl ButtonParamControlSP #define ToggleControl ToggleControlSP #define LabelControl LabelControlSP diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index fa39daa6de..55e9c176b1 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -21,6 +21,7 @@ #ifdef SUNNYPILOT #define ListWidget ListWidgetSP +#define ButtonControl ButtonControlSP #endif diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h index be905861e1..92e0307bc1 100644 --- a/selfdrive/ui/qt/offroad_home.h +++ b/selfdrive/ui/qt/offroad_home.h @@ -23,6 +23,7 @@ #define OffroadHomeImp OffroadHomeSP #define LayoutWidget LayoutWidgetSP #define Sidebar SidebarSP +#define ElidedLabel ElidedLabelSP #else #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h index 1719b6150f..3b68ed5a5f 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h @@ -17,15 +17,6 @@ #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" -// This is for compatibility purposes, until we properly do inheritance splitting -// This is because some controls were needing LabelControlSP, but the original code was using LabelControl -// which was modified in-place in the past but since we now have a new file, we will be moving it to LabelControlSP -#define LabelControl LabelControlSP //Not used on SP private code -#define ElidedLabel ElidedLabelSP //Not used on SP private code -#define ButtonControl ButtonControlSP //Not used on SP private code -#define Toggle ToggleSP //Not used on SP private code -#define ToggleControl ToggleControlSP //Not used on SP private code - QFrame *horizontal_line(QWidget *parent = nullptr); class ElidedLabelSP : public QLabel { diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 58972d0860..91472eb214 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -8,6 +8,7 @@ #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#define ElidedLabel ElidedLabelSP #else #include "selfdrive/ui/qt/widgets/controls.h" #endif From 08abc5c9eb19db43055e626f063f45106eab83d1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 21 Jul 2024 15:35:09 +0200 Subject: [PATCH 477/575] map stuff --- selfdrive/ui/qt/maps/map.cc | 4 ++++ selfdrive/ui/qt/maps/map.h | 4 ++++ selfdrive/ui/qt/maps/map_eta.cc | 4 ++++ selfdrive/ui/qt/maps/map_instructions.cc | 4 ++++ selfdrive/ui/qt/maps/map_panel.cc | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 490eb118ca..3d1ec99947 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -8,7 +8,11 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const int INTERACTION_TIMEOUT = 100; diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 36b385d42d..86c906b0e8 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -20,7 +20,11 @@ #include "cereal/messaging/messaging.h" #include "common/params.h" #include "common/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif #include "selfdrive/ui/qt/maps/map_eta.h" #include "selfdrive/ui/qt/maps/map_instructions.h" diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc index 0eb77e36ce..868c6ca550 100644 --- a/selfdrive/ui/qt/maps/map_eta.cc +++ b/selfdrive/ui/qt/maps/map_eta.cc @@ -4,7 +4,11 @@ #include #include "selfdrive/ui/qt/maps/map_helpers.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const float MANEUVER_TRANSITION_THRESHOLD = 10; diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc index ba8cb356bd..6bfeefbafc 100644 --- a/selfdrive/ui/qt/maps/map_instructions.cc +++ b/selfdrive/ui/qt/maps/map_instructions.cc @@ -4,7 +4,11 @@ #include #include "selfdrive/ui/qt/maps/map_helpers.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif const QString ICON_SUFFIX = ".png"; diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index c4cc20e21d..75dc85b01e 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -6,7 +6,11 @@ #include "selfdrive/ui/qt/maps/map.h" #include "selfdrive/ui/qt/maps/map_settings.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#else #include "selfdrive/ui/ui.h" +#endif MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); From d164bf2f026c4c9e88ad0a15c3989ce5eb877638 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 21 Jul 2024 16:10:54 +0200 Subject: [PATCH 478/575] Fixed bug where the toggles were on the parent's instance as opposed to the derived class instance and fixed the index as well --- selfdrive/ui/qt/offroad/experimental_mode.cc | 7 ++++++- .../ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index b2c6f3323a..88fadd439b 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -7,13 +7,18 @@ #include #include "selfdrive/ui/ui.h" +#ifdef SUNNYPILOT +#define TOGGLES_PANEL_INDEX 3 +#else +#define TOGGLES_PANEL_INDEX 2 +#endif ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) { chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); experimental_pixmap = QPixmap("../assets/img_experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation); // go to toggles and expand experimental mode description - connect(this, &QPushButton::clicked, [=]() { emit openSettings(3, "ExperimentalMode"); }); + connect(this, &QPushButton::clicked, [=]() { emit openSettings(TOGGLES_PANEL_INDEX, "ExperimentalMode"); }); setFixedHeight(125); QHBoxLayout *main_layout = new QHBoxLayout; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h index 16e16dc549..356e908920 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h @@ -16,7 +16,6 @@ private slots: void updateState(const UIStateSP &s); private: - std::map toggles; ButtonParamControlSP *long_personality_setting; ButtonParamControlSP *accel_personality_setting; From 1a809cadf5e04536e76e887905cd53f8d229d596 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 15:58:19 -0400 Subject: [PATCH 479/575] more fixes --- selfdrive/car/nissan/interface.py | 23 +++++------------------ selfdrive/car/subaru/interface.py | 23 +++++------------------ 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index a977c6de21..4a9e5a9125 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -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 - self.CS.button_events.append(be) - - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + 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 5d83ea4917..7eefeb66eb 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -143,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 - self.CS.button_events.append(be) - - # MADS BUTTON - if self.CS.out.madsEnabled != self.CS.madsEnabled: - if self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = False - else: - if not self.mads_event_lock: - self.CS.button_events.append(create_mads_event(self.mads_event_lock)) - self.mads_event_lock = True - - ret.buttonEvents = self.CS.button_events + 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) From 73afab7e8dd751a8ade9169c198a7f3de5c598df Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 16:35:21 -0400 Subject: [PATCH 480/575] make sure main button is pressed --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index e8e1dba1a3..79558bec86 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -222,7 +222,7 @@ class CarInterface(CarInterfaceBase): self.accEnabled = True if self.enable_mads: - if not self.CS.prev_mads_enabled and self.CS.mads_enabled: + if not self.CS.prev_mads_enabled and self.CS.mads_enabled and self.CS.main_buttons[-1] != 1: self.CS.madsEnabled = True if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: self.CS.madsEnabled = not self.CS.madsEnabled From 1037bb15a0451ecac4dcca3f7c2b8fe75df141cb Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 17:16:48 -0400 Subject: [PATCH 481/575] oops flipped main --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 79558bec86..da29f1cf2a 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -222,7 +222,7 @@ class CarInterface(CarInterfaceBase): self.accEnabled = True if self.enable_mads: - if not self.CS.prev_mads_enabled and self.CS.mads_enabled and self.CS.main_buttons[-1] != 1: + if not self.CS.prev_mads_enabled and self.CS.mads_enabled and self.CS.main_buttons[-1] == 1: self.CS.madsEnabled = True if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: self.CS.madsEnabled = not self.CS.madsEnabled From 8e5e4a611f116d7b84f3cef5ee201125e3c194e1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 22 Jul 2024 10:22:39 +0200 Subject: [PATCH 482/575] Update mousePressEvent behavior in HomeWindowSP This update stops calling the parent mousePressEvent function in HomeWindowSP due to conflicts with sidebar code. Previous implementation led to undesired sidebar behaviour following a tap beyond the 300px of the left. Added debug logging to provide more context on the behavior of sidebar and screen wake timeout. --- selfdrive/ui/sunnypilot/qt/sp_priv_home.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc index 18f43cdb1b..480c25f77b 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/util.h" @@ -27,7 +28,9 @@ void HomeWindowSP::updateState(const UIState &s) { //OVERRIDE } void HomeWindowSP::mousePressEvent(QMouseEvent* e) { - HomeWindow::mousePressEvent(e); // We call it first so that we could potentially override whatever was done by parent + // We are not calling the parent for the time being because it only handles sidebar, and the sidebar code conflicts + // with ours as we turn off the sidebar after it was turned on by the parent when the tap happens beyond the 300px of the left. + // HomeWindow::mousePressEvent(e); if (uiStateSP()->scene.started) { if (uiStateSP()->scene.onroadScreenOff != -2) { @@ -45,7 +48,9 @@ void HomeWindowSP::mousePressEvent(QMouseEvent* e) { // Will have to revisit later if this is not behaving as expected. // Handle sidebar collapsing if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) { + LOGD("HomeWindowSP sidebar->isVisible() [%d] | e->x() [%d] | sidebar->width() [%d]", sidebar->isVisible(), e->x(), sidebar->width()); if (onroad->wakeScreenTimeout()) { + LOGD("HomeWindowSP wakeScreenTimeout() [%d]", onroad->wakeScreenTimeout()); sidebar->setVisible(!sidebar->isVisible() && !onroad->isMapVisible()); } } From 99b02769132ccf59cc0d9864eaffaaa853709ad5 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 22 Jul 2024 13:53:00 +0200 Subject: [PATCH 483/575] Add conditional compilation for 'sunnypilot' in SConscript This commit introduces a conditional compilation for 'sunnypilot' within the SConscript file. It also adds a new 'sp_priv_text.cc' file for 'sunnypilot' and performs a significant cleanup on the existing 'text.cc' file, removing unneeded commands. This change helps to customize the build process based on the specific feature flags such as 'sunnypilot'. --- selfdrive/ui/SConscript | 3 +- selfdrive/ui/qt/text.cc | 74 +---------- selfdrive/ui/sunnypilot/qt/sp_priv_text.cc | 137 +++++++++++++++++++++ 3 files changed, 140 insertions(+), 74 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_text.cc diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a328189c51..efb57f3fa2 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -80,7 +80,8 @@ asset_obj = qt_env.Object("assets", assets) qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) # spinner and text window -qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs) +text_cc_path = "qt/text.cc" if not GetOption('sunnypilot') else "sunnypilot/qt/sp_priv_text.cc" +qt_env.Program("_text", [text_cc_path], LIBS=qt_libs) qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) # build main UI diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 4b5f8ee21e..7b14368589 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -4,41 +4,12 @@ #include #include #include -#include -#include -#include -#include "common/params.h" -#include "common/swaglog.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -std::string executeCommand(const char* cmd) { - std::array buffer{}; - std::ostringstream result; - std::unique_ptr pipe(popen(cmd, "r"), pclose); - if (!pipe) { - LOGW("Failed to open pipe"); - throw std::runtime_error("popen() failed!"); - } - while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { - result << buffer.data(); - } - return result.str(); -} - -// The format is intentional! -QString text = QString("%1\n%2\n%3\n%4\n%5\n%6\n%7") - .arg(" ________________________________________") - .arg("| |") - .arg("| " + QObject::tr("Update downloaded. Ready to reboot.") + " |") - .arg("| |") - .arg("| " + QObject::tr("Update: Check and Download Update") + " |") - .arg("| " + QObject::tr("Reboot: Reboot Device") + " |") - .arg("|________________________________________|"); - int main(int argc, char *argv[]) { initApp(argc, argv); QApplication a(argc, argv); @@ -51,7 +22,6 @@ int main(int argc, char *argv[]) { QLabel *label = new QLabel(argv[1]); label->setWordWrap(true); label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - label->setAlignment(Qt::AlignTop | Qt::AlignLeft); ScrollView *scroll = new ScrollView(label); scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); main_layout->addWidget(scroll, 0, 0, Qt::AlignTop); @@ -62,54 +32,16 @@ int main(int argc, char *argv[]) { }); QPushButton *btn = new QPushButton(); - QPushButton *update_btn = new QPushButton(); - update_btn->setText(QObject::tr("Update")); #ifdef __aarch64__ btn->setText(QObject::tr("Reboot")); - QFutureWatcher watcher; QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() { - btn->setEnabled(false); - update_btn->setEnabled(false); - update_btn->setText(QObject::tr("Updating...")); - const std::string git_branch = Params().get("GitBranch"); - const std::string git_remote = Params().get("GitRemote"); - const std::string to_home_dir = "cd /data/openpilot"; - const std::string check_remote = "git remote | grep origin-update"; - const std::string reset_remote = "git remote remove origin-update && git remote add origin-update " + git_remote; - const std::string add_remote = "git remote add origin-update " + git_remote; - const std::string fetch_remote = "git fetch origin-update " + git_branch; - const std::string reset_branch = "git reset --hard origin-update/" + git_branch + " 2>&1"; - - std::string remote_cmd = add_remote; - if (!std::system((to_home_dir + " && " + check_remote).c_str())) { - remote_cmd = reset_remote; - } - const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; - - QFuture future = QtConcurrent::run([=]() { - label->clear(); - std::string output = executeCommand(cmd.c_str()); - //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); - QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(output) + text)); - }); - QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { - btn->setEnabled(true); - update_btn->setEnabled(true); - update_btn->setText(QObject::tr("Update")); - }); - watcher.setFuture(future); - }); #else - update_btn->setEnabled(false); btn->setText(QObject::tr("Exit")); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); #endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); - main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); window.setStyleSheet(R"( * { @@ -126,11 +58,7 @@ int main(int argc, char *argv[]) { border-radius: 20px; margin-right: 40px; } - QPushButton:disabled { - color: #33FFFFFF; - border: 2px solid #33FFFFFF; - } )"); return a.exec(); -} +} \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_text.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_text.cc new file mode 100644 index 0000000000..143b305af7 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_text.cc @@ -0,0 +1,137 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/params.h" +#include "common/swaglog.h" +#include "system/hardware/hw.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +std::string executeCommand(const char* cmd) { + std::array buffer{}; + std::ostringstream result; + std::unique_ptr pipe(popen(cmd, "r"), pclose); + if (!pipe) { + LOGW("Failed to open pipe"); + throw std::runtime_error("popen() failed!"); + } + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result << buffer.data(); + } + return result.str(); +} + +// The format is intentional! +QString text = QString("%1\n%2\n%3\n%4\n%5\n%6\n%7") + .arg(" ________________________________________") + .arg("| |") + .arg("| " + QObject::tr("Update downloaded. Ready to reboot.") + " |") + .arg("| |") + .arg("| " + QObject::tr("Update: Check and Download Update") + " |") + .arg("| " + QObject::tr("Reboot: Reboot Device") + " |") + .arg("|________________________________________|"); + +int main(int argc, char *argv[]) { + initApp(argc, argv); + QApplication a(argc, argv); + QWidget window; + setMainWindow(&window); + + QGridLayout *main_layout = new QGridLayout(&window); + main_layout->setMargin(50); + + QLabel *label = new QLabel(argv[1]); + label->setWordWrap(true); + label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + ScrollView *scroll = new ScrollView(label); + scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + main_layout->addWidget(scroll, 0, 0, Qt::AlignTop); + + // Scroll to the bottom + QObject::connect(scroll->verticalScrollBar(), &QAbstractSlider::rangeChanged, [=]() { + scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum()); + }); + + QPushButton *btn = new QPushButton(); + QPushButton *update_btn = new QPushButton(); + update_btn->setText(QObject::tr("Update")); +#ifdef __aarch64__ + btn->setText(QObject::tr("Reboot")); + QFutureWatcher watcher; + QObject::connect(btn, &QPushButton::clicked, [=]() { + Hardware::reboot(); + }); + QObject::connect(update_btn, &QPushButton::clicked, [=, &watcher]() { + btn->setEnabled(false); + update_btn->setEnabled(false); + update_btn->setText(QObject::tr("Updating...")); + const std::string git_branch = Params().get("GitBranch"); + const std::string git_remote = Params().get("GitRemote"); + const std::string to_home_dir = "cd /data/openpilot"; + const std::string check_remote = "git remote | grep origin-update"; + const std::string reset_remote = "git remote remove origin-update && git remote add origin-update " + git_remote; + const std::string add_remote = "git remote add origin-update " + git_remote; + const std::string fetch_remote = "git fetch origin-update " + git_branch; + const std::string reset_branch = "git reset --hard origin-update/" + git_branch + " 2>&1"; + + std::string remote_cmd = add_remote; + if (!std::system((to_home_dir + " && " + check_remote).c_str())) { + remote_cmd = reset_remote; + } + const std::string cmd = to_home_dir + "; " + remote_cmd + "; " + fetch_remote + "; " + reset_branch; + + QFuture future = QtConcurrent::run([=]() { + label->clear(); + std::string output = executeCommand(cmd.c_str()); + //LOGW("CHECK OUTPUT PLS\n%s", output.c_str()); + QMetaObject::invokeMethod(label, "setText", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(output) + text)); + }); + QObject::connect(&watcher, &QFutureWatcher::finished, [=]() { + btn->setEnabled(true); + update_btn->setEnabled(true); + update_btn->setText(QObject::tr("Update")); + }); + watcher.setFuture(future); + }); +#else + update_btn->setEnabled(false); + btn->setText(QObject::tr("Exit")); + QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); +#endif + main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); + main_layout->addWidget(update_btn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); + + window.setStyleSheet(R"( + * { + outline: none; + color: white; + background-color: black; + font-size: 60px; + } + QPushButton { + padding: 50px; + padding-right: 100px; + padding-left: 100px; + border: 2px solid white; + border-radius: 20px; + margin-right: 40px; + } + QPushButton:disabled { + color: #33FFFFFF; + border: 2px solid #33FFFFFF; + } + )"); + + return a.exec(); +} From cc437018084446da1ffb5102b4980c867d377767 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 22 Jul 2024 13:56:07 +0200 Subject: [PATCH 484/575] Refactor code related to Sunnypilot's special utilities This commit updates various scripts involved in Sunnypilot's special utilities. Modifications primarily include simplification of several functions in 'util.cc', addition of required exports in 'SConscript', and restructuring `getUserAgent`, `getDongleId`, and `getCarNames` methods under `sp_priv_util.h` and `sp_priv_util.cc`. The changes help segregate Sunnypilot specific utility functions, resulting in cleaner and more maintainable code. --- selfdrive/ui/SConscript | 5 +- selfdrive/ui/qt/api.cc | 3 + selfdrive/ui/qt/util.cc | 116 ++++-------------- selfdrive/ui/qt/util.h | 11 +- selfdrive/ui/sunnypilot/SConscript | 7 +- .../settings/sp_priv_sunnylink_settings.h | 2 +- .../settings/sp_priv_vehicle_settings.cc | 1 + .../settings/sp_priv_vehicle_settings.h | 1 - .../qt/onroad/sp_priv_annotated_camera.cc | 1 + selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc | 1 + selfdrive/ui/sunnypilot/qt/sp_priv_util.cc | 77 ++++++++++++ selfdrive/ui/sunnypilot/qt/sp_priv_util.h | 20 +++ 12 files changed, 140 insertions(+), 105 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_util.cc create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_util.h diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index efb57f3fa2..242a4c6daa 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -20,11 +20,12 @@ qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] sp_widgets_src = [] sp_maps_widgets_src = [] sp_qt_src = [] +sp_qt_util = [] if GetOption('sunnypilot'): SConscript(['sunnypilot/SConscript']) - Import('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src') + Import('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src', "sp_qt_util") -qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) +qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"] + sp_qt_util, LIBS=base_libs) widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index f3314221b8..6dc01e0df6 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -19,6 +19,9 @@ #include "common/util.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#endif namespace CommaApi { diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 173d9ba970..886402b157 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -29,54 +29,38 @@ QString getBrand() { return QObject::tr("sunnypilot"); } -QString getUserAgent(bool sunnylink) { - return (sunnylink ? "sunnypilot-" : "openpilot-") + getVersion(); +QString getUserAgent() { + return "openpilot-" + getVersion(); +} + +std::optional getParamIgnoringDefault(const std::string ¶m_name, const std::string &default_value) { + std::string value = Params().get(param_name); + + if (!value.empty() && value != default_value) + return QString::fromStdString(value); + + return {}; } std::optional getDongleId() { - std::string id = Params().get("DongleId"); - - if (!id.empty() && (id != "UnregisteredDevice")) { - return QString::fromStdString(id); - } else { - return {}; - } + return getParamIgnoringDefault("DongleId", "UnregisteredDevice"); } -std::optional getSunnylinkDongleId() { - std::string id = Params().get("SunnylinkDongleId"); +QMap getFromJsonFile(const QString &path) { + QFile f(path); + f.open(QIODevice::ReadOnly | QIODevice::Text); + QString val = f.readAll(); - if (!id.empty() && (id != "UnregisteredDevice")) { - return QString::fromStdString(id); - } else { - return {}; + QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object(); + QMap map; + for (auto key : obj.keys()) { + map[key] = obj[key].toString(); } + return map; } QMap getSupportedLanguages() { - QFile f(":/languages.json"); - f.open(QIODevice::ReadOnly | QIODevice::Text); - QString val = f.readAll(); - - QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object(); - QMap map; - for (auto key : obj.keys()) { - map[key] = obj[key].toString(); - } - return map; -} - -QMap getCarNames() { - QFile f("/data/openpilot/selfdrive/car/sunnypilot_carname.json"); - f.open(QIODevice::ReadOnly | QIODevice::Text); - QString val = f.readAll(); - - QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object(); - QMap map; - for (auto key : obj.keys()) { - map[key] = obj[key].toString(); - } - return map; + return getFromJsonFile(":/languages.json"); } QString timeAgo(const QDateTime &date) { @@ -179,60 +163,6 @@ QPixmap loadPixmap(const QString &fileName, const QSize &size, Qt::AspectRatioMo } } -void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom){ - qreal w_2 = rect.width() / 2; - qreal h_2 = rect.height() / 2; - - xRadiusTop = 100 * qMin(xRadiusTop, w_2) / w_2; - yRadiusTop = 100 * qMin(yRadiusTop, h_2) / h_2; - - xRadiusBottom = 100 * qMin(xRadiusBottom, w_2) / w_2; - yRadiusBottom = 100 * qMin(yRadiusBottom, h_2) / h_2; - - qreal x = rect.x(); - qreal y = rect.y(); - qreal w = rect.width(); - qreal h = rect.height(); - - qreal rxx2Top = w*xRadiusTop/100; - qreal ryy2Top = h*yRadiusTop/100; - - qreal rxx2Bottom = w*xRadiusBottom/100; - qreal ryy2Bottom = h*yRadiusBottom/100; - - QPainterPath path; - path.arcMoveTo(x, y, rxx2Top, ryy2Top, 180); - path.arcTo(x, y, rxx2Top, ryy2Top, 180, -90); - path.arcTo(x+w-rxx2Top, y, rxx2Top, ryy2Top, 90, -90); - path.arcTo(x+w-rxx2Bottom, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 0, -90); - path.arcTo(x, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 270, -90); - path.closeSubpath(); - - painter.drawPath(path); -} - -QColor interpColor(float xv, std::vector xp, std::vector fp) { - assert(xp.size() == fp.size()); - - int N = xp.size(); - int hi = 0; - - while (hi < N and xv > xp[hi]) hi++; - int low = hi - 1; - - if (hi == N && xv > xp[low]) { - return fp[fp.size() - 1]; - } else if (hi == 0){ - return fp[0]; - } else { - return QColor( - (xv - xp[low]) * (fp[hi].red() - fp[low].red()) / (xp[hi] - xp[low]) + fp[low].red(), - (xv - xp[low]) * (fp[hi].green() - fp[low].green()) / (xp[hi] - xp[low]) + fp[low].green(), - (xv - xp[low]) * (fp[hi].blue() - fp[low].blue()) / (xp[hi] - xp[low]) + fp[low].blue(), - (xv - xp[low]) * (fp[hi].alpha() - fp[low].alpha()) / (xp[hi] - xp[low]) + fp[low].alpha()); - } -} - static QHash load_bootstrap_icons() { QHash icons; @@ -297,4 +227,4 @@ void ParamWatcher::fileChanged(const QString &path) { void ParamWatcher::addParam(const QString ¶m_name) { watcher->addPath(QString::fromStdString(params.getParamPath(param_name.toStdString()))); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index d4d56560ea..94b83f0ab3 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -21,11 +21,11 @@ QString getVersion(); QString getBrand(); -QString getUserAgent(bool sunnylink = false); +QString getUserAgent(); +std::optional getParamIgnoringDefault(const std::string ¶m_name, const std::string &default_value); std::optional getDongleId(); -std::optional getSunnylinkDongleId(); +QMap getFromJsonFile(const QString &path); QMap getSupportedLanguages(); -QMap getCarNames(); void setQtSurfaceFormat(); void sigTermHandler(int s); QString timeAgo(const QDateTime &date); @@ -34,9 +34,6 @@ void initApp(int argc, char *argv[], bool disable_hidpi = true); QWidget* topWidget(QWidget* widget); QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); QPixmap bootstrapPixmap(const QString &id); - -void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom); -QColor interpColor(float xv, std::vector xp, std::vector fp); bool hasLongitudinalControl(const cereal::CarParams::Reader &car_params); struct InterFont : public QFont { @@ -62,4 +59,4 @@ private: QFileSystemWatcher *watcher; QHash params_hash; Params params; -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 6ef7344337..4cd896d9de 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -27,6 +27,11 @@ sp_maps_widgets_src = [ "sunnypilot/qt/maps/sp_priv_map.cc" ] +sp_qt_util = [ + # "#selfdrive/ui/sunnypilot/qt/sp_priv_api.cc", + "#selfdrive/ui/sunnypilot/qt/sp_priv_util.cc", +] + network_src = [ "sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc", "sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc", @@ -51,4 +56,4 @@ qt_src = [ sp_widgets_src = widgets_src + network_src sp_qt_src = qt_src -Export('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src') +Export('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src', "sp_qt_util") diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h index 579514737d..5b1530fc43 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h @@ -6,7 +6,7 @@ #include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h" #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc index f237e75ee9..3c50b4bd37 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc @@ -1,4 +1,5 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" VehiclePanel::VehiclePanel(QWidget *parent) : QWidget(parent) { main_layout = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h index f30810358c..290fc9364c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h @@ -4,7 +4,6 @@ #include "common/watchdog.h" #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc index 3048f00987..c95e1009b1 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc @@ -8,6 +8,7 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" static std::pair getFeatureStatus(int value, QStringList text_list, QStringList color_list, bool condition, QString off_text) { diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc index e682d87b69..e5f924c289 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc @@ -5,6 +5,7 @@ #include #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" #include "common/params.h" diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc new file mode 100644 index 0000000000..e1c79c5aa5 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc @@ -0,0 +1,77 @@ +#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#include "selfdrive/ui/qt/util.h" + +#include +#include + +#include +#include +#include + +#include "system/hardware/hw.h" + +QString getUserAgent(bool sunnylink) { + return (sunnylink ? "sunnypilot-" : "openpilot-") + getVersion(); +} + +std::optional getSunnylinkDongleId() { + return getParamIgnoringDefault("SunnylinkDongleId", "UnregisteredDevice"); +} + +QMap getCarNames() { + return getFromJsonFile("../car/sunnypilot_carname.json"); +} + +void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom){ + qreal w_2 = rect.width() / 2; + qreal h_2 = rect.height() / 2; + + xRadiusTop = 100 * qMin(xRadiusTop, w_2) / w_2; + yRadiusTop = 100 * qMin(yRadiusTop, h_2) / h_2; + + xRadiusBottom = 100 * qMin(xRadiusBottom, w_2) / w_2; + yRadiusBottom = 100 * qMin(yRadiusBottom, h_2) / h_2; + + qreal x = rect.x(); + qreal y = rect.y(); + qreal w = rect.width(); + qreal h = rect.height(); + + qreal rxx2Top = w*xRadiusTop/100; + qreal ryy2Top = h*yRadiusTop/100; + + qreal rxx2Bottom = w*xRadiusBottom/100; + qreal ryy2Bottom = h*yRadiusBottom/100; + + QPainterPath path; + path.arcMoveTo(x, y, rxx2Top, ryy2Top, 180); + path.arcTo(x, y, rxx2Top, ryy2Top, 180, -90); + path.arcTo(x+w-rxx2Top, y, rxx2Top, ryy2Top, 90, -90); + path.arcTo(x+w-rxx2Bottom, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 0, -90); + path.arcTo(x, y+h-ryy2Bottom, rxx2Bottom, ryy2Bottom, 270, -90); + path.closeSubpath(); + + painter.drawPath(path); +} + +QColor interpColor(float xv, std::vector xp, std::vector fp) { + assert(xp.size() == fp.size()); + + int N = xp.size(); + int hi = 0; + + while (hi < N and xv > xp[hi]) hi++; + int low = hi - 1; + + if (hi == N && xv > xp[low]) { + return fp[fp.size() - 1]; + } else if (hi == 0){ + return fp[0]; + } else { + return QColor( + (xv - xp[low]) * (fp[hi].red() - fp[low].red()) / (xp[hi] - xp[low]) + fp[low].red(), + (xv - xp[low]) * (fp[hi].green() - fp[low].green()) / (xp[hi] - xp[low]) + fp[low].green(), + (xv - xp[low]) * (fp[hi].blue() - fp[low].blue()) / (xp[hi] - xp[low]) + fp[low].blue(), + (xv - xp[low]) * (fp[hi].alpha() - fp[low].alpha()) / (xp[hi] - xp[low]) + fp[low].alpha()); + } +} \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h new file mode 100644 index 0000000000..8e2ced1a7e --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "cereal/gen/cpp/car.capnp.h" +#include "common/params.h" + +QString getUserAgent(bool sunnylink = false); +std::optional getSunnylinkDongleId(); +QMap getCarNames(); +void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom); +QColor interpColor(float xv, std::vector xp, std::vector fp); \ No newline at end of file From 47420fda5da87ab5b5bffd6feb347f44814ada7e Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 22 Jul 2024 14:54:05 +0200 Subject: [PATCH 485/575] Improve HttpRequest class for QT and refactor associated classes The presented change refactors the HttpRequest class in the QT API for the OpenPilot project. Unused OpenSSL libraries were removed, and the HttpRequest class was modified to improve readability. The HttpRequest::sendRequest method was refactored, and the logic to prepare the network request was extracted to a new method called HttpRequest::prepareRequest. In addition, the user agent retrieval logic was encapsulated within the HttpRequest class. The RequestRepeater class was similarly refactored. Timer connection and cache processing logic were moved into separate methods for better code organization. Several other classes that depend on HttpRequest and RequestRepeater were modified to reflect these changes. Handled security-related changes for SunnyPilot. Added a new file `sp_priv_api.cc` for Sunnylink-specific API operations. Removed unnecessary includes and updated SConscript accordingly. --- selfdrive/ui/qt/api.cc | 167 ++-------------- selfdrive/ui/qt/api.h | 26 +-- selfdrive/ui/qt/request_repeater.cc | 44 ++--- selfdrive/ui/qt/request_repeater.h | 21 +- selfdrive/ui/sunnypilot/SConscript | 2 + .../services/sp_priv_base_device_service.cc | 10 +- .../services/sp_priv_base_device_service.h | 7 +- .../settings/sp_priv_sunnylink_settings.cc | 15 +- .../settings/sp_priv_sunnylink_settings.h | 1 - selfdrive/ui/sunnypilot/qt/sp_priv_api.cc | 187 ++++++++++++++++++ selfdrive/ui/sunnypilot/qt/sp_priv_api.h | 34 ++++ .../sunnypilot/qt/sp_priv_request_repeater.cc | 36 ++++ .../sunnypilot/qt/sp_priv_request_repeater.h | 26 +++ selfdrive/ui/sunnypilot/qt/sp_priv_util.cc | 1 + selfdrive/ui/sunnypilot/qt/sp_priv_util.h | 7 - 15 files changed, 366 insertions(+), 218 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_api.cc create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_api.h create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.cc create mode 100644 selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index 6dc01e0df6..2e7c39ea6a 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -10,18 +9,13 @@ #include #include #include -#include #include #include -#include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" -#endif namespace CommaApi { @@ -51,130 +45,11 @@ QByteArray rsa_sign(const QByteArray &data) { return sig; } -void derive_aes_key_iv_from_rsa(EVP_PKEY *rsa_key, unsigned char *aes_key, unsigned char *aes_iv) { - unsigned char hash[SHA256_DIGEST_LENGTH]; - size_t pub_len; - unsigned char *pub_key; - - // Convert RSA key to public key in DER format for simplicity - pub_len = i2d_PublicKey(rsa_key, NULL); - pub_key = (unsigned char *)malloc(pub_len); - unsigned char *tmp = pub_key; - i2d_PublicKey(rsa_key, &tmp); - - // Hash the public key to derive bytes for AES key and IV - SHA256(pub_key, pub_len, hash); - - // Assuming AES-256-CBC, we need 32 bytes for the key and 16 for the IV - memcpy(aes_key, hash, 32); // First 32 bytes for AES key - memcpy(aes_iv, hash + 32 - 16, 16); // Last 16 bytes for AES IV - - free(pub_key); -} - -EVP_PKEY *load_public_key(const char *file) { - EVP_PKEY *key = NULL; - FILE *fp = fopen(file, "r"); - if (fp) { - key = PEM_read_PUBKEY(fp, NULL, NULL, NULL); - fclose(fp); - } - return key; -} - -EVP_PKEY *load_private_key(const char *file) { - EVP_PKEY *key = NULL; - FILE *fp = fopen(file, "r"); - if (fp) { - key = PEM_read_PrivateKey(fp, NULL, NULL, NULL); - fclose(fp); - } - return key; -} - -QByteArray rsa_encrypt(const QByteArray &data) { - EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key - unsigned char aes_key[32], aes_iv[16]; - derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv); - - EVP_CIPHER_CTX *ctx; - if (!(ctx = EVP_CIPHER_CTX_new())) { - // Handle error: Allocate memory failed - return {}; - } - - if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) { - qDebug() << "Failed to initialize encryption"; - EVP_CIPHER_CTX_free(ctx); - return {}; - } - - int ciphertext_len; - int len = data.size(); - unsigned char out[len + AES_BLOCK_SIZE]; - auto *in = (unsigned char*) data.constData(); - if (EVP_EncryptUpdate(ctx, out, &ciphertext_len, in, len) != 1) { - qDebug() << "Failed to update encryption"; - EVP_CIPHER_CTX_free(ctx); - return {}; - } - - if (EVP_EncryptFinal_ex(ctx, out + ciphertext_len, &len) != 1) { - // Handle error: Encryption finalize failed - qDebug() << "Failed to finalize encryption"; - EVP_CIPHER_CTX_free(ctx); - return {}; - } - - //qDebug() << "Encrypted data length: %d", ciphertext_len + len; // Print the length of encrypted data - EVP_CIPHER_CTX_free(ctx); - return {reinterpret_cast(out), ciphertext_len + len}; -} - -QByteArray rsa_decrypt(const QByteArray &data) { - EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key - unsigned char aes_key[32], aes_iv[16]; - derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv); - - EVP_CIPHER_CTX *ctx; - if (!(ctx = EVP_CIPHER_CTX_new())) { - qDebug() << "Failed to allocate memory for EVP_CIPHER_CTX"; - return {}; - } - - if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) { - qDebug() << "Failed to initialize EVP"; - EVP_CIPHER_CTX_free(ctx); - return {}; - } - - int len = data.size(); - unsigned char out[len + AES_BLOCK_SIZE]; - auto *in = (unsigned char*) data.constData(); - if (EVP_DecryptUpdate(ctx, out, &len, in, len) != 1) { - qDebug() << "Failed to update decryption"; - EVP_CIPHER_CTX_free(ctx); - return {}; - } - - int final_len; - if (EVP_DecryptFinal_ex(ctx, out + len, &final_len) != 1) { - qDebug() << "Failed to finalize decryption"; - EVP_CIPHER_CTX_free(ctx); - return {}; - } - EVP_CIPHER_CTX_free(ctx); - - //qDebug() << "Decrypted data length: %d", len + final_len; // Print the length of decrypted data - return {reinterpret_cast(out), len + final_len}; -} - -QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) { +QString create_jwt(const QJsonObject &payloads, int expiry) { QJsonObject header = {{"alg", "RS256"}}; auto t = QDateTime::currentSecsSinceEpoch(); - auto dongle_id = sunnylink ? getSunnylinkDongleId() : getDongleId(); - QJsonObject payload = {{"identity", dongle_id.value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}}; + QJsonObject payload = {{"identity", getDongleId().value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}}; for (auto it = payloads.begin(); it != payloads.end(); ++it) { payload.insert(it.key(), it.value()); } @@ -189,7 +64,7 @@ QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) { } // namespace CommaApi -HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout, const bool sunnylink) : create_jwt(create_jwt), sunnylink(sunnylink), QObject(parent) { +HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { networkTimer = new QTimer(this); networkTimer->setSingleShot(true); networkTimer->setInterval(timeout); @@ -204,40 +79,38 @@ bool HttpRequest::timeout() const { return reply && reply->error() == QNetworkReply::OperationCanceledError; } -void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method, const QByteArray &payload) { - LOGD("Requesting %s", qPrintable(requestURL)); - if (active()) { - qDebug() << "HttpRequest is active"; - return; - } +QNetworkRequest HttpRequest::prepareRequest(const QString &requestURL) +{ + QNetworkRequest request; QString token; if (create_jwt) { - token = CommaApi::create_jwt({}, 3600, sunnylink); + token = GetJwtToken(); } else { QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json")); QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8()); token = json_d["access_token"].toString(); } - QNetworkRequest request; request.setUrl(QUrl(requestURL)); - request.setRawHeader("User-Agent", getUserAgent(sunnylink).toUtf8()); - if (!payload.isEmpty()) { - request.setRawHeader("Content-Type", "application/json"); - } + request.setRawHeader("User-Agent", GetUserAgent().toUtf8()); if (!token.isEmpty()) { request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8()); } + return request; +} - if (method == HttpRequest::Method::GET) { +void HttpRequest::sendRequest(const QString &requestURL, const Method method) { + if (active()) { + qDebug() << "HttpRequest is active"; + return; + } + + QNetworkRequest request = prepareRequest(requestURL); + if (method == Method::GET) { reply = nam()->get(request); - } else if (method == HttpRequest::Method::DELETE) { + } else if (method == Method::DELETE) { reply = nam()->deleteResource(request); - } else if (method == HttpRequest::Method::POST) { - reply = nam()->post(request, payload); - } else if (method == HttpRequest::Method::PUT) { - reply = nam()->put(request, payload); } networkTimer->start(); @@ -272,4 +145,4 @@ void HttpRequest::requestFinished() { QNetworkAccessManager *HttpRequest::nam() { static QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(qApp); return networkAccessManager; -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h index bbebc502e5..f0c486b289 100644 --- a/selfdrive/ui/qt/api.h +++ b/selfdrive/ui/qt/api.h @@ -6,14 +6,13 @@ #include #include "common/util.h" +#include "selfdrive/ui/qt/util.h" namespace CommaApi { const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); QByteArray rsa_sign(const QByteArray &data); -QByteArray rsa_encrypt(const QByteArray &data); -QByteArray rsa_decrypt(const QByteArray &data); -QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600, bool sunnylink = false); +QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600); } // namespace CommaApi @@ -25,26 +24,27 @@ class HttpRequest : public QObject { Q_OBJECT public: - enum class Method {GET, DELETE, POST, PUT}; + enum class Method { GET, DELETE, POST, PUT }; - explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000, const bool sunnylink = false); - void sendRequest(const QString &requestURL, const Method method = Method::GET, const QByteArray &payload = {}); - bool active() const; - bool timeout() const; + explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); + virtual void sendRequest(const QString &requestURL, Method method); + void sendRequest(const QString &requestURL) { sendRequest(requestURL, Method::GET); } + [[nodiscard]] bool active() const; + [[nodiscard]] bool timeout() const; signals: void requestDone(const QString &response, bool success, QNetworkReply::NetworkError error); protected: QNetworkReply *reply = nullptr; - -private: static QNetworkAccessManager *nam(); QTimer *networkTimer = nullptr; bool create_jwt; - bool sunnylink; + virtual QNetworkRequest prepareRequest(const QString& requestURL); + [[nodiscard]] virtual QString GetJwtToken() const { return CommaApi::create_jwt(); } + [[nodiscard]] virtual QString GetUserAgent() const { return getUserAgent(); } -private slots: +protected slots: void requestTimeout(); void requestFinished(); -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index 8212d3db94..cba00d8e00 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -1,36 +1,36 @@ #include "selfdrive/ui/qt/request_repeater.h" -#include "common/swaglog.h" - -RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey, - int period, bool whileOnroad, bool sunnylink) : HttpRequest(parent, true, 20000, sunnylink) { - request_url = requestURL; - while_onroad = whileOnroad; +RequestRepeater::RequestRepeater(QObject *parent, const QString &url, const QString &cache_key, + int period, bool trigger_while_onroad) : HttpRequest(parent) { timer = new QTimer(this); timer->setTimerType(Qt::VeryCoarseTimer); - connect(timer, &QTimer::timeout, [=]() { this->timerTick(); }); + + connectTimer(url, trigger_while_onroad); + timer->start(period * 1000); - if (!cacheKey.isEmpty()) { - prevResp = QString::fromStdString(params.get(cacheKey.toStdString())); + setupCacheProcess(cache_key); +} + +void RequestRepeater::connectTimer(const QString &url, bool trigger_while_onroad) { + QObject::connect(timer, &QTimer::timeout, [=]() { + if ((!uiState()->scene.started || trigger_while_onroad) && device()->isAwake() && !active()) { + sendRequest(url); + } + }); +} + +void RequestRepeater::setupCacheProcess(const QString &cache_key) { + if (!cache_key.isEmpty()) { + prevResp = QString::fromStdString(params.get(cache_key.toStdString())); if (!prevResp.isEmpty()) { QTimer::singleShot(500, [=]() { emit requestDone(prevResp, true, QNetworkReply::NoError); }); } - connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + QObject::connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) { if (success && resp != prevResp) { - params.put(cacheKey.toStdString(), resp.toStdString()); + params.put(cache_key.toStdString(), resp.toStdString()); prevResp = resp; } }); } - - // Don't wait for the timer to fire to send the first request - ForceUpdate(); -} - -void RequestRepeater::timerTick() { - if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) { - LOGD("Sending request for %s", qPrintable(request_url)); - sendRequest(request_url); - } -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h index 43ce6492c9..121887b35e 100644 --- a/selfdrive/ui/qt/request_repeater.h +++ b/selfdrive/ui/qt/request_repeater.h @@ -1,28 +1,23 @@ #pragma once -#include "common/swaglog.h" #include "common/util.h" -#include "selfdrive/ui/qt/api.h" + #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_api.h" #else #include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/api.h" #endif class RequestRepeater : public HttpRequest { +public: + void connectTimer(const QString& url, bool trigger_while_onroad); + void setupCacheProcess(const QString& chars); + RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool while_onroad=false); private: Params params; QTimer *timer; QString prevResp; - QString request_url; - bool while_onroad; - void timerTick(); - -public: - RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool whileOnroad=false, bool sunnylink = false); - void ForceUpdate() { - LOGD("Forcing update for %s", qPrintable(request_url)); - timerTick(); - } -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 4cd896d9de..6fe4a93d53 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -1,6 +1,7 @@ widgets_src = [ "sunnypilot/sp_priv_ui.cc", "sunnypilot/qt/sp_priv_window.cc", + "sunnypilot/qt/sp_priv_request_repeater.cc", "sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc", @@ -40,6 +41,7 @@ network_src = [ ] qt_src = [ + "sunnypilot/qt/sp_priv_api.cc", "sunnypilot/qt/sp_priv_home.cc", "sunnypilot/qt/sp_priv_offroad_home.cc", "sunnypilot/qt/sp_priv_sidebar.cc", diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc index 5caf67a631..91914f8827 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc @@ -1,5 +1,7 @@ #include "selfdrive/ui/sunnypilot/qt/network/sunnylink//services/sp_priv_base_device_service.h" +#include + #include "common/swaglog.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" @@ -29,14 +31,14 @@ void BaseDeviceService::loadDeviceData(const QString &url, bool poll) { if (poll && !isCurrentyPolling()) { LOGD("Polling %s", qPrintable(fullUrl)); LOGD("Cache key: SunnylinkCache_%s", qPrintable(QString(getCacheKey()))); - repeater = new RequestRepeater(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true); - connect(repeater, &RequestRepeater::requestDone, this, &BaseDeviceService::handleResponse); + repeater = new RequestRepeaterSP(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true); + connect(repeater, &RequestRepeaterSP::requestDone, this, &BaseDeviceService::handleResponse); } else if (isCurrentyPolling()) { repeater->ForceUpdate(); } else { LOGD("Sending one-time %s", qPrintable(fullUrl)); - initial_request = new HttpRequest(this, true, 10000, true); - connect(initial_request, &HttpRequest::requestDone, this, &BaseDeviceService::handleResponse); + initial_request = new HttpRequestSP(this, true, 10000, true); + connect(initial_request, &HttpRequestSP::requestDone, this, &BaseDeviceService::handleResponse); } } diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h index 9b8900a328..90abfd294d 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h @@ -1,7 +1,6 @@ #pragma once -#include "selfdrive/ui/qt/api.h" -#include "selfdrive/ui/qt/request_repeater.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h" #include "selfdrive/ui/qt/util.h" class BaseDeviceService : public QObject { @@ -14,8 +13,8 @@ protected: static bool is_sunnylink_enabled() { return Params().getBool("SunnylinkEnabled");} ParamWatcher* param_watcher; - HttpRequest* initial_request = nullptr; - RequestRepeater* repeater = nullptr; + HttpRequestSP* initial_request = nullptr; + RequestRepeaterSP* repeater = nullptr; public: explicit BaseDeviceService(QObject* parent = nullptr); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc index 1912349eb3..bbe8d00ae4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc @@ -1,4 +1,5 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_api.h" #include #include @@ -253,7 +254,7 @@ QByteArray BackupSettings::backupParams(const bool encrypt) { // Compress the QByteArray. QByteArray compressedData = qCompress(jsonData); - QByteArray processed_data = encrypt ? CommaApi::rsa_encrypt(compressedData) : compressedData; + QByteArray processed_data = encrypt ? SunnylinkApi::rsa_encrypt(compressedData) : compressedData; // Encode the compressed QByteArray in Base64. auto converted_params_processed_base64_data = QString(processed_data.toBase64()); @@ -279,8 +280,8 @@ QByteArray BackupSettings::backupParams(const bool encrypt) { void BackupSettings::sendParams(const QByteArray &payload) { if (auto sl_dongle_id = getSunnylinkDongleId()) { QString url = SUNNYLINK_BASE_URL + "/backup/" + *sl_dongle_id; - auto request = new HttpRequest(this, true, 10000, true); - connect(request, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + auto request = new HttpRequestSP(this, true, 10000, true); + connect(request, &HttpRequestSP::requestDone, [=](const QString &resp, bool success) { if (success && resp != "[]") { ConfirmationDialog::alert(tr("Settings backed up for sunnylink Device ID:") + " " + *sl_dongle_id, this); } else if (resp == "[]") { @@ -319,7 +320,7 @@ void BackupSettings::restoreParams(const QString &resp) { if (configValue.isString()) { // Decode the Base64-encoded "config" value const QByteArray configJsonDataDecoded = QByteArray::fromBase64(configValue.toString().toUtf8()); - const QByteArray configJsonDataCompressed = isEncrypted ? CommaApi::rsa_decrypt(configJsonDataDecoded) : configJsonDataDecoded; + const QByteArray configJsonDataCompressed = isEncrypted ? SunnylinkApi::rsa_decrypt(configJsonDataDecoded) : configJsonDataDecoded; const QByteArray configJsonData = qUncompress(configJsonDataCompressed); // Convert the decompressed JSON data to a QJsonDocument @@ -347,8 +348,8 @@ void BackupSettings::restoreParams(const QString &resp) { void BackupSettings::getParams() { if (auto sl_dongle_id = getSunnylinkDongleId()) { QString url = SUNNYLINK_BASE_URL + "/backup/" + *sl_dongle_id; - auto request = new HttpRequest(this, true, 10000, true); - connect(request, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + auto request = new HttpRequestSP(this, true, 10000, true); + connect(request, &HttpRequestSP::requestDone, [=](const QString &resp, bool success) { bool restart_ui = false; if (success && resp != "[]") { restoreParams(resp); @@ -409,7 +410,7 @@ void SunnylinkSponsorQRWidget::refresh() { QString qrString; if (sponsor_pair) { - QString token = CommaApi::create_jwt({}, 3600, true); + QString token = SunnylinkApi::create_jwt({}, 3600, true); auto sl_dongle_id = getSunnylinkDongleId(); QByteArray payload = QString("1|" + sl_dongle_id.value_or("") + "|" + token).toUtf8().toBase64(); qrString = SUNNYLINK_BASE_URL + "/sso?state=" + payload; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h index 5b1530fc43..a09b5ae488 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h @@ -6,7 +6,6 @@ #include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h" #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_api.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_api.cc new file mode 100644 index 0000000000..02f5d9c1a3 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_api.cc @@ -0,0 +1,187 @@ +#include "selfdrive/ui/sunnypilot/qt/sp_priv_api.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "sp_priv_util.h" +#include "common/util.h" +#include "system/hardware/hw.h" +#include "selfdrive/ui/qt/util.h" + +namespace SunnylinkApi { + +QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) { + QJsonObject header = {{"alg", "RS256"}}; + + auto t = QDateTime::currentSecsSinceEpoch(); + auto dongle_id = sunnylink ? getSunnylinkDongleId() : getDongleId(); + QJsonObject payload = {{"identity", dongle_id.value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}}; + for (auto it = payloads.begin(); it != payloads.end(); ++it) { + payload.insert(it.key(), it.value()); + } + + auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals; + QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' + + QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts); + + auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256); + return jwt + "." + CommaApi::rsa_sign(hash).toBase64(b64_opts); +} + + void derive_aes_key_iv_from_rsa(EVP_PKEY *rsa_key, unsigned char *aes_key, unsigned char *aes_iv) { + unsigned char hash[SHA256_DIGEST_LENGTH]; + size_t pub_len; + unsigned char *pub_key; + + // Convert RSA key to public key in DER format for simplicity + pub_len = i2d_PublicKey(rsa_key, NULL); + pub_key = (unsigned char *)malloc(pub_len); + unsigned char *tmp = pub_key; + i2d_PublicKey(rsa_key, &tmp); + + // Hash the public key to derive bytes for AES key and IV + SHA256(pub_key, pub_len, hash); + + // Assuming AES-256-CBC, we need 32 bytes for the key and 16 for the IV + memcpy(aes_key, hash, 32); // First 32 bytes for AES key + memcpy(aes_iv, hash + 32 - 16, 16); // Last 16 bytes for AES IV + + free(pub_key); +} + +EVP_PKEY *load_public_key(const char *file) { + EVP_PKEY *key = NULL; + FILE *fp = fopen(file, "r"); + if (fp) { + key = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + fclose(fp); + } + return key; +} + +EVP_PKEY *load_private_key(const char *file) { + EVP_PKEY *key = NULL; + FILE *fp = fopen(file, "r"); + if (fp) { + key = PEM_read_PrivateKey(fp, NULL, NULL, NULL); + fclose(fp); + } + return key; +} + +QByteArray rsa_encrypt(const QByteArray &data) { + EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key + unsigned char aes_key[32], aes_iv[16]; + derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv); + + EVP_CIPHER_CTX *ctx; + if (!(ctx = EVP_CIPHER_CTX_new())) { + // Handle error: Allocate memory failed + return {}; + } + + if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) { + qDebug() << "Failed to initialize encryption"; + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + int ciphertext_len; + int len = data.size(); + unsigned char out[len + AES_BLOCK_SIZE]; + auto *in = (unsigned char*) data.constData(); + if (EVP_EncryptUpdate(ctx, out, &ciphertext_len, in, len) != 1) { + qDebug() << "Failed to update encryption"; + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + if (EVP_EncryptFinal_ex(ctx, out + ciphertext_len, &len) != 1) { + // Handle error: Encryption finalize failed + qDebug() << "Failed to finalize encryption"; + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + //qDebug() << "Encrypted data length: %d", ciphertext_len + len; // Print the length of encrypted data + EVP_CIPHER_CTX_free(ctx); + return {reinterpret_cast(out), ciphertext_len + len}; +} + +QByteArray rsa_decrypt(const QByteArray &data) { + EVP_PKEY *rsa_key = load_public_key(Path::rsa_pub_file().c_str()); // Load your RSA key + unsigned char aes_key[32], aes_iv[16]; + derive_aes_key_iv_from_rsa(rsa_key, aes_key, aes_iv); + + EVP_CIPHER_CTX *ctx; + if (!(ctx = EVP_CIPHER_CTX_new())) { + qDebug() << "Failed to allocate memory for EVP_CIPHER_CTX"; + return {}; + } + + if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, aes_key, aes_iv) != 1) { + qDebug() << "Failed to initialize EVP"; + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + int len = data.size(); + unsigned char out[len + AES_BLOCK_SIZE]; + auto *in = (unsigned char*) data.constData(); + if (EVP_DecryptUpdate(ctx, out, &len, in, len) != 1) { + qDebug() << "Failed to update decryption"; + EVP_CIPHER_CTX_free(ctx); + return {}; + } + + int final_len; + if (EVP_DecryptFinal_ex(ctx, out + len, &final_len) != 1) { + qDebug() << "Failed to finalize decryption"; + EVP_CIPHER_CTX_free(ctx); + return {}; + } + EVP_CIPHER_CTX_free(ctx); + + //qDebug() << "Decrypted data length: %d", len + final_len; // Print the length of decrypted data + return {reinterpret_cast(out), len + final_len}; +} + + +} // namespace CommaApi + +void HttpRequestSP::sendRequest(const QString& requestURL, Method method, const QByteArray& payload) { + if (active()) { + return; + } + QNetworkRequest request = prepareRequest(requestURL); + + if(!payload.isEmpty() && (method == Method::POST || method == Method::PUT)) { + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + } + + switch (method) { + case Method::GET: + reply = nam()->get(request); + break; + case Method::DELETE: + reply = nam()->deleteResource(request); + break; + case Method::POST: + reply = nam()->post(request, payload); + break; + case Method::PUT: + reply = nam()->put(request, payload); + break; + } + + networkTimer->start(); + connect(reply, &QNetworkReply::finished, this, &HttpRequestSP::requestFinished); +} diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_api.h b/selfdrive/ui/sunnypilot/qt/sp_priv_api.h new file mode 100644 index 0000000000..a5f1ae308e --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_api.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +#include "selfdrive/ui/qt/api.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#include "common/util.h" + +namespace SunnylinkApi { + QByteArray rsa_encrypt(const QByteArray& data); + QByteArray rsa_decrypt(const QByteArray& data); + QString create_jwt(const QJsonObject& payloads = {}, int expiry = 3600, bool sunnylink = false); +} + +class HttpRequestSP : public HttpRequest { + Q_OBJECT + +public: + explicit HttpRequestSP(QObject* parent, bool create_jwt = true, int timeout = 20000, bool sunnylink = false) : + HttpRequest(parent, create_jwt, timeout), sunnylink(sunnylink) {} + + using HttpRequest::sendRequest; + void sendRequest(const QString& requestURL, Method method, const QByteArray& payload); + +private: + bool sunnylink; + +protected: + [[nodiscard]] QString GetJwtToken() const override { return SunnylinkApi::create_jwt({}, 3600, sunnylink); } + [[nodiscard]] QString GetUserAgent() const override { return getUserAgent(sunnylink); } +}; diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.cc new file mode 100644 index 0000000000..6e9252acdd --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.cc @@ -0,0 +1,36 @@ +#include "selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h" + +#include "common/swaglog.h" + +RequestRepeaterSP::RequestRepeaterSP(QObject *parent, const QString &requestURL, const QString &cacheKey, + int period, bool whileOnroad, bool sunnylink) : HttpRequestSP(parent, true, 20000, sunnylink) { + request_url = requestURL; + while_onroad = whileOnroad; + timer = new QTimer(this); + timer->setTimerType(Qt::VeryCoarseTimer); + connect(timer, &QTimer::timeout, [=]() { this->timerTick(); }); + timer->start(period * 1000); + + if (!cacheKey.isEmpty()) { + prevResp = QString::fromStdString(params.get(cacheKey.toStdString())); + if (!prevResp.isEmpty()) { + QTimer::singleShot(500, [=]() { emit requestDone(prevResp, true, QNetworkReply::NoError); }); + } + connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + if (success && resp != prevResp) { + params.put(cacheKey.toStdString(), resp.toStdString()); + prevResp = resp; + } + }); + } + + // Don't wait for the timer to fire to send the first request + ForceUpdate(); +} + +void RequestRepeaterSP::timerTick() { + if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) { + LOGD("Sending request for %s", qPrintable(request_url)); + sendRequest(request_url); + } +} diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h b/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h new file mode 100644 index 0000000000..9243ab4f27 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h @@ -0,0 +1,26 @@ +#pragma once + +#include "selfdrive/ui/qt/request_repeater.h" + +#include "common/swaglog.h" +#include "common/util.h" +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/qt/sp_priv_api.h" + +class RequestRepeaterSP : public HttpRequestSP { + +private: + Params params; + QTimer *timer; + QString prevResp; + QString request_url; + bool while_onroad; + void timerTick(); + +public: + RequestRepeaterSP(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool whileOnroad=false, bool sunnylink = false); + void ForceUpdate() { + LOGD("Forcing update for %s", qPrintable(request_url)); + timerTick(); + } +}; diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc index e1c79c5aa5..85c141f951 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc @@ -5,6 +5,7 @@ #include #include +#include #include #include diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h index 8e2ced1a7e..4938b81832 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_util.h @@ -3,16 +3,9 @@ #include #include -#include -#include #include -#include -#include #include -#include "cereal/gen/cpp/car.capnp.h" -#include "common/params.h" - QString getUserAgent(bool sunnylink = false); std::optional getSunnylinkDongleId(); QMap getCarNames(); From 5bcd0a2d3d7bd5576c09f3803cf0bc3d13ecef5d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 22 Jul 2024 18:24:47 +0200 Subject: [PATCH 486/575] Refactor onboarding process and terms page in Sunnypilot The code modifications refactor the onboarding window and terms page in Sunnypilot. It includes moving OnboardingWindow to the protective area, and changing OnboardingWindowSP into a subclass of OnboardingWindow, which enhances code encapsulation and reusability. The expressions for the terms agreement pages are also refined for better readability. initial onboarding --- selfdrive/ui/qt/offroad/onboarding.cc | 20 +--- selfdrive/ui/qt/offroad/onboarding.h | 17 ++-- selfdrive/ui/qt/window.h | 2 +- selfdrive/ui/sunnypilot/SConscript | 1 + .../qt/offroad/settings/sp_priv_onboarding.cc | 97 +++++++++++++++++++ .../qt/offroad/settings/sp_priv_onboarding.h | 31 ++++++ selfdrive/ui/sunnypilot/qt/sp_priv_window.cc | 3 +- selfdrive/ui/sunnypilot/qt/sp_priv_window.h | 2 + 8 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.h diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index 014e9a6f05..8bf92aa06b 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -106,8 +106,7 @@ void TermsPage::showEvent(QShowEvent *event) { text->setAttribute(Qt::WA_AlwaysStackOnTop); text->setClearColor(QColor("#1B1B1B")); - std::string tc_text = sunnypilot_tc ? "../assets/offroad/sp_tc.html" : "../assets/offroad/tc.html"; - QString text_view = util::read_file(tc_text).c_str(); + QString text_view = util::read_file("../assets/offroad/tc.html").c_str(); text->rootContext()->setContextProperty("text_view", text_view); text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml")); @@ -186,8 +185,6 @@ void OnboardingWindow::updateActiveScreen() { setCurrentIndex(0); } else if (!training_done) { setCurrentIndex(1); - } else if (!accepted_terms_sp) { - setCurrentIndex(3); } else { emit onboardingDone(); } @@ -195,13 +192,11 @@ void OnboardingWindow::updateActiveScreen() { OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { std::string current_terms_version = params.get("TermsVersion"); - std::string current_terms_version_sp = params.get("TermsVersionSunnypilot"); std::string current_training_version = params.get("TrainingVersion"); accepted_terms = params.get("HasAcceptedTerms") == current_terms_version; - accepted_terms_sp = params.get("HasAcceptedTermsSP") == current_terms_version_sp; training_done = params.get("CompletedTrainingVersion") == current_training_version; - TermsPage* terms = new TermsPage(false, this); + TermsPage* terms = new TermsPage(this); addWidget(terms); connect(terms, &TermsPage::acceptedTerms, [=]() { params.put("HasAcceptedTerms", current_terms_version); @@ -222,15 +217,6 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { addWidget(declinePage); connect(declinePage, &DeclinePage::getBack, [=]() { updateActiveScreen(); }); - TermsPage* terms_sp = new TermsPage(true, this); - addWidget(terms_sp); // index = 3 - connect(terms_sp, &TermsPage::acceptedTerms, [=]() { - params.put("HasAcceptedTermsSP", current_terms_version_sp); - accepted_terms_sp = true; - updateActiveScreen(); - }); - connect(terms_sp, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); }); - setStyleSheet(R"( * { color: white; @@ -245,4 +231,4 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { } )"); updateActiveScreen(); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 008d86032c..098b0823a7 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -63,16 +63,17 @@ class TermsPage : public QFrame { Q_OBJECT public: - explicit TermsPage(bool sunnypilot = false, QWidget *parent = 0) : QFrame(parent), sunnypilot_tc(sunnypilot) {} + explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {} public slots: void enableAccept(); +protected: + QPushButton *accept_btn; + private: void showEvent(QShowEvent *event) override; - QPushButton *accept_btn; - bool sunnypilot_tc = false; signals: void acceptedTerms(); @@ -98,14 +99,14 @@ class OnboardingWindow : public QStackedWidget { public: explicit OnboardingWindow(QWidget *parent = 0); inline void showTrainingGuide() { setCurrentIndex(1); } - inline bool completed() const { return accepted_terms && accepted_terms_sp && training_done; } + virtual inline bool completed() const { return accepted_terms && training_done; } -private: - void updateActiveScreen(); +protected: + virtual void updateActiveScreen(); Params params; - bool accepted_terms = false, accepted_terms_sp = false, training_done = false; + bool accepted_terms = false, training_done = false; signals: void onboardingDone(); -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 7bd3bb9f0a..ac8db59e57 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -17,6 +17,7 @@ protected: explicit MainWindow(QWidget* parent, HomeWindow* hw = nullptr, SettingsWindow* sw = nullptr, OnboardingWindow* ow = nullptr); HomeWindow *homeWindow; SettingsWindow *settingsWindow; + OnboardingWindow *onboardingWindow; virtual void closeSettings(); private: @@ -24,5 +25,4 @@ private: void openSettings(int index = 0, const QString ¶m = ""); QStackedLayout *main_layout; - OnboardingWindow *onboardingWindow; }; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 6fe4a93d53..cce75a042a 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -45,6 +45,7 @@ qt_src = [ "sunnypilot/qt/sp_priv_home.cc", "sunnypilot/qt/sp_priv_offroad_home.cc", "sunnypilot/qt/sp_priv_sidebar.cc", + "sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc", "sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc", "sunnypilot/qt/offroad/settings/sp_priv_settings.cc", "sunnypilot/qt/onroad/sp_priv_buttons.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc new file mode 100644 index 0000000000..d30b9cb37b --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc @@ -0,0 +1,97 @@ +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.h" + +#include + +#include +#include +#include + +#include "common/util.h" +#include "common/params.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/input.h" + +void TermsPageSP::showEvent(QShowEvent *event) { + // late init, building QML widget takes 200ms + if (layout()) { + return; + } + + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(45, 35, 45, 45); + main_layout->setSpacing(0); + + QLabel *title = new QLabel(tr("Terms & Conditions")); + title->setStyleSheet("font-size: 90px; font-weight: 600;"); + main_layout->addWidget(title); + + main_layout->addSpacing(30); + + QQuickWidget *text = new QQuickWidget(this); + text->setResizeMode(QQuickWidget::SizeRootObjectToView); + text->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + text->setAttribute(Qt::WA_AlwaysStackOnTop); + text->setClearColor(QColor("#1B1B1B")); + + std::string tc_text = sunnypilot_tc ? "../assets/offroad/sp_tc.html" : "../assets/offroad/tc.html"; + QString text_view = util::read_file(tc_text).c_str(); + text->rootContext()->setContextProperty("text_view", text_view); + + text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml")); + + main_layout->addWidget(text, 1); + main_layout->addSpacing(50); + + QObject *obj = (QObject*)text->rootObject(); + QObject::connect(obj, SIGNAL(scroll()), SLOT(enableAccept())); + + QHBoxLayout* buttons = new QHBoxLayout; + buttons->setMargin(0); + buttons->setSpacing(45); + main_layout->addLayout(buttons); + + QPushButton *decline_btn = new QPushButton(tr("Decline")); + buttons->addWidget(decline_btn); + QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms); + + accept_btn = new QPushButton(tr("Scroll to accept")); + accept_btn->setEnabled(false); + accept_btn->setStyleSheet(R"( + QPushButton { + background-color: #465BEA; + } + QPushButton:pressed { + background-color: #3049F4; + } + QPushButton:disabled { + background-color: #4F4F4F; + } + )"); + buttons->addWidget(accept_btn); + QObject::connect(accept_btn, &QPushButton::clicked, this, &TermsPage::acceptedTerms); +} + +void OnboardingWindowSP::updateActiveScreen() { + if(accepted_terms && training_done && !accepted_terms_sp) { + setCurrentIndex(3); + } else { + OnboardingWindow::updateActiveScreen(); + } +} + +OnboardingWindowSP::OnboardingWindowSP(QWidget *parent) : OnboardingWindow(parent) { + std::string current_terms_version_sp = params.get("TermsVersionSunnypilot"); + accepted_terms_sp = params.get("HasAcceptedTermsSP") == current_terms_version_sp; + LOGD("accepted_terms_sp: %s", params.get("HasAcceptedTermsSP").c_str()); + + auto* terms_sp = new TermsPageSP(true, parent); + addWidget(terms_sp); // index = 3 + connect(terms_sp, &TermsPageSP::acceptedTerms, [=]() { + params.put("HasAcceptedTermsSP", current_terms_version_sp); + accepted_terms_sp = true; + updateActiveScreen(); + }); + connect(terms_sp, &TermsPageSP::declinedTerms, [=]() { setCurrentIndex(2); }); + + OnboardingWindowSP::updateActiveScreen(); +} \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.h new file mode 100644 index 0000000000..bd13f106ba --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "common/params.h" +#include "selfdrive/ui/qt/qt_window.h" + +class TermsPageSP : public TermsPage { + Q_OBJECT + + +public: + explicit TermsPageSP(bool sunnypilot = false, QWidget *parent = 0) : TermsPage(parent), sunnypilot_tc(sunnypilot) {} + + +private: + bool sunnypilot_tc = false; + void showEvent(QShowEvent *event) override; +}; + +class OnboardingWindowSP : public OnboardingWindow { + Q_OBJECT + +public: + explicit OnboardingWindowSP(QWidget *parent = 0); + inline bool completed() const override { return accepted_terms && accepted_terms_sp && training_done; } + +private: + bool accepted_terms_sp = false; + void updateActiveScreen() override; +}; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc b/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc index a6b7fac15a..69a35ed699 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc @@ -1,9 +1,10 @@ #include "sp_priv_window.h" MainWindowSP::MainWindowSP(QWidget *parent) : MainWindow(parent, new HomeWindowSP(parent), - new SettingsWindowSP(parent), new OnboardingWindow(parent)) { + new SettingsWindowSP(parent), new OnboardingWindowSP(parent)) { homeWindow = dynamic_cast(MainWindow::homeWindow); settingsWindow = dynamic_cast(MainWindow::settingsWindow); + onboardingWindow = dynamic_cast(MainWindow::onboardingWindow); } void MainWindowSP::closeSettings() { diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_window.h b/selfdrive/ui/sunnypilot/qt/sp_priv_window.h index cf67270cf3..73a867bde9 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_window.h +++ b/selfdrive/ui/sunnypilot/qt/sp_priv_window.h @@ -3,6 +3,7 @@ #include "selfdrive/ui/qt/window.h" #include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" +#include "offroad/settings/sp_priv_onboarding.h" class MainWindowSP : public MainWindow { Q_OBJECT @@ -13,5 +14,6 @@ explicit MainWindowSP(QWidget *parent = 0); private: HomeWindowSP *homeWindow; SettingsWindowSP *settingsWindow; + OnboardingWindowSP *onboardingWindow; void closeSettings() override; }; From 09068fc34e059f61174a22fccd251015210e73f4 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 23 Jul 2024 09:53:50 +0200 Subject: [PATCH 487/575] cleaning up --- selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc index 65b42e4fe5..a39906e983 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc @@ -53,7 +53,6 @@ void OnroadWindowSP::mousePressEvent(QMouseEvent* e) { } } #endif -#ifdef SUNNYPILOT if (onroad_settings != nullptr && !isMapVisible()) { if (wakeScreenTimeout()) { onroad_settings->setVisible(false); @@ -61,7 +60,6 @@ void OnroadWindowSP::mousePressEvent(QMouseEvent* e) { } // propagation event to parent(HomeWindow) QWidget::mousePressEvent(e); -#endif } void OnroadWindowSP::createMapWidget() { @@ -84,7 +82,6 @@ void OnroadWindowSP::createMapWidget() { } void OnroadWindowSP::createOnroadSettingsWidget() { -#ifdef SUNNYPILOT auto os = new OnroadSettingsPanel(this); onroad_settings = os; @@ -98,7 +95,6 @@ void OnroadWindowSP::createOnroadSettingsWidget() { // hidden by default os->setVisible(false); -#endif } void OnroadWindowSP::offroadTransition(bool offroad) { From 223cfd2c348aa3d7b7aac3455fb845dd14447405 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 23 Jul 2024 09:54:30 +0200 Subject: [PATCH 488/575] Refactor map helper includes for SunnyPilot This update refactors the inclusion of map helper files in various source files for compatibility with SunnyPilot. This involves changes like replacing "#include selfdrive/ui/qt/maps/map_helpers.h" with "#include selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h". Added logging information in "sp_priv_onroad_home.cc" to debug the creation of map widgets. This refactoring is necessary to support map related functionalities in the SunnyPilot environment. --- selfdrive/navd/main.cc | 4 ++++ selfdrive/ui/qt/maps/map.cc | 3 ++- selfdrive/ui/qt/maps/map_eta.cc | 3 ++- selfdrive/ui/qt/maps/map_helpers.cc | 4 ++++ selfdrive/ui/qt/maps/map_instructions.cc | 3 ++- selfdrive/ui/qt/maps/map_panel.cc | 3 ++- selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc | 8 +++++++- 7 files changed, 23 insertions(+), 5 deletions(-) diff --git a/selfdrive/navd/main.cc b/selfdrive/navd/main.cc index 2e7b4d3b60..077fd6a0e5 100644 --- a/selfdrive/navd/main.cc +++ b/selfdrive/navd/main.cc @@ -6,7 +6,11 @@ #include "common/util.h" #include "selfdrive/ui/qt/util.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#else #include "selfdrive/ui/qt/maps/map_helpers.h" +#endif #include "selfdrive/navd/map_renderer.h" #include "system/hardware/hw.h" diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 3d1ec99947..48be94bb9d 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -6,11 +6,12 @@ #include #include "common/swaglog.h" -#include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" #else +#include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc index 868c6ca550..335a44fdad 100644 --- a/selfdrive/ui/qt/maps/map_eta.cc +++ b/selfdrive/ui/qt/maps/map_eta.cc @@ -3,11 +3,12 @@ #include #include -#include "selfdrive/ui/qt/maps/map_helpers.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" #else #include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" #endif const float MANEUVER_TRANSITION_THRESHOLD = 10; diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 50e1401164..a62c1cbfdf 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -1,4 +1,8 @@ +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#else #include "selfdrive/ui/qt/maps/map_helpers.h" +#endif #include #include diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc index 6bfeefbafc..6c3d797080 100644 --- a/selfdrive/ui/qt/maps/map_instructions.cc +++ b/selfdrive/ui/qt/maps/map_instructions.cc @@ -3,11 +3,12 @@ #include #include -#include "selfdrive/ui/qt/maps/map_helpers.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" #else #include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" #endif const QString ICON_SUFFIX = ".png"; diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index 75dc85b01e..66ad95e185 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -3,13 +3,14 @@ #include #include -#include "selfdrive/ui/qt/maps/map.h" #include "selfdrive/ui/qt/maps/map_settings.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h" #else #include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/maps/map.h" #endif MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc index a39906e983..bde287399d 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc @@ -4,13 +4,15 @@ #include #ifdef ENABLE_MAPS -#include "selfdrive/ui/qt/maps/map_helpers.h" +#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" #include "selfdrive/ui/qt/maps/map_panel.h" #endif #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h" #endif +#include + #include "selfdrive/ui/qt/util.h" OnroadWindowSP::OnroadWindowSP(QWidget *parent) : OnroadWindow(parent) { // QObject::disconnect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); @@ -64,6 +66,7 @@ void OnroadWindowSP::mousePressEvent(QMouseEvent* e) { void OnroadWindowSP::createMapWidget() { #ifdef ENABLE_MAPS + LOGD("Creating map widget"); auto m = new MapPanel(get_mapbox_settings()); map = m; @@ -101,9 +104,12 @@ void OnroadWindowSP::offroadTransition(bool offroad) { if (!offroad) { #ifdef ENABLE_MAPS + LOGD("We'd like to create the map widget, the condition is map is [%s] and hasPrime is [%s] and MAPBOX_TOKEN is [%s]", map == nullptr ? "null" : "not null", uiStateSP()->hasPrime() ? "true" : "false", MAPBOX_TOKEN.isEmpty() ? "empty" : "not empty"); if (map == nullptr && (uiStateSP()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) { createMapWidget(); } +#else + LOGD("Maps are disabled"); #endif if (onroad_settings == nullptr) { createOnroadSettingsWidget(); From 353b22168f43e8dcadb67e364ddb932bb6c02f55 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 23 Jul 2024 10:14:53 +0200 Subject: [PATCH 489/575] Refactor networking files and include network UI for SunnyPilot This commit refactors the networking files and includes a SunnyPilot network UI. Changes in existing files result in the removal of some features not required for SunnyPilot while retaining others. For instance, the 'ToggleControl' functionality for Sunnypilot was removed as Sunnypilot does not require this feature. Other changes include adjustments to the network's user interface layout and the removal of unnecessary widgets. This refactor was necessitated by the need to provide slightly different functionalities between Comma's default UI and the Sunnypilot UI while avoiding code duplication whenever possible. The newly created files sp_priv_networking.{c,h} include the relevant elements of the network UI specifically modified for SunnyPilot. These modifications are expected to enhance the overall user experience and usability of the network interface in the SunnyPilot UI. Please note that the 'ngrok' and 'hotspotOnBoot' functionalities have been excluded from Sunnypilot as they are not required in its use case. The SConscript file has also been updated accordingly to include the required configurations for the new network UI files. --- selfdrive/ui/qt/network/networking.cc | 63 +-- selfdrive/ui/qt/network/networking.h | 6 +- selfdrive/ui/sunnypilot/SConscript | 1 + .../qt/network/sp_priv_networking.cc | 422 ++++++++++++++++++ .../qt/network/sp_priv_networking.h | 102 +++++ .../qt/offroad/settings/sp_priv_settings.cc | 4 +- 6 files changed, 538 insertions(+), 60 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.cc create mode 100644 selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 1aafb06c48..a6921f1c90 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -8,13 +8,14 @@ #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#define ToggleControl ToggleControlSP #else #include "selfdrive/ui/ui.h" #endif #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/qt/home.h" static const int ICON_WIDTH = 49; @@ -30,29 +31,17 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) { wifiScreen = new QWidget(this); QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen); vlayout->setContentsMargins(20, 20, 20, 20); - QHBoxLayout* hlayout = new QHBoxLayout(); - QPushButton* scanButton = new QPushButton(tr("Scan")); - scanButton->setObjectName("scan_btn"); - scanButton->setFixedSize(400, 100); - connect(wifi, &WifiManager::refreshSignal, this, [=]() { scanButton->setText(tr("Scan")); scanButton->setEnabled(true); }); - connect(scanButton, &QPushButton::clicked, [=]() { scanButton->setText(tr("Scanning...")); scanButton->setEnabled(false); wifi->requestScan(); }); - - hlayout->addWidget(scanButton); - hlayout->addStretch(1); // Pushes the button all the way to the left - if (show_advanced) { - hlayout->setSpacing(10); - QPushButton* advancedSettings = new QPushButton(tr("Advanced")); advancedSettings->setObjectName("advanced_btn"); + advancedSettings->setStyleSheet("margin-right: 30px;"); advancedSettings->setFixedSize(400, 100); connect(advancedSettings, &QPushButton::clicked, [=]() { main_layout->setCurrentWidget(an); }); - hlayout->addWidget(advancedSettings); + vlayout->addSpacing(10); + vlayout->addWidget(advancedSettings, 0, Qt::AlignRight); + vlayout->addSpacing(10); } - vlayout->addLayout(hlayout); - vlayout->addSpacing(10); - wifiWidget = new WifiUI(this, wifi); wifiWidget->setObjectName("wifiWidget"); connect(wifiWidget, &WifiUI::connectToNetwork, this, &Networking::connectToNetwork); @@ -73,7 +62,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) { setPalette(pal); setStyleSheet(R"( - #wifiWidget > QPushButton, #back_btn, #advanced_btn, #scan_btn{ + #wifiWidget > QPushButton, #back_btn, #advanced_btn { font-size: 50px; margin: 0px; padding: 15px; @@ -82,7 +71,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) { color: #dddddd; background-color: #393939; } - #back_btn:pressed, #advanced_btn:pressed, #scan_btn:pressed { + #back_btn:pressed, #advanced_btn:pressed { background-color: #4a4a4a; } )"); @@ -143,23 +132,10 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid ListWidget *list = new ListWidget(this); // Enable tethering layout - const bool set_hotspot_on_boot = params.getBool("HotspotOnBoot") && params.getBool("HotspotOnBootConfirmed"); - tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled() || set_hotspot_on_boot); + tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled()); list->addItem(tetheringToggle); QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering); - hotspotOnBootToggle = new ToggleControl( - tr("Retain hotspot/tethering state"), - tr("Enabling this toggle will retain the hotspot/tethering toggle state across reboots."), - "", - params.getBool("HotspotOnBoot") - ); - hotspotOnBootToggle->setEnabled(wifi->isTetheringEnabled() || set_hotspot_on_boot); - QObject::connect(hotspotOnBootToggle, &ToggleControl::toggleFlipped, [=](bool state) { - params.putBool("HotspotOnBoot", state); - }); - list->addItem(hotspotOnBootToggle); - // Change tethering password ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT")); connect(editPasswordButton, &ButtonControl::clicked, [=]() { @@ -230,19 +206,6 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid }); list->addItem(hiddenNetworkButton); - // Ngrok - QProcess process; - process.start("sudo service ngrok status | grep running"); - process.waitForFinished(); - QString output = QString(process.readAllStandardOutput()); - bool ngrokRunning = !output.isEmpty(); - ToggleControl *ngrokToggle = new ToggleControl(tr("Ngrok Service"), "", "", ngrokRunning); - connect(ngrokToggle, &ToggleControl::toggleFlipped, [=](bool state) { - if (state) std::system("sudo ngrok service start"); - else std::system("sudo ngrok service stop"); - }); - list->addItem(ngrokToggle); - // Set initial config wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered); @@ -264,14 +227,8 @@ void AdvancedNetworking::refresh() { } void AdvancedNetworking::toggleTethering(bool enabled) { - params.putBool("HotspotOnBootConfirmed", enabled); wifi->setTetheringEnabled(enabled); tetheringToggle->setEnabled(false); - - hotspotOnBootToggle->setEnabled(enabled); - if (!enabled) { - params.remove("HotspotOnBoot"); - } } // WifiUI functions @@ -423,4 +380,4 @@ void WifiItem::setItem(const Network &n, const QPixmap &status_icon, bool show_f iconLabel->setPixmap(status_icon); strengthLabel->setPixmap(strength_icon); -} +} \ No newline at end of file diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index 502b47a276..c7111675da 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -8,8 +8,6 @@ #include "selfdrive/ui/qt/widgets/toggle.h" #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#define ListWidget ListWidgetSP -#define ToggleControl ToggleControlSP #define LabelControl LabelControlSP #define ElidedLabel ElidedLabelSP #else @@ -76,8 +74,6 @@ private: WifiManager* wifi = nullptr; Params params; - ToggleControl* hotspotOnBootToggle; - signals: void backPress(); void requestWifiScreen(); @@ -109,4 +105,4 @@ public slots: private slots: void connectToNetwork(const Network n); void wrongPassword(const QString &ssid); -}; +}; \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index cce75a042a..951973583c 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -2,6 +2,7 @@ widgets_src = [ "sunnypilot/sp_priv_ui.cc", "sunnypilot/qt/sp_priv_window.cc", "sunnypilot/qt/sp_priv_request_repeater.cc", + "sunnypilot/qt/network/sp_priv_networking.cc", "sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc", "sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc", diff --git a/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.cc b/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.cc new file mode 100644 index 0000000000..f51ffeb2bc --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.cc @@ -0,0 +1,422 @@ +#include "selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h" + +#include + +#include +#include +#include +#include "selfdrive/ui/qt/widgets/ssh_keys.h" + +#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +static const int ICON_WIDTH = 49; + +// Networking functions + +NetworkingSP::NetworkingSP(QWidget* parent, bool show_advanced) : QFrame(parent) { + main_layout = new QStackedLayout(this); + + wifi = new WifiManager(this); + connect(wifi, &WifiManager::refreshSignal, this, &NetworkingSP::refresh); + connect(wifi, &WifiManager::wrongPassword, this, &NetworkingSP::wrongPassword); + + wifiScreen = new QWidget(this); + QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen); + vlayout->setContentsMargins(20, 20, 20, 20); + QHBoxLayout* hlayout = new QHBoxLayout(); + QPushButton* scanButton = new QPushButton(tr("Scan")); + scanButton->setObjectName("scan_btn"); + scanButton->setFixedSize(400, 100); + connect(wifi, &WifiManager::refreshSignal, this, [=]() { scanButton->setText(tr("Scan")); scanButton->setEnabled(true); }); + connect(scanButton, &QPushButton::clicked, [=]() { scanButton->setText(tr("Scanning...")); scanButton->setEnabled(false); wifi->requestScan(); }); + + hlayout->addWidget(scanButton); + hlayout->addStretch(1); // Pushes the button all the way to the left + + if (show_advanced) { + hlayout->setSpacing(10); + + QPushButton* advancedSettings = new QPushButton(tr("Advanced")); + advancedSettings->setObjectName("advanced_btn"); + advancedSettings->setFixedSize(400, 100); + connect(advancedSettings, &QPushButton::clicked, [=]() { main_layout->setCurrentWidget(an); }); + hlayout->addWidget(advancedSettings); + } + + vlayout->addLayout(hlayout); + vlayout->addSpacing(10); + + wifiWidget = new WifiUISP(this, wifi); + wifiWidget->setObjectName("wifiWidget"); + connect(wifiWidget, &WifiUISP::connectToNetwork, this, &NetworkingSP::connectToNetwork); + + ScrollView *wifiScroller = new ScrollView(wifiWidget, this); + wifiScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + vlayout->addWidget(wifiScroller, 1); + main_layout->addWidget(wifiScreen); + + an = new AdvancedNetworkingSP(this, wifi); + connect(an, &AdvancedNetworkingSP::backPress, [=]() { main_layout->setCurrentWidget(wifiScreen); }); + connect(an, &AdvancedNetworkingSP::requestWifiScreen, [=]() { main_layout->setCurrentWidget(wifiScreen); }); + main_layout->addWidget(an); + + QPalette pal = palette(); + pal.setColor(QPalette::Window, QColor(0x29, 0x29, 0x29)); + setAutoFillBackground(true); + setPalette(pal); + + setStyleSheet(R"( + #wifiWidget > QPushButton, #back_btn, #advanced_btn, #scan_btn{ + font-size: 50px; + margin: 0px; + padding: 15px; + border-width: 0; + border-radius: 30px; + color: #dddddd; + background-color: #393939; + } + #back_btn:pressed, #advanced_btn:pressed, #scan_btn:pressed { + background-color: #4a4a4a; + } + )"); + main_layout->setCurrentWidget(wifiScreen); +} + +void NetworkingSP::refresh() { + wifiWidget->refresh(); + an->refresh(); +} + +void NetworkingSP::connectToNetwork(const Network n) { + if (wifi->isKnownConnection(n.ssid)) { + wifi->activateWifiConnection(n.ssid); + } else if (n.security_type == SecurityType::OPEN) { + wifi->connect(n, false); + } else if (n.security_type == SecurityType::WPA) { + QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); + if (!pass.isEmpty()) { + wifi->connect(n, false, pass); + } + } +} + +void NetworkingSP::wrongPassword(const QString &ssid) { + if (wifi->seenNetworks.contains(ssid)) { + const Network &n = wifi->seenNetworks.value(ssid); + QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); + if (!pass.isEmpty()) { + wifi->connect(n, false, pass); + } + } +} + +void NetworkingSP::showEvent(QShowEvent *event) { + wifi->start(); +} + +void NetworkingSP::hideEvent(QHideEvent *event) { + main_layout->setCurrentWidget(wifiScreen); + wifi->stop(); +} + +// AdvancedNetworkingSP functions + +AdvancedNetworkingSP::AdvancedNetworkingSP(QWidget* parent, WifiManager* wifi): QWidget(parent), wifi(wifi) { + + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setMargin(40); + main_layout->setSpacing(20); + + // Back button + QPushButton* back = new QPushButton(tr("Back")); + back->setObjectName("back_btn"); + back->setFixedSize(400, 100); + connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); + main_layout->addWidget(back, 0, Qt::AlignLeft); + + ListWidgetSP *list = new ListWidgetSP(this); + // Enable tethering layout + const bool set_hotspot_on_boot = params.getBool("HotspotOnBoot") && params.getBool("HotspotOnBootConfirmed"); + tetheringToggle = new ToggleControlSP(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled() || set_hotspot_on_boot); + list->addItem(tetheringToggle); + QObject::connect(tetheringToggle, &ToggleControlSP::toggleFlipped, this, &AdvancedNetworkingSP::toggleTethering); + + hotspotOnBootToggle = new ToggleControlSP( + tr("Retain hotspot/tethering state"), + tr("Enabling this toggle will retain the hotspot/tethering toggle state across reboots."), + "", + params.getBool("HotspotOnBoot") + ); + hotspotOnBootToggle->setEnabled(wifi->isTetheringEnabled() || set_hotspot_on_boot); + QObject::connect(hotspotOnBootToggle, &ToggleControlSP::toggleFlipped, [=](bool state) { + params.putBool("HotspotOnBoot", state); + }); + list->addItem(hotspotOnBootToggle); + + // Change tethering password + ButtonControlSP *editPasswordButton = new ButtonControlSP(tr("Tethering Password"), tr("EDIT")); + connect(editPasswordButton, &ButtonControlSP::clicked, [=]() { + QString pass = InputDialog::getText(tr("Enter new tethering password"), this, "", true, 8, wifi->getTetheringPassword()); + if (!pass.isEmpty()) { + wifi->changeTetheringPassword(pass); + } + }); + list->addItem(editPasswordButton); + + // IP address + ipLabel = new LabelControlSP(tr("IP Address"), wifi->ipv4_address); + list->addItem(ipLabel); + + // SSH keys + list->addItem(new SshToggle()); + list->addItem(new SshControl()); + + // Roaming toggle + const bool roamingEnabled = params.getBool("GsmRoaming"); + roamingToggle = new ToggleControlSP(tr("Enable Roaming"), "", "", roamingEnabled); + QObject::connect(roamingToggle, &ToggleControlSP::toggleFlipped, [=](bool state) { + params.putBool("GsmRoaming", state); + wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")), params.getBool("GsmMetered")); + }); + list->addItem(roamingToggle); + + // APN settings + editApnButton = new ButtonControlSP(tr("APN Setting"), tr("EDIT")); + connect(editApnButton, &ButtonControlSP::clicked, [=]() { + const QString cur_apn = QString::fromStdString(params.get("GsmApn")); + QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed(); + + if (apn.isEmpty()) { + params.remove("GsmApn"); + } else { + params.put("GsmApn", apn.toStdString()); + } + wifi->updateGsmSettings(params.getBool("GsmRoaming"), apn, params.getBool("GsmMetered")); + }); + list->addItem(editApnButton); + + // Metered toggle + const bool metered = params.getBool("GsmMetered"); + meteredToggle = new ToggleControlSP(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered connection"), "", metered); + QObject::connect(meteredToggle, &SshToggle::toggleFlipped, [=](bool state) { + params.putBool("GsmMetered", state); + wifi->updateGsmSettings(params.getBool("GsmRoaming"), QString::fromStdString(params.get("GsmApn")), state); + }); + list->addItem(meteredToggle); + + // Hidden Network + hiddenNetworkButton = new ButtonControlSP(tr("Hidden Network"), tr("CONNECT")); + connect(hiddenNetworkButton, &ButtonControlSP::clicked, [=]() { + QString ssid = InputDialog::getText(tr("Enter SSID"), this, "", false, 1); + if (!ssid.isEmpty()) { + QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(ssid), true, -1); + Network hidden_network; + hidden_network.ssid = ssid.toUtf8(); + if (!pass.isEmpty()) { + hidden_network.security_type = SecurityType::WPA; + wifi->connect(hidden_network, true, pass); + } else { + wifi->connect(hidden_network, true); + } + emit requestWifiScreen(); + } + }); + list->addItem(hiddenNetworkButton); + + // Ngrok + QProcess process; + process.start("sudo service ngrok status | grep running"); + process.waitForFinished(); + QString output = QString(process.readAllStandardOutput()); + bool ngrokRunning = !output.isEmpty(); + ToggleControlSP *ngrokToggle = new ToggleControlSP(tr("Ngrok Service"), "", "", ngrokRunning); + connect(ngrokToggle, &ToggleControlSP::toggleFlipped, [=](bool state) { + if (state) std::system("sudo ngrok service start"); + else std::system("sudo ngrok service stop"); + }); + list->addItem(ngrokToggle); + + // Set initial config + wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered); + + connect(uiState(), &UIState::primeTypeChanged, this, [=](PrimeType prime_type) { + bool gsmVisible = prime_type == PrimeType::NONE || prime_type == PrimeType::LITE; + roamingToggle->setVisible(gsmVisible); + editApnButton->setVisible(gsmVisible); + meteredToggle->setVisible(gsmVisible); + }); + + main_layout->addWidget(new ScrollView(list, this)); + main_layout->addStretch(1); +} + +void AdvancedNetworkingSP::refresh() { + ipLabel->setText(wifi->ipv4_address); + tetheringToggle->setEnabled(true); + update(); +} + +void AdvancedNetworkingSP::toggleTethering(bool enabled) { + params.putBool("HotspotOnBootConfirmed", enabled); + wifi->setTetheringEnabled(enabled); + tetheringToggle->setEnabled(false); + + hotspotOnBootToggle->setEnabled(enabled); + if (!enabled) { + params.remove("HotspotOnBoot"); + } +} + +// WifiUISP functions + +WifiUISP::WifiUISP(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); + + // load imgs + for (const auto &s : {"low", "medium", "high", "full"}) { + QPixmap pix(ASSET_PATH + "/offroad/icon_wifi_strength_" + s + ".svg"); + strengths.push_back(pix.scaledToHeight(68, Qt::SmoothTransformation)); + } + lock = QPixmap(ASSET_PATH + "offroad/icon_lock_closed.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); + checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); + circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); + + scanningLabel = new QLabel(tr("Scanning for networks...")); + scanningLabel->setStyleSheet("font-size: 65px;"); + main_layout->addWidget(scanningLabel, 0, Qt::AlignCenter); + + wifi_list_widget = new ListWidgetSP(this); + wifi_list_widget->setVisible(false); + main_layout->addWidget(wifi_list_widget); + + setStyleSheet(R"( + QScrollBar::handle:vertical { + min-height: 0px; + border-radius: 4px; + background-color: #8A8A8A; + } + #forgetBtn { + font-size: 32px; + font-weight: 600; + color: #292929; + background-color: #BDBDBD; + border-width: 1px solid #828282; + border-radius: 5px; + padding: 40px; + padding-bottom: 16px; + padding-top: 16px; + } + #forgetBtn:pressed { + background-color: #828282; + } + #connecting { + font-size: 32px; + font-weight: 600; + color: white; + border-radius: 0; + padding: 27px; + padding-left: 43px; + padding-right: 43px; + background-color: black; + } + #ssidLabel { + text-align: left; + border: none; + padding-top: 50px; + padding-bottom: 50px; + } + #ssidLabel:disabled { + color: #696969; + } + )"); +} + +void WifiUISP::refresh() { + bool is_empty = wifi->seenNetworks.isEmpty(); + scanningLabel->setVisible(is_empty); + wifi_list_widget->setVisible(!is_empty); + if (is_empty) return; + + setUpdatesEnabled(false); + + const bool is_tethering_enabled = wifi->isTetheringEnabled(); + QList sortedNetworks = wifi->seenNetworks.values(); + std::sort(sortedNetworks.begin(), sortedNetworks.end(), compare_by_strength); + + int n = 0; + for (Network &network : sortedNetworks) { + QPixmap status_icon; + if (network.connected == ConnectedType::CONNECTED) { + status_icon = checkmark; + } else if (network.security_type == SecurityType::UNSUPPORTED) { + status_icon = circled_slash; + } else if (network.security_type == SecurityType::WPA) { + status_icon = lock; + } + bool show_forget_btn = wifi->isKnownConnection(network.ssid) && !is_tethering_enabled; + QPixmap strength = strengths[strengthLevel(network.strength)]; + + auto item = getItem(n++); + item->setItem(network, status_icon, show_forget_btn, strength); + item->setVisible(true); + } + for (; n < wifi_items.size(); ++n) wifi_items[n]->setVisible(false); + + setUpdatesEnabled(true); +} + +WifiItemSP *WifiUISP::getItem(int n) { + auto item = n < wifi_items.size() ? wifi_items[n] : wifi_items.emplace_back(new WifiItemSP(tr("CONNECTING..."), tr("FORGET"))); + if (!item->parentWidget()) { + QObject::connect(item, &WifiItemSP::connectToNetwork, this, &WifiUISP::connectToNetwork); + QObject::connect(item, &WifiItemSP::forgotNetwork, [this](const Network n) { + if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(n.ssid)), tr("Forget"), this)) + wifi->forgetConnection(n.ssid); + }); + wifi_list_widget->addItem(item); + } + return item; +} + +// WifiItemSP + +WifiItemSP::WifiItemSP(const QString &connecting_text, const QString &forget_text, QWidget *parent) : QWidget(parent) { + QHBoxLayout *hlayout = new QHBoxLayout(this); + hlayout->setContentsMargins(44, 0, 73, 0); + hlayout->setSpacing(50); + + hlayout->addWidget(ssidLabel = new ElidedLabelSP()); + ssidLabel->setObjectName("ssidLabel"); + ssidLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + hlayout->addWidget(connecting = new QPushButton(connecting_text), 0, Qt::AlignRight); + connecting->setObjectName("connecting"); + hlayout->addWidget(forgetBtn = new QPushButton(forget_text), 0, Qt::AlignRight); + forgetBtn->setObjectName("forgetBtn"); + hlayout->addWidget(iconLabel = new QLabel(), 0, Qt::AlignRight); + hlayout->addWidget(strengthLabel = new QLabel(), 0, Qt::AlignRight); + + iconLabel->setFixedWidth(ICON_WIDTH); + QObject::connect(forgetBtn, &QPushButton::clicked, [this]() { emit forgotNetwork(network); }); + QObject::connect(ssidLabel, &ElidedLabelSP::clicked, [this]() { + if (network.connected == ConnectedType::DISCONNECTED) emit connectToNetwork(network); + }); +} + +void WifiItemSP::setItem(const Network &n, const QPixmap &status_icon, bool show_forget_btn, const QPixmap &strength_icon) { + network = n; + + ssidLabel->setText(n.ssid); + ssidLabel->setEnabled(n.security_type != SecurityType::UNSUPPORTED); + ssidLabel->setFont(InterFont(55, network.connected == ConnectedType::DISCONNECTED ? QFont::Normal : QFont::Bold)); + + connecting->setVisible(n.connected == ConnectedType::CONNECTING); + forgetBtn->setVisible(show_forget_btn); + + iconLabel->setPixmap(status_icon); + strengthLabel->setPixmap(strength_icon); +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h b/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h new file mode 100644 index 0000000000..71f9d57895 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +#include "selfdrive/ui/qt/network/wifi_manager.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" + +class WifiItemSP : public QWidget { + Q_OBJECT +public: + explicit WifiItemSP(const QString &connecting_text, const QString &forget_text, QWidget* parent = nullptr); + void setItem(const Network& n, const QPixmap &icon, bool show_forget_btn, const QPixmap &strength); + +signals: + // Cannot pass Network by reference. it may change after the signal is sent. + void connectToNetwork(const Network n); + void forgotNetwork(const Network n); + +protected: + ElidedLabelSP* ssidLabel; + QPushButton* connecting; + QPushButton* forgetBtn; + QLabel* iconLabel; + QLabel* strengthLabel; + Network network; +}; + +class WifiUISP : public QWidget { + Q_OBJECT + +public: + explicit WifiUISP(QWidget *parent = 0, WifiManager* wifi = 0); + +private: + WifiItemSP *getItem(int n); + + WifiManager *wifi = nullptr; + QLabel *scanningLabel = nullptr; + QPixmap lock; + QPixmap checkmark; + QPixmap circled_slash; + QVector strengths; + ListWidgetSP *wifi_list_widget = nullptr; + std::vector wifi_items; + +signals: + void connectToNetwork(const Network n); + +public slots: + void refresh(); +}; + +class AdvancedNetworkingSP : public QWidget { + Q_OBJECT +public: + explicit AdvancedNetworkingSP(QWidget* parent = 0, WifiManager* wifi = 0); + +private: + LabelControlSP* ipLabel; + ToggleControlSP* tetheringToggle; + ToggleControlSP* roamingToggle; + ButtonControlSP* editApnButton; + ButtonControlSP* hiddenNetworkButton; + ToggleControlSP* meteredToggle; + WifiManager* wifi = nullptr; + Params params; + + ToggleControlSP* hotspotOnBootToggle; + +signals: + void backPress(); + void requestWifiScreen(); + +public slots: + void toggleTethering(bool enabled); + void refresh(); +}; + +class NetworkingSP : public QFrame { + Q_OBJECT + +public: + explicit NetworkingSP(QWidget* parent = 0, bool show_advanced = true); + WifiManager* wifi = nullptr; + +private: + QStackedLayout* main_layout = nullptr; + QWidget* wifiScreen = nullptr; + AdvancedNetworkingSP* an = nullptr; + WifiUISP* wifiWidget; + + void showEvent(QShowEvent* event) override; + void hideEvent(QHideEvent* event) override; + +public slots: + void refresh(); + +private slots: + void connectToNetwork(const Network n); + void wrongPassword(const QString &ssid); +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc index b826c212b6..0c92b86c04 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc @@ -4,7 +4,7 @@ #include #include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h" -#include "selfdrive/ui/qt/network/networking.h" +#include "selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h" #include "selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h" TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { @@ -332,7 +332,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { QList panels = { PanelInfo(" " + tr("Device"), device, "../assets/navigation/icon_home.svg"), - PanelInfo(" " + tr("Network"), new Networking(this), "../assets/offroad/icon_network.png"), + PanelInfo(" " + tr("Network"), new NetworkingSP(this), "../assets/offroad/icon_network.png"), PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/offroad/icon_wifi_strength_full.svg"), PanelInfo(" " + tr("Toggles"), toggles, "../assets/offroad/icon_toggle.png"), PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../assets/offroad/icon_software.png"), From f55febc85582f8918cf830944b2c8be25732277e Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 24 Jul 2024 21:08:44 +0200 Subject: [PATCH 490/575] remove sp_priv from the names Renamed files by removing sp_priv_ prefix --- selfdrive/navd/main.cc | 2 +- selfdrive/navd/map_renderer.cc | 2 +- selfdrive/ui/SConscript | 2 +- selfdrive/ui/main.cc | 2 +- selfdrive/ui/qt/body.h | 2 +- selfdrive/ui/qt/home.h | 6 +- selfdrive/ui/qt/maps/map.cc | 4 +- selfdrive/ui/qt/maps/map.h | 2 +- selfdrive/ui/qt/maps/map_eta.cc | 4 +- selfdrive/ui/qt/maps/map_helpers.cc | 2 +- selfdrive/ui/qt/maps/map_instructions.cc | 4 +- selfdrive/ui/qt/maps/map_panel.cc | 4 +- selfdrive/ui/qt/maps/map_settings.h | 2 +- selfdrive/ui/qt/network/networking.cc | 2 +- selfdrive/ui/qt/network/networking.h | 2 +- selfdrive/ui/qt/network/wifi_manager.cc | 2 +- selfdrive/ui/qt/offroad/settings.cc | 2 +- selfdrive/ui/qt/offroad/settings.h | 4 +- selfdrive/ui/qt/offroad/software_settings.cc | 2 +- selfdrive/ui/qt/offroad_home.h | 6 +- selfdrive/ui/qt/onroad/buttons.h | 2 +- selfdrive/ui/qt/onroad/onroad_home.h | 2 +- selfdrive/ui/qt/request_repeater.h | 4 +- selfdrive/ui/qt/sidebar.h | 2 +- selfdrive/ui/qt/widgets/cameraview.h | 2 +- selfdrive/ui/qt/widgets/ssh_keys.h | 2 +- selfdrive/ui/qt/widgets/wifi.h | 2 +- selfdrive/ui/sunnypilot/SConscript | 88 +- .../sunnypilot/qt/{sp_priv_api.cc => api.cc} | 4 +- .../ui/sunnypilot/qt/{sp_priv_api.h => api.h} | 2 +- ...{sp_priv_json_fetcher.h => json_fetcher.h} | 0 .../qt/{sp_priv_home.cc => home.cc} | 2 +- .../sunnypilot/qt/{sp_priv_home.h => home.h} | 6 +- .../qt/maps/{sp_priv_map.cc => map.cc} | 4 +- .../qt/maps/{sp_priv_map.h => map.h} | 2 +- .../{sp_priv_map_helpers.h => map_helpers.h} | 0 .../{sp_priv_networking.cc => networking.cc} | 4 +- .../{sp_priv_networking.h => networking.h} | 2 +- .../{sp_priv_role_model.h => role_model.h} | 0 ...nsor_role_model.h => sponsor_role_model.h} | 0 .../{sp_priv_user_model.h => user_model.h} | 0 ...vice_service.cc => base_device_service.cc} | 6 +- ...device_service.h => base_device_service.h} | 2 +- ...p_priv_role_service.cc => role_service.cc} | 2 +- ...{sp_priv_role_service.h => role_service.h} | 4 +- ...p_priv_user_service.cc => user_service.cc} | 2 +- ...{sp_priv_user_service.h => user_service.h} | 4 +- ...unnylink_client.cc => sunnylink_client.cc} | 4 +- ..._sunnylink_client.h => sunnylink_client.h} | 4 +- ...p_priv_device_panel.cc => device_panel.cc} | 2 +- ...{sp_priv_device_panel.h => device_panel.h} | 2 +- ...isplay_settings.cc => display_settings.cc} | 2 +- ..._display_settings.h => display_settings.h} | 2 +- ...ing_settings.cc => monitoring_settings.cc} | 2 +- ...oring_settings.h => monitoring_settings.h} | 4 +- .../{sp_priv_onboarding.cc => onboarding.cc} | 2 +- .../{sp_priv_onboarding.h => onboarding.h} | 0 ...ocations_fetcher.h => locations_fetcher.h} | 2 +- ...iv_models_fetcher.cc => models_fetcher.cc} | 2 +- ...priv_models_fetcher.h => models_fetcher.h} | 6 +- ...p_priv_osm_settings.cc => osm_settings.cc} | 2 +- ...{sp_priv_osm_settings.h => osm_settings.h} | 8 +- .../{sp_priv_settings.cc => settings.cc} | 8 +- .../{sp_priv_settings.h => settings.h} | 0 ...tware_settings.cc => software_settings.cc} | 2 +- ...oftware_settings.h => software_settings.h} | 4 +- ...link_settings.cc => sunnylink_settings.cc} | 4 +- ...nylink_settings.h => sunnylink_settings.h} | 8 +- ...settings.cc => custom_offsets_settings.cc} | 2 +- ...s_settings.h => custom_offsets_settings.h} | 4 +- ...ge_settings.cc => lane_change_settings.cc} | 2 +- ...ange_settings.h => lane_change_settings.h} | 6 +- ...priv_mads_settings.cc => mads_settings.cc} | 2 +- ...p_priv_mads_settings.h => mads_settings.h} | 6 +- ...ngs.cc => speed_limit_control_settings.cc} | 2 +- ...tings.h => speed_limit_control_settings.h} | 6 +- ...ings.cc => speed_limit_policy_settings.cc} | 2 +- ...ttings.h => speed_limit_policy_settings.h} | 6 +- ...ngs.cc => speed_limit_warning_settings.cc} | 2 +- ...tings.h => speed_limit_warning_settings.h} | 6 +- ...lot_settings.cc => sunnypilot_settings.cc} | 2 +- ...pilot_settings.h => sunnypilot_settings.h} | 18 +- ...iv_trips_settings.cc => trips_settings.cc} | 2 +- ...priv_trips_settings.h => trips_settings.h} | 4 +- ...ehicle_settings.cc => vehicle_settings.cc} | 4 +- ..._vehicle_settings.h => vehicle_settings.h} | 6 +- ...isuals_settings.cc => visuals_settings.cc} | 2 +- ..._visuals_settings.h => visuals_settings.h} | 4 +- ...p_priv_offroad_home.cc => offroad_home.cc} | 4 +- ...{sp_priv_offroad_home.h => offroad_home.h} | 0 ...nnotated_camera.cc => annotated_camera.cc} | 4 +- ..._annotated_camera.h => annotated_camera.h} | 4 +- .../onroad/{sp_priv_buttons.cc => buttons.cc} | 2 +- .../onroad/{sp_priv_buttons.h => buttons.h} | 2 +- ...p_priv_developer_ui.cc => developer_ui.cc} | 2 +- ...{sp_priv_developer_ui.h => developer_ui.h} | 2 +- .../{sp_priv_ui_elements.h => ui_elements.h} | 0 ...{sp_priv_onroad_home.cc => onroad_home.cc} | 6 +- .../{sp_priv_onroad_home.h => onroad_home.h} | 2 +- ..._onroad_settings.cc => onroad_settings.cc} | 6 +- ...iv_onroad_settings.h => onroad_settings.h} | 4 +- ...ings_panel.cc => onroad_settings_panel.cc} | 6 +- ...ttings_panel.h => onroad_settings_panel.h} | 0 ...equest_repeater.cc => request_repeater.cc} | 2 +- ..._request_repeater.h => request_repeater.h} | 4 +- .../qt/{sp_priv_sidebar.cc => sidebar.cc} | 4 +- .../qt/{sp_priv_sidebar.h => sidebar.h} | 2 +- .../qt/{sp_priv_text.cc => text.cc} | 0 .../qt/{sp_priv_ui_scene.h => ui_scene.h} | 0 .../qt/{sp_priv_util.cc => util.cc} | 2 +- .../sunnypilot/qt/{sp_priv_util.h => util.h} | 0 .../{sp_priv_controls.cc => controls.cc} | 4 +- .../{sp_priv_controls.h => controls.h} | 2 +- ...{sp_priv_drive_stats.cc => drive_stats.cc} | 2 +- .../{sp_priv_drive_stats.h => drive_stats.h} | 0 .../{sp_priv_scrollview.cc => scrollview.cc} | 2 +- .../{sp_priv_scrollview.h => scrollview.h} | 0 .../widgets/{sp_priv_toggle.cc => toggle.cc} | 2 +- .../qt/widgets/{sp_priv_toggle.h => toggle.h} | 0 .../qt/{sp_priv_window.cc => window.cc} | 2 +- .../qt/{sp_priv_window.h => window.h} | 6 +- .../ui/sunnypilot/sp_priv_sunnypilot_main.h | 20 - selfdrive/ui/sunnypilot/sunnypilot_main.h | 20 + .../ui/sunnypilot/{sp_priv_ui.cc => ui.cc} | 4 +- .../ui/sunnypilot/{sp_priv_ui.h => ui.h} | 8 +- selfdrive/ui/translations/main_ar.ts | 2127 +++++++++++++++- selfdrive/ui/translations/main_de.ts | 2127 +++++++++++++++- selfdrive/ui/translations/main_es.ts | 2209 ++++++++++++++++- selfdrive/ui/translations/main_fr.ts | 2127 +++++++++++++++- selfdrive/ui/translations/main_ja.ts | 2127 +++++++++++++++- selfdrive/ui/translations/main_ko.ts | 2127 +++++++++++++++- selfdrive/ui/translations/main_pt-BR.ts | 2127 +++++++++++++++- selfdrive/ui/translations/main_th.ts | 2127 +++++++++++++++- selfdrive/ui/translations/main_tr.ts | 2123 +++++++++++++++- selfdrive/ui/translations/main_zh-CHS.ts | 2127 +++++++++++++++- selfdrive/ui/translations/main_zh-CHT.ts | 2127 +++++++++++++++- selfdrive/ui/ui.cc | 2 +- selfdrive/ui/ui.h | 2 +- tools/cabana/detailwidget.h | 2 +- 139 files changed, 23610 insertions(+), 367 deletions(-) rename selfdrive/ui/sunnypilot/qt/{sp_priv_api.cc => api.cc} (98%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_api.h => api.h} (95%) rename selfdrive/ui/sunnypilot/qt/common/{sp_priv_json_fetcher.h => json_fetcher.h} (100%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_home.cc => home.cc} (97%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_home.h => home.h} (81%) rename selfdrive/ui/sunnypilot/qt/maps/{sp_priv_map.cc => map.cc} (96%) rename selfdrive/ui/sunnypilot/qt/maps/{sp_priv_map.h => map.h} (91%) rename selfdrive/ui/sunnypilot/qt/maps/{sp_priv_map_helpers.h => map_helpers.h} (100%) rename selfdrive/ui/sunnypilot/qt/network/{sp_priv_networking.cc => networking.cc} (99%) rename selfdrive/ui/sunnypilot/qt/network/{sp_priv_networking.h => networking.h} (97%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/models/{sp_priv_role_model.h => role_model.h} (100%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/models/{sp_priv_sponsor_role_model.h => sponsor_role_model.h} (100%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/models/{sp_priv_user_model.h => user_model.h} (100%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{sp_priv_base_device_service.cc => base_device_service.cc} (90%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{sp_priv_base_device_service.h => base_device_service.h} (91%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{sp_priv_role_service.cc => role_service.cc} (96%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{sp_priv_role_service.h => role_service.h} (81%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{sp_priv_user_service.cc => user_service.cc} (96%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/services/{sp_priv_user_service.h => user_service.h} (81%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/{sp_priv_sunnylink_client.cc => sunnylink_client.cc} (67%) rename selfdrive/ui/sunnypilot/qt/network/sunnylink/{sp_priv_sunnylink_client.h => sunnylink_client.h} (87%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_device_panel.cc => device_panel.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_device_panel.h => device_panel.h} (86%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_display_settings.cc => display_settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_display_settings.h => display_settings.h} (93%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_monitoring_settings.cc => monitoring_settings.cc} (91%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_monitoring_settings.h => monitoring_settings.h} (73%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_onboarding.cc => onboarding.cc} (97%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_onboarding.h => onboarding.h} (100%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/osm/{sp_priv_locations_fetcher.h => locations_fetcher.h} (97%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/osm/{sp_priv_models_fetcher.cc => models_fetcher.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/osm/{sp_priv_models_fetcher.h => models_fetcher.h} (96%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_osm_settings.cc => osm_settings.cc} (99%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_osm_settings.h => osm_settings.h} (96%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_settings.cc => settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_settings.h => settings.h} (100%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_software_settings.cc => software_settings.cc} (99%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_software_settings.h => software_settings.h} (96%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_sunnylink_settings.cc => sunnylink_settings.cc} (99%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_sunnylink_settings.h => sunnylink_settings.h} (89%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_custom_offsets_settings.cc => custom_offsets_settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_custom_offsets_settings.h => custom_offsets_settings.h} (82%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_lane_change_settings.cc => lane_change_settings.cc} (99%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_lane_change_settings.h => lane_change_settings.h} (82%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_mads_settings.cc => mads_settings.cc} (99%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_mads_settings.h => mads_settings.h} (68%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_speed_limit_control_settings.cc => speed_limit_control_settings.cc} (99%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_speed_limit_control_settings.h => speed_limit_control_settings.h} (91%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_speed_limit_policy_settings.cc => speed_limit_policy_settings.cc} (97%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_speed_limit_policy_settings.h => speed_limit_policy_settings.h} (92%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_speed_limit_warning_settings.cc => speed_limit_warning_settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/{sp_priv_speed_limit_warning_settings.h => speed_limit_warning_settings.h} (91%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_sunnypilot_settings.cc => sunnypilot_settings.cc} (99%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_sunnypilot_settings.h => sunnypilot_settings.h} (88%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_trips_settings.cc => trips_settings.cc} (90%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_trips_settings.h => trips_settings.h} (68%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_vehicle_settings.cc => vehicle_settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_vehicle_settings.h => vehicle_settings.h} (89%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_visuals_settings.cc => visuals_settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/offroad/settings/{sp_priv_visuals_settings.h => visuals_settings.h} (77%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_offroad_home.cc => offroad_home.cc} (89%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_offroad_home.h => offroad_home.h} (100%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_annotated_camera.cc => annotated_camera.cc} (99%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_annotated_camera.h => annotated_camera.h} (97%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_buttons.cc => buttons.cc} (97%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_buttons.h => buttons.h} (94%) rename selfdrive/ui/sunnypilot/qt/onroad/developer_ui/{sp_priv_developer_ui.cc => developer_ui.cc} (98%) rename selfdrive/ui/sunnypilot/qt/onroad/developer_ui/{sp_priv_developer_ui.h => developer_ui.h} (93%) rename selfdrive/ui/sunnypilot/qt/onroad/developer_ui/{sp_priv_ui_elements.h => ui_elements.h} (100%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_onroad_home.cc => onroad_home.cc} (95%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_onroad_home.h => onroad_home.h} (95%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_onroad_settings.cc => onroad_settings.cc} (98%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_onroad_settings.h => onroad_settings.h} (92%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_onroad_settings_panel.cc => onroad_settings_panel.cc} (75%) rename selfdrive/ui/sunnypilot/qt/onroad/{sp_priv_onroad_settings_panel.h => onroad_settings_panel.h} (100%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_request_repeater.cc => request_repeater.cc} (95%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_request_repeater.h => request_repeater.h} (85%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_sidebar.cc => sidebar.cc} (97%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_sidebar.h => sidebar.h} (93%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_text.cc => text.cc} (100%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_ui_scene.h => ui_scene.h} (100%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_util.cc => util.cc} (97%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_util.h => util.h} (100%) rename selfdrive/ui/sunnypilot/qt/widgets/{sp_priv_controls.cc => controls.cc} (98%) rename selfdrive/ui/sunnypilot/qt/widgets/{sp_priv_controls.h => controls.h} (99%) rename selfdrive/ui/sunnypilot/qt/widgets/{sp_priv_drive_stats.cc => drive_stats.cc} (98%) rename selfdrive/ui/sunnypilot/qt/widgets/{sp_priv_drive_stats.h => drive_stats.h} (100%) rename selfdrive/ui/sunnypilot/qt/widgets/{sp_priv_scrollview.cc => scrollview.cc} (82%) rename selfdrive/ui/sunnypilot/qt/widgets/{sp_priv_scrollview.h => scrollview.h} (100%) rename selfdrive/ui/sunnypilot/qt/widgets/{sp_priv_toggle.cc => toggle.cc} (90%) rename selfdrive/ui/sunnypilot/qt/widgets/{sp_priv_toggle.h => toggle.h} (100%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_window.cc => window.cc} (96%) rename selfdrive/ui/sunnypilot/qt/{sp_priv_window.h => window.h} (64%) delete mode 100644 selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h create mode 100644 selfdrive/ui/sunnypilot/sunnypilot_main.h rename selfdrive/ui/sunnypilot/{sp_priv_ui.cc => ui.cc} (99%) rename selfdrive/ui/sunnypilot/{sp_priv_ui.h => ui.h} (95%) diff --git a/selfdrive/navd/main.cc b/selfdrive/navd/main.cc index 077fd6a0e5..da188bb26b 100644 --- a/selfdrive/navd/main.cc +++ b/selfdrive/navd/main.cc @@ -7,7 +7,7 @@ #include "common/util.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" #else #include "selfdrive/ui/qt/maps/map_helpers.h" #endif diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index ecbff25768..f24e6bb876 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -9,7 +9,7 @@ #include "common/timing.h" #include "common/swaglog.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" #else #include "selfdrive/ui/qt/maps/map_helpers.h" #endif diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 242a4c6daa..253173311c 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -81,7 +81,7 @@ asset_obj = qt_env.Object("assets", assets) qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) # spinner and text window -text_cc_path = "qt/text.cc" if not GetOption('sunnypilot') else "sunnypilot/qt/sp_priv_text.cc" +text_cc_path = "qt/text.cc" if not GetOption('sunnypilot') else "sunnypilot/qt/text.cc" qt_env.Program("_text", [text_cc_path], LIBS=qt_libs) qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc index c47b6d7920..baf09c79f1 100644 --- a/selfdrive/ui/main.cc +++ b/selfdrive/ui/main.cc @@ -8,7 +8,7 @@ #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/sp_priv_window.h" +#include "selfdrive/ui/sunnypilot/qt/window.h" #define MainWindow MainWindowSP #else #include "selfdrive/ui/qt/window.h" diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h index 68495aaf73..ac3cd2ce1c 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -6,7 +6,7 @@ #include "common/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index c64592d49a..a56a3e0bad 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -16,9 +16,9 @@ #include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h" -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/offroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h" #define OnroadWindow OnroadWindowSP #define OffroadHome OffroadHomeSP #else diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 48be94bb9d..f9059c6f08 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -8,8 +8,8 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" #else #include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/ui.h" diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 86c906b0e8..fd4218bfc1 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -21,7 +21,7 @@ #include "common/params.h" #include "common/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc index 335a44fdad..e153884576 100644 --- a/selfdrive/ui/qt/maps/map_eta.cc +++ b/selfdrive/ui/qt/maps/map_eta.cc @@ -4,8 +4,8 @@ #include #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" #else #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/maps/map_helpers.h" diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index a62c1cbfdf..6b7b05e786 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -1,5 +1,5 @@ #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" #else #include "selfdrive/ui/qt/maps/map_helpers.h" #endif diff --git a/selfdrive/ui/qt/maps/map_instructions.cc b/selfdrive/ui/qt/maps/map_instructions.cc index 6c3d797080..b631548fe7 100644 --- a/selfdrive/ui/qt/maps/map_instructions.cc +++ b/selfdrive/ui/qt/maps/map_instructions.cc @@ -4,8 +4,8 @@ #include #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" #else #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/maps/map_helpers.h" diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index 66ad95e185..4f92c98482 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -6,8 +6,8 @@ #include "selfdrive/ui/qt/maps/map_settings.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map.h" #else #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/maps/map.h" diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index 123612b128..40b0d35a6a 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -14,7 +14,7 @@ #include "common/params.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #else #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index a6921f1c90..4f3d333ffd 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -7,7 +7,7 @@ #include #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index c7111675da..5831d66dc9 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -7,7 +7,7 @@ #include "selfdrive/ui/qt/widgets/ssh_keys.h" #include "selfdrive/ui/qt/widgets/toggle.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #define LabelControl LabelControlSP #define ElidedLabel ElidedLabelSP #else diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc index 7cfa17f774..b12207fab5 100644 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -3,7 +3,7 @@ #include #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index e147444e7f..fa43623c67 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -18,7 +18,7 @@ #include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/widgets/ssh_keys.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h" +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" #endif TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index dcc2f132c4..988c22b16d 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -14,8 +14,8 @@ #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #define ListWidget ListWidgetSP #define ParamControl ParamControlSP #define ButtonControl ButtonControlSP diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 55e9c176b1..db3bd9ecb2 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -12,7 +12,7 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #else #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/qt/offroad_home.h b/selfdrive/ui/qt/offroad_home.h index 92e0307bc1..6b785e0cd0 100644 --- a/selfdrive/ui/qt/offroad_home.h +++ b/selfdrive/ui/qt/offroad_home.h @@ -16,9 +16,9 @@ #include "selfdrive/ui/ui.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/sidebar.h" #define OnroadWindow OnroadWindowSP #define OffroadHomeImp OffroadHomeSP #define LayoutWidget LayoutWidgetSP diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h index 791b87506b..6d30c1ac91 100644 --- a/selfdrive/ui/qt/onroad/buttons.h +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -3,7 +3,7 @@ #include #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h index 3ca4494a75..cedc050acb 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.h +++ b/selfdrive/ui/qt/onroad/onroad_home.h @@ -4,7 +4,7 @@ #include "selfdrive/ui/qt/onroad/alerts.h" #if SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h" #define AnnotatedCameraWidget AnnotatedCameraWidgetSP #define UIState UIStateSP #else diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h index 121887b35e..e1d302e304 100644 --- a/selfdrive/ui/qt/request_repeater.h +++ b/selfdrive/ui/qt/request_repeater.h @@ -3,8 +3,8 @@ #include "common/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_api.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/api.h" #else #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/api.h" diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index a427d8210b..eecc439abf 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -7,7 +7,7 @@ #include "cereal/messaging/messaging.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 09a1479bf6..62999b2aa4 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -25,7 +25,7 @@ #include "msgq/visionipc/visionipc_client.h" #include "system/camerad/cameras/camera_common.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h index 944d6890ea..80e0f9a729 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.h +++ b/selfdrive/ui/qt/widgets/ssh_keys.h @@ -4,7 +4,7 @@ #include "system/hardware/hw.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #define ToggleControl ToggleControlSP #define ButtonControl ButtonControlSP #else diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h index bdcd9aa2ca..3010abc6ff 100644 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -5,7 +5,7 @@ #include #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #else #include "selfdrive/ui/ui.h" #endif diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 951973583c..945c9ccdb2 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -1,60 +1,60 @@ widgets_src = [ - "sunnypilot/sp_priv_ui.cc", - "sunnypilot/qt/sp_priv_window.cc", - "sunnypilot/qt/sp_priv_request_repeater.cc", - "sunnypilot/qt/network/sp_priv_networking.cc", - "sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc", - "sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc", - "sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc", - "sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc", - "sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc", - "sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc", - "sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc", - "sunnypilot/qt/widgets/sp_priv_drive_stats.cc", - "sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc", - "sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc", - "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc", - "sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc", - "sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc", - "sunnypilot/qt/widgets/sp_priv_controls.cc", - "sunnypilot/qt/widgets/sp_priv_scrollview.cc", - "sunnypilot/qt/widgets/sp_priv_toggle.cc" + "sunnypilot/ui.cc", + "sunnypilot/qt/window.cc", + "sunnypilot/qt/request_repeater.cc", + "sunnypilot/qt/network/networking.cc", + "sunnypilot/qt/offroad/settings/display_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot_settings.cc", + "sunnypilot/qt/offroad/settings/vehicle_settings.cc", + "sunnypilot/qt/offroad/settings/visuals_settings.cc", + "sunnypilot/qt/offroad/settings/trips_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc", + "sunnypilot/qt/offroad/settings/monitoring_settings.cc", + "sunnypilot/qt/offroad/settings/osm_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc", + "sunnypilot/qt/widgets/drive_stats.cc", + "sunnypilot/qt/offroad/settings/software_settings.cc", + "sunnypilot/qt/offroad/settings/osm/models_fetcher.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc", + "sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc", + "sunnypilot/qt/offroad/settings/sunnylink_settings.cc", + "sunnypilot/qt/widgets/controls.cc", + "sunnypilot/qt/widgets/scrollview.cc", + "sunnypilot/qt/widgets/toggle.cc" ] sp_maps_widgets_src = [ - "sunnypilot/qt/maps/sp_priv_map.cc" + "sunnypilot/qt/maps/map.cc" ] sp_qt_util = [ - # "#selfdrive/ui/sunnypilot/qt/sp_priv_api.cc", - "#selfdrive/ui/sunnypilot/qt/sp_priv_util.cc", + # "#selfdrive/ui/sunnypilot/qt/api.cc", + "#selfdrive/ui/sunnypilot/qt/util.cc", ] network_src = [ - "sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc", - "sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc", - "sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc", - "sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc" + "sunnypilot/qt/network/sunnylink/sunnylink_client.cc", + "sunnypilot/qt/network/sunnylink/services/base_device_service.cc", + "sunnypilot/qt/network/sunnylink/services/role_service.cc", + "sunnypilot/qt/network/sunnylink/services/user_service.cc" ] qt_src = [ - "sunnypilot/qt/sp_priv_api.cc", - "sunnypilot/qt/sp_priv_home.cc", - "sunnypilot/qt/sp_priv_offroad_home.cc", - "sunnypilot/qt/sp_priv_sidebar.cc", - "sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc", - "sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc", - "sunnypilot/qt/offroad/settings/sp_priv_settings.cc", - "sunnypilot/qt/onroad/sp_priv_buttons.cc", - "sunnypilot/qt/onroad/sp_priv_onroad_home.cc", - "sunnypilot/qt/onroad/sp_priv_onroad_settings.cc", - "sunnypilot/qt/onroad/sp_priv_annotated_camera.cc", - "sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc", - "sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc", + "sunnypilot/qt/api.cc", + "sunnypilot/qt/home.cc", + "sunnypilot/qt/offroad_home.cc", + "sunnypilot/qt/sidebar.cc", + "sunnypilot/qt/offroad/settings/onboarding.cc", + "sunnypilot/qt/offroad/settings/device_panel.cc", + "sunnypilot/qt/offroad/settings/settings.cc", + "sunnypilot/qt/onroad/buttons.cc", + "sunnypilot/qt/onroad/onroad_home.cc", + "sunnypilot/qt/onroad/onroad_settings.cc", + "sunnypilot/qt/onroad/annotated_camera.cc", + "sunnypilot/qt/onroad/onroad_settings_panel.cc", + "sunnypilot/qt/onroad/developer_ui/developer_ui.cc", ] sp_widgets_src = widgets_src + network_src diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_api.cc b/selfdrive/ui/sunnypilot/qt/api.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/sp_priv_api.cc rename to selfdrive/ui/sunnypilot/qt/api.cc index 02f5d9c1a3..e9498bb37b 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_api.cc +++ b/selfdrive/ui/sunnypilot/qt/api.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/sp_priv_api.h" +#include "selfdrive/ui/sunnypilot/qt/api.h" #include #include @@ -11,7 +11,7 @@ #include #include -#include "sp_priv_util.h" +#include "util.h" #include "common/util.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_api.h b/selfdrive/ui/sunnypilot/qt/api.h similarity index 95% rename from selfdrive/ui/sunnypilot/qt/sp_priv_api.h rename to selfdrive/ui/sunnypilot/qt/api.h index a5f1ae308e..47c3b04d17 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_api.h +++ b/selfdrive/ui/sunnypilot/qt/api.h @@ -6,7 +6,7 @@ #include #include "selfdrive/ui/qt/api.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" #include "common/util.h" namespace SunnylinkApi { diff --git a/selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h b/selfdrive/ui/sunnypilot/qt/common/json_fetcher.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h rename to selfdrive/ui/sunnypilot/qt/common/json_fetcher.h diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc b/selfdrive/ui/sunnypilot/qt/home.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/sp_priv_home.cc rename to selfdrive/ui/sunnypilot/qt/home.cc index 480c25f77b..846ecaab99 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.cc +++ b/selfdrive/ui/sunnypilot/qt/home.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" +#include "selfdrive/ui/sunnypilot/qt/home.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h b/selfdrive/ui/sunnypilot/qt/home.h similarity index 81% rename from selfdrive/ui/sunnypilot/qt/sp_priv_home.h rename to selfdrive/ui/sunnypilot/qt/home.h index cc01a05bd5..f55ae04c3a 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_home.h +++ b/selfdrive/ui/sunnypilot/qt/home.h @@ -11,12 +11,12 @@ #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/qt/home.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/sidebar.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h" #define OnroadWindow OnroadWindowSP #else #include "selfdrive/ui/qt/sidebar.h" diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc b/selfdrive/ui/sunnypilot/qt/maps/map.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc rename to selfdrive/ui/sunnypilot/qt/maps/map.cc index 926151cae7..7cb4a77539 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.cc +++ b/selfdrive/ui/sunnypilot/qt/maps/map.cc @@ -1,7 +1,7 @@ -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map.h" #include "common/swaglog.h" -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" MapWindowSP::MapWindowSP(const QMapLibre::Settings &settings) : MapWindow(settings) { QObject::disconnect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState); diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h b/selfdrive/ui/sunnypilot/qt/maps/map.h similarity index 91% rename from selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h rename to selfdrive/ui/sunnypilot/qt/maps/map.h index 6c97d23756..534353f488 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map.h +++ b/selfdrive/ui/sunnypilot/qt/maps/map.h @@ -1,7 +1,7 @@ #pragma once #include "selfdrive/ui/qt/maps/map.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h b/selfdrive/ui/sunnypilot/qt/maps/map_helpers.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h rename to selfdrive/ui/sunnypilot/qt/maps/map_helpers.h diff --git a/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.cc b/selfdrive/ui/sunnypilot/qt/network/networking.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.cc rename to selfdrive/ui/sunnypilot/qt/network/networking.cc index f51ffeb2bc..0f653df426 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.cc +++ b/selfdrive/ui/sunnypilot/qt/network/networking.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h" +#include "selfdrive/ui/sunnypilot/qt/network/networking.h" #include @@ -7,7 +7,7 @@ #include #include "selfdrive/ui/qt/widgets/ssh_keys.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/scrollview.h" diff --git a/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h b/selfdrive/ui/sunnypilot/qt/network/networking.h similarity index 97% rename from selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h rename to selfdrive/ui/sunnypilot/qt/network/networking.h index 71f9d57895..97f7c41fd0 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h +++ b/selfdrive/ui/sunnypilot/qt/network/networking.h @@ -4,7 +4,7 @@ #include #include "selfdrive/ui/qt/network/wifi_manager.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" class WifiItemSP : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_sponsor_role_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc similarity index 90% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc index 91914f8827..209951f1ac 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc @@ -1,10 +1,10 @@ -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink//services/sp_priv_base_device_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink//services/base_device_service.h" -#include +#include "selfdrive/ui/sunnypilot/qt/request_repeater.h" #include "common/swaglog.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h" BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent), initial_request(nullptr), repeater(nullptr) { param_watcher = new ParamWatcher(this); diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h similarity index 91% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h index 90abfd294d..84700af446 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h" +#include "selfdrive/ui/sunnypilot/qt/request_repeater.h" #include "selfdrive/ui/qt/util.h" class BaseDeviceService : public QObject { diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc index d9acba4a40..44340b1847 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h similarity index 81% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h index 9c7d951ef7..dadad7d52c 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h @@ -2,8 +2,8 @@ #include -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" class RoleService : public BaseDeviceService { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc index f230e59e7c..cd008cf700 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h similarity index 81% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h index dba8dd444c..5cc3c4d44d 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h @@ -2,8 +2,8 @@ #include -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_base_device_service.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_user_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h" class UserService : public BaseDeviceService { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc similarity index 67% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc index 98bc734d95..b56d9bd582 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.cc +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc @@ -1,5 +1,5 @@ -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" SunnylinkClient::SunnylinkClient(QObject* parent) : QObject(parent) { role_service = new RoleService(parent); diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h similarity index 87% rename from selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h rename to selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h index f63a0f44dc..f331162e5d 100644 --- a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h @@ -2,8 +2,8 @@ #include -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_role_service.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/sp_priv_user_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h" class SunnylinkClient : public QObject { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc index 161f082985..c2686d6ccf 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h similarity index 86% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h index 6543b00636..5980e4de20 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" class DevicePanelSP : public DevicePanel { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc index 20f4e7a274..b028cf5d02 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h similarity index 93% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h index a076f0039a..7058bdcdea 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h @@ -3,7 +3,7 @@ #include #include -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" class OnroadScreenOff : public OptionControlSP { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.cc similarity index 91% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.cc index d8c1458247..2406c857b8 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h" MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) { main_layout = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h similarity index 73% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h index 3d30a4f830..c1471e7a0a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h @@ -5,8 +5,8 @@ #include #include -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" class MonitoringPanel : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.cc index d30b9cb37b..de55d68528 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_onboarding.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/onboarding.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/locations_fetcher.h similarity index 97% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/locations_fetcher.h index 38e34544a7..834adac5ce 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/locations_fetcher.h @@ -8,7 +8,7 @@ #include #include -#include "selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/common/json_fetcher.h" static const std::tuple defaultLocation = std::make_tuple("== None ==", ""); // New class LocationsFetcher that handles web requests and JSON parsing diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.cc index 296ea6cf33..759a412eab 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h index 628ee68687..d84057eec1 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h @@ -11,11 +11,11 @@ #include "common/swaglog.h" #include "common/util.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/qt/common/sp_priv_json_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/common/json_fetcher.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #else #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.cc index 4fe227e831..91b1d39824 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h index a83fe9a619..f54372181d 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h @@ -13,11 +13,11 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/network/wifi_manager.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_locations_fetcher.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/locations_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "system/hardware/hw.h" constexpr int FAST_REFRESH_INTERVAL = 1000; // ms diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc index 0c92b86c04..ee42547566 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc @@ -1,11 +1,11 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" #include #include -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_device_panel.h" -#include "selfdrive/ui/sunnypilot/qt/network/sp_priv_networking.h" -#include "selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h" +#include "selfdrive/ui/sunnypilot/qt/network/networking.h" +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { // param, title, desc, icon diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.cc index eb2d0e2b43..3541806c0e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h similarity index 96% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h index ab573ea5cf..4621c47f9b 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h @@ -1,9 +1,9 @@ #pragma once #include "common/model.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/qt/offroad/settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/sp_priv_models_fetcher.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm/models_fetcher.h" class SoftwarePanelSP final : public SoftwarePanel { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.cc index bbe8d00ae4..01dc78a3a6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.cc @@ -1,5 +1,5 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_api.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h" +#include "selfdrive/ui/sunnypilot/qt/api.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h similarity index 89% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h index a09b5ae488..9ffd08ff74 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h @@ -3,11 +3,11 @@ #include #include -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sp_priv_sunnylink_client.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://stg.api.sunnypilot.ai").c_str(); // sponsor QR code diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc index 28c9690e8f..ec50b10b24 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h" CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h similarity index 82% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h index 094b69dc22..d99f648711 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h @@ -3,8 +3,8 @@ #include #include -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" class CameraOffset : public OptionControlSP { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc index 756e5a8192..54bb94e4a5 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h similarity index 82% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h index e135e50a1e..363e22fbd5 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h @@ -3,9 +3,9 @@ #include #include -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" class AutoLaneChangeTimer : public OptionControlSP { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc index 75852b9dc4..c40f09cded 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h similarity index 68% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h index 86ba749db9..82c9cfe431 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h @@ -3,9 +3,9 @@ #include #include -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" class MadsSettings : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc index 0c0a90aeb2..6e0f8e1ea0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h similarity index 91% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h index 59237e0a1c..c248a7487e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h @@ -4,9 +4,9 @@ #include #include -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" class SpeedLimitValueOffset : public OptionControlSP { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc index e3dc87a0c7..c0f7655f24 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h" SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h similarity index 92% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h index 5124f3c109..d3e8f88b29 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h @@ -3,9 +3,9 @@ #include #include -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" class SpeedLimitPolicySettings : public QWidget { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc index c35876a18e..f674996269 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.cc @@ -1,4 +1,4 @@ -#include "../../settings/sunnypilot/sp_priv_speed_limit_warning_settings.h" +#include "../../settings/sunnypilot/speed_limit_warning_settings.h" SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.h similarity index 91% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.h index 732e623d31..6992c91a3e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.h @@ -4,9 +4,9 @@ #include #include -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" class SpeedLimitWarningValueOffset : public OptionControlSP { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc index ef2f9d56c8..a0c3931e71 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h similarity index 88% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h index b669932cfb..861e8e06e0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h @@ -4,16 +4,16 @@ #include #include "common/model.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_custom_offsets_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_lane_change_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_mads_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_control_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_warning_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/custom_offsets_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/lane_change_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_control_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_warning_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" class TorqueFriction : public OptionControlSP { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.cc similarity index 90% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.cc index 413ea5a016..3afdf325a5 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h" TripsPanel::TripsPanel(QWidget* parent) : QFrame(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h similarity index 68% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h index 91044f2b4e..12dd1f3083 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h @@ -1,11 +1,11 @@ #pragma once #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #else #include "selfdrive/ui/qt/widgets/controls.h" #endif -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" class TripsPanel : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc index 3c50b4bd37..9838e7a3cb 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc @@ -1,5 +1,5 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" VehiclePanel::VehiclePanel(QWidget *parent) : QWidget(parent) { main_layout = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h similarity index 89% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h index 290fc9364c..870c6e9708 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h @@ -3,9 +3,9 @@ #include #include "common/watchdog.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" class VehiclePanel : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc rename to selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.cc index 0d2e843a27..607ce3cfa5 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h similarity index 77% rename from selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h rename to selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h index 9b386b6b35..0a79c64b6f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h @@ -3,8 +3,8 @@ #include #include -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" class VisualsPanel : public ListWidgetSP { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc b/selfdrive/ui/sunnypilot/qt/offroad_home.cc similarity index 89% rename from selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc rename to selfdrive/ui/sunnypilot/qt/offroad_home.cc index ea643e36d8..3d9a28ebd6 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad_home.cc @@ -1,8 +1,8 @@ -#include "selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/offroad_home.h" #include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/widgets/prime.h" #include -#include "selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h" +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" #ifdef ENABLE_MAPS #define LEFT_WIDGET MapSettings diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h b/selfdrive/ui/sunnypilot/qt/offroad_home.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/sp_priv_offroad_home.h rename to selfdrive/ui/sunnypilot/qt/offroad_home.h diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc similarity index 99% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc rename to selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc index c95e1009b1..5149d0c6ac 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h" #include #include @@ -8,7 +8,7 @@ #include "common/swaglog.h" #include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" static std::pair getFeatureStatus(int value, QStringList text_list, QStringList color_list, bool condition, QString off_text) { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h similarity index 97% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h rename to selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h index 341d684af1..b92e36f9ac 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h @@ -6,8 +6,8 @@ #include "selfdrive/ui/qt/onroad/buttons.h" #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h" -#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/buttons.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h" const int subsign_img_size = 35; const int blinker_size = 120; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc b/selfdrive/ui/sunnypilot/qt/onroad/buttons.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc rename to selfdrive/ui/sunnypilot/qt/onroad/buttons.cc index 3ec138425d..bed7e12ac7 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/buttons.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/buttons.h" #include "selfdrive/ui/qt/util.h" diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h b/selfdrive/ui/sunnypilot/qt/onroad/buttons.h similarity index 94% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h rename to selfdrive/ui/sunnypilot/qt/onroad/buttons.h index a64eede128..0f6f75ad54 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_buttons.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/buttons.h @@ -3,7 +3,7 @@ #include #include "selfdrive/ui/qt/onroad/buttons.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/ui.h" diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc rename to selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.cc index e251495273..f0c38254a6 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h similarity index 93% rename from selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h rename to selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h index 12f4c1d5dc..9b3d1dbffa 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_developer_ui.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/developer_ui.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_ui_elements.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h" class DeveloperUi { public: diff --git a/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_ui_elements.h b/selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/onroad/developer_ui/sp_priv_ui_elements.h rename to selfdrive/ui/sunnypilot/qt/onroad/developer_ui/ui_elements.h diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc similarity index 95% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc index bde287399d..61ee146592 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc @@ -1,14 +1,14 @@ -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h" #include #include #ifdef ENABLE_MAPS -#include "selfdrive/ui/sunnypilot/qt/maps/sp_priv_map_helpers.h" +#include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/maps/map_panel.h" #endif #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" #endif #include diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h similarity index 95% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h index 746239cf3d..3d20859643 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_home.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h @@ -4,7 +4,7 @@ #include "common/params.h" #include "selfdrive/ui/qt/onroad/alerts.h" -// #include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_annotated_camera.h" +// #include "selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h" class OnroadWindowSP : public OnroadWindow { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc index daf6c07ba6..a57cfc9701 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" #include #include @@ -8,8 +8,8 @@ #include #include "common/util.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/sp_priv_speed_limit_policy_settings.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/speed_limit_policy_settings.h" OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) { setContentsMargins(0, 0, 0, 0); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h similarity index 92% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h index d8df42b96e..f4ca6f65b8 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h @@ -6,10 +6,10 @@ #include #include "common/params.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/qt/util.h" #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #else #include "selfdrive/ui/qt/widgets/controls.h" #endif diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc similarity index 75% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc index ee722a696f..ee3effdbe8 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.cc @@ -1,11 +1,11 @@ -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h" #include #include -#include "selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings.h" +#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_settings.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" OnroadSettingsPanel::OnroadSettingsPanel(QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h b/selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/onroad/sp_priv_onroad_settings_panel.h rename to selfdrive/ui/sunnypilot/qt/onroad/onroad_settings_panel.h diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.cc b/selfdrive/ui/sunnypilot/qt/request_repeater.cc similarity index 95% rename from selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.cc rename to selfdrive/ui/sunnypilot/qt/request_repeater.cc index 6e9252acdd..b69d02509d 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.cc +++ b/selfdrive/ui/sunnypilot/qt/request_repeater.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h" +#include "selfdrive/ui/sunnypilot/qt/request_repeater.h" #include "common/swaglog.h" diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h b/selfdrive/ui/sunnypilot/qt/request_repeater.h similarity index 85% rename from selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h rename to selfdrive/ui/sunnypilot/qt/request_repeater.h index 9243ab4f27..72a3e3d506 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_request_repeater.h +++ b/selfdrive/ui/sunnypilot/qt/request_repeater.h @@ -4,8 +4,8 @@ #include "common/swaglog.h" #include "common/util.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_api.h" +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/api.h" class RequestRepeaterSP : public HttpRequestSP { diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc b/selfdrive/ui/sunnypilot/qt/sidebar.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc rename to selfdrive/ui/sunnypilot/qt/sidebar.cc index e5f924c289..368158c6e3 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.cc +++ b/selfdrive/ui/sunnypilot/qt/sidebar.cc @@ -1,11 +1,11 @@ -#include "selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h" +#include "selfdrive/ui/sunnypilot/qt/sidebar.h" #include #include #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" #include "common/params.h" diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h b/selfdrive/ui/sunnypilot/qt/sidebar.h similarity index 93% rename from selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h rename to selfdrive/ui/sunnypilot/qt/sidebar.h index 46207ff791..d593c18f38 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_sidebar.h +++ b/selfdrive/ui/sunnypilot/qt/sidebar.h @@ -6,7 +6,7 @@ #include #include "selfdrive/ui/qt/sidebar.h" -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" class SidebarSP : public Sidebar { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_text.cc b/selfdrive/ui/sunnypilot/qt/text.cc similarity index 100% rename from selfdrive/ui/sunnypilot/qt/sp_priv_text.cc rename to selfdrive/ui/sunnypilot/qt/text.cc diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h b/selfdrive/ui/sunnypilot/qt/ui_scene.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h rename to selfdrive/ui/sunnypilot/qt/ui_scene.h diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc b/selfdrive/ui/sunnypilot/qt/util.cc similarity index 97% rename from selfdrive/ui/sunnypilot/qt/sp_priv_util.cc rename to selfdrive/ui/sunnypilot/qt/util.cc index 85c141f951..8d8e9ee014 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_util.cc +++ b/selfdrive/ui/sunnypilot/qt/util.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/sp_priv_util.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" #include "selfdrive/ui/qt/util.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_util.h b/selfdrive/ui/sunnypilot/qt/util.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/sp_priv_util.h rename to selfdrive/ui/sunnypilot/qt/util.h diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/controls.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc rename to selfdrive/ui/sunnypilot/qt/widgets/controls.cc index 313a2ba926..6b123c5902 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.cc @@ -1,8 +1,8 @@ -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #include #include -#include +#include "selfdrive/ui/sunnypilot/sunnypilot_main.h" QFrame *horizontal_line(QWidget *parent) { QFrame *line = new QFrame(parent); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h similarity index 99% rename from selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h rename to selfdrive/ui/sunnypilot/qt/widgets/controls.h index 3b68ed5a5f..704efc8bd6 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h @@ -15,7 +15,7 @@ #include "common/params.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/input.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/toggle.h" QFrame *horizontal_line(QWidget *parent = nullptr); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.cc b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc similarity index 98% rename from selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.cc rename to selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc index 58ced84ad0..2ac3f59893 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" #include #include diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.h b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/widgets/sp_priv_drive_stats.h rename to selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.cc b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc similarity index 82% rename from selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.cc rename to selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc index 05d1335c1c..1241f9cdce 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/widgets/sp_priv_scrollview.h rename to selfdrive/ui/sunnypilot/qt/widgets/scrollview.h diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.cc b/selfdrive/ui/sunnypilot/qt/widgets/toggle.cc similarity index 90% rename from selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.cc rename to selfdrive/ui/sunnypilot/qt/widgets/toggle.cc index b6b37e224b..2de76393d6 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/toggle.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/toggle.h" #include diff --git a/selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h b/selfdrive/ui/sunnypilot/qt/widgets/toggle.h similarity index 100% rename from selfdrive/ui/sunnypilot/qt/widgets/sp_priv_toggle.h rename to selfdrive/ui/sunnypilot/qt/widgets/toggle.h diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc b/selfdrive/ui/sunnypilot/qt/window.cc similarity index 96% rename from selfdrive/ui/sunnypilot/qt/sp_priv_window.cc rename to selfdrive/ui/sunnypilot/qt/window.cc index 69a35ed699..fc8d0ce9a0 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_window.cc +++ b/selfdrive/ui/sunnypilot/qt/window.cc @@ -1,4 +1,4 @@ -#include "sp_priv_window.h" +#include "window.h" MainWindowSP::MainWindowSP(QWidget *parent) : MainWindow(parent, new HomeWindowSP(parent), new SettingsWindowSP(parent), new OnboardingWindowSP(parent)) { diff --git a/selfdrive/ui/sunnypilot/qt/sp_priv_window.h b/selfdrive/ui/sunnypilot/qt/window.h similarity index 64% rename from selfdrive/ui/sunnypilot/qt/sp_priv_window.h rename to selfdrive/ui/sunnypilot/qt/window.h index 73a867bde9..fa84fac56f 100644 --- a/selfdrive/ui/sunnypilot/qt/sp_priv_window.h +++ b/selfdrive/ui/sunnypilot/qt/window.h @@ -1,9 +1,9 @@ #pragma once #include "selfdrive/ui/qt/window.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_home.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_settings.h" -#include "offroad/settings/sp_priv_onboarding.h" +#include "selfdrive/ui/sunnypilot/qt/home.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" +#include "offroad/settings/onboarding.h" class MainWindowSP : public MainWindow { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h b/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h deleted file mode 100644 index 5327b0ad86..0000000000 --- a/selfdrive/ui/sunnypilot/sp_priv_sunnypilot_main.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_display_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnypilot_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_vehicle_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_visuals_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_trips_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_monitoring_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_osm_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_software_settings.h" -#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sp_priv_sunnylink_settings.h" - - -inline void ReplaceWidget(QWidget* old_widget, QWidget* new_widget) { - if (old_widget && old_widget->parentWidget() && old_widget->parentWidget()->layout()) { - old_widget->parentWidget()->layout()->replaceWidget(old_widget, new_widget); - old_widget->hide(); - old_widget->deleteLater(); - } -} diff --git a/selfdrive/ui/sunnypilot/sunnypilot_main.h b/selfdrive/ui/sunnypilot/sunnypilot_main.h new file mode 100644 index 0000000000..ca9b509400 --- /dev/null +++ b/selfdrive/ui/sunnypilot/sunnypilot_main.h @@ -0,0 +1,20 @@ +#pragma once + +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/trips_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/monitoring_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/osm_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_settings.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_settings.h" + + +inline void ReplaceWidget(QWidget* old_widget, QWidget* new_widget) { + if (old_widget && old_widget->parentWidget() && old_widget->parentWidget()->layout()) { + old_widget->parentWidget()->layout()->replaceWidget(old_widget, new_widget); + old_widget->hide(); + old_widget->deleteLater(); + } +} diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.cc b/selfdrive/ui/sunnypilot/ui.cc similarity index 99% rename from selfdrive/ui/sunnypilot/sp_priv_ui.cc rename to selfdrive/ui/sunnypilot/ui.cc index 4a8240bdad..7460a7acf7 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/sunnypilot/sp_priv_ui.h" +#include "selfdrive/ui/sunnypilot/ui.h" #include #include @@ -11,7 +11,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "common/watchdog.h" -#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sp_priv_role_model.h" +#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 diff --git a/selfdrive/ui/sunnypilot/sp_priv_ui.h b/selfdrive/ui/sunnypilot/ui.h similarity index 95% rename from selfdrive/ui/sunnypilot/sp_priv_ui.h rename to selfdrive/ui/sunnypilot/ui.h index 162bc2f294..149f601860 100644 --- a/selfdrive/ui/sunnypilot/sp_priv_ui.h +++ b/selfdrive/ui/sunnypilot/ui.h @@ -8,10 +8,10 @@ #include "cereal/messaging/messaging.h" #include "common/timing.h" -#include "qt/network/sunnylink/models/sp_priv_role_model.h" -#include "qt/network/sunnylink/models/sp_priv_sponsor_role_model.h" -#include "qt/network/sunnylink/models/sp_priv_user_model.h" -#include "selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h" +#include "qt/network/sunnylink/models/role_model.h" +#include "qt/network/sunnylink/models/sponsor_role_model.h" +#include "qt/network/sunnylink/models/user_model.h" +#include "selfdrive/ui/sunnypilot/qt/ui_scene.h" #include "system/hardware/hw.h" const int UI_ROAD_NAME_MARGIN_X = 14; diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index d8146723a4..6c706ffeee 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -87,6 +87,89 @@ من أجل "%1"
+ + AdvancedNetworkingSP + + Back + السابق + + + Enable Tethering + تمكين الربط + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + كلمة مرور الربط + + + EDIT + تعديل + + + Enter new tethering password + أدخل كلمة مرور الربط الجديدة + + + IP Address + عنوان IP + + + Enable Roaming + تمكين التجوال + + + APN Setting + إعدادات APN + + + Enter APN + إدخال APN + + + leave blank for automatic configuration + اتركه فارغاً من أجل التكوين التلقائي + + + Cellular Metered + محدود بالاتصال الخلوي + + + Prevent large data uploads when on a metered connection + منع تحميل البيانات الكبيرة عندما يكون الاتصال محدوداً + + + Hidden Network + شبكة مخفية + + + CONNECT + الاتصال + + + Enter SSID + أدخل SSID + + + Enter password + أدخل كلمة المرور + + + for "%1" + من أجل "%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - SPEED + SPEED LIMIT - LIMIT + LIMIT + + + + AnnotatedCameraWidgetSP + + km/h + كم/س + + + mph + ميل/س + + + MAX + MAX + + + SPEED + SPEED + + + LIMIT + LIMIT + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ إلغاء + + CustomOffsetsSettings + + Back + السابق + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - يجب عليك قبول الشروط والأحكام من أجل استخدام openpilot. + يجب عليك قبول الشروط والأحكام من أجل استخدام openpilot. Back @@ -135,6 +333,10 @@ Decline, uninstall %1 رفض، إلغاء التثبيت %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - يحتاج openpilot أن يتم ضبط الجهاز ضمن حدود 4 درجات يميناً أو يساراً و5 درجات نحو الأعلى أو 9 نحو الأسفل. يقوم openpilot بالمعايرة باستمرار، ونادراً ما يحتاج إلى عملية إعادة الضبط. + يحتاج openpilot أن يتم ضبط الجهاز ضمن حدود 4 درجات يميناً أو يساراً و5 درجات نحو الأعلى أو 9 نحو الأسفل. يقوم openpilot بالمعايرة باستمرار، ونادراً ما يحتاج إلى عملية إعادة الضبط. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR إقران + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + عرض + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + إعادة الضبط + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + إعادة الضبط + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -349,6 +700,79 @@ جارٍ التثبيت... + + LaneChangeSettings + + Back + السابق + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -390,6 +814,48 @@ بانتظار الطريق + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + م + + + hr + س + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -420,6 +886,33 @@ كلمة مرور خاطئة + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + متقدم + + + Enter password + أدخل كلمة المرور + + + for "%1" + من أجل "%1" + + + Wrong password + كلمة مرور خاطئة + + OffroadAlert @@ -472,6 +965,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. لقد اكتشف openpilot تغييراً في موقع تركيب الجهاز. تأكد من تثبيت الجهاز بشكل كامل في موقعه وتثبيته بإحكام على الزجاج الأمامي. + + 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. + +%1 + + + + 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. + + OffroadHome @@ -511,6 +1014,186 @@ إعادة التشغيل + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + د + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + التحقق + + + Country + + + + SELECT + اختيار + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + تحديث + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -541,6 +1224,51 @@ إلغاء + + ParamControlSP + + Enable + تمكين + + + Cancel + إلغاء + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + كم/س + + + mph + ميل/س + + PrimeAdWidget @@ -595,7 +1323,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -650,6 +1378,30 @@ now الآن + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -692,6 +1444,147 @@ This may take up to a minute. تم تفعيل إعادة ضبط النظام. اضغط على تأكيد لمسح جميع المحتويات والإعدادات. اضغط على إلغاء لاستئناف التمهيد. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -715,6 +1608,61 @@ This may take up to a minute. البرنامج + + SettingsWindowSP + + × + × + + + Device + الجهاز + + + Network + الشبكة + + + sunnylink + + + + Toggles + المثبتتات + + + Software + البرنامج + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -908,6 +1856,100 @@ This may take up to a minute. 5G + + SidebarSP + + TEMP + درجة الحرارة + + + HIGH + مرتفع + + + GOOD + جيد + + + OK + موافق + + + DISABLED + + + + OFFLINE + غير متصل + + + REGIST... + + + + ONLINE + متصل + + + ERROR + خطأ + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -983,6 +2025,233 @@ This may take up to a minute. أحدث نسخة، آخر تحقق %1 + + SoftwarePanelSP + + Driving Model + + + + SELECT + اختيار + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + إعادة ضبط المعايرة + + + Warning: You are on a metered connection! + + + + Continue + متابعة + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + كم/س + + + mph + ميل/س + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + غير متاح + + + km/h + كم/س + + + mph + ميل/س + + SshControl @@ -1029,6 +2298,399 @@ This may take up to a minute. تمكين SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + غير متاح + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + إقران + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1048,15 +2710,30 @@ This may take up to a minute. أوافق + + TermsPageSP + + Terms & Conditions + الشروط والأحكام + + + Decline + رفض + + + Scroll to accept + قم بالتمرير للقبول + + TogglesPanel Enable openpilot - تمكين openpilot + تمكين openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - استخدم نظام openpilot من أجل الضبط التكيفي للسرعة والحفاظ على مساعدة السائق للبقاء في المسار. انتباهك مطلوب في جميع الأوقات مع استخدام هذه الميزة. يعمل هذا التغيير في الإعدادات عند إيقاف تشغيل السيارة. + استخدم نظام openpilot من أجل الضبط التكيفي للسرعة والحفاظ على مساعدة السائق للبقاء في المسار. انتباهك مطلوب في جميع الأوقات مع استخدام هذه الميزة. يعمل هذا التغيير في الإعدادات عند إيقاف تشغيل السيارة. Enable Lane Departure Warnings @@ -1092,19 +2769,19 @@ This may take up to a minute. Show ETA in 24h Format - إظهار الوقت المقدر للوصول بصيغة 24 ساعة + إظهار الوقت المقدر للوصول بصيغة 24 ساعة Use 24h format instead of am/pm - استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء + استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء Show Map on Left Side of UI - عرض الخريطة على الجانب الأيسر من واجهة المستخدم + عرض الخريطة على الجانب الأيسر من واجهة المستخدم Show map on left side when in split screen view. - عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. + عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. openpilot Longitudinal Control (Alpha) @@ -1186,6 +2863,252 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + التحكم الطولي openpilot (ألفا) + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + الوضع التجريبي + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + فك الارتباط عن دواسة الوقود + + + When enabled, pressing the accelerator pedal will disengage openpilot. + عند تمكين هذه الميزة، فإن الضغط على دواسة الوقود سيؤدي إلى فك ارتباط openpilot. + + + Enable Lane Departure Warnings + قم بتمكين تحذيرات مغادرة المسار + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + تلقي التنبيهات من أجل الالتفاف للعودة إلى المسار عندما تنحرف سيارتك فوق الخط المحدد للمسار دون تشغيل إشارة الانعطاف عند القيادة لمسافة تزيد عن 31 ميل/سا (50 كم/سا). + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + تسجيل وتحميل كاميرا السائق + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + تحميل البيانات من الكاميرا المواجهة للسائق، والمساعدة في تحسين خوارزمية مراقبة السائق. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + استخدام النظام المتري + + + Display speed in km/h instead of mph. + عرض السرعة بواحدات كم/سا بدلاً من ميل/سا. + + + Show ETA in 24h Format + إظهار الوقت المقدر للوصول بصيغة 24 ساعة + + + Use 24h format instead of am/pm + استخدام صيغة 24 ساعة بدلاً من صباحاً/مساء + + + Show Map on Left Side of UI + عرض الخريطة على الجانب الأيسر من واجهة المستخدم + + + Show map on left side when in split screen view. + عرض الخريطة عل الجانب الأيسر عندما تكون وضعية العرض بطريقة الشاشة المنقسمة. + + + Aggressive + الهجومي + + + Moderate + + + + Standard + القياسي + + + Relaxed + الراحة + + + Driving Personality + شخصية القيادة + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + يتم وضع openpilot بشكل قياسي في <b>وضعية الراحة</b>. يمكن الوضع التجريبي <b>ميزات المستوى ألفا</b> التي لا تكون جاهزة في وضع الراحة: + + + End-to-End Longitudinal Control + التحكم الطولي من طرف إلى طرف + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + تصور القيادة الديد + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + الوضع التجريبي غير متوفر حالياً في هذه السيارة نظراً لاستخدام رصيد التحكم التكيفي بالسرعة من أجل التحكم الطولي. + + + openpilot longitudinal control may come in a future update. + قد يتم الحصول على التحكم الطولي في openpilot في عمليات التحديث المستقبلية. + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1222,6 +3145,165 @@ This may take up to a minute. فشل التحديث + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1268,4 +3350,27 @@ This may take up to a minute. نسيان + + WifiUISP + + Scanning for networks... + يتم البحث عن شبكات... + + + CONNECTING... + يتم الاتصال... + + + FORGET + نسيان هذه الشبكة + + + Forget Wi-Fi Network "%1"? + هل تريد نسيان شبكة الواي فاي "%1"؟ + + + Forget + نسيان + + diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 010aa4d304..3d79b34005 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -87,6 +87,89 @@ für "%1" + + AdvancedNetworkingSP + + Back + Zurück + + + Enable Tethering + Tethering aktivieren + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + Tethering Passwort + + + EDIT + ÄNDERN + + + Enter new tethering password + Neues tethering Passwort eingeben + + + IP Address + IP Adresse + + + Enable Roaming + Roaming aktivieren + + + APN Setting + APN Einstellungen + + + Enter APN + APN eingeben + + + leave blank for automatic configuration + für automatische Konfiguration leer lassen + + + Cellular Metered + Getaktete Verbindung + + + Prevent large data uploads when on a metered connection + Hochladen großer Dateien über getaktete Verbindungen unterbinden + + + Hidden Network + + + + CONNECT + CONNECT + + + Enter SSID + SSID eingeben + + + Enter password + Passwort eingeben + + + for "%1" + für "%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - Geschwindigkeit + Geschwindigkeit LIMIT - LIMIT + LIMIT + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + Geschwindigkeit + + + LIMIT + LIMIT + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ Abbrechen + + CustomOffsetsSettings + + Back + Zurück + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. + Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. Back @@ -135,6 +333,10 @@ Decline, uninstall %1 Ablehnen, deinstallieren %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 9° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig. + Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 9° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + ANSEHEN + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + RESET + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + Zurücksetzen + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -345,6 +696,79 @@ Installiere... + + LaneChangeSettings + + Back + Zurück + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -386,6 +810,48 @@ + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + std + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -416,6 +882,33 @@ Falsches Passwort + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + Erweitert + + + Enter password + Passwort eingeben + + + for "%1" + für "%1" + + + Wrong password + Falsches Passwort + + OffroadAlert @@ -467,6 +960,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +1009,186 @@ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + min + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + ÜBERPRÜFEN + + + Country + + + + SELECT + AUSWÄHLEN + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + Aktualisieren + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1219,51 @@ Aktivieren + + ParamControlSP + + Enable + Aktivieren + + + Cancel + Abbrechen + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -590,7 +1318,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -633,6 +1361,30 @@ now + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -674,6 +1426,147 @@ This may take up to a minute. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -697,6 +1590,61 @@ This may take up to a minute. Software + + SettingsWindowSP + + × + x + + + Device + Gerät + + + Network + Netzwerk + + + sunnylink + + + + Toggles + Schalter + + + Software + Software + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -891,6 +1839,100 @@ This may take up to a minute. 5G + + SidebarSP + + TEMP + TEMP + + + HIGH + HOCH + + + GOOD + GUT + + + OK + OK + + + DISABLED + + + + OFFLINE + OFFLINE + + + REGIST... + + + + ONLINE + ONLINE + + + ERROR + FEHLER + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -967,6 +2009,233 @@ This may take up to a minute. + + SoftwarePanelSP + + Driving Model + + + + SELECT + AUSWÄHLEN + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Neu kalibrieren + + + Warning: You are on a metered connection! + + + + Continue + Fortsetzen + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + Nicht verfügbar + + + km/h + km/h + + + mph + mph + + SshControl @@ -1013,6 +2282,399 @@ This may take up to a minute. SSH aktivieren + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + Nicht verfügbar + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1032,15 +2694,30 @@ This may take up to a minute. Zustimmen + + TermsPageSP + + Terms & Conditions + Benutzungsbedingungen + + + Decline + Ablehnen + + + Scroll to accept + Scrolle herunter um zu akzeptieren + + TogglesPanel Enable openpilot - Openpilot aktivieren + Openpilot aktivieren Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - Benutze das Openpilot System als adaptiven Tempomaten und Spurhalteassistenten. Deine Aufmerksamkeit ist jederzeit erforderlich, um diese Funktion zu nutzen. Diese Einstellung wird übernommen, wenn das Auto aus ist. + Benutze das Openpilot System als adaptiven Tempomaten und Spurhalteassistenten. Deine Aufmerksamkeit ist jederzeit erforderlich, um diese Funktion zu nutzen. Diese Einstellung wird übernommen, wenn das Auto aus ist. Enable Lane Departure Warnings @@ -1072,21 +2749,21 @@ This may take up to a minute. Use 24h format instead of am/pm - Benutze das 24Stunden Format anstatt am/pm + Benutze das 24Stunden Format anstatt am/pm Show Map on Left Side of UI Too long for UI - Zeige die Karte auf der linken Seite + Zeige die Karte auf der linken Seite Show map on left side when in split screen view. - Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. + Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. Show ETA in 24h Format Too long for UI - Zeige die Ankunftszeit im 24 Stunden Format + Zeige die Ankunftszeit im 24 Stunden Format Experimental Mode @@ -1172,6 +2849,252 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + Experimenteller Modus + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + Bei Gasbetätigung ausschalten + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Wenn aktiviert, deaktiviert sich Openpilot sobald das Gaspedal betätigt wird. + + + Enable Lane Departure Warnings + Spurverlassenswarnungen aktivieren + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Erhalte Warnungen, zurück in die Spur zu lenken, wenn dein Auto über eine erkannte Fahrstreifenmarkierung ohne aktivierten Blinker mit mehr als 50 km/h fährt. + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + Fahrerkamera aufnehmen und hochladen + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Lade Daten der Fahreraufmerksamkeitsüberwachungskamera hoch, um die Fahreraufmerksamkeitsüberwachungsalgorithmen zu verbessern. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Benutze das metrische System + + + Display speed in km/h instead of mph. + Zeige die Geschwindigkeit in km/h anstatt von mph. + + + Show ETA in 24h Format + Zeige die Ankunftszeit im 24 Stunden Format + + + Use 24h format instead of am/pm + Benutze das 24Stunden Format anstatt am/pm + + + Show Map on Left Side of UI + Zeige die Karte auf der linken Seite + + + Show map on left side when in split screen view. + Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. + + + Aggressive + + + + Moderate + + + + Standard + + + + Relaxed + + + + Driving Personality + + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + Openpilot fährt standardmäßig im <b>entspannten Modus</b>. Der Experimentelle Modus aktiviert<b>Alpha-level Funktionen</b>, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden: + + + End-to-End Longitudinal Control + + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + Neue Fahrvisualisierung + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar da es den eingebauten adaptiven Tempomaten des Autos benutzt. + + + openpilot longitudinal control may come in a future update. + + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1208,6 +3131,165 @@ This may take up to a minute. Aktualisierung fehlgeschlagen + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1254,4 +3336,27 @@ This may take up to a minute. Vergessen + + WifiUISP + + Scanning for networks... + Suche nach Netzwerken... + + + CONNECTING... + VERBINDEN... + + + FORGET + VERGESSEN + + + Forget Wi-Fi Network "%1"? + WLAN Netzwerk "%1" vergessen? + + + Forget + Vergessen + + diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index ee262c0953..c7bc23bf17 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -87,6 +87,89 @@ para "%1" + + AdvancedNetworkingSP + + Back + + + + Enable Tethering + Activar Tether + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + Contraseña de Tethering + + + EDIT + EDITAR + + + Enter new tethering password + Nueva contraseña de tethering + + + IP Address + Dirección IP + + + Enable Roaming + Activar Roaming + + + APN Setting + Configuración de APN + + + Enter APN + Insertar APN + + + leave blank for automatic configuration + dejar en blanco para configuración automática + + + Cellular Metered + Plano de datos limitado + + + Prevent large data uploads when on a metered connection + Evitar grandes descargas de datos cuando tiene una conexión limitada + + + Hidden Network + Red Oculta + + + CONNECT + + + + Enter SSID + Ingrese SSID + + + Enter password + + + + for "%1" + para "%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - VELOCIDAD + VELOCIDAD LIMIT - LIMITE + LIMITE + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + VELOCIDAD + + + LIMIT + LIMITE + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ Cancelar + + CustomOffsetsSettings + + Back + + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Debe aceptar los terminos y condiciones para poder utilizar openpilot. + Debe aceptar los terminos y condiciones para poder utilizar openpilot. Back @@ -135,6 +333,37 @@ Decline, uninstall %1 Rechazar, desinstalar %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + + + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + DevicePanel @@ -240,7 +469,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot requiere que el dispositivo sea montado entre 4° izquierda o derecha y entre 5° arriba o 9° abajo. openpilot está constantemente en calibración, reiniciar es rara vez necesario. + openpilot requiere que el dispositivo sea montado entre 4° izquierda o derecha y entre 5° arriba o 9° abajo. openpilot está constantemente en calibración, reiniciar es rara vez necesario. Your device is pointed %1° %2 and %3° %4. @@ -278,6 +507,155 @@ Disengage to Power Off Desactivar para apagar + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + VER + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + REINICIAR + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + Reiniciar + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -318,6 +696,162 @@ Instalando... + + LaneChangeSettings + + Back + + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS + + + + Waiting for route + + + + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + + + + hr + + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -348,6 +882,33 @@ Contraseña equivocada + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + Avanzado + + + Enter password + + + + for "%1" + para "%1" + + + Wrong password + Contraseña equivocada + + OffroadAlert @@ -400,6 +961,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot detectó un cambio en la posición de montaje del dispositivo. Asegúrese de que el dispositivo esté completamente asentado en el soporte y que el soporte esté firmemente asegurado al parabrisas. + + 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. + +%1 + + + + 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. + + OffroadHome @@ -439,6 +1010,186 @@ Reiniciar Dispositivo + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + VERIFICAR + + + Country + + + + SELECT + SELECCIONAR + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + ACTUALIZAR + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -469,6 +1220,51 @@ Cancelar + + ParamControlSP + + Enable + Activar + + + Cancel + Cancelar + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -497,7 +1293,11 @@ Remote snapshots - Istantánea remota + Istantánea remota + + + Turn-by-turn navigation + @@ -523,7 +1323,7 @@ openpilot - openpilot + openpilot now @@ -550,6 +1350,46 @@ hace %n días + + km + + + + m + + + + mi + + + + ft + + + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -592,6 +1432,147 @@ Esto puede tardar hasta un minuto. No es posible montar una partición de datos. Partición corrompida. Confirme para borrar y reiniciar su dispositivo. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -615,6 +1596,61 @@ Esto puede tardar hasta un minuto. Software + + SettingsWindowSP + + × + × + + + Device + Dispositivo + + + Network + Red + + + sunnylink + + + + Toggles + Ajustes + + + Software + Software + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -808,6 +1844,100 @@ Esto puede tardar hasta un minuto. 5G + + SidebarSP + + TEMP + TEMP + + + HIGH + ALTA + + + GOOD + BUENO + + + OK + OK + + + DISABLED + + + + OFFLINE + SIN LINEA + + + REGIST... + + + + ONLINE + EN LINEA + + + ERROR + ERROR + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -883,6 +2013,233 @@ Esto puede tardar hasta un minuto. actualizado, último chequeo %1 + + SoftwarePanelSP + + Driving Model + + + + SELECT + SELECCIONAR + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Reiniciar Calibración + + + Warning: You are on a metered connection! + + + + Continue + Continuar + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -929,6 +2286,399 @@ Esto puede tardar hasta un minuto. Habilitar SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + EMPAREJAR + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -948,15 +2698,30 @@ Esto puede tardar hasta un minuto. Aceptar + + TermsPageSP + + Terms & Conditions + Terminos & Condiciones + + + Decline + Rechazar + + + Scroll to accept + Desliza para aceptar + + TogglesPanel Enable openpilot - Activar openpilot + Activar openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - Utilice el sistema openpilot para control de crucero adaptativo y asistencia al conductor para mantenerse en el carril. Se requiere su atención en todo momento para utilizar esta función. Cambiar esta configuración solo tendrá efecto con el auto apagado. + Utilice el sistema openpilot para control de crucero adaptativo y asistencia al conductor para mantenerse en el carril. Se requiere su atención en todo momento para utilizar esta función. Cambiar esta configuración solo tendrá efecto con el auto apagado. openpilot Longitudinal Control (Alpha) @@ -1070,6 +2835,252 @@ Esto puede tardar hasta un minuto. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. Activar el control longitudinal experimental para permitir el modo Experimental. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + Control longitudinal de openpilot (Alfa) + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + Modo Experimental + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + Desactivar con el Acelerador + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Cuando esté activado, presionar el acelerador deshabilitará el openpilot. + + + Enable Lane Departure Warnings + Activar Aviso de Salida de Carril + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Recibir alertas para volver dentro del carril cuando su vehículo se sale fuera del carril sin que esté activado la señal de giro mientras esté conduciendo por encima de 50 km/h (31 mph). + + + Always-On Driver Monitoring + Monitoreo del Conductor Siempre Activo + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + Grabar y Subir Cámara del Conductor + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Subir datos de la cámara del conductor para ayudar a mejorar el algoritmo de monitorización del conductor. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Usar Sistema Métrico + + + Display speed in km/h instead of mph. + Mostrar velocidad en km/h en vez de mph. + + + Show ETA in 24h Format + + + + Use 24h format instead of am/pm + + + + Show Map on Left Side of UI + + + + Show map on left side when in split screen view. + + + + Aggressive + Agresivo + + + Moderate + + + + Standard + Estándar + + + Relaxed + Relajado + + + Driving Personality + Personalidad de conducción + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot por defecto conduce en <b>modo chill</b>. El modo Experimental activa <b>recursos de nível-alfa</b> que no están listos para el modo chill. Los recursos del modo expeimental están listados abajo: + + + End-to-End Longitudinal Control + 🌮 Control Longitudinal de Punta a Punta 🌮 + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + Nueva Visualización de la conducción + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + La visualización de la conducción cambiará a la cámara que enfoca la carretera a velocidades bajas para mostrar mejor los giros. El logo del modo experimental se mostrará en la esquina superior derecha. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + El modo Experimental no está disponible actualmente para este auto, ya que el ACC del auto está siendo usado para el control longitudinal. + + + openpilot longitudinal control may come in a future update. + El control longitudinal de openpilot podrá llegar en futuras actualizaciones. + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1106,6 +3117,165 @@ Esto puede tardar hasta un minuto. Actualización fallida + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1152,4 +3322,27 @@ Esto puede tardar hasta un minuto. Olvidar + + WifiUISP + + Scanning for networks... + Buscando redes... + + + CONNECTING... + CONECTANDO... + + + FORGET + OLVIDAR + + + Forget Wi-Fi Network "%1"? + Olvidar Red Wi-Fi "%1"? + + + Forget + Olvidar + + diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index dde6adadd3..a89468a1a8 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -87,6 +87,89 @@ pour "%1" + + AdvancedNetworkingSP + + Back + Retour + + + Enable Tethering + Activer le partage de connexion + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + Mot de passe du partage de connexion + + + EDIT + MODIFIER + + + Enter new tethering password + Entrez le nouveau mot de passe du partage de connexion + + + IP Address + Adresse IP + + + Enable Roaming + Activer l'itinérance + + + APN Setting + Paramètre APN + + + Enter APN + Entrer le nom du point d'accès + + + leave blank for automatic configuration + laisser vide pour une configuration automatique + + + Cellular Metered + Connexion cellulaire limitée + + + Prevent large data uploads when on a metered connection + Éviter les transferts de données importants sur une connexion limitée + + + Hidden Network + Réseau Caché + + + CONNECT + CONNECTER + + + Enter SSID + Entrer le SSID + + + Enter password + Entrer le mot de passe + + + for "%1" + pour "%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - VITESSE + VITESSE LIMIT - LIMITE + LIMITE + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mi/h + + + MAX + MAX + + + SPEED + VITESSE + + + LIMIT + LIMITE + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ Annuler + + CustomOffsetsSettings + + Back + Retour + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Vous devez accepter les conditions générales pour utiliser openpilot. + Vous devez accepter les conditions générales pour utiliser openpilot. Back @@ -135,6 +333,10 @@ Decline, uninstall %1 Refuser, désinstaller %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -255,7 +457,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot nécessite que l'appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 9° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire. + openpilot nécessite que l'appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 9° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + VOIR + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + RÉINITIALISER + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + Réinitialiser + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -345,6 +696,79 @@ Installation... + + LaneChangeSettings + + Back + Retour + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -386,6 +810,48 @@ En attente d'un trajet + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + h + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -416,6 +882,33 @@ Mot de passe incorrect + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + Avancé + + + Enter password + Entrer le mot de passe + + + for "%1" + pour "%1" + + + Wrong password + Mot de passe incorrect + + OffroadAlert @@ -468,6 +961,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot a détecté un changement dans la position de montage de l'appareil. Assurez-vous que l'appareil est totalement inséré dans le support et que le support est fermement fixé au pare-brise. + + 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. + +%1 + + + + 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. + + OffroadHome @@ -507,6 +1010,186 @@ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + min + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + VÉRIFIER + + + Country + + + + SELECT + SÉLECTIONNER + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + MISE À JOUR + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -537,6 +1220,51 @@ Annuler + + ParamControlSP + + Enable + Activer + + + Cancel + Annuler + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mi/h + + PrimeAdWidget @@ -591,7 +1319,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -634,6 +1362,30 @@ now + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -676,6 +1428,147 @@ Cela peut prendre jusqu'à une minute. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -699,6 +1592,61 @@ Cela peut prendre jusqu'à une minute. Logiciel + + SettingsWindowSP + + × + × + + + Device + Appareil + + + Network + Réseau + + + sunnylink + + + + Toggles + Options + + + Software + Logiciel + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -892,6 +1840,100 @@ Cela peut prendre jusqu'à une minute. 5G + + SidebarSP + + TEMP + TEMP + + + HIGH + HAUT + + + GOOD + BON + + + OK + OK + + + DISABLED + + + + OFFLINE + HORS LIGNE + + + REGIST... + + + + ONLINE + EN LIGNE + + + ERROR + ERREUR + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -967,6 +2009,233 @@ Cela peut prendre jusqu'à une minute. à jour, dernière vérification %1 + + SoftwarePanelSP + + Driving Model + + + + SELECT + SÉLECTIONNER + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Réinitialiser la calibration + + + Warning: You are on a metered connection! + + + + Continue + Continuer + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mi/h + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mi/h + + SshControl @@ -1013,6 +2282,399 @@ Cela peut prendre jusqu'à une minute. Activer SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1032,15 +2694,30 @@ Cela peut prendre jusqu'à une minute. Accepter + + TermsPageSP + + Terms & Conditions + Termes & Conditions + + + Decline + Refuser + + + Scroll to accept + Faire défiler pour accepter + + TogglesPanel Enable openpilot - Activer openpilot + Activer openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - Utilisez le système openpilot pour le régulateur de vitesse adaptatif et l'assistance au maintien de voie. Votre attention est requise en permanence pour utiliser cette fonctionnalité. La modification de ce paramètre prend effet lorsque la voiture est éteinte. + Utilisez le système openpilot pour le régulateur de vitesse adaptatif et l'assistance au maintien de voie. Votre attention est requise en permanence pour utiliser cette fonctionnalité. La modification de ce paramètre prend effet lorsque la voiture est éteinte. openpilot Longitudinal Control (Alpha) @@ -1092,19 +2769,19 @@ Cela peut prendre jusqu'à une minute. Show ETA in 24h Format - Afficher l'heure d'arrivée en format 24h + Afficher l'heure d'arrivée en format 24h Use 24h format instead of am/pm - Utiliser le format 24h plutôt que am/pm + Utiliser le format 24h plutôt que am/pm Show Map on Left Side of UI - Afficher la carte à gauche de l'interface + Afficher la carte à gauche de l'interface Show map on left side when in split screen view. - Afficher la carte à gauche en mode écran scindé. + Afficher la carte à gauche en mode écran scindé. Aggressive @@ -1170,6 +2847,252 @@ Cela peut prendre jusqu'à une minute. Enable driver monitoring even when openpilot is not engaged. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + Contrôle longitudinal openpilot (Alpha) + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + Mode expérimental + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + Désengager avec la pédale d'accélérateur + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Lorsqu'il est activé, appuyer sur la pédale d'accélérateur désengagera openpilot. + + + Enable Lane Departure Warnings + Activer les avertissements de sortie de voie + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Recevez des alertes pour revenir dans la voie lorsque votre véhicule dérive au-delà d'une ligne de voie détectée sans clignotant activé en roulant à plus de 31 mph (50 km/h). + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + Enregistrer et télécharger la caméra conducteur + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Publiez les données de la caméra orientée vers le conducteur et aidez à améliorer l'algorithme de surveillance du conducteur. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Utiliser le système métrique + + + Display speed in km/h instead of mph. + Afficher la vitesse en km/h au lieu de mph. + + + Show ETA in 24h Format + Afficher l'heure d'arrivée en format 24h + + + Use 24h format instead of am/pm + Utiliser le format 24h plutôt que am/pm + + + Show Map on Left Side of UI + Afficher la carte à gauche de l'interface + + + Show map on left side when in split screen view. + Afficher la carte à gauche en mode écran scindé. + + + Aggressive + Aggressif + + + Moderate + + + + Standard + Standard + + + Relaxed + Détendu + + + Driving Personality + Personnalité de conduite + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + Par défaut, openpilot conduit en <b>mode détente</b>. Le mode expérimental permet d'activer des <b>fonctionnalités alpha</b> qui ne sont pas prêtes pour le mode détente. Les fonctionnalités expérimentales sont listées ci-dessous : + + + End-to-End Longitudinal Control + Contrôle longitudinal de bout en bout + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + Nouvelle visualisation de la conduite + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + La visualisation de la conduite passera sur la caméra grand angle dirigée vers la route à faible vitesse afin de mieux montrer certains virages. Le logo du mode expérimental s'affichera également dans le coin supérieur droit. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + Le mode expérimental est actuellement indisponible pour cette voiture car le régulateur de vitesse adaptatif d'origine est utilisé pour le contrôle longitudinal. + + + openpilot longitudinal control may come in a future update. + Le contrôle longitudinal openpilot pourrait être disponible dans une future mise à jour. + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1206,6 +3129,165 @@ Cela peut prendre jusqu'à une minute. Échec de la mise à jour + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1252,4 +3334,27 @@ Cela peut prendre jusqu'à une minute. Oublier + + WifiUISP + + Scanning for networks... + Recherche de réseaux... + + + CONNECTING... + CONNEXION... + + + FORGET + OUBLIER + + + Forget Wi-Fi Network "%1"? + Oublier le réseau Wi-Fi "%1" ? + + + Forget + Oublier + + diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index e0fb60620b..46564f7834 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -87,6 +87,89 @@ ネットワーク名:%1 + + AdvancedNetworkingSP + + Back + 戻る + + + Enable Tethering + テザリングを有効化 + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + テザリングパスワード + + + EDIT + 編集 + + + Enter new tethering password + 新しいテザリングパスワードを入力 + + + IP Address + IP アドレス + + + Enable Roaming + ローミングを有効化 + + + APN Setting + APN 設定 + + + Enter APN + APN を入力 + + + leave blank for automatic configuration + 自動で設定するには、空白のままにしてください。 + + + Cellular Metered + 従量制通信設定 + + + Prevent large data uploads when on a metered connection + 大量のデータのアップロードを防止します。 + + + Hidden Network + + + + CONNECT + 接続 + + + Enter SSID + SSID を入力 + + + Enter password + パスワードを入力 + + + for "%1" + ネットワーク名:%1 + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - 速度 + 速度 LIMIT - 制限速度 + 制限速度 + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + 最高速度 + + + SPEED + 速度 + + + LIMIT + 制限速度 + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ キャンセル + + CustomOffsetsSettings + + Back + 戻る + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - openpilot をご利用される前に、利用規約に同意する必要があります。 + openpilot をご利用される前に、利用規約に同意する必要があります。 Back @@ -135,6 +333,10 @@ Decline, uninstall %1 拒否して %1 をアンインストール + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilotの本体は、左右4°以内、上5°、下9°以内の角度で取付ける必要があります。継続してキャリブレーションを続けているので、手動でリセットを行う必要はほぼありません。 + openpilotの本体は、左右4°以内、上5°、下9°以内の角度で取付ける必要があります。継続してキャリブレーションを続けているので、手動でリセットを行う必要はほぼありません。 Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + 見る + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + リセット + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + リセット + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +695,79 @@ インストールしています... + + LaneChangeSettings + + Back + 戻る + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +809,48 @@ + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + メートル + + + hr + 時間 + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +881,33 @@ パスワードが間違っています + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + 詳細 + + + Enter password + パスワードを入力 + + + for "%1" + ネットワーク名:%1 + + + Wrong password + パスワードが間違っています + + OffroadAlert @@ -466,6 +959,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -505,6 +1008,186 @@ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + 確認 + + + Country + + + + SELECT + 選択 + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + 更新 + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -535,6 +1218,51 @@ を有効化 + + ParamControlSP + + Enable + を有効化 + + + Cancel + キャンセル + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -589,7 +1317,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -629,6 +1357,30 @@ now + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -670,6 +1422,147 @@ This may take up to a minute. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -693,6 +1586,61 @@ This may take up to a minute. ソフトウェア + + SettingsWindowSP + + × + × + + + Device + デバイス + + + Network + ネットワーク + + + sunnylink + + + + Toggles + 機能設定 + + + Software + ソフトウェア + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -886,6 +1834,100 @@ This may take up to a minute. 5G + + SidebarSP + + TEMP + 温度 + + + HIGH + 高温 + + + GOOD + 最適 + + + OK + OK + + + DISABLED + + + + OFFLINE + オフライン + + + REGIST... + + + + ONLINE + オンライン + + + ERROR + エラー + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -961,6 +2003,233 @@ This may take up to a minute. + + SoftwarePanelSP + + Driving Model + + + + SELECT + 選択 + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + キャリブレーションをリセット + + + Warning: You are on a metered connection! + + + + Continue + 続ける + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1007,6 +2276,399 @@ This may take up to a minute. SSH を有効化 + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1026,15 +2688,30 @@ This may take up to a minute. 同意 + + TermsPageSP + + Terms & Conditions + 利用規約 + + + Decline + 拒否 + + + Scroll to accept + スクロールして同意 + + TogglesPanel Enable openpilot - openpilot を有効化 + openpilot を有効化 Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - openpilotによるアダプティブクルーズコントロールとレーンキーピングドライバーアシストを利用します。この機能を利用する際は、常に前方への注意が必要です。この設定を変更すると、車の電源が切れた時に反映されます。 + openpilotによるアダプティブクルーズコントロールとレーンキーピングドライバーアシストを利用します。この機能を利用する際は、常に前方への注意が必要です。この設定を変更すると、車の電源が切れた時に反映されます。 Enable Lane Departure Warnings @@ -1070,19 +2747,19 @@ This may take up to a minute. Show ETA in 24h Format - 24時間表示 + 24時間表示 Use 24h format instead of am/pm - AM/PM の代わりに24時間形式を使用します + AM/PM の代わりに24時間形式を使用します Show Map on Left Side of UI - ディスプレイの左側にマップを表示 + ディスプレイの左側にマップを表示 Show map on left side when in split screen view. - 分割画面表示の場合、ディスプレイの左側にマップを表示します。 + 分割画面表示の場合、ディスプレイの左側にマップを表示します。 Experimental Mode @@ -1164,6 +2841,252 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + 実験モード + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + アクセルを踏むと openpilot を中断 + + + When enabled, pressing the accelerator pedal will disengage openpilot. + この機能を有効化すると、openpilotを利用中にアクセルを踏むとopenpilotによる運転サポートを中断します。 + + + Enable Lane Departure Warnings + 車線逸脱警報機能を有効化 + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 時速31マイル(50km)を超えるスピードで走行中、ウインカーを作動させずに検出された車線ライン上に車両が触れた場合、手動で車線内に戻るように警告を行います。 + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + 車内カメラの録画とアップロード + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 車内カメラの映像をアップロードし、ドライバー監視システムのアルゴリズムの向上に役立てます。 + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + メートル法を使用 + + + Display speed in km/h instead of mph. + 速度は mph ではなく km/h で表示されます。 + + + Show ETA in 24h Format + 24時間表示 + + + Use 24h format instead of am/pm + AM/PM の代わりに24時間形式を使用します + + + Show Map on Left Side of UI + ディスプレイの左側にマップを表示 + + + Show map on left side when in split screen view. + 分割画面表示の場合、ディスプレイの左側にマップを表示します。 + + + Aggressive + + + + Moderate + + + + Standard + + + + Relaxed + + + + Driving Personality + + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 + + + End-to-End Longitudinal Control + + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + 新しい運転画面 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + この車のACCがアクセル制御を行うため実験モードを利用することができません。 + + + openpilot longitudinal control may come in a future update. + + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1200,6 +3123,165 @@ This may take up to a minute. 更新失敗 + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1246,4 +3328,27 @@ This may take up to a minute. 削除 + + WifiUISP + + Scanning for networks... + ネットワークをスキャン中... + + + CONNECTING... + 接続中... + + + FORGET + 削除 + + + Forget Wi-Fi Network "%1"? + Wi-Fiネットワーク%1を削除してもよろしいですか? + + + Forget + 削除 + + diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 56fc5014ee..7ca586ceda 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -87,6 +87,89 @@ "%1"에 접속하려면 비밀번호가 필요합니다 + + AdvancedNetworkingSP + + Back + 뒤로 + + + Enable Tethering + 테더링 사용 + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + 테더링 비밀번호 + + + EDIT + 편집 + + + Enter new tethering password + 새 테더링 비밀번호를 입력하세요 + + + IP Address + IP 주소 + + + Enable Roaming + 로밍 사용 + + + APN Setting + APN 설정 + + + Enter APN + APN 입력 + + + leave blank for automatic configuration + 자동 설정하려면 빈 칸으로 두세요 + + + Cellular Metered + 데이터 요금제 + + + Prevent large data uploads when on a metered connection + 데이터 요금제 연결 시 대용량 데이터 업로드를 방지합니다 + + + Hidden Network + 숨겨진 네트워크 + + + CONNECT + + + + Enter SSID + SSID 입력 + + + Enter password + 비밀번호를 입력하세요 + + + for "%1" + "%1"에 접속하려면 비밀번호가 필요합니다 + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - SPEED + SPEED LIMIT - LIMIT + LIMIT + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + SPEED + + + LIMIT + LIMIT + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ 취소 + + CustomOffsetsSettings + + Back + 뒤로 + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - openpilot을 사용하려면 이용약관에 동의해야 합니다. + openpilot을 사용하려면 이용약관에 동의해야 합니다. Back @@ -135,6 +333,10 @@ Decline, uninstall %1 거절, %1 제거 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot 장치는 좌우 4°, 위로 5°, 아래로 9° 이내 각도로 장착되어야 합니다. openpilot은 지속적으로 자동 보정되며 재설정은 거의 필요하지 않습니다. + openpilot 장치는 좌우 4°, 위로 5°, 아래로 9° 이내 각도로 장착되어야 합니다. openpilot은 지속적으로 자동 보정되며 재설정은 거의 필요하지 않습니다. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR 동기화 + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + 보기 + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + 초기화 + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + 초기화 + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +695,79 @@ 설치 중... + + LaneChangeSettings + + Back + 뒤로 + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +809,48 @@ 경로를 기다리는 중 + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + 시간 + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +881,33 @@ 비밀번호가 틀렸습니다 + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + 고급 설정 + + + Enter password + 비밀번호를 입력하세요 + + + for "%1" + "%1"에 접속하려면 비밀번호가 필요합니다 + + + Wrong password + 비밀번호가 틀렸습니다 + + OffroadAlert @@ -467,6 +960,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 장치 온도가 너무 높습니다. 시작하기 전에 온도를 낮춰주세요. 현재 내부 부품 온도: %1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +1009,186 @@ 장치를 재부팅하세요 + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + 확인 + + + Country + + + + SELECT + 선택 + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + 업데이트 + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1219,51 @@ 활성화 + + ParamControlSP + + Enable + 활성화 + + + Cancel + 취소 + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -590,7 +1318,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -630,6 +1358,30 @@ now now + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -672,6 +1424,147 @@ This may take up to a minute. 시스템 재설정이 시작되었습니다. 모든 콘텐츠와 설정을 지우려면 확인을 누르시고 부팅을 재개하려면 취소를 누르세요. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -695,6 +1588,61 @@ This may take up to a minute. 소프트웨어 + + SettingsWindowSP + + × + × + + + Device + 장치 + + + Network + 네트워크 + + + sunnylink + + + + Toggles + 토글 + + + Software + 소프트웨어 + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -888,6 +1836,100 @@ This may take up to a minute. 5G + + SidebarSP + + TEMP + 온도 + + + HIGH + 높음 + + + GOOD + 좋음 + + + OK + OK + + + DISABLED + + + + OFFLINE + 연결 안됨 + + + REGIST... + + + + ONLINE + 온라인 + + + ERROR + 오류 + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -963,6 +2005,233 @@ This may take up to a minute. 업데이트 안함 + + SoftwarePanelSP + + Driving Model + + + + SELECT + 선택 + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + 캘리브레이션 초기화 + + + Warning: You are on a metered connection! + + + + Continue + 계속 + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1009,6 +2278,399 @@ This may take up to a minute. SSH 사용 + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 동기화 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1028,15 +2690,30 @@ This may take up to a minute. 동의 + + TermsPageSP + + Terms & Conditions + 이용약관 + + + Decline + 거절 + + + Scroll to accept + 동의하려면 아래로 스크롤하세요 + + TogglesPanel Enable openpilot - openpilot 사용 + openpilot 사용 Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - 어댑티브 크루즈 컨트롤 및 차로 유지 보조를 위해 openpilot 시스템을 사용할 수 있습니다. 이 기능을 사용할 때에는 언제나 주의를 기울여야 합니다. 설정을 변경하면 차량 시동이 꺼졌을 때 적용됩니다. + 어댑티브 크루즈 컨트롤 및 차로 유지 보조를 위해 openpilot 시스템을 사용할 수 있습니다. 이 기능을 사용할 때에는 언제나 주의를 기울여야 합니다. 설정을 변경하면 차량 시동이 꺼졌을 때 적용됩니다. Enable Lane Departure Warnings @@ -1072,19 +2749,19 @@ This may take up to a minute. Show ETA in 24h Format - 24시간 형식으로 도착 예정 시간 표시 + 24시간 형식으로 도착 예정 시간 표시 Use 24h format instead of am/pm - 오전/오후 대신 24시간 형식 사용 + 오전/오후 대신 24시간 형식 사용 Show Map on Left Side of UI - UI 왼쪽에 지도 표시 + UI 왼쪽에 지도 표시 Show map on left side when in split screen view. - 분할 화면 보기에서 지도를 왼쪽에 표시합니다. + 분할 화면 보기에서 지도를 왼쪽에 표시합니다. Experimental Mode @@ -1166,6 +2843,252 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + openpilot 가감속 제어 (알파) + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + 실험 모드 + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + 가속페달 조작 시 해제 + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 활성화된 경우 가속 페달을 밟으면 openpilot이 해제됩니다. + + + Enable Lane Departure Warnings + 차선 이탈 경고 활성화 + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 차량이 50km/h(31mph) 이상의 속도로 주행할 때 방향지시등이 켜지지 않은 상태에서 차선을 벗어나면 경고합니다. + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + 운전자 카메라 녹화 및 업로드 + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 운전자 카메라의 영상 데이터를 업로드하여 운전자 모니터링 알고리즘을 개선합니다. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + 미터법 사용 + + + Display speed in km/h instead of mph. + mph 대신 km/h로 속도를 표시합니다. + + + Show ETA in 24h Format + 24시간 형식으로 도착 예정 시간 표시 + + + Use 24h format instead of am/pm + 오전/오후 대신 24시간 형식 사용 + + + Show Map on Left Side of UI + UI 왼쪽에 지도 표시 + + + Show map on left side when in split screen view. + 분할 화면 보기에서 지도를 왼쪽에 표시합니다. + + + Aggressive + 공격적 + + + Moderate + + + + Standard + 표준 + + + Relaxed + 편안한 + + + Driving Personality + 주행 모드 + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot은 기본적으로 <b>안정 모드</b>로 주행합니다. 실험 모드는 안정화되지 않은 <b>알파 수준의 기능</b>을 활성화합니다. 실험 모드의 기능은 아래와 같습니다: + + + End-to-End Longitudinal Control + E2E 가감속 제어 + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + 새로운 주행 시각화 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 차량에 장착된 ACC로 가감속을 제어하기 때문에 현재 이 차량에서는 실험 모드를 사용할 수 없습니다. + + + openpilot longitudinal control may come in a future update. + openpilot 가감속 제어는 향후 업데이트에서 지원될 수 있습니다. + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1202,6 +3125,165 @@ This may take up to a minute. 업데이트 실패 + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1248,4 +3330,27 @@ This may take up to a minute. 삭제 + + WifiUISP + + Scanning for networks... + 네트워크 검색 중... + + + CONNECTING... + 연결 중... + + + FORGET + 삭제 + + + Forget Wi-Fi Network "%1"? + Wi-Fi "%1"에 자동으로 연결하지 않겠습니까? + + + Forget + 삭제 + + diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index feaa6e86a1..f51614bd0e 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -87,6 +87,89 @@ para "%1" + + AdvancedNetworkingSP + + Back + Voltar + + + Enable Tethering + Ativar Tether + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + Senha Tethering + + + EDIT + EDITAR + + + Enter new tethering password + Insira nova senha tethering + + + IP Address + Endereço IP + + + Enable Roaming + Ativar Roaming + + + APN Setting + APN Config + + + Enter APN + Insira APN + + + leave blank for automatic configuration + deixe em branco para configuração automática + + + Cellular Metered + Plano de Dados Limitado + + + Prevent large data uploads when on a metered connection + Evite grandes uploads de dados quando estiver em uma conexão limitada + + + Hidden Network + Rede Oculta + + + CONNECT + + + + Enter SSID + Digite o SSID + + + Enter password + Insira a senha + + + for "%1" + para "%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - MAX + MAX LIMIT - VELO + VELO + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + LIMITE + + + SPEED + MAX + + + LIMIT + VELO + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ Cancelar + + CustomOffsetsSettings + + Back + Voltar + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Você precisa aceitar os Termos e Condições para utilizar openpilot. + Você precisa aceitar os Termos e Condições para utilizar openpilot. Back @@ -135,6 +333,10 @@ Decline, uninstall %1 Rejeitar, desintalar %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 9° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário. + O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 9° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR PAREAR + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + VER + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + RESET + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + Resetar + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -345,6 +696,79 @@ Instalando... + + LaneChangeSettings + + Back + Voltar + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -386,6 +810,48 @@ Aguardando rota + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + hr + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -416,6 +882,33 @@ Senha incorreta + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + Avançado + + + Enter password + Insira a senha + + + for "%1" + para "%1" + + + Wrong password + Senha incorreta + + OffroadAlert @@ -468,6 +961,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 Temperatura do dispositivo muito alta. O sistema está sendo resfriado antes de iniciar. A temperatura atual do componente interno é: %1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -507,6 +1010,186 @@ Reinicie o Dispositivo + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + min + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + VERIFICAR + + + Country + + + + SELECT + SELECIONE + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + ATUALIZAÇÃO + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -537,6 +1220,51 @@ Ativar + + ParamControlSP + + Enable + Ativar + + + Cancel + Cancelar + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -591,7 +1319,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -634,6 +1362,30 @@ now agora + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -676,6 +1428,147 @@ Isso pode levar até um minuto. Reinicialização do sistema acionada. Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancel para retomar a inicialização. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -699,6 +1592,61 @@ Isso pode levar até um minuto. Software + + SettingsWindowSP + + × + × + + + Device + Dispositivo + + + Network + Rede + + + sunnylink + + + + Toggles + Ajustes + + + Software + Software + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -892,6 +1840,100 @@ Isso pode levar até um minuto. 5G + + SidebarSP + + TEMP + TEMP + + + HIGH + ALTA + + + GOOD + BOA + + + OK + OK + + + DISABLED + + + + OFFLINE + OFFLINE + + + REGIST... + + + + ONLINE + ONLINE + + + ERROR + ERRO + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -967,6 +2009,233 @@ Isso pode levar até um minuto. nunca + + SoftwarePanelSP + + Driving Model + + + + SELECT + SELECIONE + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Reinicializar Calibragem + + + Warning: You are on a metered connection! + + + + Continue + Continuar + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1013,6 +2282,399 @@ Isso pode levar até um minuto. Habilitar SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + PAREAR + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1032,15 +2694,30 @@ Isso pode levar até um minuto. Concordo + + TermsPageSP + + Terms & Conditions + Termos & Condições + + + Decline + Declinar + + + Scroll to accept + Role a tela para aceitar + + TogglesPanel Enable openpilot - Ativar openpilot + Ativar openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - Use o sistema openpilot para controle de cruzeiro adaptativo e assistência ao motorista de manutenção de faixa. Sua atenção é necessária o tempo todo para usar esse recurso. A alteração desta configuração tem efeito quando o carro é desligado. + Use o sistema openpilot para controle de cruzeiro adaptativo e assistência ao motorista de manutenção de faixa. Sua atenção é necessária o tempo todo para usar esse recurso. A alteração desta configuração tem efeito quando o carro é desligado. Enable Lane Departure Warnings @@ -1076,19 +2753,19 @@ Isso pode levar até um minuto. Show ETA in 24h Format - Mostrar ETA em Formato 24h + Mostrar ETA em Formato 24h Use 24h format instead of am/pm - Use o formato 24h em vez de am/pm + Use o formato 24h em vez de am/pm Show Map on Left Side of UI - Exibir Mapa no Lado Esquerdo + Exibir Mapa no Lado Esquerdo Show map on left side when in split screen view. - Exibir mapa do lado esquerdo quando a tela for dividida. + Exibir mapa do lado esquerdo quando a tela for dividida. Experimental Mode @@ -1170,6 +2847,252 @@ Isso pode levar até um minuto. Enable driver monitoring even when openpilot is not engaged. Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + Controle Longitudinal openpilot (Embrionário) + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + Modo Experimental + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + Desacionar com Pedal do Acelerador + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Quando ativado, pressionar o pedal do acelerador desacionará o openpilot. + + + Enable Lane Departure Warnings + Ativar Avisos de Saída de Faixa + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Receba alertas para voltar para a pista se o seu veículo sair da faixa e a seta não tiver sido acionada previamente quando em velocidades superiores a 50 km/h. + + + Always-On Driver Monitoring + Monitoramento do Motorista Sempre Ativo + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + Gravar e Upload Câmera Motorista + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Usar Sistema Métrico + + + Display speed in km/h instead of mph. + Exibir velocidade em km/h invés de mph. + + + Show ETA in 24h Format + Mostrar ETA em Formato 24h + + + Use 24h format instead of am/pm + Use o formato 24h em vez de am/pm + + + Show Map on Left Side of UI + Exibir Mapa no Lado Esquerdo + + + Show map on left side when in split screen view. + Exibir mapa do lado esquerdo quando a tela for dividida. + + + Aggressive + Disputa + + + Moderate + + + + Standard + Neutro + + + Relaxed + Calmo + + + Driving Personality + Temperamento de Direção + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-embrionário</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: + + + End-to-End Longitudinal Control + Controle Longitudinal de Ponta a Ponta + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + Nova Visualização de Condução + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + A visualização de condução fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será mostrado no canto superior direito. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + O modo Experimental está atualmente indisponível para este carro já que o ACC original do carro é usado para controle longitudinal. + + + openpilot longitudinal control may come in a future update. + O controle longitudinal openpilot poderá vir em uma atualização futura. + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1206,6 +3129,165 @@ Isso pode levar até um minuto. Falha na atualização + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1252,4 +3334,27 @@ Isso pode levar até um minuto. Esquecer + + WifiUISP + + Scanning for networks... + Procurando redes... + + + CONNECTING... + CONECTANDO... + + + FORGET + ESQUECER + + + Forget Wi-Fi Network "%1"? + Esquecer Rede Wi-Fi "%1"? + + + Forget + Esquecer + + diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index e594a6975f..21504ef438 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -87,6 +87,89 @@ สำหรับ "%1" + + AdvancedNetworkingSP + + Back + ย้อนกลับ + + + Enable Tethering + ปล่อยฮอตสปอต + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + รหัสผ่านฮอตสปอต + + + EDIT + แก้ไข + + + Enter new tethering password + ป้อนรหัสผ่านฮอตสปอตใหม่ + + + IP Address + หมายเลขไอพี + + + Enable Roaming + เปิดใช้งานโรมมิ่ง + + + APN Setting + ตั้งค่า APN + + + Enter APN + ป้อนค่า APN + + + leave blank for automatic configuration + เว้นว่างเพื่อตั้งค่าอัตโนมัติ + + + Cellular Metered + ลดการส่งข้อมูลผ่านเซลลูล่าร์ + + + Prevent large data uploads when on a metered connection + ปิดการอัพโหลดข้อมูลขนาดใหญ่เมื่อเชื่อมต่อผ่านเซลลูล่าร์ + + + Hidden Network + เครือข่ายที่ซ่อนอยู่ + + + CONNECT + เชื่อมต่อ + + + Enter SSID + ป้อนค่า SSID + + + Enter password + ใส่รหัสผ่าน + + + for "%1" + สำหรับ "%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - ความเร็ว + ความเร็ว LIMIT - จำกัด + จำกัด + + + + AnnotatedCameraWidgetSP + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + + MAX + สูงสุด + + + SPEED + ความเร็ว + + + LIMIT + จำกัด + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ ยกเลิก + + CustomOffsetsSettings + + Back + ย้อนกลับ + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - คุณต้องยอมรับเงื่อนไขและข้อตกลง เพื่อใช้งาน openpilot + คุณต้องยอมรับเงื่อนไขและข้อตกลง เพื่อใช้งาน openpilot Back @@ -135,6 +333,10 @@ Decline, uninstall %1 ปฏิเสธ และถอนการติดตั้ง %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot กำหนดให้ติดตั้งอุปกรณ์ โดยสามารถเอียงด้านซ้ายหรือขวาไม่เกิน 4° และเอียงขึ้นด้านบนไม่เกิน 5° หรือเอียงลงด้านล่างไม่เกิน 9° openpilot ทำการคาลิเบรทอย่างต่อเนื่อง แทบจะไม่จำเป็นต้องทำการรีเซ็ตการคาลิเบรท + openpilot กำหนดให้ติดตั้งอุปกรณ์ โดยสามารถเอียงด้านซ้ายหรือขวาไม่เกิน 4° และเอียงขึ้นด้านบนไม่เกิน 5° หรือเอียงลงด้านล่างไม่เกิน 9° openpilot ทำการคาลิเบรทอย่างต่อเนื่อง แทบจะไม่จำเป็นต้องทำการรีเซ็ตการคาลิเบรท Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR จับคู่ + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + ดู + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + รีเซ็ต + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + รีเซ็ต + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +695,79 @@ กำลังติดตั้ง... + + LaneChangeSettings + + Back + ย้อนกลับ + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +809,48 @@ กำลังรอเส้นทาง + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + ม. + + + hr + ชม. + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +881,33 @@ รหัสผ่านผิด + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + ขั้นสูง + + + Enter password + ใส่รหัสผ่าน + + + for "%1" + สำหรับ "%1" + + + Wrong password + รหัสผ่านผิด + + OffroadAlert @@ -467,6 +960,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot ตรวจพบการเปลี่ยนแปลงของตำแหน่งที่ติดตั้ง กรุณาตรวจสอบว่าได้เลื่อนอุปกรณ์เข้ากับจุดติดตั้งจนสุดแล้ว และจุดติดตั้งได้ยึดติดกับกระจกหน้าอย่างแน่นหนา + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +1009,186 @@ รีบูตอุปกรณ์ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + นาที + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + ตรวจสอบ + + + Country + + + + SELECT + เลือก + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + อัปเดต + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1219,51 @@ ยกเลิก + + ParamControlSP + + Enable + เปิดใช้งาน + + + Cancel + ยกเลิก + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + PrimeAdWidget @@ -590,7 +1318,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -630,6 +1358,30 @@ now ตอนนี้ + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -672,6 +1424,147 @@ This may take up to a minute. ระบบถูกรีเซ็ต กดยืนยันเพื่อลบข้อมูลและการตั้งค่าทั้งหมด กดยกเลิกเพื่อบูตต่อ + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -695,6 +1588,61 @@ This may take up to a minute. ซอฟต์แวร์ + + SettingsWindowSP + + × + × + + + Device + อุปกรณ์ + + + Network + เครือข่าย + + + sunnylink + + + + Toggles + ตัวเลือก + + + Software + ซอฟต์แวร์ + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -888,6 +1836,100 @@ This may take up to a minute. 5G + + SidebarSP + + TEMP + อุณหภูมิ + + + HIGH + สูง + + + GOOD + ดี + + + OK + พอใช้ + + + DISABLED + + + + OFFLINE + ออฟไลน์ + + + REGIST... + + + + ONLINE + ออนไลน์ + + + ERROR + เกิดข้อผิดพลาด + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -963,6 +2005,233 @@ This may take up to a minute. ล่าสุดแล้ว ตรวจสอบครั้งสุดท้ายเมื่อ %1 + + SoftwarePanelSP + + Driving Model + + + + SELECT + เลือก + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + รีเซ็ตการคาลิเบรท + + + Warning: You are on a metered connection! + + + + Continue + ดำเนินการต่อ + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + ไม่มี + + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + SshControl @@ -1009,6 +2278,399 @@ This may take up to a minute. เปิดใช้งาน SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + ไม่มี + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + จับคู่ + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1028,15 +2690,30 @@ This may take up to a minute. ยอมรับ + + TermsPageSP + + Terms & Conditions + ข้อตกลงและเงื่อนไข + + + Decline + ปฏิเสธ + + + Scroll to accept + เลื่อนเพื่อตอบรับข้อตกลง + + TogglesPanel Enable openpilot - เปิดใช้งาน openpilot + เปิดใช้งาน openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - ใช้ระบบ openpilot สำหรับระบบควบคุมความเร็วอัตโนมัติ และระบบช่วยควบคุมรถให้อยู่ในเลน คุณจำเป็นต้องให้ความสนใจตลอดเวลาที่ใช้คุณสมบัตินี้ การเปลี่ยนการตั้งค่านี้จะมีผลเมื่อคุณดับเครื่องยนต์ + ใช้ระบบ openpilot สำหรับระบบควบคุมความเร็วอัตโนมัติ และระบบช่วยควบคุมรถให้อยู่ในเลน คุณจำเป็นต้องให้ความสนใจตลอดเวลาที่ใช้คุณสมบัตินี้ การเปลี่ยนการตั้งค่านี้จะมีผลเมื่อคุณดับเครื่องยนต์ Enable Lane Departure Warnings @@ -1072,19 +2749,19 @@ This may take up to a minute. Show ETA in 24h Format - แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง + แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง Use 24h format instead of am/pm - ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm + ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm Show Map on Left Side of UI - แสดงแผนที่ที่ด้านซ้ายของหน้าจอ + แสดงแผนที่ที่ด้านซ้ายของหน้าจอ Show map on left side when in split screen view. - แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ + แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ Experimental Mode @@ -1166,6 +2843,252 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + ระบบควบคุมการเร่ง/เบรคโดย openpilot (Alpha) + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + โหมดทดลอง + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + ยกเลิกระบบช่วยขับเมื่อเหยียบคันเร่ง + + + When enabled, pressing the accelerator pedal will disengage openpilot. + เมื่อเปิดใช้งาน การกดแป้นคันเร่งจะเป็นการยกเลิกระบบช่วยขับโดย openpilot + + + Enable Lane Departure Warnings + เปิดใช้งานการเตือนการออกนอกเลน + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + รับการแจ้งเตือนให้เลี้ยวกลับเข้าเลนเมื่อรถของคุณตรวจพบการข้ามช่องจราจรโดยไม่เปิดสัญญาณไฟเลี้ยวในขณะขับขี่ที่ความเร็วเกิน 31 ไมล์ต่อชั่วโมง (50 กม./ชม) + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + บันทึกและอัปโหลดภาพจากกล้องคนขับ + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + อัปโหลดข้อมูลจากกล้องที่หันหน้าไปทางคนขับ และช่วยปรับปรุงอัลกอริธึมการตรวจสอบผู้ขับขี่ + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + ใช้ระบบเมตริก + + + Display speed in km/h instead of mph. + แสดงความเร็วเป็น กม./ชม. แทน ไมล์/ชั่วโมง + + + Show ETA in 24h Format + แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง + + + Use 24h format instead of am/pm + ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm + + + Show Map on Left Side of UI + แสดงแผนที่ที่ด้านซ้ายของหน้าจอ + + + Show map on left side when in split screen view. + แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ + + + Aggressive + ดุดัน + + + Moderate + + + + Standard + มาตรฐาน + + + Relaxed + ผ่อนคลาย + + + Driving Personality + บุคลิกการขับขี่ + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + โดยปกติ openpilot จะขับใน<b>โหมดชิล</b> เปิดโหมดทดลองเพื่อใช้<b>ความสามารถในขั้นพัฒนา</b> ซึ่งยังไม่พร้อมสำหรับโหมดชิล ความสามารถในขั้นพัฒนามีดังนี้: + + + End-to-End Longitudinal Control + ควบคุมเร่ง/เบรคแบบ End-to-End + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + การแสดงภาพการขับขี่แบบใหม่ + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + การแสดงภาพการขับขี่จะเปลี่ยนไปใช้กล้องมุมกว้างที่หันหน้าไปทางถนนเมื่ออยู่ในความเร็วต่ำ เพื่อแสดงภาพการเลี้ยวที่ดีขึ้น โลโก้โหมดการทดลองจะแสดงที่มุมบนขวาด้วย + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + ขณะนี้โหมดทดลองไม่สามารถใช้งานได้ในรถคันนี้ เนื่องจากเปิดใช้ระบบควบคุมการเร่ง/เบรคของรถที่ติดตั้งจากโรงงานอยู่ + + + openpilot longitudinal control may come in a future update. + ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1202,6 +3125,165 @@ This may take up to a minute. การอัปเดตล้มเหลว + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1248,4 +3330,27 @@ This may take up to a minute. เลิกใช้ + + WifiUISP + + Scanning for networks... + กำลังสแกนหาเครือข่าย... + + + CONNECTING... + กำลังเชื่อมต่อ... + + + FORGET + เลิกใช้ + + + Forget Wi-Fi Network "%1"? + เลิกใช้เครือข่าย Wi-Fi "%1"? + + + Forget + เลิกใช้ + + diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 48615f1699..348dec38ee 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -87,6 +87,89 @@ için "%1" + + AdvancedNetworkingSP + + Back + + + + Enable Tethering + Kişisel erişim noktasını aç + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + Kişisel erişim noktasının parolası + + + EDIT + DÜZENLE + + + Enter new tethering password + Erişim noktasına yeni bir sonraki başlatılışında çekilir. parola belirleyin. + + + IP Address + IP Adresi + + + Enable Roaming + Hücresel veri aç + + + APN Setting + APN Ayarları + + + Enter APN + APN Gir + + + leave blank for automatic configuration + otomatik yapılandırma için boş bırakın + + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + + + Hidden Network + + + + CONNECT + BAĞLANTI + + + Enter SSID + APN Gir + + + Enter password + Parolayı girin + + + for "%1" + için "%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -101,6 +184,29 @@ MAX MAX + + SPEED + HIZ + + + LIMIT + LİMİT + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + MAX + SPEED HIZ @@ -110,6 +216,91 @@ LİMİT + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + + + ConfirmationDialog @@ -121,11 +312,18 @@ Vazgeç + + CustomOffsetsSettings + + Back + + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - Openpilotu kullanmak için Kullanıcı Koşullarını kabul etmelisiniz. + Openpilotu kullanmak için Kullanıcı Koşullarını kabul etmelisiniz. Back @@ -135,6 +333,10 @@ Decline, uninstall %1 Reddet, Kurulumu kaldır. %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot, cihazın 4° sola veya 5° yukarı yada 9° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir. + openpilot, cihazın 4° sola veya 5° yukarı yada 9° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir. Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + BAK + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + SIFIRLA + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +695,79 @@ Yükleniyor... + + LaneChangeSettings + + Back + + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +809,48 @@ + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + saat + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +881,33 @@ Yalnış parola + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + Gelişmiş Seçenekler + + + Enter password + Parolayı girin + + + for "%1" + için "%1" + + + Wrong password + Yalnış parola + + OffroadAlert @@ -466,6 +959,16 @@ openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + 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. + +%1 + + + + 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. + + OffroadHome @@ -505,6 +1008,186 @@ + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + dk + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + KONTROL ET + + + Country + + + + SELECT + + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + GÜNCELLE + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -535,6 +1218,51 @@ + + ParamControlSP + + Enable + + + + Cancel + + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -589,7 +1317,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -629,6 +1357,30 @@ now + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -670,6 +1422,147 @@ This may take up to a minute. + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -693,6 +1586,61 @@ This may take up to a minute. Yazılım + + SettingsWindowSP + + × + x + + + Device + Cihaz + + + Network + + + + sunnylink + + + + Toggles + Değiştirme + + + Software + Yazılım + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -886,6 +1834,100 @@ This may take up to a minute. 5G + + SidebarSP + + TEMP + SICAKLIK + + + HIGH + YÜKSEK + + + GOOD + İYİ + + + OK + TAMAM + + + DISABLED + + + + OFFLINE + ÇEVRİMDIŞI + + + REGIST... + + + + ONLINE + ÇEVRİMİÇİ + + + ERROR + HATA + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -961,6 +2003,233 @@ This may take up to a minute. + + SoftwarePanelSP + + Driving Model + + + + SELECT + + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + Kalibrasyonu sıfırla + + + Warning: You are on a metered connection! + + + + Continue + Devam et + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1007,6 +2276,399 @@ This may take up to a minute. SSH aç + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1026,15 +2688,30 @@ This may take up to a minute. Kabul et + + TermsPageSP + + Terms & Conditions + Şartlar ve Koşullar + + + Decline + Reddet + + + Scroll to accept + Kabul etmek için kaydırın + + TogglesPanel Enable openpilot - openpilot'u aktifleştir + openpilot'u aktifleştir Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - Ayarlanabilir hız sabitleyici ve şeritte kalma yardımı için openpilot sistemini kullanın. Bu özelliği kullanırken her zaman dikkatli olmanız gerekiyor. Bu ayarın değiştirilmesi için araç kapatılıp açılması gerekiyor. + Ayarlanabilir hız sabitleyici ve şeritte kalma yardımı için openpilot sistemini kullanın. Bu özelliği kullanırken her zaman dikkatli olmanız gerekiyor. Bu ayarın değiştirilmesi için araç kapatılıp açılması gerekiyor. Enable Lane Departure Warnings @@ -1066,19 +2743,19 @@ This may take up to a minute. Show ETA in 24h Format - Tahmini varış süresini 24 saat formatı şeklinde göster + Tahmini varış süresini 24 saat formatı şeklinde göster Use 24h format instead of am/pm - 24 saat formatını kullan + 24 saat formatını kullan Show Map on Left Side of UI - Haritayı arayüzün sol tarafında göster + Haritayı arayüzün sol tarafında göster Show map on left side when in split screen view. - Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. + Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. openpilot Longitudinal Control (Alpha) @@ -1164,6 +2841,252 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Aktifleştirilirse eğer gaz pedalına basınca openpilot devre dışı kalır. + + + Enable Lane Departure Warnings + Şerit ihlali uyarı alın + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 50 km/s (31 mph) hızın üzerinde sürüş sırasında aracınız dönüş sinyali vermeden algılanan bir sonraki başlatılışında çekilir. şerit çizgisi ihlalinde şeride geri dönmek için uyarılar alın. + + + Always-On Driver Monitoring + + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + Sürücü kamerasını kayıt et. + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Sürücüye bakan kamera verisini yükleyin ve Cihazın algoritmasını geliştirmemize yardımcı olun. + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + Metrik sistemi kullan + + + Display speed in km/h instead of mph. + Hızı mph yerine km/h şeklinde görüntüleyin. + + + Show ETA in 24h Format + Tahmini varış süresini 24 saat formatı şeklinde göster + + + Use 24h format instead of am/pm + 24 saat formatını kullan + + + Show Map on Left Side of UI + Haritayı arayüzün sol tarafında göster + + + Show map on left side when in split screen view. + Bölünmüş ekran görünümündeyken haritayı sol tarafta göster. + + + Aggressive + + + + Moderate + + + + Standard + + + + Relaxed + + + + Driving Personality + + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + End-to-End Longitudinal Control + + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + + + + openpilot longitudinal control may come in a future update. + + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1200,6 +3123,165 @@ This may take up to a minute. Güncelleme başarız oldu + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1246,4 +3328,27 @@ This may take up to a minute. + + WifiUISP + + Scanning for networks... + Ağ aranıyor... + + + CONNECTING... + BAĞLANILIYOR... + + + FORGET + UNUT + + + Forget Wi-Fi Network "%1"? + Wi-Fi ağını unut "%1"? + + + Forget + + + diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 32119ee10f..89813ad786 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -87,6 +87,89 @@ 网络名称:"%1" + + AdvancedNetworkingSP + + Back + 返回 + + + Enable Tethering + 启用WiFi热点 + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + WiFi热点密码 + + + EDIT + 编辑 + + + Enter new tethering password + 输入新的WiFi热点密码 + + + IP Address + IP地址 + + + Enable Roaming + 启用数据漫游 + + + APN Setting + APN设置 + + + Enter APN + 输入APN + + + leave blank for automatic configuration + 留空以自动配置 + + + Cellular Metered + 按流量计费的手机移动网络 + + + Prevent large data uploads when on a metered connection + 当使用按流量计费的连接时,避免上传大流量数据 + + + Hidden Network + 隐藏的网络 + + + CONNECT + + + + Enter SSID + 输入 SSID + + + Enter password + 输入密码 + + + for "%1" + 网络名称:"%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - SPEED + SPEED LIMIT - LIMIT + LIMIT + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + 最高定速 + + + SPEED + SPEED + + + LIMIT + LIMIT + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ 取消 + + CustomOffsetsSettings + + Back + 返回 + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - 您必须接受条款和条件以使用openpilot。 + 您必须接受条款和条件以使用openpilot。 Back @@ -135,6 +333,10 @@ Decline, uninstall %1 拒绝并卸载%1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot要求设备安装的偏航角在左4°和右4°之间,俯仰角在上5°和下9°之间。一般来说,openpilot会持续更新校准,很少需要重置。 + openpilot要求设备安装的偏航角在左4°和右4°之间,俯仰角在上5°和下9°之间。一般来说,openpilot会持续更新校准,很少需要重置。 Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR 配对 + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + 查看 + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + 重置 + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + 重置 + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +695,79 @@ 正在安装…… + + LaneChangeSettings + + Back + 返回 + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +809,48 @@ 等待路线 + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + 小时 + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +881,33 @@ 密码错误 + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + 高级 + + + Enter password + 输入密码 + + + for "%1" + 网络名称:"%1" + + + Wrong password + 密码错误 + + OffroadAlert @@ -467,6 +960,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 设备温度过高。系统正在冷却中,等冷却完毕后才会启动。目前内部组件温度:%1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +1009,186 @@ 重启设备 + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + 分钟 + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + 查看 + + + Country + + + + SELECT + 选择 + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + 更新 + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1219,51 @@ 启用 + + ParamControlSP + + Enable + 启用 + + + Cancel + 取消 + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -590,7 +1318,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -630,6 +1358,30 @@ now 现在 + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -672,6 +1424,147 @@ This may take up to a minute. 系统重置已触发。按下“确认”以清除所有内容和设置,按下“取消”以继续启动。 + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -695,6 +1588,61 @@ This may take up to a minute. 软件 + + SettingsWindowSP + + × + × + + + Device + 设备 + + + Network + 网络 + + + sunnylink + + + + Toggles + 设定 + + + Software + 软件 + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -888,6 +1836,100 @@ This may take up to a minute. 5G + + SidebarSP + + TEMP + 设备温度 + + + HIGH + 过热 + + + GOOD + 良好 + + + OK + 一般 + + + DISABLED + + + + OFFLINE + 离线 + + + REGIST... + + + + ONLINE + 在线 + + + ERROR + 连接出错 + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -963,6 +2005,233 @@ This may take up to a minute. 从未更新 + + SoftwarePanelSP + + Driving Model + + + + SELECT + 选择 + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + 重置设备校准 + + + Warning: You are on a metered connection! + + + + Continue + 继续 + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + N/A + + + km/h + km/h + + + mph + mph + + SshControl @@ -1009,6 +2278,399 @@ This may take up to a minute. 启用SSH + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + N/A + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 配对 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1028,15 +2690,30 @@ This may take up to a minute. 同意 + + TermsPageSP + + Terms & Conditions + 条款和条件 + + + Decline + 拒绝 + + + Scroll to accept + 滑动以接受 + + TogglesPanel Enable openpilot - 启用openpilot + 启用openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - 使用openpilot进行自适应巡航和车道保持辅助。使用此功能时您必须时刻保持注意力。该设置的更改在熄火时生效。 + 使用openpilot进行自适应巡航和车道保持辅助。使用此功能时您必须时刻保持注意力。该设置的更改在熄火时生效。 Enable Lane Departure Warnings @@ -1072,19 +2749,19 @@ This may take up to a minute. Show ETA in 24h Format - 以24小时格式显示预计到达时间 + 以24小时格式显示预计到达时间 Use 24h format instead of am/pm - 使用24小时制代替am/pm + 使用24小时制代替am/pm Show Map on Left Side of UI - 在介面左侧显示地图 + 在介面左侧显示地图 Show map on left side when in split screen view. - 在分屏模式中,将地图置于屏幕左侧。 + 在分屏模式中,将地图置于屏幕左侧。 Experimental Mode @@ -1166,6 +2843,252 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. 即使在openpilot未激活时也启用驾驶员监控。 + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + openpilot纵向控制(Alpha 版) + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + 测试模式 + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + 踩油门时取消控制 + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 启用后,踩下油门踏板将取消openpilot。 + + + Enable Lane Departure Warnings + 启用车道偏离警告 + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 车速超过31mph(50km/h)时,若检测到车辆越过车道线且未打转向灯,系统将发出警告以提醒您返回车道。 + + + Always-On Driver Monitoring + 驾驶员监控常开 + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + 录制并上传驾驶员摄像头 + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + 使用公制单位 + + + Display speed in km/h instead of mph. + 显示车速时,以km/h代替mph。 + + + Show ETA in 24h Format + 以24小时格式显示预计到达时间 + + + Use 24h format instead of am/pm + 使用24小时制代替am/pm + + + Show Map on Left Side of UI + 在介面左侧显示地图 + + + Show map on left side when in split screen view. + 在分屏模式中,将地图置于屏幕左侧。 + + + Aggressive + 积极 + + + Moderate + + + + Standard + 标准 + + + Relaxed + 舒适 + + + Driving Personality + 驾驶风格 + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: + + + End-to-End Longitudinal Control + 端到端纵向控制 + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + 新驾驶视角 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 在低速时,驾驶可视化将转换为道路朝向的广角摄像头,以更好地展示某些转弯。测试模式标志也将显示在右上角。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 + + + openpilot longitudinal control may come in a future update. + openpilot纵向控制可能会在未来的更新中提供。 + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1202,6 +3125,165 @@ This may take up to a minute. 更新失败 + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1248,4 +3330,27 @@ This may take up to a minute. 忽略 + + WifiUISP + + Scanning for networks... + 正在扫描网络…… + + + CONNECTING... + 正在连接…… + + + FORGET + 忽略 + + + Forget Wi-Fi Network "%1"? + 忽略WiFi网络 "%1"? + + + Forget + 忽略 + + diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 9d1c16db9f..517dce04c0 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -87,6 +87,89 @@ 給 "%1" + + AdvancedNetworkingSP + + Back + 回上頁 + + + Enable Tethering + 啟用網路分享 + + + Retain hotspot/tethering state + + + + Enabling this toggle will retain the hotspot/tethering toggle state across reboots. + + + + Tethering Password + 網路分享密碼 + + + EDIT + 編輯 + + + Enter new tethering password + 輸入新的網路分享密碼 + + + IP Address + IP 地址 + + + Enable Roaming + 啟用漫遊 + + + APN Setting + APN 設置 + + + Enter APN + 輸入 APN + + + leave blank for automatic configuration + 留空白將自動配置 + + + Cellular Metered + 行動網路 + + + Prevent large data uploads when on a metered connection + 防止使用行動網路上傳大量的數據 + + + Hidden Network + 隱藏的網路 + + + CONNECT + + + + Enter SSID + 輸入 SSID + + + Enter password + 輸入密碼 + + + for "%1" + 給 "%1" + + + Ngrok Service + + + AnnotatedCameraWidget @@ -103,11 +186,119 @@ SPEED - 速度 + 速度 LIMIT - 速限 + 速限 + + + + AnnotatedCameraWidgetSP + + km/h + km/h + + + mph + mph + + + MAX + 最高 + + + SPEED + 速度 + + + LIMIT + 速限 + + + + AutoLaneChangeTimer + + Auto Lane Change by Blinker + + + + Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge. +Please use caution when using this feature. Only use the blinker when traffic and road conditions permit. + + + + s + + + + Off + + + + Nudge + + + + Nudgeless + + + + + BackupSettings + + Settings backed up for sunnylink Device ID: + + + + Settings updated successfully, but no additional data was returned by the server. + + + + OOPS! We made a booboo. + + + + Please try again later. + + + + Settings restored. Confirm to restart the interface. + + + + No settings found to restore. + + + + + BrightnessControl + + Brightness + + + + Manually adjusts the global brightness of the screen. + + + + Auto + + + + + CameraOffset + + Camera Offset - Laneful Only + + + + Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm + + + + cm + @@ -121,11 +312,18 @@ 取消 + + CustomOffsetsSettings + + Back + 回上頁 + + DeclinePage You must accept the Terms and Conditions in order to use openpilot. - 您必須先接受條款和條件才能使用 openpilot。 + 您必須先接受條款和條件才能使用 openpilot。 Back @@ -135,6 +333,10 @@ Decline, uninstall %1 拒絕並解除安裝 %1 + + You must accept the Terms and Conditions in order to use sunnypilot. + + DestinationWidget @@ -247,7 +449,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以內或朝下偏差 9° 以內。鏡頭在後台會持續自動校準,很少有需要重置的情況。 + openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以內或朝下偏差 9° 以內。鏡頭在後台會持續自動校準,很少有需要重置的情況。 Your device is pointed %1° %2 and %3° %4. @@ -305,6 +507,155 @@ PAIR 配對 + + sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required. + + + + + DevicePanelSP + + TOGGLE + + + + Enable or disable PIN requirement for Fleet Manager access. + + + + Are you sure you want to turn off PIN requirement? + + + + Turn Off + + + + Error Troubleshoot + + + + VIEW + 觀看 + + + Display error from the tmux session when an error has occurred from a system process. + + + + Reset Access Tokens for Map Services + + + + RESET + 重設 + + + Reset self-service access tokens for Mapbox, Amap, and Google Maps. + + + + Are you sure you want to reset access tokens for all map services? + + + + Reset + 重設 + + + Reset sunnypilot Settings + + + + Are you sure you want to reset all sunnypilot settings? + + + + Toggle Onroad/Offroad + + + + OFF + + + + Are you sure you want to unforce offroad? + + + + Unforce + + + + Are you sure you want to force offroad? + + + + Force + + + + Disengage to Force Offroad + + + + Unforce Offroad + + + + Force Offroad + + + + Fleet Manager PIN: + + + + + DisplayPanel + + Driving Screen Off: Non-Critical Events + + + + When <b>Driving Screen Off Timer</b> is not set to <b>"Always On"</b>: + + + + Enabled: Wake the brightness of the screen to display all events. + + + + Disabled: Wake the brightness of the screen to display critical events. + + + + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + DriverViewWindow @@ -344,6 +695,79 @@ 安裝中… + + LaneChangeSettings + + Back + 回上頁 + + + Pause Lateral Below Speed with Blinker + + + + Enable this toggle to pause lateral actuation with blinker when traveling below the desired speed selected below. + + + + Auto Lane Change: Delay with Blind Spot + + + + Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering. + + + + Block Lane Change: Road Edge Detection + + + + Enable this toggle to block lane change when road edge is detected on the stalk actuated side. + + + + + MadsSettings + + Enable ACC+MADS with RES+/SET- + + + + Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button. + + + + Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off. + + + + Toggle M.A.D.S. with Cruise Main + + + + Allows M.A.D.S. engagement/disengagement with "Cruise Main" cruise control button from the steering wheel. + + + + Remain Active + + + + Pause Steering + + + + Steering Mode After Braking + + + + Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot. + +Remain Active: ALC will remain active even after the brake pedal is pressed. +Pause Steering: ALC will be paused after the brake pedal is manually pressed. + + + MapETA @@ -385,6 +809,48 @@ 等待路線 + + MaxTimeOffroad + + Max Time Offroad + + + + Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road). + + + + s + + + + m + m + + + hr + 小時 + + + Always On + + + + Immediate + + + + + MonitoringPanel + + Enable Hands on Wheel Monitoring + + + + Monitor and alert when driver is not keeping the hands on the steering wheel. + + + MultiOptionDialog @@ -415,6 +881,33 @@ 密碼錯誤 + + NetworkingSP + + Scan + + + + Scanning... + + + + Advanced + 進階 + + + Enter password + 輸入密碼 + + + for "%1" + 給 "%1" + + + Wrong password + 密碼錯誤 + + OffroadAlert @@ -467,6 +960,16 @@ Device temperature too high. System cooling down before starting. Current internal component temperature: %1 裝置溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 + + 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. + +%1 + + + + 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. + + OffroadHome @@ -506,6 +1009,186 @@ 請重新啟裝置 + + OnroadScreenOff + + Driving Screen Off Timer + + + + Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs. + + + + s + + + + min + 分鐘 + + + Always On + + + + + OnroadScreenOffBrightness + + Driving Screen Off Brightness (%) + + + + When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio. + + + + Dark + + + + + OnroadSettings + + ONROAD OPTIONS + + + + <b>ONROAD SETTINGS | SUNNYPILOT</b> + + + + + OsmPanel + + Mapd Version + + + + Offline Maps ETA + + + + Time Elapsed + + + + Downloaded Maps + + + + DELETE + + + + This will delete ALL downloaded maps + +Are you sure you want to delete all the maps? + + + + Yes, delete all the maps. + + + + Database Update + + + + CHECK + 檢查 + + + Country + + + + SELECT + 選取 + + + Fetching Country list... + + + + State + + + + Fetching State list... + + + + All + + + + REFRESH + + + + UPDATE + 更新 + + + Download starting... + + + + Error: Invalid download. Retry. + + + + Download complete! + + + + + +Warning: You are on a metered connection! + + + + This will start the download process and it might take a while to complete. + + + + Continue on Metered + + + + Start Download + + + + m + + + + s + + + + Calculating... + + + + Downloaded + + + + Calculating ETA... + + + + Ready + + + + Time remaining: + + + PairingPopup @@ -536,6 +1219,51 @@ 啟用 + + ParamControlSP + + Enable + 啟用 + + + Cancel + 取消 + + + + PathOffset + + Path Offset + + + + Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately. + + + + cm + + + + + PauseLateralSpeed + + Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. + + + + Default + + + + km/h + km/h + + + mph + mph + + PrimeAdWidget @@ -590,7 +1318,7 @@ openpilot - openpilot + openpilot %n minute(s) ago @@ -630,6 +1358,30 @@ now 現在 + + sunnypilot + + + + Update downloaded. Ready to reboot. + + + + Update: Check and Download Update + + + + Reboot: Reboot Device + + + + Update + + + + Updating... + + Reset @@ -672,6 +1424,147 @@ This may take up to a minute. 系統重設已啟動。按下「確認」以清除所有內容和設定,或按下「取消」以繼續開機。 + + SPVehiclesTogglesPanel + + Hyundai/Kia/Genesis + + + + HKG CAN: Smoother Stopping Performance (Beta) + + + + Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control. + + + + Subaru + + + + Manual Parking Brake: Stop and Go (Beta) + + + + Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation! + + + + Toyota/Lexus + + + + Enable Stock Toyota Longitudinal Control + + + + sunnypilot will <b>not</b> take over control of gas and brakes. Stock Toyota longitudinal control will be used. + + + + Allow M.A.D.S. toggling w/ LKAS Button (Beta) + + + + Allows M.A.D.S. engagement/disengagement with "LKAS" button from the steering wheel. + + + + Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Toyota TSS2 Longitudinal: Custom Tuning + + + + Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation. + + + + Enable Automatic Brake Hold (AHB) + + + + WARNING: Only for Toyota/Lexus vehicles with TSS2/LSS2. USE AT YOUR OWN RISK. + + + + When you stop the vehicle completely by depressing the brake pedal, sunnypilot will activate Auto Brake Hold. + + + + Changing this setting takes effect when the car is powered off. + + + + Enable Enhanced Blind Spot Monitor + + + + Enable Toyota Stop and Go Hack + + + + sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk. + + + + Enable Toyota Door Auto Locking + + + + sunnypilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). +Reboot Required. + + + + Enable Toyota Door Auto Unlocking + + + + sunnypilot will attempt to unlock the doors when shift to gear P. +Reboot Required. + + + + Volkswagen + + + + Enable CC Only support + + + + sunnypilot supports Volkswagen MQB CC only platforms with this toggle enabled. Only enable this toggle if your car does not have ACC from the factory. + + + + Start the car to check car compatibility + + + + This platform is already supported, therefore no need to enable this toggle + + + + This platform is not supported + + + + This platform can be supported + + + + sunnypilot will use debugging CAN messages to receive unfiltered BSM signals, allowing detection of more objects. + + + + Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + + SettingsWindow @@ -695,6 +1588,61 @@ This may take up to a minute. 軟體 + + SettingsWindowSP + + × + × + + + Device + 裝置 + + + Network + 網路 + + + sunnylink + + + + Toggles + 設定 + + + Software + 軟體 + + + sunnypilot + + + + OSM + + + + Monitoring + + + + Visuals + + + + Display + + + + Trips + + + + Vehicle + + + Setup @@ -888,6 +1836,100 @@ This may take up to a minute. 5G + + SidebarSP + + TEMP + 溫度 + + + HIGH + 偏高 + + + GOOD + 正常 + + + OK + 一般 + + + DISABLED + + + + OFFLINE + 已離線 + + + REGIST... + + + + ONLINE + 已連線 + + + ERROR + 錯誤 + + + SUNNYLINK + + + + + SlcSettings + + Auto + + + + User Confirm + + + + Engage Mode + + + + Default + + + + Fixed + + + + Percentage + + + + Limit Offset + + + + Set speed limit slightly higher than actual speed limit for a more natural drive. + + + + This platform defaults to <b>Auto</b> mode. <b>User Confirm</b> mode is not supported on this platform. + + + + Select the desired mode to set the cruising speed to the speed limit: + + + + Auto: Automatic speed adjustment on motorways based on speed limit data. + + + + User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit. + + + SoftwarePanel @@ -963,6 +2005,233 @@ This may take up to a minute. 從未更新 + + SoftwarePanelSP + + Driving Model + + + + SELECT + 選取 + + + PENDING + + + + Downloading Driving model + + + + (CACHED) + + + + Driving model + + + + downloaded + + + + Downloading Navigation model + + + + Navigation model + + + + Downloading Metadata model + + + + Metadata model + + + + Downloads have failed, please try swapping the model! + + + + Failed: + + + + Fetching models... + + + + Select a Driving Model + + + + Download has started in the background. + + + + We STRONGLY suggest you to reset calibration. Would you like to do that now? + + + + Reset Calibration + 重設校準 + + + Warning: You are on a metered connection! + + + + Continue + 繼續 + + + on Metered + + + + + SpeedLimitPolicySettings + + Speed Limit Source Policy + + + + Nav + + + + Only + + + + Map + + + + Car + + + + First + + + + Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning + + + + Nav Only: Data from Mapbox active navigation only. + + + + Map Only: Data from OpenStreetMap only. + + + + Car Only: Data from the car's built-in sources (if available). + + + + Nav First: Nav -> Map -> Car + + + + Map First: Map -> Nav -> Car + + + + Car First: Car -> Nav -> Map + + + + + SpeedLimitValueOffset + + km/h + km/h + + + mph + mph + + + + SpeedLimitWarningSettings + + Off + + + + Display + + + + Chime + + + + Speed Limit Warning + + + + Warning with speed limit flash + + + + When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset. + + + + Default + + + + Fixed + + + + Percentage + + + + Warning Offset + + + + Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit. + + + + Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning. + + + + Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset. + + + + + SpeedLimitWarningValueOffset + + N/A + 無法使用 + + + km/h + km/h + + + mph + mph + + SshControl @@ -1009,6 +2278,399 @@ This may take up to a minute. 啟用 SSH 服務 + + SunnylinkPanel + + Enable sunnylink + + + + Device ID + + + + N/A + 無法使用 + + + This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that. + + + + 🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀 + + + + 👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉. + + + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 配對 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Manage Settings + + + + Backup Settings + + + + Are you sure you want to backup sunnypilot settings? + + + + Back Up + + + + Restore Settings + + + + Are you sure you want to restore the last backed up sunnypilot settings? + + + + Restore + + + + THANKS + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + Backing up... + + + + Restoring... + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + + + + SunnypilotPanel + + Enable M.A.D.S. + + + + Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement. + + + + Laneless for Curves in "Auto" Mode + + + + While in Auto Lane, switch to Laneless for current/future curves. + + + + Speed Limit Control (SLC) + + + + When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed. + + + + Enable Vision-based Turn Speed Control (V-TSC) + + + + Use vision path predictions to estimate the appropriate speed to drive through turns ahead. + + + + Enable Map Data Turn Speed Control (M-TSC) (Beta) + + + + Use curvature information from map data to define speed limits to take turns ahead. + + + + ACC +/-: Long Press Reverse + + + + Change the ACC +/- buttons behavior with cruise speed change in sunnypilot. + + + + Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric) + + + + Enabled: Short = 5 (imperial) / 10 (metric), Long=1 + + + + Custom Offsets + + + + Neural Network Lateral Control (NNLC) + + + + Enforce Torque Lateral Control + + + + Enable this to enforce sunnypilot to steer with Torque lateral control. + + + + Enable Self-Tune + + + + Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. + + + + Less Restrict Settings for Self-Tune (Beta) + + + + Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. + + + + Enable Custom Tuning + + + + Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within "selfdrive/torque_data". The values will also be used live when "Override Self-Tune" toggle is enabled. + + + + Manual Real-Time Tuning + + + + Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values. + + + + Quiet Drive 🤫 + + + + sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on. + + + + Green Traffic Light Chime (Beta) + + + + A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged. + + + + Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly. + + + + Lead Vehicle Departure Alert + + + + Enable this will notify when the leading vehicle drives away. + + + + Customize M.A.D.S. + + + + Customize Lane Change + + + + Customize Offsets + + + + Customize Speed Limit Control + + + + Customize Warning + + + + Customize Source + + + + Laneful + + + + Laneless + + + + Auto + + + + Dynamic Lane Profile + + + + Speed Limit Assist + + + + Real-time and Offline + + + + Offline Only + + + + Dynamic Lane Profile is not available with the current Driving Model + + + + Custom Offsets is not available with the current Driving Model + + + + NNLC is currently not available on this platform. + + + + Match: "Exact" is ideal, but "Fuzzy" is fine too. Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server if there are any issues: + + + + Start the car to check car compatibility + + + + NNLC Not Loaded + + + + NNLC Loaded + + + + Fuzzy + + + + Exact + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server and donate logs to get NNLC loaded for your car: + + + + Match + + + + Formerly known as <b>"NNFF"</b>, this replaces the lateral <b>"torque"</b> controller, with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy. + + + + Reach out to the sunnypilot team in the following channel at the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported: + + + + Add custom offsets to Camera and Path in sunnypilot. + + + + Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions. + + + TermsPage @@ -1028,15 +2690,30 @@ This may take up to a minute. 接受 + + TermsPageSP + + Terms & Conditions + 條款和條件 + + + Decline + 拒絕 + + + Scroll to accept + 滑動至頁尾接受條款 + + TogglesPanel Enable openpilot - 啟用 openpilot + 啟用 openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - 使用 openpilot 的主動式巡航和車道保持功能,開啟後您需要持續集中注意力,設定變更在重新啟動車輛後生效。 + 使用 openpilot 的主動式巡航和車道保持功能,開啟後您需要持續集中注意力,設定變更在重新啟動車輛後生效。 Enable Lane Departure Warnings @@ -1072,19 +2749,19 @@ This may take up to a minute. Show ETA in 24h Format - 預計到達時間單位改用 24 小時制 + 預計到達時間單位改用 24 小時制 Use 24h format instead of am/pm - 使用 24 小時制。(預設值為 12 小時制) + 使用 24 小時制。(預設值為 12 小時制) Show Map on Left Side of UI - 將地圖顯示在畫面的左側 + 將地圖顯示在畫面的左側 Show map on left side when in split screen view. - 進入分割畫面後,地圖將會顯示在畫面的左側。 + 進入分割畫面後,地圖將會顯示在畫面的左側。 Experimental Mode @@ -1166,6 +2843,252 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. 即使在openpilot未激活時也啟用駕駛監控。 + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + + TogglesPanelSP + + Enable sunnypilot + + + + Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + + + + openpilot Longitudinal Control (Alpha) + openpilot 縱向控制 (Alpha 版) + + + WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, sunnypilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. + + + + Custom Stock Longitudinal Control + + + + When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses. +This feature must be used along with SLC, and/or V-TSC, and/or M-TSC. + + + + Experimental Mode + 實驗模式 + + + Enable Dynamic Experimental Control + + + + Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal. + + + + Enable Dynamic Personality + + + + Enable this to allow sunnypilot to dynamically adjust following distance and reaction based on your "Driving Personality" setting. Instead of predefined settings for each personality, every personality now adapts dynamically according to your speed and the distance to the lead car. + + + + Disengage on Accelerator Pedal + 油門取消控車 + + + When enabled, pressing the accelerator pedal will disengage openpilot. + 啟用後,踩踏油門將會取消 openpilot 控制。 + + + Enable Lane Departure Warnings + 啟用車道偏離警告 + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + 車速在時速 50 公里 (31 英里) 以上且未打方向燈的情況下,如果偵測到車輛駛出目前車道線時,發出車道偏離警告。 + + + Always-On Driver Monitoring + 駕駛監控常開 + + + Enable driver monitoring even when sunnypilot is not engaged. + + + + Record and Upload Driver Camera + 記錄並上傳駕駛監控影像 + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + 上傳駕駛監控的錄影來協助我們提升駕駛監控的準確率。 + + + Disable Onroad Uploads + + + + Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC). + + + + Use Metric System + 使用公制單位 + + + Display speed in km/h instead of mph. + 啟用後,速度單位顯示將從 mp/h 改為 km/h。 + + + Show ETA in 24h Format + 預計到達時間單位改用 24 小時制 + + + Use 24h format instead of am/pm + 使用 24 小時制。(預設值為 12 小時制) + + + Show Map on Left Side of UI + 將地圖顯示在畫面的左側 + + + Show map on left side when in split screen view. + 進入分割畫面後,地圖將會顯示在畫面的左側。 + + + Aggressive + 積極 + + + Moderate + + + + Standard + 標準 + + + Relaxed + 舒適 + + + Driving Personality + 駕駛風格 + + + Standard is recommended. In moderate/aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. + + + + Sport + + + + Normal + + + + Eco + + + + Stock + + + + Acceleration Personality + + + + Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these acceleration personality within Onroad Settings on the driving screen. + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: + + + End-to-End Longitudinal Control + 端到端縱向控制 + + + Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + 新的駕駛視覺介面 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 在低速時,駕駛可視化將切換至道路朝向的廣角攝影機,以更好地顯示某些彎道。在右上角還會顯示「實驗模式」的標誌。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。 + + + openpilot longitudinal control may come in a future update. + openpilot 縱向控制可能會在未來的更新中提供。 + + + An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode. + + + + + TorqueFriction + + FRICTION + + + + Adjust Friction for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + + + + TorqueMaxLatAccel + + LAT_ACCEL_FACTOR + + + + Adjust Max Lateral Acceleration for the Torque Lateral Controller. <b>Live</b>: Override self-tune values; <b>Offline</b>: Override self-tune offline values at car restart. + + + + Real-time and Offline + + + + Offline Only + + Updater @@ -1202,6 +3125,165 @@ This may take up to a minute. 更新失敗 + + VehiclePanel + + Updating this setting takes effect when the car is powered off. + + + + Select your car + + + + + VisualsPanel + + Display Braking Status + + + + Enable this will turn the current speed value to red while the brake is used. + + + + Display Stand Still Timer + + + + Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions). + + + + Display DM Camera in Reverse Gear + + + + Show Driver Monitoring camera while the car is in reverse gear. + + + + OSM: Show debug UI elements + + + + OSM: Show UI elements that aid debugging. + + + + Display Feature Status + + + + Display the statuses of certain features on the driving screen. + + + + Enable Onroad Settings + + + + Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu. + + + + Speedometer: Display True Speed + + + + Display the true vehicle current speed from wheel speed sensors. + + + + Speedometer: Hide from Onroad Screen + + + + Display End-to-end Longitudinal Status (Beta) + + + + Enable this will display an icon that appears when the End-to-end model decides to start or stop. + + + + Navigation: Display in Full Screen + + + + Enable this will display the built-in navigation in full screen.<br>To switch back to driving view, <font color='yellow'>tap on the border edge</font>. + + + + Map: Display 3D Buildings + + + + Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation. + + + + Off + + + + 5 Metrics + + + + 10 Metrics + + + + Developer UI + + + + Display real-time parameters and metrics from various sources. + + + + Distance + + + + Speed + + + + Distance +Speed + + + + Display Metrics Below Chevron + + + + Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control). + + + + RAM + + + + CPU + + + + GPU + + + + Max + + + + Display Temperature on Sidebar + + + WiFiPromptWidget @@ -1248,4 +3330,27 @@ This may take up to a minute. 清除 + + WifiUISP + + Scanning for networks... + 掃描無線網路中... + + + CONNECTING... + 連線中... + + + FORGET + 清除 + + + Forget Wi-Fi Network "%1"? + 清除 Wi-Fi 網路 "%1"? + + + Forget + 清除 + + diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index ff6691a780..43bec276ae 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -240,7 +240,7 @@ void UIState::updateStatus() { } // #ifdef SUNNYPILOT -// #include "selfdrive/ui/sunnypilot/qt/sp_priv_ui_scene.h" +// #include "selfdrive/ui/sunnypilot/qt/ui_scene.h" // #define UIScene UISceneSP // #endif UIState::UIState(QObject *parent) : QObject(parent) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 8e5fe6da34..5bcac8f45c 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -113,7 +113,7 @@ typedef struct UIScene { } UIScene; #ifdef SUNNYPILOT -#include "sunnypilot/qt/sp_priv_ui_scene.h" +#include "sunnypilot/qt/ui_scene.h" #define UIScene UISceneSP #endif class UIState : public QObject { diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 91472eb214..f1700ebe20 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -7,7 +7,7 @@ #include #ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/sp_priv_controls.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #define ElidedLabel ElidedLabelSP #else #include "selfdrive/ui/qt/widgets/controls.h" From e8ca47ebfaf7d302df70dd63535472968744edd0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 26 Jul 2024 10:17:05 +0200 Subject: [PATCH 491/575] Enhance chevron display with additional lead car metrics Added a third chevron type to display Time-to-Collision (TTC) metrics for the lead car. Modified logic to handle the new metrics type, allowing for comprehensive data on distance, speed, and TTC. Adjusted text box dimensions as needed for the additional information. --- .../ui/sunnypilot/qt/onroad/annotated_camera.cc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc index 5149d0c6ac..8ed9205c7f 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc @@ -1335,20 +1335,28 @@ void AnnotatedCameraWidgetSP::drawLead(QPainter &painter, const cereal::RadarSta 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 From eb7f4274f80b01f0756612d6cd9034eb59bcf4f1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 26 Jul 2024 11:04:41 +0200 Subject: [PATCH 492/575] Merge conflict resolving -> custom stock long planner --- .../qt/offroad/settings/settings.cc | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc index ee42547566..321acec2fc 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc @@ -31,6 +31,12 @@ TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) { tr("When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses.\nThis feature must be used along with SLC, and/or V-TSC, and/or M-TSC."), "../assets/offroad/icon_blank.png", }, + { + "CustomStockLongPlanner", + tr("Use Planner Speed"), + "", + "../assets/offroad/icon_blank.png", + }, { "ExperimentalMode", tr("Experimental Mode"), @@ -197,6 +203,7 @@ void TogglesPanelSP::updateToggles() { auto custom_stock_long_toggle = toggles["CustomStockLong"]; auto dec_toggle = toggles["DynamicExperimentalControl"]; auto dynamic_personality_toggle = toggles["DynamicPersonality"]; + auto custom_stock_long_planner = toggles["CustomStockLongPlanner"]; const QString e2e_description = QString("%1
" "

%2


" "%3
" @@ -235,16 +242,22 @@ void TogglesPanelSP::updateToggles() { accel_personality_setting->setEnabled(true); op_long_toggle->setEnabled(true); custom_stock_long_toggle->setEnabled(false); + custom_stock_long_planner->setEnabled(false); params.remove("CustomStockLong"); dec_toggle->setEnabled(true); dynamic_personality_toggle->setEnabled(true); } else if (custom_stock_long_toggle->isToggled()) { op_long_toggle->setEnabled(false); - experimental_mode_toggle->setEnabled(false); - long_personality_setting->setEnabled(false); - accel_personality_setting->setEnabled(false); + experimental_mode_toggle->setEnabled(custom_stock_long_planner->isToggled()); + experimental_mode_toggle->setDescription(e2e_description); + custom_stock_long_planner->setEnabled(true); + long_personality_setting->setEnabled(custom_stock_long_planner->isToggled()); + accel_personality_setting->setEnabled(custom_stock_long_planner->isToggled()); + dec_toggle->setEnabled(experimental_mode_toggle->isToggled()); + if(!custom_stock_long_planner->isToggled()) { + params.remove("ExperimentalMode"); + } params.remove("ExperimentalLongitudinalEnabled"); - params.remove("ExperimentalMode"); } else { // no long for now experimental_mode_toggle->setEnabled(false); @@ -269,6 +282,7 @@ void TogglesPanelSP::updateToggles() { custom_stock_long_toggle->setEnabled(CP.getCustomStockLongAvailable()); dec_toggle->setEnabled(false); dynamic_personality_toggle->setEnabled(false); + custom_stock_long_planner->setEnabled(false); params.remove("DynamicExperimentalControl"); params.remove("DynamicPersonality"); } @@ -276,6 +290,7 @@ void TogglesPanelSP::updateToggles() { experimental_mode_toggle->refresh(); op_long_toggle->refresh(); custom_stock_long_toggle->refresh(); + custom_stock_long_planner->refresh(); dec_toggle->refresh(); dynamic_personality_toggle->refresh(); } else { @@ -284,6 +299,7 @@ void TogglesPanelSP::updateToggles() { custom_stock_long_toggle->setVisible(false); dec_toggle->setVisible(false); dynamic_personality_toggle->setVisible(false); + custom_stock_long_planner->setVisible(false); } } From 8465daa80f05362313258ea9dc27adafe2c72fad Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 26 Jul 2024 11:05:05 +0200 Subject: [PATCH 493/575] Merge conflict resolving -> EnforceTorqueLateral changed to use ToggleControlSP as expected --- .../ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc index 5995a1beca..1babe316cf 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc @@ -329,7 +329,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { } }); - connect(toggles["EnforceTorqueLateral"], &ToggleControl::toggleFlipped, [=](bool state) { + connect(toggles["EnforceTorqueLateral"], &ToggleControlSP::toggleFlipped, [=](bool state) { if (state) { toggles["NNFF"]->setEnabled(false); params.putBool("NNFF", false); From d20010ee3101cfa718c8f2fd536f3e026310a90d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 26 Jul 2024 17:35:45 +0200 Subject: [PATCH 494/575] Refactor option flag for UI customization Changed the build option flag from '--sunnypilot' to '--stock-ui' to improve clarity. Updated related conditions and defaults in 'selfdrive/ui/SConscript' and 'SConstruct' files for consistency. Removed unused internal developer check logic from 'SConstruct'. --- SConstruct | 48 ++++------------------------------------- selfdrive/ui/SConscript | 6 +++--- 2 files changed, 7 insertions(+), 47 deletions(-) diff --git a/SConstruct b/SConstruct index 50e60c3b95..d84c341b4b 100644 --- a/SConstruct +++ b/SConstruct @@ -7,8 +7,6 @@ import numpy as np import SCons.Errors -from openpilot.common.basedir import BASEDIR - SCons.Warnings.warningAsException(True) # pending upstream fix - https://github.com/SCons/scons/issues/4461 @@ -20,44 +18,6 @@ UBUNTU_FOCAL = int(subprocess.check_output('[ -f /etc/os-release ] && . /etc/os- Export('UBUNTU_FOCAL') _DEBUG = False -def is_internal_developer(debug=False): - def collect_required_gpg_key_ids(keys_dir): - try: - key_ids = [f.split('.')[0] for f in os.listdir(keys_dir) if f.endswith(".gpg")] - if debug: - print(f"SP: Required GPG key IDs: {key_ids}") - return key_ids - except OSError as e: - if debug: - print(f"SP: Failed to read GPG key IDs from {keys_dir}. Error: {e}") - return [] - - def is_key_available(required_gpg_key_ids): - for key_id in required_gpg_key_ids: - try: - result = subprocess.check_output(['gpg', '--list-keys', key_id], stderr=subprocess.STDOUT) - if key_id in result.decode(): - if debug: - print(f"SP: GPG key {key_id} is available.") - return True - except subprocess.CalledProcessError as e: - if debug: - print(f"SP: Failed to list GPG key {key_id}. Error:", e.output.decode().strip()) - return False - - keys_dir = os.path.join(BASEDIR, ".git-crypt/keys/default/0") - required_gpg_key_ids = collect_required_gpg_key_ids(keys_dir) - - sunnypilot = is_key_available(required_gpg_key_ids) - - if sunnypilot: - print("SP: Confirmed sunnypilot internal developer.") - print("SP: Loading sunnypilot elements ...") - elif debug: - print("SP: None of the required GPG keys are available.") - - return sunnypilot - Decider('MD5-timestamp') SetOption('num_jobs', int(os.cpu_count()/2)) @@ -113,11 +73,11 @@ AddOption('--minimal', default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS) help='the minimum build to run openpilot. no tests, tools, etc.') -AddOption('--sunnypilot', +AddOption('--stock-ui', action='store_true', - dest='sunnypilot', - default=is_internal_developer(_DEBUG) or True, # check if the current user is a sunnypilot developer. TODO: Remove the 'or True' part once the GPG keys are available. - help='build sunnypilot elements and other sunnypilot-specific items that are meant for internal development') + dest='stock_ui', + default=False, + help='Build the stock UI instead of sunnypilot UI') ## Architecture name breakdown (arch) ## - larch64: linux tici aarch64 diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 253173311c..aea739c023 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -21,7 +21,7 @@ sp_widgets_src = [] sp_maps_widgets_src = [] sp_qt_src = [] sp_qt_util = [] -if GetOption('sunnypilot'): +if not GetOption('stock_ui'): SConscript(['sunnypilot/SConscript']) Import('sp_widgets_src', 'sp_maps_widgets_src', 'sp_qt_src', "sp_qt_util") @@ -32,7 +32,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src -qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if GetOption('sunnypilot') else [] +qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if not GetOption('stock_ui') else [] if maps: base_libs += ['QMapLibre'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc", @@ -81,7 +81,7 @@ asset_obj = qt_env.Object("assets", assets) qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) # spinner and text window -text_cc_path = "qt/text.cc" if not GetOption('sunnypilot') else "sunnypilot/qt/text.cc" +text_cc_path = "qt/text.cc" if GetOption('stock_ui') else "sunnypilot/qt/text.cc" qt_env.Program("_text", [text_cc_path], LIBS=qt_libs) qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) From 7f90846971c0e322621adaa64ba2d85862df92a1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 26 Jul 2024 19:19:37 +0200 Subject: [PATCH 495/575] Refactor conditional process management for external scripts Refactor the way external scripts 'gitlab_runner.sh' and 'sunnylink_uploader.py' are included in the process list based on their existence. This improves code readability and ensures that the inclusion checks are performed at a single point. --- system/manager/process_config.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index a718ea021b..480689b1b5 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -45,8 +45,7 @@ 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")) + return not PC and params.get_bool("EnableGitlabRunner") and only_offroad(started, params, CP) def model_use_nav(started, params, CP: car.CarParams) -> bool: custom_model_metadata = CustomModelMetadata(params=params, init_only=True) @@ -62,7 +61,7 @@ def sunnylink_need_register_shim(started, params, CP: car.CarParams) -> bool: 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") + return use_sunnylink_uploader(params) procs = [ DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), @@ -120,12 +119,15 @@ 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("./gitlab_runner.sh"): + procs += [NativeProcess("gitlab_runner_start", "system/manager", ["./gitlab_runner.sh", "start"], use_gitlab_runner, sigkill=False)] + +if os.path.exists("../loggerd/sunnylink_uploader.py"): + procs += [PythonProcess("sunnylink_uploader", "system.loggerd.sunnylink_uploader", use_sunnylink_uploader_shim)] + managed_processes = {p.name: p for p in procs} From bc4005234456724efc779bc72f4dcdc46176eaed Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 27 Jul 2024 01:49:57 -0400 Subject: [PATCH 496/575] make sure to use our UI --- selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc index 8ed9205c7f..2ac715a2c5 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc @@ -1444,7 +1444,7 @@ void AnnotatedCameraWidgetSP::paintEvent(QPaintEvent *event) { painter.setPen(Qt::NoPen); if (s->scene.world_objects_visible) { - update_model(s, model); + sp_update_model(s, model); drawLaneLines(painter, s); if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) { From c10121b5708e4659d0c38cca1dc58c8df46c532e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 27 Jul 2024 14:40:50 -0400 Subject: [PATCH 497/575] unused --- SConstruct | 1 - 1 file changed, 1 deletion(-) diff --git a/SConstruct b/SConstruct index d84c341b4b..9a9f410cfc 100644 --- a/SConstruct +++ b/SConstruct @@ -16,7 +16,6 @@ TICI = os.path.isfile('/TICI') AGNOS = TICI UBUNTU_FOCAL = int(subprocess.check_output('[ -f /etc/os-release ] && . /etc/os-release && [ "$ID" = "ubuntu" ] && [ "$VERSION_ID" = "20.04" ] && echo 1 || echo 0', shell=True, encoding='utf-8').rstrip()) Export('UBUNTU_FOCAL') -_DEBUG = False Decider('MD5-timestamp') From 9d0ba054f33ec1a096e710baf70eb9431c8a7feb Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 27 Jul 2024 15:08:25 -0400 Subject: [PATCH 498/575] make sure map rendering is compiled --- SConstruct | 4 ++++ selfdrive/ui/SConscript | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 9a9f410cfc..ff91325641 100644 --- a/SConstruct +++ b/SConstruct @@ -181,6 +181,10 @@ if arch != "Darwin": cflags += ['-DSWAGLOG="\\"common/swaglog.h\\""'] cxxflags += ['-DSWAGLOG="\\"common/swaglog.h\\""'] +if not GetOption('stock_ui'): + cflags += ["-DSUNNYPILOT"] + cxxflags += ["-DSUNNYPILOT"] + ccflags_option = GetOption('ccflags') if ccflags_option: ccflags += ccflags_option.split(' ') diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index aea739c023..a825a74ab0 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -32,7 +32,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + sp_widgets_src -qt_env['CPPDEFINES'] = ["SUNNYPILOT"] if not GetOption('stock_ui') else [] +qt_env['CPPDEFINES'] = [] if maps: base_libs += ['QMapLibre'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc", From 3c8acc8c770bb0066ad215b1348db41fa6a475f4 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 27 Jul 2024 22:47:52 +0200 Subject: [PATCH 499/575] Add override to updateBrightness in DeviceSP Implemented an override for the updateBrightness function in DeviceSP. This ensures proper handling of UIState type by dynamically casting it to UIStateSP. --- selfdrive/ui/sunnypilot/ui.h | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/sunnypilot/ui.h b/selfdrive/ui/sunnypilot/ui.h index 149f601860..84a37a2eac 100644 --- a/selfdrive/ui/sunnypilot/ui.h +++ b/selfdrive/ui/sunnypilot/ui.h @@ -118,6 +118,7 @@ public: DeviceSP(QObject *parent = 0); protected: void updateBrightness(const UIStateSP &s); + void updateBrightness(const UIState &s) override { updateBrightness(dynamic_cast(s)); } }; DeviceSP *deviceSP(); From f242f6b55d0d5d6dddb0f8c2f738bbe461946881 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 27 Jul 2024 23:07:19 +0200 Subject: [PATCH 500/575] No more rave --- selfdrive/ui/sunnypilot/ui.cc | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index 7460a7acf7..592e20b387 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -293,7 +293,21 @@ DeviceSP::DeviceSP(QObject *parent) : Device(parent){ //todo: revisit this void DeviceSP::updateBrightness(const UIStateSP &s) { - Device::updateBrightness(s); + float clipped_brightness = offroad_brightness; + if (s.scene.started && s.scene.light_sensor > 0) { + clipped_brightness = s.scene.light_sensor; + + // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm + if (clipped_brightness <= 8) { + clipped_brightness = (clipped_brightness / 903.3); + } else { + clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0); + } + + // Scale back to 10% to 100% + clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); + } + int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; From 0894686bb57662ae3ff464269c0676c5e6ccfa68 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 27 Jul 2024 23:09:30 +0200 Subject: [PATCH 501/575] Refactor brightness update logic in DeviceSP Remove redundant brightness calculation from DeviceSP by leveraging Device's implementation. Introduce a conditional return to handle Sunnypilot-specific logic cleanly. --- selfdrive/ui/sunnypilot/ui.cc | 16 +--------------- selfdrive/ui/ui.cc | 3 ++- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index 592e20b387..7460a7acf7 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -293,21 +293,7 @@ DeviceSP::DeviceSP(QObject *parent) : Device(parent){ //todo: revisit this void DeviceSP::updateBrightness(const UIStateSP &s) { - float clipped_brightness = offroad_brightness; - if (s.scene.started && s.scene.light_sensor > 0) { - clipped_brightness = s.scene.light_sensor; - - // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm - if (clipped_brightness <= 8) { - clipped_brightness = (clipped_brightness / 903.3); - } else { - clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0); - } - - // Scale back to 10% to 100% - clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); - } - + Device::updateBrightness(s); int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 43bec276ae..5d6b6c5722 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -339,7 +339,8 @@ void Device::updateBrightness(const UIState &s) { // Scale back to 10% to 100% clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); } - + RETURN_IF_SUNNYPILOT + int brightness = brightness_filter.update(clipped_brightness); if (!awake) { brightness = 0; From b9785e33e75a9aed1aff579ecbd2665fb42201aa Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 27 Jul 2024 23:26:16 -0400 Subject: [PATCH 502/575] use sp's --- selfdrive/ui/qt/maps/map_panel.cc | 1 + selfdrive/ui/sunnypilot/qt/maps/map.cc | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index 4f92c98482..cd448483ff 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -8,6 +8,7 @@ #ifdef SUNNYPILOT #include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/sunnypilot/qt/maps/map.h" +#define MapWindow MapWindowSP #else #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/maps/map.h" diff --git a/selfdrive/ui/sunnypilot/qt/maps/map.cc b/selfdrive/ui/sunnypilot/qt/maps/map.cc index 7cb4a77539..9ce1236a91 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/map.cc +++ b/selfdrive/ui/sunnypilot/qt/maps/map.cc @@ -8,6 +8,10 @@ MapWindowSP::MapWindowSP(const QMapLibre::Settings &settings) : MapWindow(settin QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &MapWindowSP::updateState); } +MapWindowSP::~MapWindowSP() { + makeCurrent(); +} + void MapWindowSP::initLayers() { if (!m_map->layerExists("navLayer")) { m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiStateSP()->scene.navigate_on_openpilot_deprecated)); From a47a9928714b423e65adf67addbe577b61a2c75c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 27 Jul 2024 23:27:46 -0400 Subject: [PATCH 503/575] map: less --- selfdrive/ui/sunnypilot/qt/maps/map.cc | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/maps/map.cc b/selfdrive/ui/sunnypilot/qt/maps/map.cc index 9ce1236a91..0482668fb6 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/map.cc +++ b/selfdrive/ui/sunnypilot/qt/maps/map.cc @@ -62,19 +62,8 @@ void MapWindowSP::initLayers() { } void MapWindowSP::updateState(const UIStateSP &s) { - if (!uiStateSP()->scene.started) { - return; - } + MapWindow::updateState(s); const SubMaster &sm = *(s.sm); - update(); - - // on rising edge of a valid system time, reinitialize the map to set a new token - if (sm.valid("clocks") && !prev_time_valid) { - LOGW("Time is now valid, reinitializing map"); - m_settings.setApiKey(get_mapbox_token()); - initializeGL(); - } - prev_time_valid = sm.valid("clocks"); if (sm.updated("modelV2")) { // set path color on change, and show map on rising edge of navigate on openpilot @@ -91,8 +80,6 @@ void MapWindowSP::updateState(const UIStateSP &s) { } uiStateSP()->scene.navigate_on_openpilot_deprecated = nav_enabled; } - - MapWindow::updateState(s); } void MapWindowSP::offroadTransition(bool offroad) { From abe5fc9146d613f88092df208a0d04b0a65ea71b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 27 Jul 2024 23:58:44 -0400 Subject: [PATCH 504/575] map: working!!! --- selfdrive/ui/qt/maps/map.cc | 2 + selfdrive/ui/qt/maps/map.h | 4 +- selfdrive/ui/sunnypilot/qt/maps/map.cc | 182 ++++++++++++++++++++++++- 3 files changed, 183 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index f9059c6f08..80cd82277e 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -59,6 +59,7 @@ MapWindow::~MapWindow() { } void MapWindow::initLayers() { + RETURN_IF_SUNNYPILOT // This doesn't work from initializeGL if (!m_map->layerExists("modelPathLayer")) { qDebug() << "Initializing modelPathLayer"; @@ -118,6 +119,7 @@ void MapWindow::initLayers() { } void MapWindow::updateState(const UIState &s) { + RETURN_IF_SUNNYPILOT if (!uiState()->scene.started) { return; } diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index fd4218bfc1..00e8b6f78f 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -46,7 +46,7 @@ protected: void initLayers(); -private: +protected: void mousePressEvent(QMouseEvent *ev) final; void mouseDoubleClickEvent(QMouseEvent *ev) final; void mouseMoveEvent(QMouseEvent *ev) final; @@ -60,7 +60,7 @@ protected: bool loaded_once = false; bool prev_time_valid = true; -private: +protected: // Panning QPointF m_lastPos; int interaction_counter = 0; diff --git a/selfdrive/ui/sunnypilot/qt/maps/map.cc b/selfdrive/ui/sunnypilot/qt/maps/map.cc index 0482668fb6..d38f3f27b9 100644 --- a/selfdrive/ui/sunnypilot/qt/maps/map.cc +++ b/selfdrive/ui/sunnypilot/qt/maps/map.cc @@ -3,6 +3,10 @@ #include "common/swaglog.h" #include "selfdrive/ui/sunnypilot/qt/maps/map_helpers.h" +const float MAX_ZOOM = 17; +const float MIN_ZOOM = 14; +const float MAX_PITCH = 50; + MapWindowSP::MapWindowSP(const QMapLibre::Settings &settings) : MapWindow(settings) { QObject::disconnect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState); QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &MapWindowSP::updateState); @@ -13,8 +17,61 @@ MapWindowSP::~MapWindowSP() { } void MapWindowSP::initLayers() { + // This doesn't work from initializeGL + if (!m_map->layerExists("modelPathLayer")) { + qDebug() << "Initializing modelPathLayer"; + QVariantMap modelPath; + //modelPath["id"] = "modelPathLayer"; + modelPath["type"] = "line"; + modelPath["source"] = "modelPathSource"; + m_map->addLayer("modelPathLayer", modelPath); + m_map->setPaintProperty("modelPathLayer", "line-color", QColor("red")); + m_map->setPaintProperty("modelPathLayer", "line-width", 5.0); + m_map->setLayoutProperty("modelPathLayer", "line-cap", "round"); + } if (!m_map->layerExists("navLayer")) { + qDebug() << "Initializing navLayer"; + QVariantMap nav; + nav["type"] = "line"; + nav["source"] = "navSource"; + m_map->addLayer("navLayer", nav, "road-intersection"); + + QVariantMap transition; + transition["duration"] = 400; // ms m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiStateSP()->scene.navigate_on_openpilot_deprecated)); + m_map->setPaintProperty("navLayer", "line-color-transition", transition); + m_map->setPaintProperty("navLayer", "line-width", 7.5); + m_map->setLayoutProperty("navLayer", "line-cap", "round"); + } + if (!m_map->layerExists("pinLayer")) { + qDebug() << "Initializing pinLayer"; + m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg")); + QVariantMap pin; + pin["type"] = "symbol"; + pin["source"] = "pinSource"; + m_map->addLayer("pinLayer", pin); + m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport"); + m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker"); + m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true); + m_map->setLayoutProperty("pinLayer", "icon-allow-overlap", true); + m_map->setLayoutProperty("pinLayer", "symbol-sort-key", 0); + m_map->setLayoutProperty("pinLayer", "icon-anchor", "bottom"); + } + if (!m_map->layerExists("carPosLayer")) { + qDebug() << "Initializing carPosLayer"; + m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg")); + + QVariantMap carPos; + carPos["type"] = "symbol"; + carPos["source"] = "carPosSource"; + m_map->addLayer("carPosLayer", carPos); + m_map->setLayoutProperty("carPosLayer", "icon-pitch-alignment", "map"); + m_map->setLayoutProperty("carPosLayer", "icon-image", "label-arrow"); + m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5); + m_map->setLayoutProperty("carPosLayer", "icon-ignore-placement", true); + m_map->setLayoutProperty("carPosLayer", "icon-allow-overlap", true); + // TODO: remove, symbol-sort-key does not seem to matter outside of each layer + m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0); } if ((!m_map->layerExists("buildingsLayer")) && uiStateSP()->scene.map_3d_buildings) { // Could put this behind the cellular metered toggle in case it increases data usage qDebug() << "Initializing buildingsLayer"; @@ -62,15 +119,26 @@ void MapWindowSP::initLayers() { } void MapWindowSP::updateState(const UIStateSP &s) { - MapWindow::updateState(s); + if (!uiState()->scene.started) { + return; + } const SubMaster &sm = *(s.sm); + update(); + + // on rising edge of a valid system time, reinitialize the map to set a new token + if (sm.valid("clocks") && !prev_time_valid) { + LOGW("Time is now valid, reinitializing map"); + m_settings.setApiKey(get_mapbox_token()); + initializeGL(); + } + prev_time_valid = sm.valid("clocks"); if (sm.updated("modelV2")) { // set path color on change, and show map on rising edge of navigate on openpilot auto car_control = sm["carControl"].getCarControl(); bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabledDEPRECATED() && (sm["controlsState"].getControlsState().getEnabled() || car_control.getLatActive() || car_control.getLongActive()); - if (nav_enabled != uiStateSP()->scene.navigate_on_openpilot_deprecated) { + if (nav_enabled != uiState()->scene.navigate_on_openpilot_deprecated) { if (loaded_once) { m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled)); } @@ -78,7 +146,115 @@ void MapWindowSP::updateState(const UIStateSP &s) { emit requestVisible(true); } } - uiStateSP()->scene.navigate_on_openpilot_deprecated = nav_enabled; + uiState()->scene.navigate_on_openpilot_deprecated = nav_enabled; + } + + if (sm.updated("liveLocationKalman")) { + auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman(); + auto locationd_pos = locationd_location.getPositionGeodetic(); + auto locationd_orientation = locationd_location.getCalibratedOrientationNED(); + auto locationd_velocity = locationd_location.getVelocityCalibrated(); + auto locationd_ecef = locationd_location.getPositionECEF(); + + locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && locationd_ecef.getValid()); + if (locationd_valid) { + // Check std norm + auto pos_ecef_std = locationd_ecef.getStd(); + bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100; + locationd_valid = pos_accurate_enough; + } + + if (locationd_valid) { + last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); + last_bearing = RAD2DEG(locationd_orientation.getValue()[2]); + velocity_filter.update(std::max(10.0, locationd_velocity.getValue()[0])); + } + } + + if (sm.updated("navRoute") && sm["navRoute"].getNavRoute().getCoordinates().size()) { + auto nav_dest = coordinate_from_param("NavDestination"); + bool allow_open = std::exchange(last_valid_nav_dest, nav_dest) != nav_dest && + nav_dest && !isVisible(); + qWarning() << "Got new navRoute from navd. Opening map:" << allow_open; + + // Show map on destination set/change + if (allow_open) { + emit requestSettings(false); + emit requestVisible(true); + } + } + + loaded_once = loaded_once || (m_map && m_map->isFullyLoaded()); + if (!loaded_once) { + setError(tr("Map Loading")); + return; + } + initLayers(); + + if (!locationd_valid) { + setError(tr("Waiting for GPS")); + } else if (routing_problem) { + setError(tr("Waiting for route")); + } else { + setError(""); + } + + if (locationd_valid) { + // Update current location marker + auto point = coordinate_to_collection(*last_position); + QMapLibre::Feature feature1(QMapLibre::Feature::PointType, point, {}, {}); + QVariantMap carPosSource; + carPosSource["type"] = "geojson"; + carPosSource["data"] = QVariant::fromValue(feature1); + m_map->updateSource("carPosSource", carPosSource); + + // Map bearing isn't updated when interacting, keep location marker up to date + if (last_bearing) { + m_map->setLayoutProperty("carPosLayer", "icon-rotate", *last_bearing - m_map->bearing()); + } + } + + if (interaction_counter == 0) { + if (last_position) m_map->setCoordinate(*last_position); + if (last_bearing) m_map->setBearing(*last_bearing); + m_map->setZoom(util::map_val(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM)); + } else { + interaction_counter--; + } + + if (sm.updated("navInstruction")) { + // an invalid navInstruction packet with a nav destination is only possible if: + // - API exception/no internet + // - route response is empty + // - any time navd is waiting for recompute_countdown + routing_problem = !sm.valid("navInstruction") && coordinate_from_param("NavDestination").has_value(); + + if (sm.valid("navInstruction")) { + auto i = sm["navInstruction"].getNavInstruction(); + map_eta->updateETA(i.getTimeRemaining(), i.getTimeRemainingTypical(), i.getDistanceRemaining()); + + if (locationd_valid) { + m_map->setPitch(MAX_PITCH); // TODO: smooth pitching based on maneuver distance + map_instructions->updateInstructions(i); + } + } else { + clearRoute(); + } + } + + if (sm.rcv_frame("navRoute") != route_rcv_frame) { + qWarning() << "Updating navLayer with new route"; + auto route = sm["navRoute"].getNavRoute(); + auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates()); + QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {}); + QVariantMap navSource; + navSource["type"] = "geojson"; + navSource["data"] = QVariant::fromValue(feature); + m_map->updateSource("navSource", navSource); + m_map->setLayoutProperty("navLayer", "visibility", "visible"); + + route_rcv_frame = sm.rcv_frame("navRoute"); + updateDestinationMarker(); } } From b55ae7f5f9f42bada0e61205d3f8760e24e71920 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 28 Jul 2024 22:44:10 -0400 Subject: [PATCH 505/575] Revert "try this to make car stop a bit further" This reverts commit 04872944955f8cc506cc8e6c7412c9ce94a42b33. --- selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 2 +- selfdrive/controls/lib/longitudinal_planner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index a2df527a55..7296a28b11 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -55,7 +55,7 @@ T_IDXS = np.array(T_IDXS_LST) FCW_IDXS = T_IDXS < 5.0 T_DIFFS = np.diff(T_IDXS, prepend=[0.]) COMFORT_BRAKE = 2.5 -STOP_DISTANCE = 6.0 +STOP_DISTANCE = 8.0 def get_jerk_factor(personality=custom.LongitudinalPersonalitySP.standard): if personality==custom.LongitudinalPersonalitySP.relaxed: diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 48874282e8..62ed68a77e 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -83,7 +83,7 @@ class LongitudinalPlanner: self.dt = dt self.a_desired = init_a - self.v_desired_filter = FirstOrderFilter(init_v, 0.5, self.dt) + self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt) self.v_model_error = 0.0 self.v_desired_trajectory = np.zeros(CONTROL_N) From 41d0ee236d4b97a0309cf8bb92034009c7337e83 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 07:10:40 -0400 Subject: [PATCH 506/575] should be reverted --- selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 31e193911e..20838608be 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -55,7 +55,7 @@ T_IDXS = np.array(T_IDXS_LST) FCW_IDXS = T_IDXS < 5.0 T_DIFFS = np.diff(T_IDXS, prepend=[0.]) COMFORT_BRAKE = 2.5 -STOP_DISTANCE = 8.0 +STOP_DISTANCE = 6.0 def get_jerk_factor(personality=custom.LongitudinalPersonalitySP.standard): if personality==custom.LongitudinalPersonalitySP.relaxed: From 6c185fb6aced960bdac7303b618a215546e30679 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 08:00:16 -0400 Subject: [PATCH 507/575] MADS: Map cruise main and lateral buttons to button events --- selfdrive/car/chrysler/interface.py | 8 ++++++-- selfdrive/car/ford/interface.py | 8 ++++++-- selfdrive/car/gm/interface.py | 4 +++- selfdrive/car/honda/interface.py | 2 +- selfdrive/car/hyundai/interface.py | 16 ++++++++-------- selfdrive/car/mazda/interface.py | 5 +++-- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index d4911375e7..72883c5158 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -93,7 +93,11 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) + self.CS.button_events = [ + *self.CS.button_events, + *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), + *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}) + ] self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) @@ -108,7 +112,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_lkas_enabled != 1 and self.CS.lkas_enabled == 1: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.lkas_disabled = not self.CS.lkas_disabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 18dd7b4fa3..18cb2c940d 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -74,7 +74,11 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) + self.CS.button_events = [ + *self.CS.button_events, + *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), + *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}) + ] self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) @@ -85,7 +89,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if not self.CS.prev_lkas_enabled and self.CS.lkas_enabled: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 0e428bc13b..4a1e459722 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -218,6 +218,8 @@ class CarInterface(CarInterfaceBase): ] distance_button = self.CS.distance_button + self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) + self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) if not self.CP.pcmCruise: @@ -231,7 +233,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_lkas_enabled != 1 and self.CS.lkas_enabled == 1: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 72ab68647f..af294a63a7 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -282,7 +282,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_cruise_setting != 1 and self.CS.cruise_setting == 1: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index dbe0b62895..486ff3e389 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -205,7 +205,11 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events = 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), + *create_button_events(self.CS.lfa_enabled, self.CS.prev_lfa_enabled, {1: ButtonType.altButton1}), + *create_button_events(self.CS.main_buttons[-1], self.CS.prev_main_buttons, {1: ButtonType.altButton3}) + ] self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, self.CS.button_events, c.vCruise) @@ -214,16 +218,12 @@ class CarInterface(CarInterfaceBase): if ret.cruiseState.available: if not self.CP.pcmCruiseSpeed: - if self.CS.prev_main_buttons == 1: - if self.CS.main_buttons[-1] != 1: - self.CS.accEnabled = True - elif self.CS.prev_cruise_buttons == 4: - if self.CS.cruise_buttons[-1] != 4: - self.accEnabled = True + if any(b.type in (ButtonType.altButton3, ButtonType.cancel) and not b.pressed for b in self.CS.button_events): + self.CS.accEnabled = True if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 9447803ae6..1ad62f4923 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -42,7 +42,8 @@ class CarInterface(CarInterfaceBase): # TODO: add button types for inc and dec self.CS.button_events = [ *self.CS.button_events, - *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) + *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), + *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}) ] self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) @@ -54,7 +55,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_lkas_enabled != self.CS.lkas_enabled: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: From bda51a3a8635aa83eb31e68e312d565787b5f8a5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 14:50:10 -0400 Subject: [PATCH 508/575] different style --- selfdrive/car/chrysler/interface.py | 7 ++----- selfdrive/car/ford/interface.py | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 72883c5158..f4881dd363 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -93,11 +93,8 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events = [ - *self.CS.button_events, - *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), - *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}) - ] + self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) + self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 18cb2c940d..b0a1c15f34 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -74,11 +74,8 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events = [ - *self.CS.button_events, - *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), - *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}) - ] + self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) + self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) From 707a45cb0ac61f4f2ab86a540a1b4e07681946dd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 14:52:09 -0400 Subject: [PATCH 509/575] nah --- selfdrive/car/hyundai/interface.py | 8 +++----- selfdrive/car/mazda/interface.py | 7 ++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 486ff3e389..72a12b0ba8 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -205,11 +205,9 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events = [ - *create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT), - *create_button_events(self.CS.lfa_enabled, self.CS.prev_lfa_enabled, {1: ButtonType.altButton1}), - *create_button_events(self.CS.main_buttons[-1], self.CS.prev_main_buttons, {1: ButtonType.altButton3}) - ] + self.CS.button_events.extend(create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT)) + self.CS.button_events.extend(create_button_events(self.CS.lfa_enabled, self.CS.prev_lfa_enabled, {1: ButtonType.altButton1})) + self.CS.button_events.extend(create_button_events(self.CS.main_buttons[-1], self.CS.prev_main_buttons, {1: ButtonType.altButton3})) self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, self.CS.button_events, c.vCruise) diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 1ad62f4923..2bd4f71c78 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -40,11 +40,8 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() # TODO: add button types for inc and dec - self.CS.button_events = [ - *self.CS.button_events, - *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), - *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}) - ] + self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) + self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) From 5382db2b19118ad3e1cccffe37daee894b90cde1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 15:15:46 -0400 Subject: [PATCH 510/575] missed from merge --- selfdrive/ui/qt/request_repeater.cc | 2 +- selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc | 7 ------- selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h | 1 - selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc | 3 --- 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index e20fa01ce5..d215fe42ca 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -28,7 +28,7 @@ void RequestRepeater::setupCacheProcess(const QString &cacheKey) { } QObject::connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) { if (success && resp != prevResp) { - params.put(cache_key.toStdString(), resp.toStdString()); + params.put(cacheKey.toStdString(), resp.toStdString()); prevResp = resp; } }); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc index 598e376f7d..bebac31d90 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc @@ -1406,16 +1406,11 @@ void AnnotatedCameraWidgetSP::drawLead(QPainter &painter, const cereal::RadarSta } void AnnotatedCameraWidgetSP::paintGL() { -} - -void AnnotatedCameraWidgetSP::paintEvent(QPaintEvent *event) { UIStateSP *s = uiStateSP(); SubMaster &sm = *(s->sm); const double start_draw_t = millis_since_boot(); const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); - QPainter painter(this); - // draw camera frame { std::lock_guard lk(frame_lock); @@ -1460,10 +1455,8 @@ void AnnotatedCameraWidgetSP::paintEvent(QPaintEvent *event) { } else { CameraWidget::updateCalibration(DEFAULT_CALIBRATION); } - painter.beginNativePainting(); CameraWidget::setFrameId(model.getFrameId()); CameraWidget::paintGL(); - painter.endNativePainting(); } QPainter painter(this); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h index d291a6c5a5..92bf5056b3 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h @@ -223,7 +223,6 @@ protected: inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } - void paintEvent(QPaintEvent *event) override; void rocketFuel(QPainter &p); double prev_draw_t = 0; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc index 5c69bcee05..5b87a9f6dd 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/scrollview.cc @@ -28,9 +28,6 @@ Last updated: July 29, 2024 #include -ScrollViewSP::ScrollViewSP(QWidget *w, QWidget *parent) : ScrollView(w, parent) { -} - void ScrollViewSP::setLastScrollPosition() { lastScrollPosition = verticalScrollBar()->value(); } From b8d3f9a16f62aac28368f731e5514a223abce519 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 15:16:50 -0400 Subject: [PATCH 511/575] use upstream msgq --- msgq_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgq_repo b/msgq_repo index 7622841e5d..da77480a96 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 7622841e5d95a39c7010400dbf3f44d771eea4b4 +Subproject commit da77480a969b3a48ee8c03839e7b6a66f6b5ed97 From c923a249df0cd8de659bcf6c92fbb169aa9d9753 Mon Sep 17 00:00:00 2001 From: Jaosn Wen Date: Wed, 17 Jul 2024 11:22:05 -0400 Subject: [PATCH 512/575] MADS: HKG CAN-FD: Disallow cruise buttons to engage when pcmCruiseSpeed is off --- selfdrive/car/hyundai/carstate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index d8329c5ba7..5261fc8477 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -282,6 +282,8 @@ class CarState(CarStateBase): ret.cruiseState.standstill = False else: cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp + ret.cruiseState.available = cp_cruise_info.vl["SCC_CONTROL"]["MainMode_ACC"] == 1 if not self.CP.pcmCruiseSpeed else \ + ret.cruiseState.available ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor From e6970ea15b13bb6d6d5c32b3c5699b67e783d81c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 07:39:46 -0400 Subject: [PATCH 513/575] handle states properly --- README.md | 2 +- selfdrive/car/hyundai/carstate.py | 2 -- selfdrive/car/hyundai/interface.py | 17 +++++++++-------- selfdrive/car/interfaces.py | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c78052ad6b..fef54c6e9c 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ The goal of Modified Assistive Driving Safety (MADS) is to enhance the user driv * `SET-` button enables ACC/SCC * `CANCEL` button only disables ACC/SCC * `CRUISE (MAIN)` must be `ON` to use ACC/SCC -* `CRUISE (MAIN)` button disables sunnypilot completely when `OFF` **(strictly enforced in panda safety code)** +* `CRUISE (MAIN)` button disables ACC/SCC completely when `OFF` **(strictly enforced in panda safety code)** ### Disengage Lateral ALC on Brake Press Mode toggle Dedicated toggle to handle Lateral state on brake pedal press and release: diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 5261fc8477..d6e9279035 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -216,8 +216,6 @@ class CarState(CarStateBase): if self.CP.spFlags & HyundaiFlagsSP.SP_CAN_LFA_BTN: self.lfa_enabled = cp.vl["BCM_PO_11"]["LFA_Pressed"] - self.mads_enabled = False if not self.control_initialized else ret.cruiseState.available - if self.CP.spFlags & HyundaiFlagsSP.SP_NAV_MSG or self.CP.spFlags & HyundaiFlagsSP.SP_LKAS12: self._update_traffic_signals(cp, cp_cam) ret.cruiseState.speedLimit = self._calculate_speed_limit() * speed_conv diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index dbe0b62895..cd5a86ec75 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -207,11 +207,11 @@ class CarInterface(CarInterfaceBase): self.CS.button_events = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT) + 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, self.CS.button_events, c.vCruise) - self.CS.mads_enabled = False if not self.mads_main_toggle else self.CS.mads_enabled - if ret.cruiseState.available: if not self.CP.pcmCruiseSpeed: if self.CS.prev_main_buttons == 1: @@ -220,15 +220,16 @@ class CarInterface(CarInterfaceBase): elif self.CS.prev_cruise_buttons == 4: if self.CS.cruise_buttons[-1] != 4: self.accEnabled = True - if self.enable_mads: - if not self.CS.prev_mads_enabled and self.CS.mads_enabled: - self.CS.madsEnabled = True - if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: - self.CS.madsEnabled = not self.CS.madsEnabled - self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: self.CS.madsEnabled = False + if self.enable_mads: + if not self.CS.prev_mads_enabled and self.CS.mads_enabled: + self.CS.madsEnabled = True + if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: + self.CS.madsEnabled = not self.CS.madsEnabled + self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) + if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed: if not self.CP.pcmCruise: if any(b.type == ButtonType.cancel for b in self.CS.button_events): diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index b8ca13ec86..d91b73d14f 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -476,7 +476,7 @@ class CarInterfaceBase(ABC): events.add(EventName.spReverseGear) elif cs_out.vEgo >= 5: events.add(EventName.reverseGear) - if not cs_out.cruiseState.available: + if not cs_out.cruiseState.available and cs_out.cruiseState.enabled: events.add(EventName.wrongCarMode) if cs_out.espDisabled: events.add(EventName.espDisabled) From 4a53caff37a6c80e652d9c8cb759f2c7e21c4a1e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Jul 2024 21:15:37 -0400 Subject: [PATCH 514/575] this is better --- selfdrive/car/hyundai/interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index cd5a86ec75..4100660162 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -220,8 +220,6 @@ class CarInterface(CarInterfaceBase): elif self.CS.prev_cruise_buttons == 4: if self.CS.cruise_buttons[-1] != 4: self.accEnabled = True - else: - self.CS.madsEnabled = False if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: @@ -230,6 +228,9 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) + if not ret.cruiseState.available and self.CS.out.cruiseState.available: + self.CS.madsEnabled = False + if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed: if not self.CP.pcmCruise: if any(b.type == ButtonType.cancel for b in self.CS.button_events): From faff375b94092172211a6af7bf4d1ac4e18eddfe Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 16:35:21 -0400 Subject: [PATCH 515/575] make sure main button is pressed --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 4100660162..74f4b0b244 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -222,7 +222,7 @@ class CarInterface(CarInterfaceBase): self.accEnabled = True if self.enable_mads: - if not self.CS.prev_mads_enabled and self.CS.mads_enabled: + if not self.CS.prev_mads_enabled and self.CS.mads_enabled and self.CS.main_buttons[-1] != 1: self.CS.madsEnabled = True if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: self.CS.madsEnabled = not self.CS.madsEnabled From 294476fc334c92269a1be5b0cef4b67d875f1e15 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 17:16:48 -0400 Subject: [PATCH 516/575] oops flipped main --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 74f4b0b244..d941799d60 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -222,7 +222,7 @@ class CarInterface(CarInterfaceBase): self.accEnabled = True if self.enable_mads: - if not self.CS.prev_mads_enabled and self.CS.mads_enabled and self.CS.main_buttons[-1] != 1: + if not self.CS.prev_mads_enabled and self.CS.mads_enabled and self.CS.main_buttons[-1] == 1: self.CS.madsEnabled = True if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: self.CS.madsEnabled = not self.CS.madsEnabled From bdb7fa4bf8f9c6f7ed2c31e1dbd57669e3193dd3 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 15:44:05 -0400 Subject: [PATCH 517/575] same behavior --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fef54c6e9c..c78052ad6b 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ The goal of Modified Assistive Driving Safety (MADS) is to enhance the user driv * `SET-` button enables ACC/SCC * `CANCEL` button only disables ACC/SCC * `CRUISE (MAIN)` must be `ON` to use ACC/SCC -* `CRUISE (MAIN)` button disables ACC/SCC completely when `OFF` **(strictly enforced in panda safety code)** +* `CRUISE (MAIN)` button disables sunnypilot completely when `OFF` **(strictly enforced in panda safety code)** ### Disengage Lateral ALC on Brake Press Mode toggle Dedicated toggle to handle Lateral state on brake pedal press and release: From 7935c2bea9467f5ce2474e658b4a10d8af1f5138 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 15:47:56 -0400 Subject: [PATCH 518/575] dev-c3 still uses priv --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 223a45f69c..2fb99d017c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "panda"] path = panda - url = https://github.com/sunnyhaibin/panda.git + url = ../../sunnyhaibin/panda-special.git [submodule "opendbc"] path = opendbc url = https://github.com/sunnyhaibin/opendbc.git From 6185b82ed378775be9f746d41eb1dc5d08638eb8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 29 Jul 2024 21:18:09 -0400 Subject: [PATCH 519/575] MADS: Honda: Fix PCM ACC disengage fault --- selfdrive/car/honda/interface.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 72ab68647f..55009da4be 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -288,20 +288,24 @@ class CarInterface(CarInterfaceBase): else: self.CS.madsEnabled = False - if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed: + min_enable_speed_pcm = self.CP.pcmCruise and self.CP.minEnableSpeed > 0 + + if not self.CP.pcmCruise or min_enable_speed_pcm or not self.CP.pcmCruiseSpeed: 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) - ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads else False if self.CP.pcmCruise else self.CS.accEnabled + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads or (min_enable_speed_pcm and self.CP.pcmCruiseSpeed) \ + else False if self.CP.pcmCruise \ + else self.CS.accEnabled - if self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed: + if min_enable_speed_pcm and self.CP.pcmCruiseSpeed: if ret.gasPressed and not ret.cruiseState.enabled: 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, - min_enable_speed_pcm=(self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed), + min_enable_speed_pcm=(min_enable_speed_pcm and self.CP.pcmCruiseSpeed), gap_button=(self.CS.cruise_setting == 3)) ret.buttonEvents = [ From 4c664020f285f6971a24dff2deff3562e90d3c42 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 30 Jul 2024 11:44:40 -0400 Subject: [PATCH 520/575] handle cruise main button universally --- selfdrive/car/hyundai/carstate.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index d6e9279035..ccea6fa84d 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -62,6 +62,11 @@ class CarState(CarStateBase): self.escc_aeb_dec_cmd = 0 self._speed_limit_clu = 0 + def get_main_enabled(self, ret) -> bool: + if self.prev_main_buttons != 1 and self.main_buttons[-1] == 1: + self.mainEnabled = not self.mainEnabled + return ret.cruiseState.available and self.mainEnabled + def update(self, cp, cp_cam): if self.CP.carFingerprint in CANFD_CAR: return self.update_canfd(cp, cp_cam) @@ -207,10 +212,7 @@ class CarState(CarStateBase): self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"]) self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"]) if self.CP.openpilotLongitudinalControl: - if self.prev_main_buttons != 1: - if self.main_buttons[-1] == 1: - self.mainEnabled = not self.mainEnabled - ret.cruiseState.available = ret.cruiseState.available and self.mainEnabled + ret.cruiseState.available = self.get_main_enabled(ret) self.prev_mads_enabled = self.mads_enabled self.prev_lfa_enabled = self.lfa_enabled if self.CP.spFlags & HyundaiFlagsSP.SP_CAN_LFA_BTN: @@ -280,8 +282,7 @@ class CarState(CarStateBase): ret.cruiseState.standstill = False else: cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp - ret.cruiseState.available = cp_cruise_info.vl["SCC_CONTROL"]["MainMode_ACC"] == 1 if not self.CP.pcmCruiseSpeed else \ - ret.cruiseState.available + ret.cruiseState.available = cp_cruise_info.vl["SCC_CONTROL"]["MainMode_ACC"] == 1 ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor @@ -296,6 +297,8 @@ class CarState(CarStateBase): self.prev_cruise_buttons = self.cruise_buttons[-1] self.prev_main_buttons = self.main_buttons[-1] + if self.CP.openpilotLongitudinalControl: + ret.cruiseState.available = self.get_main_enabled(ret) self.cruise_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["CRUISE_BUTTONS"]) self.main_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["ADAPTIVE_CRUISE_MAIN_BTN"]) self.prev_mads_enabled = self.mads_enabled From bce46b63da5472d05be9b7fd7a75a38576da656f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 30 Jul 2024 14:17:30 -0400 Subject: [PATCH 521/575] both --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 9f30830fca..b3c96dcd0b 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -221,7 +221,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled and \ - any(b.type == ButtonType.altButton3 and b.pressed for b in self.CS.button_events): + any(b.type == ButtonType.altButton3 for b in self.CS.button_events): self.CS.madsEnabled = True if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled From b4fd73141ef8b3365186e3b04ed8162517706209 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 30 Jul 2024 17:46:24 -0400 Subject: [PATCH 522/575] not needed --- selfdrive/car/hyundai/carstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index ccea6fa84d..bea798ba24 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -117,7 +117,7 @@ class CarState(CarStateBase): # cruise state if self.CP.openpilotLongitudinalControl: # These are not used for engage/disengage since openpilot keeps track of state using the buttons - ret.cruiseState.available = cp.vl["TCS13"]["ACCEnable"] == 0 and self.mainEnabled + ret.cruiseState.available = cp.vl["TCS13"]["ACCEnable"] == 0 ret.cruiseState.enabled = cp.vl["TCS13"]["ACC_REQ"] == 1 ret.cruiseState.standstill = False ret.cruiseState.nonAdaptive = False From b2ba2aaab1bf02b8fbb38b33993414fcf85e215a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Jul 2024 09:24:59 -0400 Subject: [PATCH 523/575] car: Check params before car state is updated --- selfdrive/car/chrysler/interface.py | 1 - selfdrive/car/ford/interface.py | 1 - selfdrive/car/gm/interface.py | 1 - selfdrive/car/honda/interface.py | 1 - selfdrive/car/hyundai/interface.py | 1 - selfdrive/car/interfaces.py | 45 ++++++++++++++------------- selfdrive/car/mazda/interface.py | 1 - selfdrive/car/nissan/interface.py | 1 - selfdrive/car/subaru/interface.py | 1 - selfdrive/car/toyota/interface.py | 1 - selfdrive/car/volkswagen/interface.py | 1 - 11 files changed, 24 insertions(+), 31 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index f4881dd363..6112ded880 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -91,7 +91,6 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - self.sp_update_params() self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index b0a1c15f34..7d8d5754e3 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -72,7 +72,6 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - self.sp_update_params() self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 4a1e459722..9bba662906 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -204,7 +204,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback) - self.sp_update_params() distance_button = 0 diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index af294a63a7..70124a8892 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -266,7 +266,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) - self.sp_update_params() self.CS.button_events = [ *create_button_events(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT), diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 72a12b0ba8..f0d0154d69 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -203,7 +203,6 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - self.sp_update_params() self.CS.button_events.extend(create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT)) self.CS.button_events.extend(create_button_events(self.CS.lfa_enabled, self.CS.prev_lfa_enabled, {1: ButtonType.altButton1})) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index b8ca13ec86..0a88762e57 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -239,16 +239,10 @@ class CarInterfaceBase(ABC): self.gear_warning = 0 self.cruise_cancelled_btn = True self.acc_mads_combo = self.param_s.get_bool("AccMadsCombo") - self.is_metric = self.param_s.get_bool("IsMetric") - self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") - self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8")) self.prev_acc_mads_combo = False self.mads_event_lock = True self.gap_button_counter = 0 self.experimental_mode_hold = False - self.experimental_mode = self.param_s.get_bool("ExperimentalMode") - self._frame = 0 - self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam") self.mads_main_toggle = self.param_s.get_bool("MadsCruiseMain") self.lkas_toggle = self.param_s.get_bool("LkasToggle") self.last_mads_init = 0. @@ -429,6 +423,8 @@ class CarInterfaceBase(ABC): self.CS.button_events = [] + self.CS.sp_update_params() + # get CarState ret = self._update(c) @@ -472,7 +468,7 @@ class CarInterfaceBase(ABC): else: events.add(EventName.wrongGear) if cs_out.gearShifter == GearShifter.reverse: - if not self.reverse_dm_cam and cs_out.vEgo < 5: + if not self.CS.reverse_dm_cam and cs_out.vEgo < 5: events.add(EventName.spReverseGear) elif cs_out.vEgo >= 5: events.add(EventName.reverseGear) @@ -634,9 +630,9 @@ class CarInterfaceBase(ABC): if self.CP.openpilotLongitudinalControl: self.toggle_exp_mode(gap_button) - lane_change_speed_min = get_min_lateral_speed(self.pause_lateral_speed, self.is_metric) + lane_change_speed_min = get_min_lateral_speed(self.CS.pause_lateral_speed, self.CS.sp_is_metric) - cs_out.belowLaneChangeSpeed = cs_out.vEgo < lane_change_speed_min and self.below_speed_pause + cs_out.belowLaneChangeSpeed = cs_out.vEgo < lane_change_speed_min and self.CS.below_speed_pause if cs_out.gearShifter in [GearShifter.park, GearShifter.reverse] or cs_out.doorOpen or \ (cs_out.seatbeltUnlatched and cs_out.gearShifter != GearShifter.park): @@ -669,7 +665,7 @@ class CarInterfaceBase(ABC): 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) + self.param_s.put_bool_nonblocking("ExperimentalMode", not self.CS.experimental_mode) else: self.gap_button_counter = 0 self.experimental_mode_hold = False @@ -735,17 +731,6 @@ class CarInterfaceBase(ABC): return events, cs_out - def sp_update_params(self): - self.experimental_mode = self.param_s.get_bool("ExperimentalMode") - self._frame += 1 - if self._frame % 100 == 0: - self.is_metric = self.param_s.get_bool("IsMetric") - self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") - self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8")) - if self._frame % 300 == 0: - self._frame = 0 - self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam") - class RadarInterfaceBase(ABC): def __init__(self, CP): self.rcp = None @@ -786,6 +771,13 @@ class CarStateBase(ABC): self.button_events: list[capnp.lib.capnp._DynamicStructBuilder] = [] + self._frame = 0 + self.experimental_mode = self.param_s.get_bool("ExperimentalMode") + self.sp_is_metric = self.param_s.get_bool("IsMetric") + self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") + self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8")) + self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam") + Q = [[0.0, 0.0], [0.0, 100.0]] R = 0.3 A = [[1.0, DT_CTRL], [0.0, 1.0]] @@ -848,6 +840,17 @@ class CarStateBase(ABC): return bool(left_blinker_stalk or self.left_blinker_cnt > 0), bool(right_blinker_stalk or self.right_blinker_cnt > 0) + def sp_update_params(self): + self.experimental_mode = self.param_s.get_bool("ExperimentalMode") + self._frame += 1 + if self._frame % 100 == 0: + self.sp_is_metric = self.param_s.get_bool("IsMetric") + self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") + self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8")) + if self._frame % 300 == 0: + self._frame = 0 + self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam") + def update_custom_stock_long(self, cruise_button, final_speed_kph, target_speed, v_set_dis, speed_diff, button_type): customStockLong = car.CarState.CustomStockLong.new_message() customStockLong.cruiseButton = 0 if cruise_button is None else cruise_button diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 2bd4f71c78..7b9c6120ec 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -37,7 +37,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - self.sp_update_params() # TODO: add button types for inc and dec self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 4a9e5a9125..661ac668a5 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -32,7 +32,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): ret = self.CS.update(self.cp, self.cp_adas, self.cp_cam) - self.sp_update_params() self.CS.button_events = create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}) diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 7eefeb66eb..185ee8e54d 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -116,7 +116,6 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) - self.sp_update_params() self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 1b0528d12c..5214cc1265 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -207,7 +207,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) - self.sp_update_params() distance_button = 0 diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index d965cf2e8b..0d57539a1b 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -113,7 +113,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam, self.cp_ext, self.CP.transmissionType) - self.sp_update_params() self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) From afb0c3fe42c1064b7bb176b6e25156c5290f0df4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 30 Jul 2024 22:59:47 -0400 Subject: [PATCH 524/575] read params in a thread instead --- selfdrive/car/card.py | 31 +++++++++++++++++++++++++++---- selfdrive/car/interfaces.py | 33 ++++++++------------------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 98be4bf82f..6f83d93239 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import os +import threading import time +from types import SimpleNamespace import cereal.messaging as messaging @@ -90,6 +92,8 @@ class Car: self.events = Events() + self.params_list: SimpleNamespace | None = None + # card is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) @@ -98,7 +102,7 @@ class Car: # Update carState from CAN can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True) - CS = self.CI.update(self.CC_prev, can_strs) + CS = self.CI.update(self.CC_prev, can_strs, self.params_list) self.sm.update(0) @@ -186,10 +190,29 @@ class Car: self.initialized_prev = initialized self.CS_prev = CS.as_reader() + def sp_params_thread(self, event): + while not event.is_set(): + params_list = { + "experimental_mode": self.params.get_bool("ExperimentalMode"), + "is_metric": self.params.get_bool("IsMetric"), + "below_speed_pause": self.params.get_bool("BelowSpeedPause"), + "pause_lateral_speed": int(self.params.get("PauseLateralSpeed", encoding="utf8")), + "reverse_dm_cam": self.params.get_bool("ReverseDmCam"), + } + self.params_list = SimpleNamespace(**params_list) + time.sleep(0.1) + def card_thread(self): - while True: - self.step() - self.rk.monitor_time() + event = threading.Event() + thread = threading.Thread(target=self.sp_params_thread, args=(event, )) + try: + thread.start() + while True: + self.step() + self.rk.monitor_time() + finally: + event.set() + thread.join() def main(): diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 0a88762e57..b1c5fe2647 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -7,6 +7,7 @@ import tomllib from abc import abstractmethod, ABC from difflib import SequenceMatcher from enum import StrEnum +from types import SimpleNamespace from typing import Any, NamedTuple from collections.abc import Callable from functools import cache @@ -415,15 +416,14 @@ class CarInterfaceBase(ABC): def _update(self, c: car.CarControl) -> car.CarState: pass - def update(self, c: car.CarControl, can_strings: list[bytes]) -> car.CarState: + def update(self, c: car.CarControl, can_strings: list[bytes], params_list: SimpleNamespace) -> car.CarState: # parse can for cp in self.can_parsers: if cp is not None: cp.update_strings(can_strings) self.CS.button_events = [] - - self.CS.sp_update_params() + self.CS.params_list = params_list # get CarState ret = self._update(c) @@ -468,7 +468,7 @@ class CarInterfaceBase(ABC): else: events.add(EventName.wrongGear) if cs_out.gearShifter == GearShifter.reverse: - if not self.CS.reverse_dm_cam and cs_out.vEgo < 5: + if not self.CS.params_list.reverse_dm_cam and cs_out.vEgo < 5: events.add(EventName.spReverseGear) elif cs_out.vEgo >= 5: events.add(EventName.reverseGear) @@ -630,9 +630,9 @@ class CarInterfaceBase(ABC): if self.CP.openpilotLongitudinalControl: self.toggle_exp_mode(gap_button) - lane_change_speed_min = get_min_lateral_speed(self.CS.pause_lateral_speed, self.CS.sp_is_metric) + lane_change_speed_min = get_min_lateral_speed(self.CS.params_list.pause_lateral_speed, self.CS.params_list.is_metric) - cs_out.belowLaneChangeSpeed = cs_out.vEgo < lane_change_speed_min and self.CS.below_speed_pause + cs_out.belowLaneChangeSpeed = cs_out.vEgo < lane_change_speed_min and self.CS.params_list.below_speed_pause if cs_out.gearShifter in [GearShifter.park, GearShifter.reverse] or cs_out.doorOpen or \ (cs_out.seatbeltUnlatched and cs_out.gearShifter != GearShifter.park): @@ -665,7 +665,7 @@ class CarInterfaceBase(ABC): 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.CS.experimental_mode) + self.param_s.put_bool_nonblocking("ExperimentalMode", not self.CS.params_list.experimental_mode) else: self.gap_button_counter = 0 self.experimental_mode_hold = False @@ -770,13 +770,7 @@ class CarStateBase(ABC): self.control_initialized = False self.button_events: list[capnp.lib.capnp._DynamicStructBuilder] = [] - - self._frame = 0 - self.experimental_mode = self.param_s.get_bool("ExperimentalMode") - self.sp_is_metric = self.param_s.get_bool("IsMetric") - self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") - self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8")) - self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam") + self.params_list: SimpleNamespace | None = None Q = [[0.0, 0.0], [0.0, 100.0]] R = 0.3 @@ -840,17 +834,6 @@ class CarStateBase(ABC): return bool(left_blinker_stalk or self.left_blinker_cnt > 0), bool(right_blinker_stalk or self.right_blinker_cnt > 0) - def sp_update_params(self): - self.experimental_mode = self.param_s.get_bool("ExperimentalMode") - self._frame += 1 - if self._frame % 100 == 0: - self.sp_is_metric = self.param_s.get_bool("IsMetric") - self.below_speed_pause = self.param_s.get_bool("BelowSpeedPause") - self.pause_lateral_speed = int(self.param_s.get("PauseLateralSpeed", encoding="utf8")) - if self._frame % 300 == 0: - self._frame = 0 - self.reverse_dm_cam = self.param_s.get_bool("ReverseDmCam") - def update_custom_stock_long(self, cruise_button, final_speed_kph, target_speed, v_set_dis, speed_diff, button_type): customStockLong = car.CarState.CustomStockLong.new_message() customStockLong.cruiseButton = 0 if cruise_button is None else cruise_button From 03722f9090e2f96d092c90b6d62af2b81a2cdd54 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 30 Jul 2024 23:57:04 -0400 Subject: [PATCH 525/575] try this out --- selfdrive/car/honda/interface.py | 15 ++++++++------- selfdrive/car/interfaces.py | 5 ++--- selfdrive/controls/controlsd.py | 4 +++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 55009da4be..7e92cc0d74 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -288,25 +288,26 @@ class CarInterface(CarInterfaceBase): else: self.CS.madsEnabled = False - min_enable_speed_pcm = self.CP.pcmCruise and self.CP.minEnableSpeed > 0 + min_enable_speed_pcm = self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed if not self.CP.pcmCruise or min_enable_speed_pcm or not self.CP.pcmCruiseSpeed: 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) - ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads or (min_enable_speed_pcm and self.CP.pcmCruiseSpeed) \ + ret.cruiseState.enabled = ret.cruiseState.enabled if not self.enable_mads or min_enable_speed_pcm \ else False if self.CP.pcmCruise \ else self.CS.accEnabled - if min_enable_speed_pcm and self.CP.pcmCruiseSpeed: + if min_enable_speed_pcm: if ret.gasPressed and not ret.cruiseState.enabled: self.CS.accEnabled = False - self.CS.accEnabled = ret.cruiseState.enabled or self.CS.accEnabled + if ret.cruiseState.enabled and not self.CS.out.cruiseState.enabled: + self.CS.accEnabled = True + elif not ret.cruiseState.enabled: + self.CS.accEnabled = False - ret, self.CS = self.get_sp_common_state(ret, self.CS, - min_enable_speed_pcm=(min_enable_speed_pcm and self.CP.pcmCruiseSpeed), - gap_button=(self.CS.cruise_setting == 3)) + ret, self.CS = self.get_sp_common_state(ret, self.CS, gap_button=(self.CS.cruise_setting == 3)) ret.buttonEvents = [ *self.CS.button_events, diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index b8ca13ec86..376249197e 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -621,9 +621,8 @@ class CarInterfaceBase(ABC): else: return CS.madsEnabled - 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 + def get_sp_common_state(self, cs_out, CS, gear_allowed=True, gap_button=False): + cs_out.cruiseState.enabled = CS.accEnabled if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed else cs_out.cruiseState.enabled if not self.enable_mads: if cs_out.cruiseState.enabled and not CS.out.cruiseState.enabled: diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 2af32f8a0c..214e439400 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -739,7 +739,9 @@ class Controls: CC.angularVelocity = angular_rate_value CC.cruiseControl.override = self.enabled_long and not CC.longActive and self.CP.openpilotLongitudinalControl - CC.cruiseControl.cancel = CS.cruiseState.enabled and (not self.enabled_long or (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill))) + CC.cruiseControl.cancel = CS.cruiseState.enabled and \ + (not self.enabled_long or (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or + (self.CP.openpilotLongitudinalControl and self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed and not self.CS_prev.accEnabled)) if self.joystick_mode and self.sm.recv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]: CC.cruiseControl.cancel = True From 053b07e4c0413583cec5fd3697ac486757d9fc7b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 31 Jul 2024 17:04:00 -0400 Subject: [PATCH 526/575] honda only --- selfdrive/controls/controlsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 214e439400..a8dcc3b5e4 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -741,7 +741,7 @@ class Controls: CC.cruiseControl.override = self.enabled_long and not CC.longActive and self.CP.openpilotLongitudinalControl CC.cruiseControl.cancel = CS.cruiseState.enabled and \ (not self.enabled_long or (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or - (self.CP.openpilotLongitudinalControl and self.CP.pcmCruise and self.CP.minEnableSpeed > 0 and self.CP.pcmCruiseSpeed and not self.CS_prev.accEnabled)) + (any(b.type == ButtonType.cancel for b in CS.buttonEvents) and self.CP.carName == "honda")) if self.joystick_mode and self.sm.recv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]: CC.cruiseControl.cancel = True From edddc496d6f3d1b8ede1ad6a78b91a402b28baae Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 30 Jul 2024 06:58:35 -0800 Subject: [PATCH 527/575] MADS: Map cruise main and lateral buttons to button events * MADS: Map cruise main and lateral buttons to button events * different style * nah (cherry picked from commit 4d7afe3a7bbcd4f31ee309a6aa2f5db93367daae) --- selfdrive/car/chrysler/interface.py | 3 ++- selfdrive/car/ford/interface.py | 3 ++- selfdrive/car/gm/interface.py | 4 +++- selfdrive/car/honda/interface.py | 2 +- selfdrive/car/hyundai/interface.py | 14 ++++++-------- selfdrive/car/mazda/interface.py | 8 +++----- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index d4911375e7..f4881dd363 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -94,6 +94,7 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) + self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) @@ -108,7 +109,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_lkas_enabled != 1 and self.CS.lkas_enabled == 1: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.lkas_disabled = not self.CS.lkas_disabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 18dd7b4fa3..b0a1c15f34 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -75,6 +75,7 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) + self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) @@ -85,7 +86,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if not self.CS.prev_lkas_enabled and self.CS.lkas_enabled: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 0e428bc13b..4a1e459722 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -218,6 +218,8 @@ class CarInterface(CarInterfaceBase): ] distance_button = self.CS.distance_button + self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) + self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) if not self.CP.pcmCruise: @@ -231,7 +233,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_lkas_enabled != 1 and self.CS.lkas_enabled == 1: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 72ab68647f..af294a63a7 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -282,7 +282,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_cruise_setting != 1 and self.CS.cruise_setting == 1: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index dbe0b62895..72a12b0ba8 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -205,7 +205,9 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events = create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT) + self.CS.button_events.extend(create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT)) + self.CS.button_events.extend(create_button_events(self.CS.lfa_enabled, self.CS.prev_lfa_enabled, {1: ButtonType.altButton1})) + self.CS.button_events.extend(create_button_events(self.CS.main_buttons[-1], self.CS.prev_main_buttons, {1: ButtonType.altButton3})) self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, self.CS.button_events, c.vCruise) @@ -214,16 +216,12 @@ class CarInterface(CarInterfaceBase): if ret.cruiseState.available: if not self.CP.pcmCruiseSpeed: - if self.CS.prev_main_buttons == 1: - if self.CS.main_buttons[-1] != 1: - self.CS.accEnabled = True - elif self.CS.prev_cruise_buttons == 4: - if self.CS.cruise_buttons[-1] != 4: - self.accEnabled = True + if any(b.type in (ButtonType.altButton3, ButtonType.cancel) and not b.pressed for b in self.CS.button_events): + self.CS.accEnabled = True if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_lfa_enabled != 1 and self.CS.lfa_enabled == 1: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 9447803ae6..2bd4f71c78 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -40,10 +40,8 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() # TODO: add button types for inc and dec - 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.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) + self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) @@ -54,7 +52,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.prev_lkas_enabled != self.CS.lkas_enabled: + if any(b.type == ButtonType.altButton1 and b.pressed for b in self.CS.button_events): self.CS.madsEnabled = not self.CS.madsEnabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: From 17ec1576593895cc03830b5e8c9b8a45ca3a757c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 31 Jul 2024 18:46:30 -0400 Subject: [PATCH 528/575] reimplement --- selfdrive/car/chrysler/interface.py | 7 +++++-- selfdrive/car/ford/interface.py | 7 +++++-- selfdrive/car/gm/interface.py | 5 ++++- selfdrive/car/hyundai/interface.py | 8 +++++--- selfdrive/car/mazda/interface.py | 7 +++++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index f4881dd363..38d7cf0828 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -93,8 +93,11 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) - self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) + self.CS.button_events = [ + *self.CS.button_events, + *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), + *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}), + ] self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index b0a1c15f34..080663c9cf 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -74,8 +74,11 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) - self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) + self.CS.button_events = [ + *self.CS.button_events, + *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), + *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}), + ] self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 4a1e459722..726af2afac 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -218,7 +218,10 @@ class CarInterface(CarInterfaceBase): ] distance_button = self.CS.distance_button - self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) + self.CS.button_events = [ + *self.CS.button_events, + *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}), + ] self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 72a12b0ba8..e2ad23a7b6 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -205,9 +205,11 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam) self.sp_update_params() - self.CS.button_events.extend(create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT)) - self.CS.button_events.extend(create_button_events(self.CS.lfa_enabled, self.CS.prev_lfa_enabled, {1: ButtonType.altButton1})) - self.CS.button_events.extend(create_button_events(self.CS.main_buttons[-1], self.CS.prev_main_buttons, {1: ButtonType.altButton3})) + self.CS.button_events = [ + *create_button_events(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT), + *create_button_events(self.CS.lfa_enabled, self.CS.prev_lfa_enabled, {1: ButtonType.altButton1}), + *create_button_events(self.CS.main_buttons[-1], self.CS.prev_main_buttons, {1: ButtonType.altButton3}), + ] self.CS.accEnabled = self.get_sp_v_cruise_non_pcm_state(ret, self.CS.accEnabled, self.CS.button_events, c.vCruise) diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 2bd4f71c78..8b113cf4db 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -40,8 +40,11 @@ class CarInterface(CarInterfaceBase): self.sp_update_params() # TODO: add button types for inc and dec - self.CS.button_events.extend(create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise})) - self.CS.button_events.extend(create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1})) + self.CS.button_events = [ + *self.CS.button_events, + *create_button_events(self.CS.distance_button, self.CS.prev_distance_button, {1: ButtonType.gapAdjustCruise}), + *create_button_events(self.CS.lkas_enabled, self.CS.prev_lkas_enabled, {1: ButtonType.altButton1}), + ] self.CS.mads_enabled = self.get_sp_cruise_main_state(ret, self.CS) From e136db12625d1a5fd7ca3da5fdf6eebb34e39403 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 31 Jul 2024 20:30:26 -0400 Subject: [PATCH 529/575] initialize list --- selfdrive/car/card.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 6f83d93239..11c839f469 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -92,7 +92,13 @@ class Car: self.events = Events() - self.params_list: SimpleNamespace | None = None + self.params_list: SimpleNamespace = SimpleNamespace( + experimental_mode=self.params.get_bool("ExperimentalMode"), + is_metric=self.params.get_bool("IsMetric"), + below_speed_pause=self.params.get_bool("BelowSpeedPause"), + pause_lateral_speed=int(self.params.get("PauseLateralSpeed", encoding="utf8")), + reverse_dm_cam=self.params.get_bool("ReverseDmCam"), + ) # card is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) From 544531ab91af83d3a4a66446bbf56182709569e0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 31 Jul 2024 20:38:28 -0400 Subject: [PATCH 530/575] Do this instead --- selfdrive/car/card.py | 8 +------- selfdrive/car/interfaces.py | 8 +++++++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 11c839f469..6f83d93239 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -92,13 +92,7 @@ class Car: self.events = Events() - self.params_list: SimpleNamespace = SimpleNamespace( - experimental_mode=self.params.get_bool("ExperimentalMode"), - is_metric=self.params.get_bool("IsMetric"), - below_speed_pause=self.params.get_bool("BelowSpeedPause"), - pause_lateral_speed=int(self.params.get("PauseLateralSpeed", encoding="utf8")), - reverse_dm_cam=self.params.get_bool("ReverseDmCam"), - ) + self.params_list: SimpleNamespace | None = None # card is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index b1c5fe2647..10704ff660 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -770,7 +770,13 @@ class CarStateBase(ABC): self.control_initialized = False self.button_events: list[capnp.lib.capnp._DynamicStructBuilder] = [] - self.params_list: SimpleNamespace | None = None + self.params_list: SimpleNamespace = SimpleNamespace( + experimental_mode=False, + is_metric=False, + below_speed_pause=False, + pause_lateral_speed=0, + reverse_dm_cam=False + ) Q = [[0.0, 0.0], [0.0, 100.0]] R = 0.3 From b0a4b7f8ace93d731bbc3d709fed2a7cdb56840c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 31 Jul 2024 20:49:22 -0400 Subject: [PATCH 531/575] type hint --- selfdrive/car/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 6f83d93239..7821d4b99e 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -190,7 +190,7 @@ class Car: self.initialized_prev = initialized self.CS_prev = CS.as_reader() - def sp_params_thread(self, event): + def sp_params_thread(self, event: threading.Event) -> None: while not event.is_set(): params_list = { "experimental_mode": self.params.get_bool("ExperimentalMode"), From 257f3c1f48db650202d175c02ac40a2788855975 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 1 Aug 2024 06:28:20 -0400 Subject: [PATCH 532/575] enable for CAN-FD --- selfdrive/car/hyundai/carcontroller.py | 7 ++++--- selfdrive/car/hyundai/hyundaicanfd.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 4b7c4ac52b..b41ff020a0 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -221,6 +221,9 @@ class CarController(CarControllerBase): if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: can_sends.append([0x7b1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", self.CAN.ECAN]) + if self.CP.openpilotLongitudinalControl: + self.make_jerk(CS, accel, actuators) + # CAN-FD platforms if self.CP.carFingerprint in CANFD_CAR: hda2 = self.CP.flags & HyundaiFlags.CANFD_HDA2 @@ -249,7 +252,7 @@ class CarController(CarControllerBase): can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)) if self.frame % 2 == 0: can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled and CS.out.cruiseState.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, - set_speed_in_units, hud_control)) + set_speed_in_units, hud_control, self.jerk_u, self.jerk_l)) self.accel_last = accel else: # button presses @@ -287,8 +290,6 @@ class CarController(CarControllerBase): 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)] * 25) - else: - self.make_jerk(CS, accel, actuators) # 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/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index 8225166bfa..cdb2d40c3c 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -121,7 +121,7 @@ def create_lfahda_cluster(packer, CAN, enabled, lat_active, lateral_paused, blin return packer.make_can_msg("LFAHDA_CLUSTER", CAN.ECAN, values) -def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control): +def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control, upper_jerk, lower_jerk): jerk = 5 jn = jerk / 50 if not enabled or gas_override: @@ -137,8 +137,8 @@ def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_ov "aReqValue": a_val, "aReqRaw": a_raw, "VSetDis": set_speed, - "JerkLowerLimit": jerk if enabled else 1, - "JerkUpperLimit": 3.0, + "JerkLowerLimit": lower_jerk, + "JerkUpperLimit": upper_jerk, "ACC_ObjDist": 1, "ObjValid": 0, From 29bf1cc294ca12060523f6bf4c8350639ac0a26e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 1 Aug 2024 07:46:45 -0400 Subject: [PATCH 533/575] ParamManager --- selfdrive/car/card.py | 15 ++++++--------- selfdrive/car/interfaces.py | 9 ++------- selfdrive/car/param_manager.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 selfdrive/car/param_manager.py diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 7821d4b99e..5dd0ce041b 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -16,6 +16,7 @@ from openpilot.common.realtime import config_realtime_process, Priority, Ratekee from openpilot.selfdrive.pandad import can_list_to_can_capnp from openpilot.selfdrive.car.car_helpers import get_car, get_one_can from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.car.param_manager import ParamManager from openpilot.selfdrive.controls.lib.events import Events REPLAY = "REPLAY" in os.environ @@ -92,7 +93,9 @@ class Car: self.events = Events() - self.params_list: SimpleNamespace | None = None + self.param_manager: ParamManager = ParamManager() + self.param_manager.update(self.params) + self.params_list: SimpleNamespace = self.param_manager.get_params() # card is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) @@ -192,14 +195,8 @@ class Car: def sp_params_thread(self, event: threading.Event) -> None: while not event.is_set(): - params_list = { - "experimental_mode": self.params.get_bool("ExperimentalMode"), - "is_metric": self.params.get_bool("IsMetric"), - "below_speed_pause": self.params.get_bool("BelowSpeedPause"), - "pause_lateral_speed": int(self.params.get("PauseLateralSpeed", encoding="utf8")), - "reverse_dm_cam": self.params.get_bool("ReverseDmCam"), - } - self.params_list = SimpleNamespace(**params_list) + self.param_manager.update(self.params) + self.params_list = self.param_manager.get_params() time.sleep(0.1) def card_thread(self): diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 10704ff660..bda574f54d 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -20,6 +20,7 @@ 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, ButtonEvents +from openpilot.selfdrive.car.param_manager import ParamManager 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 @@ -770,13 +771,7 @@ class CarStateBase(ABC): self.control_initialized = False self.button_events: list[capnp.lib.capnp._DynamicStructBuilder] = [] - self.params_list: SimpleNamespace = SimpleNamespace( - experimental_mode=False, - is_metric=False, - below_speed_pause=False, - pause_lateral_speed=0, - reverse_dm_cam=False - ) + self.params_list: SimpleNamespace = ParamManager().get_params() Q = [[0.0, 0.0], [0.0, 100.0]] R = 0.3 diff --git a/selfdrive/car/param_manager.py b/selfdrive/car/param_manager.py new file mode 100644 index 0000000000..070ca8c93e --- /dev/null +++ b/selfdrive/car/param_manager.py @@ -0,0 +1,30 @@ +from types import SimpleNamespace + +from openpilot.common.params import Params + + +class ParamManager: + def __init__(self): + self.params_list: SimpleNamespace = self.convert_to_namespace({ + "below_speed_pause": False, + "experimental_mode": False, + "is_metric": False, + "pause_lateral_speed": 0, + "reverse_dm_cam": False, + }) + + @staticmethod + def convert_to_namespace(data: dict) -> SimpleNamespace: + return SimpleNamespace(**data) + + def get_params(self) -> SimpleNamespace: + return self.params_list + + def update(self, params: Params) -> None: + self.params_list = self.convert_to_namespace({ + "below_speed_pause": params.get_bool("BelowSpeedPause"), + "experimental_mode": params.get_bool("ExperimentalMode"), + "is_metric": params.get_bool("IsMetric"), + "pause_lateral_speed": int(params.get("PauseLateralSpeed", encoding="utf8")), + "reverse_dm_cam": params.get_bool("ReverseDmCam"), + }) From fefe08384220485c5e6bde3530d1c2c2db83435c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 1 Aug 2024 10:28:31 -0400 Subject: [PATCH 534/575] oops --- selfdrive/car/hyundai/carstate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index bea798ba24..fcd44cc1cd 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -297,10 +297,10 @@ class CarState(CarStateBase): self.prev_cruise_buttons = self.cruise_buttons[-1] self.prev_main_buttons = self.main_buttons[-1] - if self.CP.openpilotLongitudinalControl: - ret.cruiseState.available = self.get_main_enabled(ret) self.cruise_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["CRUISE_BUTTONS"]) self.main_buttons.extend(cp.vl_all[self.cruise_btns_msg_canfd]["ADAPTIVE_CRUISE_MAIN_BTN"]) + if self.CP.openpilotLongitudinalControl: + ret.cruiseState.available = self.get_main_enabled(ret) self.prev_mads_enabled = self.mads_enabled self.prev_lfa_enabled = self.lfa_enabled self.lfa_enabled = cp.vl[self.cruise_btns_msg_canfd]["LFA_BTN"] From 891cba85dae662f80566019651634fcd8d536a34 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 1 Aug 2024 13:27:31 -0400 Subject: [PATCH 535/575] make them internal --- selfdrive/car/card.py | 6 +++--- selfdrive/car/param_manager.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 5dd0ce041b..fe6f67296c 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -95,7 +95,7 @@ class Car: self.param_manager: ParamManager = ParamManager() self.param_manager.update(self.params) - self.params_list: SimpleNamespace = self.param_manager.get_params() + self._params_list: SimpleNamespace = self.param_manager.get_params() # card is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) @@ -105,7 +105,7 @@ class Car: # Update carState from CAN can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True) - CS = self.CI.update(self.CC_prev, can_strs, self.params_list) + CS = self.CI.update(self.CC_prev, can_strs, self._params_list) self.sm.update(0) @@ -196,7 +196,7 @@ class Car: def sp_params_thread(self, event: threading.Event) -> None: while not event.is_set(): self.param_manager.update(self.params) - self.params_list = self.param_manager.get_params() + self._params_list = self.param_manager.get_params() time.sleep(0.1) def card_thread(self): diff --git a/selfdrive/car/param_manager.py b/selfdrive/car/param_manager.py index 070ca8c93e..85018aaa02 100644 --- a/selfdrive/car/param_manager.py +++ b/selfdrive/car/param_manager.py @@ -5,7 +5,7 @@ from openpilot.common.params import Params class ParamManager: def __init__(self): - self.params_list: SimpleNamespace = self.convert_to_namespace({ + self._params_list: SimpleNamespace = self._create_namespace({ "below_speed_pause": False, "experimental_mode": False, "is_metric": False, @@ -14,14 +14,14 @@ class ParamManager: }) @staticmethod - def convert_to_namespace(data: dict) -> SimpleNamespace: + def _create_namespace(data: dict) -> SimpleNamespace: return SimpleNamespace(**data) def get_params(self) -> SimpleNamespace: - return self.params_list + return self._params_list def update(self, params: Params) -> None: - self.params_list = self.convert_to_namespace({ + self._params_list = self._create_namespace({ "below_speed_pause": params.get_bool("BelowSpeedPause"), "experimental_mode": params.get_bool("ExperimentalMode"), "is_metric": params.get_bool("IsMetric"), From 9aa452a3c62029b27275779e8c6103da9bbe7343 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 1 Aug 2024 13:29:07 -0400 Subject: [PATCH 536/575] wtf how did i miss --- selfdrive/car/hyundai/carcontroller.py | 2 +- selfdrive/car/hyundai/hyundaicanfd.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index f640f6529a..1fbb080058 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -235,7 +235,7 @@ class CarController(CarControllerBase): if hda2: can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)) if self.frame % 2 == 0: - can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled and CS.out.cruiseState.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, + can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CS, CC.enabled and CS.out.cruiseState.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, set_speed_in_units, hud_control)) self.accel_last = accel else: diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index 8225166bfa..12b84ad0f6 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -121,7 +121,7 @@ def create_lfahda_cluster(packer, CAN, enabled, lat_active, lateral_paused, blin return packer.make_can_msg("LFAHDA_CLUSTER", CAN.ECAN, values) -def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control): +def create_acc_control(packer, CAN, CS, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control): jerk = 5 jn = jerk / 50 if not enabled or gas_override: @@ -132,7 +132,7 @@ def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_ov values = { "ACCMode": 0 if not enabled else (2 if gas_override else 1), - "MainMode_ACC": 1, + "MainMode_ACC": 1 if CS.mainEnabled else 0, "StopReq": 1 if stopping else 0, "aReqValue": a_val, "aReqRaw": a_raw, From 6e381f02baf493a014474dde4d6c3f72dfc78938 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 1 Aug 2024 18:47:14 -0400 Subject: [PATCH 537/575] car: Move all live param checks to ParamManger --- selfdrive/car/chrysler/carcontroller.py | 19 +++++---------- selfdrive/car/honda/carcontroller.py | 23 +++++++----------- selfdrive/car/hyundai/carcontroller.py | 23 +++++++----------- selfdrive/car/interfaces.py | 10 +++----- selfdrive/car/mazda/carcontroller.py | 23 +++++++----------- selfdrive/car/param_manager.py | 20 ++++++++++++++++ selfdrive/car/subaru/carcontroller.py | 9 +------ selfdrive/car/toyota/carcontroller.py | 24 +++++-------------- selfdrive/car/toyota/interface.py | 2 +- selfdrive/car/volkswagen/carcontroller.py | 19 +++++---------- .../settings/sunnypilot/mads_settings.cc | 4 ++-- 11 files changed, 69 insertions(+), 107 deletions(-) diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index 54bed40b51..9de0d4a547 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -30,9 +30,6 @@ class CarController(CarControllerBase): self.sm = messaging.SubMaster(['longitudinalPlanSP']) self.param_s = Params() - self.is_metric = self.param_s.get_bool("IsMetric") - self.speed_limit_control_enabled = False - self.last_speed_limit_sign_tap = False self.last_speed_limit_sign_tap_prev = False self.speed_limit = 0. self.speed_limit_offset = 0 @@ -72,23 +69,19 @@ class CarController(CarControllerBase): self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed - if self.frame % 200 == 0: - self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc") - self.is_metric = self.param_s.get_bool("IsMetric") - self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap") - self.v_cruise_min = FCA_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1) + self.v_cruise_min = FCA_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1) can_sends = [] if not self.CP.pcmCruiseSpeed: - if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap: + if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap: self.sl_force_active_timer = self.frame self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False) - self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap + self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap - sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) - sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) - sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) + sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) + sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) + sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) slc_active = not sl_inactive and not sl_temp_inactive self.slc_active_stock = slc_active diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index dd40d07712..22c2eefe82 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -132,9 +132,6 @@ class CarController(CarControllerBase): self.sm = messaging.SubMaster(['longitudinalPlanSP']) self.param_s = Params() - self.is_metric = self.param_s.get_bool("IsMetric") - self.speed_limit_control_enabled = False - self.last_speed_limit_sign_tap = False self.last_speed_limit_sign_tap_prev = False self.speed_limit = 0. self.speed_limit_offset = 0 @@ -173,11 +170,7 @@ class CarController(CarControllerBase): self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed - if self.frame % 200 == 0: - self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc") - self.is_metric = self.param_s.get_bool("IsMetric") - self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap") - self.v_cruise_min = HONDA_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1) + self.v_cruise_min = HONDA_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1) actuators = CC.actuators hud_control = CC.hudControl @@ -213,14 +206,14 @@ class CarController(CarControllerBase): self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V)) if not self.CP.pcmCruiseSpeed: - if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap: + if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap: self.sl_force_active_timer = self.frame self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False) - self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap + self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap - sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) - sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) - sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) + sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) + sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) + sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) slc_active = not sl_inactive and not sl_temp_inactive self.slc_active_stock = slc_active @@ -432,8 +425,8 @@ class CarController(CarControllerBase): return min(target_speed_kph, curve_speed) def get_button_control(self, CS, final_speed, v_cruise_kph_prev): - self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1)) - self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH)) + self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1)) + self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not CS.params_list.is_metric else CV.MS_TO_KPH)) cruise_button = self.get_button_type(self.button_type) return cruise_button diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index f640f6529a..00e4cd919a 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -72,9 +72,6 @@ class CarController(CarControllerBase): self.sm = messaging.SubMaster(sub_services) self.param_s = Params() - self.is_metric = self.param_s.get_bool("IsMetric") - self.speed_limit_control_enabled = False - self.last_speed_limit_sign_tap = False self.last_speed_limit_sign_tap_prev = False self.speed_limit = 0. self.speed_limit_offset = 0 @@ -127,11 +124,7 @@ class CarController(CarControllerBase): self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed - if self.frame % 200 == 0: - self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc") - self.is_metric = self.param_s.get_bool("IsMetric") - self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap") - self.v_cruise_min = HYUNDAI_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1) + self.v_cruise_min = HYUNDAI_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1) actuators = CC.actuators hud_control = CC.hudControl @@ -175,14 +168,14 @@ class CarController(CarControllerBase): blinking_icon = (self.frame - self.disengage_blink) * DT_CTRL < 1.0 if self.lat_disengage_init else False if not self.CP.pcmCruiseSpeed: - if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap: + if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap: self.sl_force_active_timer = self.frame self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False) - self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap + self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap - sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) - sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) - sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) + sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) + sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) + sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) slc_active = not sl_inactive and not sl_temp_inactive self.slc_active_stock = slc_active @@ -433,8 +426,8 @@ class CarController(CarControllerBase): return min(target_speed_kph, curve_speed) def get_button_control(self, CS, final_speed, v_cruise_kph_prev): - self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1)) - self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH)) + self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1)) + self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not CS.params_list.is_metric else CV.MS_TO_KPH)) cruise_button = self.get_button_type(self.button_type) return cruise_button diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index bda574f54d..ca4a508554 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -240,13 +240,10 @@ class CarInterfaceBase(ABC): self.mads_ndlob = self.enable_mads and not self.mads_disengage_lateral_on_brake self.gear_warning = 0 self.cruise_cancelled_btn = True - self.acc_mads_combo = self.param_s.get_bool("AccMadsCombo") self.prev_acc_mads_combo = False self.mads_event_lock = True self.gap_button_counter = 0 self.experimental_mode_hold = False - self.mads_main_toggle = self.param_s.get_bool("MadsCruiseMain") - self.lkas_toggle = self.param_s.get_bool("LkasToggle") self.last_mads_init = 0. self.madsEnabledInit = False self.madsEnabledInitPrev = False @@ -551,7 +548,7 @@ class CarInterfaceBase(ABC): return v_cruise != V_CRUISE_UNSET def get_acc_mads(self, cruiseState_enabled, acc_enabled, mads_enabled): - if self.acc_mads_combo: + if self.CS.params_list.acc_mads_combo: if not self.prev_acc_mads_combo and (cruiseState_enabled or acc_enabled): mads_enabled = True self.prev_acc_mads_combo = (cruiseState_enabled or acc_enabled) @@ -591,7 +588,7 @@ class CarInterfaceBase(ABC): def get_sp_cruise_main_state(self, cs_out, CS): if not CS.control_initialized: mads_enabled = False - elif not self.mads_main_toggle: + elif not self.CS.params_list.mads_main_toggle: mads_enabled = False else: mads_enabled = cs_out.cruiseState.available @@ -603,7 +600,7 @@ class CarInterfaceBase(ABC): self.madsEnabledInit = False self.madsEnabledInitPrev = False return False - if not self.mads_main_toggle or self.prev_acc_mads_combo: + if not self.CS.params_list.mads_main_toggle or self.prev_acc_mads_combo: return CS.madsEnabled if not self.madsEnabledInit and CS.madsEnabled: self.madsEnabledInit = True @@ -762,7 +759,6 @@ class CarStateBase(ABC): self.cluster_speed_hyst_gap = 0.0 self.cluster_min_speed = 0.0 # min speed before dropping to 0 - self.param_s = Params() self.accEnabled = False self.madsEnabled = False self.disengageByBrake = False diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index 368198e1c8..705dedc328 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -24,9 +24,6 @@ class CarController(CarControllerBase): self.sm = messaging.SubMaster(['longitudinalPlanSP']) self.param_s = Params() - self.is_metric = self.param_s.get_bool("IsMetric") - self.speed_limit_control_enabled = False - self.last_speed_limit_sign_tap = False self.last_speed_limit_sign_tap_prev = False self.speed_limit = 0. self.speed_limit_offset = 0 @@ -65,23 +62,19 @@ class CarController(CarControllerBase): self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed - if self.frame % 200 == 0: - self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc") - self.is_metric = self.param_s.get_bool("IsMetric") - self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap") - self.v_cruise_min = MAZDA_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1) + self.v_cruise_min = MAZDA_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1) can_sends = [] if not self.CP.pcmCruiseSpeed: - if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap: + if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap: self.sl_force_active_timer = self.frame self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False) - self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap + self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap - sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) - sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) - sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) + sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) + sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) + sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) slc_active = not sl_inactive and not sl_temp_inactive self.slc_active_stock = slc_active @@ -229,8 +222,8 @@ class CarController(CarControllerBase): return min(target_speed_kph, curve_speed) def get_button_control(self, CS, final_speed, v_cruise_kph_prev): - self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not self.is_metric else 1)) - self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not self.is_metric else CV.MS_TO_KPH)) + self.init_speed = round(min(final_speed, v_cruise_kph_prev) * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1)) + self.v_set_dis = round(CS.out.cruiseState.speed * (CV.MS_TO_MPH if not CS.params_list.is_metric else CV.MS_TO_KPH)) cruise_button = self.get_button_type(self.button_type) return cruise_button diff --git a/selfdrive/car/param_manager.py b/selfdrive/car/param_manager.py index 85018aaa02..c7df87b9b7 100644 --- a/selfdrive/car/param_manager.py +++ b/selfdrive/car/param_manager.py @@ -6,11 +6,21 @@ from openpilot.common.params import Params class ParamManager: def __init__(self): self._params_list: SimpleNamespace = self._create_namespace({ + "acc_mads_combo": False, "below_speed_pause": False, "experimental_mode": False, "is_metric": False, + "last_speed_limit_sign_tap": False, + "mads_main_toggle": False, "pause_lateral_speed": 0, + "reverse_acc_change": False, "reverse_dm_cam": False, + "speed_limit_control_enabled": False, + "subaru_manual_parking_brake": False, + "toyota_auto_lock_by_speed": False, + "toyota_auto_unlock_by_shifter": False, + "toyota_lkas_toggle": False, + "toyota_sng_hack": False, }) @staticmethod @@ -22,9 +32,19 @@ class ParamManager: def update(self, params: Params) -> None: self._params_list = self._create_namespace({ + "acc_mads_combo": params.get_bool("AccMadsCombo"), "below_speed_pause": params.get_bool("BelowSpeedPause"), "experimental_mode": params.get_bool("ExperimentalMode"), "is_metric": params.get_bool("IsMetric"), + "last_speed_limit_sign_tap": params.get_bool("LastSpeedLimitSignTap"), + "mads_main_toggle": params.get_bool("MadsCruiseMain"), "pause_lateral_speed": int(params.get("PauseLateralSpeed", encoding="utf8")), + "reverse_acc_change": params.get_bool("ReverseAccChange"), "reverse_dm_cam": params.get_bool("ReverseDmCam"), + "speed_limit_control_enabled": params.get_bool("EnableSlc"), + "subaru_manual_parking_brake": params.get_bool("SubaruManualParkingBrakeSng"), + "toyota_auto_lock_by_speed": params.get_bool("ToyotaAutoLockBySpeed"), + "toyota_auto_unlock_by_shifter": params.get_bool("ToyotaAutoUnlockByShifter"), + "toyota_lkas_toggle": params.get_bool("LkasToggle"), + "toyota_sng_hack": params.get_bool("ToyotaSnG"), }) diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index 198b17ace6..7574d33af8 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -1,6 +1,5 @@ from cereal import car from openpilot.common.numpy_fast import clip, interp -from openpilot.common.params import Params from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance from openpilot.selfdrive.car.interfaces import CarControllerBase @@ -25,12 +24,9 @@ class CarController(CarControllerBase): self.cruise_button_prev = 0 self.steer_rate_counter = 0 - self.param_s = Params() - self.subaru_sng = False if CP.spFlags & SubaruFlagsSP.SP_SUBARU_SNG: self.subaru_sng = True - self.manual_parking_brake = self.param_s.get_bool("SubaruManualParkingBrakeSng") self.prev_close_distance = 0 self.prev_standstill = False self.standstill_start = 0 @@ -47,9 +43,6 @@ class CarController(CarControllerBase): hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel - if self.frame % 250 == 0 and self.subaru_sng: - self.manual_parking_brake = self.param_s.get_bool("SubaruManualParkingBrakeSng") - can_sends = [] # *** steering *** @@ -191,7 +184,7 @@ class CarController(CarControllerBase): and CS.close_distance > self.prev_close_distance): # distance with lead car is increasing self.sng_acc_resume = True elif not (self.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID)): - if self.manual_parking_brake: + if CS.params_list.subaru_manual_parking_brake and self.subaru_sng: # Send brake message with non-zero speed in standstill to avoid non-EPB ACC disengage if (CC.enabled # ACC active and CS.car_follow == 1 # lead car diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 89f5992961..b8185ee6ff 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -1,7 +1,6 @@ from cereal import car from common.conversions import Conversions as CV from openpilot.common.numpy_fast import clip, interp -from openpilot.common.params import Params from openpilot.selfdrive.car import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, \ create_gas_interceptor_command, make_can_msg from openpilot.selfdrive.car.interfaces import CarControllerBase @@ -52,27 +51,16 @@ class CarController(CarControllerBase): self.gas = 0 self.accel = 0 - self.param_s = Params() - self._is_metric = self.param_s.get_bool("IsMetric") - self._reverse_acc_change = self.param_s.get_bool("ReverseAccChange") - self._sng_hack = self.param_s.get_bool("ToyotaSnG") - self.left_blindspot_debug_enabled = False self.right_blindspot_debug_enabled = False self.last_blindspot_frame = 0 - self._auto_lock_by_speed = self.param_s.get_bool("ToyotaAutoLockBySpeed") - self._auto_unlock_by_shifter = self.param_s.get_bool("ToyotaAutoUnlockByShifter") - self._auto_lock_speed = 10 * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) + self._auto_lock_speed = 0.0 self._auto_lock_once = False self._gear_prev = GearShifter.park def update(self, CC, CS, now_nanos): - if self.frame % 200 == 0: - self._is_metric = self.param_s.get_bool("IsMetric") - self._auto_lock_by_speed = self.param_s.get_bool("ToyotaAutoLockBySpeed") - self._auto_unlock_by_shifter = self.param_s.get_bool("ToyotaAutoUnlockByShifter") - self._auto_lock_speed = 10 * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) + self._auto_lock_speed = 10 * (CV.KPH_TO_MS if CS.params_list.is_metric else CV.MPH_TO_MS) actuators = CC.actuators hud_control = CC.hudControl @@ -88,11 +76,11 @@ class CarController(CarControllerBase): gear = CS.out.gearShifter if not CS.out.doorOpen: if gear == GearShifter.park and self._gear_prev != gear: - if self._auto_unlock_by_shifter: + if CS.params_list.toyota_auto_unlock_by_shifter: can_sends.append(make_can_msg(0x750, UNLOCK_CMD, 0)) self._auto_lock_once = False elif gear == GearShifter.drive and not self._auto_lock_once and CS.out.vEgo >= self._auto_lock_speed: - if self._auto_lock_by_speed: + if CS.params_list.toyota_auto_lock_by_speed: can_sends.append(make_can_msg(0x750, LOCK_CMD, 0)) self._auto_lock_once = True self._gear_prev = gear @@ -173,7 +161,7 @@ class CarController(CarControllerBase): # on entering standstill, send standstill request if CS.out.standstill and not self.last_standstill and (self.CP.carFingerprint not in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptorDEPRECATED) and \ - not self._sng_hack: + not CS.params_list.toyota_sng_hack: self.standstill_req = True if CS.pcm_acc_status != 8: # pcm entered standstill or it's disabled @@ -188,7 +176,7 @@ class CarController(CarControllerBase): # we can spam can to cancel the system even if we are using lat only control if (self.frame % 3 == 0 and self.CP.openpilotLongitudinalControl) or pcm_cancel_cmd: lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged - reverse_acc = 2 if self._reverse_acc_change else 1 + reverse_acc = 2 if CS.params_list.reverse_acc_change else 1 # Press distance button until we are at the correct bar length. Only change while enabled to avoid skipping startup popup if self.frame % 6 == 0 and self.CP.openpilotLongitudinalControl: diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 5214cc1265..f1bcfecb49 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -220,7 +220,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.lkas_toggle: + if self.CS.params_list.lkas_toggle: if self.CS.lta_status_active: if (self.CS.prev_lkas_enabled == 16 and self.CS.lkas_enabled == 0) or \ (self.CS.prev_lkas_enabled == 0 and self.CS.lkas_enabled == 16): diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index d690b7963d..4589805b5c 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -34,9 +34,6 @@ class CarController(CarControllerBase): self.sm = messaging.SubMaster(['longitudinalPlanSP']) self.param_s = Params() - self.is_metric = self.param_s.get_bool("IsMetric") - self.speed_limit_control_enabled = False - self.last_speed_limit_sign_tap = False self.last_speed_limit_sign_tap_prev = False self.speed_limit = 0. self.speed_limit_offset = 0 @@ -79,24 +76,20 @@ class CarController(CarControllerBase): self.v_tsc = self.sm['longitudinalPlanSP'].visionTurnSpeed self.m_tsc = self.sm['longitudinalPlanSP'].turnSpeed - if self.frame % 200 == 0: - self.speed_limit_control_enabled = self.param_s.get_bool("EnableSlc") - self.is_metric = self.param_s.get_bool("IsMetric") - self.last_speed_limit_sign_tap = self.param_s.get_bool("LastSpeedLimitSignTap") - self.v_cruise_min = VOLKSWAGEN_V_CRUISE_MIN[self.is_metric] * (CV.KPH_TO_MPH if not self.is_metric else 1) + self.v_cruise_min = VOLKSWAGEN_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1) actuators = CC.actuators hud_control = CC.hudControl can_sends = [] if not self.CP.pcmCruiseSpeed: - if not self.last_speed_limit_sign_tap_prev and self.last_speed_limit_sign_tap: + if not self.last_speed_limit_sign_tap_prev and CS.params_list.last_speed_limit_sign_tap: self.sl_force_active_timer = self.frame self.param_s.put_bool_nonblocking("LastSpeedLimitSignTap", False) - self.last_speed_limit_sign_tap_prev = self.last_speed_limit_sign_tap + self.last_speed_limit_sign_tap_prev = CS.params_list.last_speed_limit_sign_tap - sl_force_active = self.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) - sl_inactive = not sl_force_active and (not self.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) - sl_temp_inactive = not sl_force_active and (self.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) + sl_force_active = CS.params_list.speed_limit_control_enabled and (self.frame < (self.sl_force_active_timer * DT_CTRL + 2.0)) + sl_inactive = not sl_force_active and (not CS.params_list.speed_limit_control_enabled or (True if self.slc_state == 0 else False)) + sl_temp_inactive = not sl_force_active and (CS.params_list.speed_limit_control_enabled and (True if self.slc_state == 1 else False)) slc_active = not sl_inactive and not sl_temp_inactive self.slc_active_stock = slc_active diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc index 610ee160bc..8dc11cbeac 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc @@ -101,7 +101,7 @@ void MadsSettings::updateToggles() { const bool enable_mads = params.getBool("EnableMads"); const bool enabled = is_offroad && enable_mads; - toggles["AccMadsCombo"]->setEnabled(enabled); - toggles["MadsCruiseMain"]->setEnabled(enabled); + toggles["AccMadsCombo"]->setEnabled(enable_mads); + toggles["MadsCruiseMain"]->setEnabled(enable_mads); dlob_settings->setEnabled(enabled); } From ea7a280daae2075bf161796fb32b04ac43ef9f62 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 2 Aug 2024 14:01:30 +0200 Subject: [PATCH 538/575] Revert "Add .git-crypt and .venv to the blacklist" This reverts commit f51a6f54 Revert "Add git-crypt unlock attempt in provision script" This reverts commit f4dac4b4 Revert "ci: git-crypt: Decrypt prior to building" This reverts commit faf52492b67dd995b1643c49de602d82929c1e5d. Revert "git-crypt: add collaborators" This reverts commit bc1aad6f6a66fbeb490997ee7f8a282feb5f6dfd. --- .git-crypt/.gitattributes | 4 - ...6E1EB7856273777A0C8BB4EF8EA444C1E7B69C.gpg | Bin 725 -> 0 bytes ...D8EDBE6BD70DFB803D59292470ADBDD426DEB5.gpg | Bin 725 -> 0 bytes ...8F802F45946A6A3E16AD1B1609CD53B15C2462.gpg | Bin 735 -> 0 bytes .git-crypt/provision.sh | 110 ------------------ .gitlab-ci.yml | 2 +- release/release_files.py | 2 - 7 files changed, 1 insertion(+), 117 deletions(-) delete mode 100644 .git-crypt/.gitattributes delete mode 100644 .git-crypt/keys/default/0/106E1EB7856273777A0C8BB4EF8EA444C1E7B69C.gpg delete mode 100644 .git-crypt/keys/default/0/91D8EDBE6BD70DFB803D59292470ADBDD426DEB5.gpg delete mode 100644 .git-crypt/keys/default/0/E08F802F45946A6A3E16AD1B1609CD53B15C2462.gpg delete mode 100755 .git-crypt/provision.sh diff --git a/.git-crypt/.gitattributes b/.git-crypt/.gitattributes deleted file mode 100644 index 665b10e8f0..0000000000 --- a/.git-crypt/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -# Do not edit this file. To specify the files to encrypt, create your own -# .gitattributes file in the directory where your files are. -* !filter !diff -*.gpg binary diff --git a/.git-crypt/keys/default/0/106E1EB7856273777A0C8BB4EF8EA444C1E7B69C.gpg b/.git-crypt/keys/default/0/106E1EB7856273777A0C8BB4EF8EA444C1E7B69C.gpg deleted file mode 100644 index 97b89be16eef468dea1688448132cd49bc1a9205..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 725 zcmV;`0xJE50t^Eiti!r3*)-+>5CEM;BTRKyWmz_3gO6PMXMbPN8+OdA6CadusYg9^ zWEg3Hz?fKrSv+uXx{Tssm0G=WQZdR4NKEO3c+2^|&X>&>$KG$#mLSy6=!V~-Blgi< z4rbWQe~-g;X9ORi=kxVsUp%TuJzT8$KfwLD=!;G22C0wERyD)W8*Cqx$nwrUQ}{`SO70zzskdift(^gjR0vt;&(*}}_sKQrIoG1GHILGca1FFrY23uBQ6ca*) zN2ZBxDgh+>4NJ=^wpG%=0|AIJdW$G(>Cj!#x{XK+oplLEf2E9Ui6cd(9*v zH2SrDh492MO=i<_&_X5mlVw}px}?7bLxm!ItMLI`Vl-3GhjO*>3*S^}x#3+R_LqTs z0@tS3baUe2=5JPH*zgRlaWq~6WGGaggBmy;;TaC7@~U7}3DrU33Z9lF@?;bC*_7)rtkjg8j6|?1VHGyK^v9fhF$}jmr$w}(@|UW zCDSs`cxVBK0OX@n!pU+=3s>=PP<`7I*TgwzlW}bCJCxAdJ=4{%NGkuX2|Y$;ev}{L z5K_5bU1AU%jBP?KVo4zu5kLdZ>i=eEjD7&CVB(9&Se)dt^i%Wd@R;T2ZC?d*n7`AV zWZI}=|MbRfHX7&T2=1TyZJAdaN5iQH?*Z3{`z4N5Y6=`NNKGrYI!f*$v1R`6NzE=$ zQaUW|sFgpJ$pX0W)o~=x)WCHO?{R4=qJRk7)Z}YSJT&nYoyuc;14=$octKO1;nyrH zh?nA@kC?#@x0f28qYC*srv67oHzn+DI^+(3ps`LSlfzcJ@he*!Hn*PEW{;&fCcx4B zqTYwap!a3@OfA*S>CD|^9VJ2)+7+0f&_EVya;dcq=`$JeyO{H!VN6!XkO@SR1MjPX z9LQDJ3%q65Zt!N@N3znu0|6YI82ww*3PRzyAIJeTfu$`dI#tndf?i0VwJCCjU#Zeg z4-(gUMpGm7v8X7c9iP9JD(my5N%*Q2yv0j`6r9vf!p4S`EmHT5gs5-@0>f-CT&9}f z`AuXtw#?1kk{ghL1gqZYO&3}31ZbTi23N5N1fdQh2YSxD{`dF-fEt2;y1)6ri0-mj zn!xr}jV&Qmo;v${hx}{D(h9&5Ra}+i#}apkZbKLQI=iu;U@^hnFk;ij8&U+bgn?l6xnAgEx#Dza?w=UzyMmxvM+Qe+SWlz+UQ?Q5m53(MN8h>Y>D@RZ-j5M)P(Kq>$`lN{RFr{12*0F2 znlSADOAt%tK)Tu_aX3x5P=wumS{_Dv-_i20k{~-J6SYQl>~UN0JXnmf;WYQ+ zl=rpmLzyLjcWaVY!<;pOvWuLA(=M<#_tbnAXLS)ki|#8FaJ)whiG&coR=Z_ikWR>? zFBgrx0Tgi?(Q}^(EIoM>nkS(~sCSo~1hPsEAML=nFNbsqHIK9R^QNoeBBF7c`X>Pv z2tZj&5Q(v;8Tl^wn9uy`m|qfw_BRewH3nw`$fLi)1Zji~Pel2nzBJymh@fGl!s)#%;n5V(c*u;yCs?@dDe?5QF{V6*~;eqdI%ym|q}v^cIxfgJah zqPTravGaxq?yS&u?A~3E+J@@>Am<`ioGZhA`B?4FL%P5Ju*TZc+T%>mQZjHZ3PF!AeqNfQ7bGbH`6kQ_jEj zxk`Gih-)5$|KoWu-$MlpAwwb|lTjPXCnRW-KEjg+vFbb8bf`&zYCkC94P{ekg-Euy zRaURgwANGWf>8S~<}tx-0%z3@hQAg0*mp#WlH>^*hKrdC1X+)1(5^$P0q!#8j`fGP zk8f_lbc5xFGx{2PXsOrdN6YR9AJA=?S=57 RnAcT|%MwwGhU?B@M#$H9W /dev/null; then - echo "gpg could not be found. Please install gpg to continue." - exit 1 -fi - -# Check if git-crypt is installed -if ! command -v git-crypt &> /dev/null; then - # Check if /TICI file exists - if [ -f "/TICI" ]; then - echo "/TICI file detected. Attempting to install git-crypt..." - sudo apt update && sudo apt install git-crypt -y - else - echo "git-crypt could not be found. Please install git-crypt to continue." - exit 1 - fi -fi - -# Attempt to unlock with git-crypt -if git-crypt unlock &> /dev/null; then - echo "git-crypt unlock successful. You already have access." - exit 0 -fi - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -no_prompt=0 -# Attempt to retrieve name and email from git config, default to "undefined" -name=$(git config --get user.name) -email=$(git config --get user.email) -name=${name:-"undefined"} -email=${email:-"undefined"} -passphrase="" - -# Function to display help message -show_help() { - echo "Usage: $0 [options]" - echo "" - echo "Options:" - echo " --name Set the user's name. Default is git config user.name or 'undefined'." - echo " --email Set the user's email. Default is git config user.email or 'undefined'." - echo " --passphrase Set the passphrase for the GPG key." - echo " --no-prompt Run without interactive prompts, using defaults or provided values." - echo " -h, --help Display this help message and exit." - echo "" - exit 0 -} - -# Check for help flag before parsing other arguments -for arg in "$@"; do - case $arg in - -h|--help) - show_help - ;; - esac -done - -# Parse named arguments -while [[ "$#" -gt 0 ]]; do - case $1 in - --name) name="$2"; shift ;; - --email) email="$2"; shift ;; - --passphrase) passphrase="$2"; shift ;; - --no-prompt) no_prompt=1 ;; - *) echo "Unknown parameter passed: $1"; exit 1 ;; - esac - shift -done - -# No need to prompt for missing information if no-prompt is enabled or defaults are used - -# Set protection line based on passphrase -if [[ -z "$passphrase" ]]; then - protection_line="%no-protection" -else - protection_line="Passphrase: $passphrase" -fi - -# Generate and process GPG key -GPG_OUTPUT=$(echo " -%echo Generating a basic OpenPGP key -Key-Type: RSA -Key-Length: 4096 -Subkey-Type: RSA -Subkey-Length: 4096 -Name-Real: $name -Name-Comment: Generated from git config or default -Name-Email: $email -Expire-Date: 0 -$protection_line -%commit -%echo done -" | gpg --batch --generate-key - 2>&1) - -# Extract and export the key ID -KEY_ID=$(echo "$GPG_OUTPUT" | grep -o '[0-9A-F]\{40\}' | head -n 1) -if [ -z "$KEY_ID" ]; then - echo "Failed to generate GPG key or extract key ID." - exit 1 -fi - -INSTALL_KEY_PATH=${DIR}/keys/install_new_key_sp -mkdir -p ${INSTALL_KEY_PATH} -gpg --export $KEY_ID > ${INSTALL_KEY_PATH}/$KEY_ID.gpg -git add ${INSTALL_KEY_PATH}/$KEY_ID.gpg -git commit -m "Add new public key to install." -git push - -echo "Public key exported to ${INSTALL_KEY_PATH}/${KEY_ID}.gpg" \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 33d6289f63..f8324710f5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -147,7 +147,7 @@ build: when: manual - if: $NEW_BRANCH when: always - + check no source code sent: image: alpine stage: sanity diff --git a/release/release_files.py b/release/release_files.py index 4ae0aad1ed..31d1518d74 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -88,8 +88,6 @@ sunnypilot_blacklist = [ "codecov.yml", "conftest.py", "poetry.lock", - ".git-crypt/", - ".venv", ] # Merge the blacklists From 1972ef8c49510f4e76749fdadf21305bd5e1ea5e Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 2 Aug 2024 14:55:14 +0200 Subject: [PATCH 539/575] updating gitmodule to proper panda --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 2fb99d017c..223a45f69c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "panda"] path = panda - url = ../../sunnyhaibin/panda-special.git + url = https://github.com/sunnyhaibin/panda.git [submodule "opendbc"] path = opendbc url = https://github.com/sunnyhaibin/opendbc.git From 043de47ed145b8910d2d6d14b33a3351da117e4f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 2 Aug 2024 09:56:44 -0400 Subject: [PATCH 540/575] car: Fix typo with `ParamManager` variable --- selfdrive/car/toyota/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index f1bcfecb49..527964cead 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -220,7 +220,7 @@ class CarInterface(CarInterfaceBase): if self.enable_mads: if not self.CS.prev_mads_enabled and self.CS.mads_enabled: self.CS.madsEnabled = True - if self.CS.params_list.lkas_toggle: + if self.CS.params_list.toyota_lkas_toggle: if self.CS.lta_status_active: if (self.CS.prev_lkas_enabled == 16 and self.CS.lkas_enabled == 0) or \ (self.CS.prev_lkas_enabled == 0 and self.CS.lkas_enabled == 16): From 59ca1374a04a0a2401101fe09911cd9c2fc996b8 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Wed, 31 Jul 2024 10:11:13 +0200 Subject: [PATCH 541/575] Improving authentication mechanism for the websocket and help with local dev --- system/athena/sunnylinkd.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 3b4c81b680..62b86eead4 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -5,6 +5,7 @@ from __future__ import annotations import base64 import gzip import os +import ssl import threading import time @@ -29,7 +30,8 @@ SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require DISALLOW_LOG_UPLOAD = threading.Event() params = Params() -sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8')) +sunnylink_dongle_id = params.get("SunnylinkDongleId", encoding='utf-8') +sunnylink_api = SunnylinkApi(sunnylink_dongle_id) def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: cloudlog.info("sunnylinkd.handle_long_poll started") sm = messaging.SubMaster(['deviceState']) @@ -204,7 +206,7 @@ def main(exit_event: threading.Event = None): UploadQueueCache.initialize(upload_queue) - ws_uri = SUNNYLINK_ATHENA_HOST + ws_uri = f"{SUNNYLINK_ATHENA_HOST}" conn_start = None conn_retries = 0 while (exit_event is None or not exit_event.is_set()) and sunnylink_ready(params): @@ -214,8 +216,9 @@ def main(exit_event: threading.Event = None): cloudlog.event("sunnylinkd.main.connecting_ws", ws_uri=ws_uri, retries=conn_retries) ws = create_connection(ws_uri, - cookie="jwt=" + sunnylink_api.get_token(), + header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True, + sslopt={"cert_reqs": ssl.CERT_NONE if "localhost" in ws_uri else ssl.CERT_REQUIRED}, timeout=SUNNYLINK_RECONNECT_TIMEOUT_S) cloudlog.event("sunnylinkd.main.connected_ws", ws_uri=ws_uri, retries=conn_retries, duration=time.monotonic() - conn_start) From 5f1d0a535f4edf3651d80c3e58cc6610edb43d26 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 2 Aug 2024 11:27:29 -0400 Subject: [PATCH 542/575] ci: Update build number on dev builds with Discord notification --- .gitlab-ci.yml | 3 +++ release/ci/discord_template_notify_dev_public.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 13b30a6af2..23881001fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -230,6 +230,9 @@ notify new dev build: needs: ["publish to public github prebuilt"] # This notify shall only happen after a publish to github public variables: TEMPLATE: "discord_template_notify_dev_public.json" + before_script: + - !reference [".notify_discord", "before_script"] + - export EXTRA_VERSION_IDENTIFIER=$((CI_PIPELINE_IID + BASE_BUILD_NUMER)) rules: - if: $NEW_BRANCH == "dev-c3" variables: diff --git a/release/ci/discord_template_notify_dev_public.json b/release/ci/discord_template_notify_dev_public.json index d1a7b37f96..c933ea10b4 100644 --- a/release/ci/discord_template_notify_dev_public.json +++ b/release/ci/discord_template_notify_dev_public.json @@ -2,7 +2,7 @@ "embeds": [ { "title": "🎉 sunnypilot `${NEW_BRANCH}` New Update 🎉", - "description": "[sunnypilot](${PUBLIC_REPO_URL}): Build #${CI_PIPELINE_IID} of branch [${NEW_BRANCH}](${PUBLIC_REPO_URL}/tree/${NEW_BRANCH}) has been published.\n\nDrive safe! 🚗💨", + "description": "[sunnypilot](${PUBLIC_REPO_URL}): Build #${EXTRA_VERSION_IDENTIFIER} of branch [${NEW_BRANCH}](${PUBLIC_REPO_URL}/tree/${NEW_BRANCH}) has been published.\n\nDrive safe! 🚗💨", "color": 4321431 } ] From 1fb3ab1d1a5d3a16297fc5f8672c2254b50975ee Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 2 Aug 2024 18:45:59 -0400 Subject: [PATCH 543/575] no more body --- body | 1 - 1 file changed, 1 deletion(-) delete mode 160000 body diff --git a/body b/body deleted file mode 160000 index 0e74db67ae..0000000000 --- a/body +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e74db67ae6aaa7c30054bd4335dcafe69a5aa72 From 4ec8cb370085066a13f38660efb525dfd6e8eb40 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 3 Aug 2024 18:00:45 +0200 Subject: [PATCH 544/575] Refactor update process execution. Changed update process execution from a Python to a native process. Updated signal-based process control to match new process configurations. --- selfdrive/ui/qt/offroad/software_settings.cc | 4 ++-- system/manager/process_config.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index db3bd9ecb2..a3f2a8549a 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -26,7 +26,7 @@ void SoftwarePanel::checkForUpdates() { - std::system("pkill -SIGUSR1 -f system.updated.updated"); + std::system("pkill -SIGUSR1 -f updated"); } SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { @@ -45,7 +45,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { if (downloadBtn->text() == tr("CHECK")) { checkForUpdates(); } else { - std::system("pkill -SIGHUP -f system.updated.updated"); + std::system("pkill -SIGHUP -f updated"); } }); addItem(downloadBtn); diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 1db62dc2c4..0fad995375 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -102,7 +102,7 @@ procs = [ PythonProcess("radard", "selfdrive.controls.radard", only_onroad), PythonProcess("hardwared", "system.hardware.hardwared", always_run), PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC), - PythonProcess("updated", "system.updated.updated", only_offroad, enabled=not PC), + NativeProcess("updated", "system/updated", ["./updated.py"], only_offroad, enabled=not PC), PythonProcess("uploader", "system.loggerd.uploader", always_run), PythonProcess("statsd", "system.statsd", always_run), From 66bba6c032a8ad3aaa5bc7e827dee37fe435ca62 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 3 Aug 2024 17:01:21 -0400 Subject: [PATCH 545/575] Fix from sync --- selfdrive/locationd/paramsd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index bb59ceecb2..2447ffd9a8 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -9,6 +9,7 @@ from cereal import car, log from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.numpy_fast import clip +from openpilot.common.transformations.orientation import rot_from_euler from openpilot.selfdrive.car.chrysler.values import ChryslerFlagsSP from openpilot.selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR From 1757e77aaae67b5ebb2e05270aa064889656a3a4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 3 Aug 2024 21:49:05 -0400 Subject: [PATCH 546/575] Hyundai Longitudinal: Enable Cruise Main when started --- selfdrive/car/hyundai/carstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index fcd44cc1cd..d078212121 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -55,7 +55,7 @@ class CarState(CarStateBase): self.lfa_enabled = False self.prev_lfa_enabled = False - self.mainEnabled = False + self.mainEnabled = True self.escc_aeb_warning = 0 self.escc_aeb_dec_cmd_act = 0 self.escc_cmd_act = 0 From 1cfe4859549ea118ee63030c3579d3ef03019af5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 4 Aug 2024 07:50:18 -0400 Subject: [PATCH 547/575] =?UTF-8?q?don't=20do=20this=20for=20the=20ancient?= =?UTF-8?q?s=20(@devtekve=20=F0=9F=98=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- selfdrive/car/hyundai/carstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index d078212121..63291a9511 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -55,7 +55,7 @@ class CarState(CarStateBase): self.lfa_enabled = False self.prev_lfa_enabled = False - self.mainEnabled = True + self.mainEnabled = True if CP.spFlags & HyundaiFlagsSP.SP_CAN_LFA_BTN or CP.carFingerprint in CANFD_CAR else False self.escc_aeb_warning = 0 self.escc_aeb_dec_cmd_act = 0 self.escc_cmd_act = 0 From 8955d1b04381983a80b0cd55a14d08583e462bfc Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 4 Aug 2024 08:32:31 -0400 Subject: [PATCH 548/575] okay, toggle per @devtekve xD --- common/params.cc | 1 + selfdrive/car/hyundai/carstate.py | 2 +- selfdrive/car/hyundai/interface.py | 4 ++++ selfdrive/car/param_manager.py | 2 ++ .../qt/offroad/settings/vehicle_settings.cc | 19 +++++++++++++++++++ .../qt/offroad/settings/vehicle_settings.h | 1 + selfdrive/ui/translations/main_ar.ts | 8 ++++++++ selfdrive/ui/translations/main_de.ts | 8 ++++++++ selfdrive/ui/translations/main_es.ts | 8 ++++++++ selfdrive/ui/translations/main_fr.ts | 8 ++++++++ selfdrive/ui/translations/main_ja.ts | 8 ++++++++ selfdrive/ui/translations/main_ko.ts | 8 ++++++++ selfdrive/ui/translations/main_pt-BR.ts | 8 ++++++++ selfdrive/ui/translations/main_th.ts | 8 ++++++++ selfdrive/ui/translations/main_tr.ts | 8 ++++++++ selfdrive/ui/translations/main_zh-CHS.ts | 8 ++++++++ selfdrive/ui/translations/main_zh-CHT.ts | 8 ++++++++ system/manager/manager.py | 1 + 18 files changed, 117 insertions(+), 1 deletion(-) diff --git a/common/params.cc b/common/params.cc index c613cf0a36..5cb98babf4 100644 --- a/common/params.cc +++ b/common/params.cc @@ -261,6 +261,7 @@ std::unordered_map keys = { {"HandsOnWheelMonitoring", PERSISTENT | BACKUP}, {"HasAcceptedTermsSP", PERSISTENT}, {"HideVEgoUi", PERSISTENT | BACKUP}, + {"HyundaiCruiseMainDefault", PERSISTENT | BACKUP}, {"HkgSmoothStop", PERSISTENT | BACKUP}, {"HotspotOnBoot", PERSISTENT}, {"HotspotOnBootConfirmed", PERSISTENT}, diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 63291a9511..fcd44cc1cd 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -55,7 +55,7 @@ class CarState(CarStateBase): self.lfa_enabled = False self.prev_lfa_enabled = False - self.mainEnabled = True if CP.spFlags & HyundaiFlagsSP.SP_CAN_LFA_BTN or CP.carFingerprint in CANFD_CAR else False + self.mainEnabled = False self.escc_aeb_warning = 0 self.escc_aeb_dec_cmd_act = 0 self.escc_cmd_act = 0 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index c2140d2925..a6f35abb18 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -202,6 +202,10 @@ class CarInterface(CarInterfaceBase): enable_radar_tracks(logcan, sendcan, bus=0, addr=0x7d0, config_data_id=b'\x01\x42') def _update(self, c): + if not self.CS.control_initialized: + can_cruise_main_default = self.CP.spFlags & HyundaiFlagsSP.SP_CAN_LFA_BTN and not self.CP.flags & HyundaiFlags.CANFD and self.CS.params_list.hyundai_cruise_main_default + self.CS.mainEnabled = True if can_cruise_main_default or self.CP.carFingerprint in CANFD_CAR else False + ret = self.CS.update(self.cp, self.cp_cam) self.CS.button_events = [ diff --git a/selfdrive/car/param_manager.py b/selfdrive/car/param_manager.py index c7df87b9b7..6f393bd2d2 100644 --- a/selfdrive/car/param_manager.py +++ b/selfdrive/car/param_manager.py @@ -11,6 +11,7 @@ class ParamManager: "experimental_mode": False, "is_metric": False, "last_speed_limit_sign_tap": False, + "hyundai_cruise_main_default": False, "mads_main_toggle": False, "pause_lateral_speed": 0, "reverse_acc_change": False, @@ -37,6 +38,7 @@ class ParamManager: "experimental_mode": params.get_bool("ExperimentalMode"), "is_metric": params.get_bool("IsMetric"), "last_speed_limit_sign_tap": params.get_bool("LastSpeedLimitSignTap"), + "hyundai_cruise_main_default": params.get_bool("HyundaiCruiseMainDefault"), "mads_main_toggle": params.get_bool("MadsCruiseMain"), "pause_lateral_speed": int(params.get("PauseLateralSpeed", encoding="utf8")), "reverse_acc_change": params.get_bool("ReverseAccChange"), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc index 10ff22c028..16c237bf45 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.cc @@ -109,6 +109,13 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge hkgSmoothStop->setConfirmation(true, false); addItem(hkgSmoothStop); + hyundaiCruiseMainDefault = new ParamControlSP( + "HyundaiCruiseMainDefault", + tr("HKG CAN: Enable Cruise Main By Default"), + tr("Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS"), + "../assets/offroad/icon_blank.png"); + addItem(hyundaiCruiseMainDefault); + // Subaru addItem(new LabelControlSP(tr("Subaru"))); auto subaruManualParkingBrakeSng = new ParamControlSP( @@ -194,6 +201,7 @@ SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidge connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { is_onroad = !offroad; hkgSmoothStop->setEnabled(offroad); + hyundaiCruiseMainDefault->setEnabled(offroad); toyotaTss2LongTune->setEnabled(offroad); toyotaEnhancedBsm->setEnabled(offroad); toyotaSngHack->setEnabled(offroad); @@ -223,6 +231,17 @@ void SPVehiclesTogglesPanel::updateToggles() { capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); cereal::CarParams::Reader CP = cmsg.getRoot(); + // Hyundai/Kia/Genesis + { + if (CP.getCarName() == "hyundai") { + if ((CP.getSpFlags() & 2) and !(CP.getFlags() & 8192)) { + hyundaiCruiseMainDefault->setEnabled(true); + } else { + hyundaiCruiseMainDefault->setEnabled(false); + } + } + } + // Toyota: Enhanced Blind Spot Monitor { if (CP.getCarName() == "toyota") { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h index ddf61f6e76..9d172be239 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle_settings.h @@ -67,6 +67,7 @@ private: Params params; bool is_onroad = false; + ParamControlSP *hyundaiCruiseMainDefault; ParamControlSP *stockLongToyota; ParamControlSP *toyotaEnhancedBsm; diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index 7dae31a53e..6bc28355ca 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -1583,6 +1583,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 6b8fd1efc1..54091977cc 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -1565,6 +1565,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index 4b0262e699..edd6417cd2 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -1563,6 +1563,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 0071a33e47..0201456ff4 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -1567,6 +1567,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 5db645c9f4..e648fbbade 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -1561,6 +1561,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 1a55d59a3b..80b718abe4 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1563,6 +1563,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 856ef26ca8..4888cc3dd9 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -1567,6 +1567,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 220b6575c4..9f28dd8c36 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -1563,6 +1563,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index a8c3992fd2..c253b763e4 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -1561,6 +1561,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index fddf413a50..71b7b5ebda 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1563,6 +1563,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 684447b113..8a9683c9b4 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -1563,6 +1563,14 @@ Reboot Required. Tested on RAV4 TSS1, Lexus LSS1, Toyota TSS1/1.5, and Prius TSS2. + + HKG CAN: Enable Cruise Main By Default + + + + Enabling this toggle sets CRUISE MAIN to ON by default when the car starts, without engaging MADS. The user still needs to manually engage MADS + + SettingsWindow diff --git a/system/manager/manager.py b/system/manager/manager.py index 8169617302..e4537cac78 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -71,6 +71,7 @@ def manager_init() -> None: ("FeatureStatus", "1"), ("HandsOnWheelMonitoring", "0"), ("HasAcceptedTermsSP", "0"), + ("HyundaiCruiseMainDefault", "0"), ("HideVEgoUi", "0"), ("LastSpeedLimitSignTap", "0"), ("LkasToggle", "0"), From 5aef30a9cd983f24946354d4467a55653e227573 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 4 Aug 2024 10:21:50 -0400 Subject: [PATCH 549/575] Only HKG --- selfdrive/controls/lib/longcontrol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index d0727be8bf..1ee8a7b013 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -75,7 +75,7 @@ class LongControl: if output_accel > self.CP.stopAccel: output_accel = min(output_accel, 0.0) output_accel -= self.CP.stoppingDecelRate * DT_CTRL - elif output_accel < self.CP.stopAccel: + elif output_accel < self.CP.stopAccel == 0.0: output_accel = min(output_accel, 0.0) output_accel += self.CP.stoppingDecelRate * DT_CTRL self.reset() From a59f0e3c480bd943c16fc1d0c0157418ef03c0b4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 4 Aug 2024 12:07:15 -0400 Subject: [PATCH 550/575] Toyota: comma Pedal: Fix upstream merge conflicts --- selfdrive/car/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index ba3805b01f..a772d9c16a 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -218,7 +218,7 @@ def create_gas_interceptor_command(packer, gas_amount, idx): values["GAS_COMMAND"] = gas_amount * 255. values["GAS_COMMAND2"] = gas_amount * 255. - dat = packer.make_can_msg("GAS_COMMAND", 0, values)[2] + dat = packer.make_can_msg("GAS_COMMAND", 0, values)[1] checksum = crc8_pedal(dat[:-1]) values["CHECKSUM_PEDAL"] = checksum From c70db1030c0b3d4df37f947adf36f6d5c6cb61e5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 4 Aug 2024 08:09:00 -0800 Subject: [PATCH 551/575] Toyota: comma Pedal: Fix upstream merge conflicts (#393) --- selfdrive/car/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index ba3805b01f..a772d9c16a 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -218,7 +218,7 @@ def create_gas_interceptor_command(packer, gas_amount, idx): values["GAS_COMMAND"] = gas_amount * 255. values["GAS_COMMAND2"] = gas_amount * 255. - dat = packer.make_can_msg("GAS_COMMAND", 0, values)[2] + dat = packer.make_can_msg("GAS_COMMAND", 0, values)[1] checksum = crc8_pedal(dat[:-1]) values["CHECKSUM_PEDAL"] = checksum From dc9a8ddca73738e277486c4992ba0ec07bcc14e6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 4 Aug 2024 17:11:22 -0400 Subject: [PATCH 552/575] Oops --- selfdrive/car/hyundai/carcontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index bd4d23ab27..f039c2ca98 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -543,7 +543,7 @@ class CarController(CarControllerBase): is_ice = not self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV) rate_up = 0.1 rate_down = 0.1 - if long_control == LongCtrlState.off or CS.out.standstill: + if long_control == LongCtrlState.off or (long_control == LongCtrlState.stopping and CS.out.standstill): self.accel_raw, self.accel_val = 0, 0 else: #self.accel_val = clip(self.accel_raw, self.accel_last - rate_down, self.accel_last + rate_up) From a4dc2649709f26bb5ef9ace782cba25d4508a865 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 5 Aug 2024 18:26:42 -0400 Subject: [PATCH 553/575] left this out wow --- selfdrive/car/hyundai/carcontroller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index f039c2ca98..849f2fced3 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -145,6 +145,7 @@ class CarController(CarControllerBase): if self.frame % 200 == 0: self.custom_stock_planner_speed = self.param_s.get_bool("CustomStockLongPlanner") self.v_cruise_min = HYUNDAI_V_CRUISE_MIN[CS.params_list.is_metric] * (CV.KPH_TO_MPH if not CS.params_list.is_metric else 1) + self.v_target_plan = min(CC.vCruise * CV.KPH_TO_MS, self.speeds) actuators = CC.actuators hud_control = CC.hudControl From d979148643030436980d0eb4c38379589ce8e593 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 5 Aug 2024 19:04:09 -0400 Subject: [PATCH 554/575] different start accel and less stopping accel --- selfdrive/car/hyundai/interface.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 20dace7228..7824292d30 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -104,10 +104,14 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.startingState = True ret.vEgoStarting = 0.1 - ret.startAccel = 1.6 - ret.stopAccel = -1.0 + ret.stopAccel = -0.5 ret.longitudinalActuatorDelay = 0.5 + if ret.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): + ret.startAccel = 1.0 + else: + ret.startAccel = 1.5 + if DBC[ret.carFingerprint]["radar"] is None: if ret.spFlags & (HyundaiFlagsSP.SP_ENHANCED_SCC | HyundaiFlagsSP.SP_CAMERA_SCC_LEAD): ret.radarUnavailable = False From 1ef7574a3f06d1dfb567b4a18856abd500c096a4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 5 Aug 2024 19:37:40 -0400 Subject: [PATCH 555/575] try releasing stop req faster --- selfdrive/controls/controlsd.py | 8 +++++++- selfdrive/controls/lib/longcontrol.py | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 519fddaf6a..0a97c7ed7c 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -638,9 +638,15 @@ class Controls: self.LoC.reset() if not self.joystick_mode: + speeds = long_plan.speeds + if len(speeds): + resume = self.enabled_long and CS.standstill and speeds[-1] > 0.1 + else: + resume = False + # accel PID loop pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_helper.v_cruise_kph * CV.KPH_TO_MS) - actuators.accel = self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits) + actuators.accel = self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits, resume) # Steering PID loop and lateral MPC if self.model_use_lateral_planner: diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index e3cd4bb654..4c52c1a63d 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -11,10 +11,10 @@ LongCtrlState = car.CarControl.Actuators.LongControlState def long_control_state_trans(CP, active, long_control_state, v_ego, - should_stop, brake_pressed, cruise_standstill): + should_stop, brake_pressed, cruise_standstill, resume): # Ignore cruise standstill if car has a gas interceptor cruise_standstill = cruise_standstill and not CP.enableGasInterceptorDEPRECATED - stopping_condition = should_stop + stopping_condition = should_stop and not resume starting_condition = (not should_stop and not cruise_standstill and not brake_pressed) @@ -58,14 +58,14 @@ class LongControl: def reset(self): self.pid.reset() - def update(self, active, CS, a_target, should_stop, accel_limits): + def update(self, active, CS, a_target, should_stop, accel_limits, resume): """Update longitudinal control. This updates the state machine and runs a PID loop""" self.pid.neg_limit = accel_limits[0] self.pid.pos_limit = accel_limits[1] self.long_control_state = long_control_state_trans(self.CP, active, self.long_control_state, CS.vEgo, should_stop, CS.brakePressed, - CS.cruiseState.standstill) + CS.cruiseState.standstill, resume) if self.long_control_state == LongCtrlState.off: self.reset() output_accel = 0. From d31cde706618edcab09e524d46528dad11ab9e99 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 5 Aug 2024 19:54:34 -0400 Subject: [PATCH 556/575] hkg only --- selfdrive/controls/controlsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 0a97c7ed7c..89c3d89360 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -640,7 +640,7 @@ class Controls: if not self.joystick_mode: speeds = long_plan.speeds if len(speeds): - resume = self.enabled_long and CS.standstill and speeds[-1] > 0.1 + resume = self.enabled_long and CS.standstill and speeds[-1] > 0.1 and self.CP.carName == "hyundai" else: resume = False From a5b4e1bb4b41b5062f697e8047a9cf402cb9d9c9 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 5 Aug 2024 22:52:18 -0400 Subject: [PATCH 557/575] wrong place to condition --- selfdrive/controls/lib/longcontrol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 4c52c1a63d..975556b759 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -14,8 +14,8 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, should_stop, brake_pressed, cruise_standstill, resume): # Ignore cruise standstill if car has a gas interceptor cruise_standstill = cruise_standstill and not CP.enableGasInterceptorDEPRECATED - stopping_condition = should_stop and not resume - starting_condition = (not should_stop and + stopping_condition = should_stop + starting_condition = ((not should_stop or resume) and not cruise_standstill and not brake_pressed) started_condition = v_ego > CP.vEgoStarting From 5df890546fec75e948b87804e0706dd1abe6ab64 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 00:44:59 -0400 Subject: [PATCH 558/575] lower stop accel --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 7824292d30..c3ba41e4e4 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -104,7 +104,7 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.startingState = True ret.vEgoStarting = 0.1 - ret.stopAccel = -0.5 + ret.stopAccel = -1.0 ret.longitudinalActuatorDelay = 0.5 if ret.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): From 67f5afb7a2fae57c3d82579aa2c05ae253ada4c7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 00:48:32 -0400 Subject: [PATCH 559/575] default vEgoStarting --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index c3ba41e4e4..35ad043a69 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -103,7 +103,7 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.startingState = True - ret.vEgoStarting = 0.1 + ret.vEgoStarting = 0.5 ret.stopAccel = -1.0 ret.longitudinalActuatorDelay = 0.5 From db593e718f2474d8ea3420b1923b8212a6c964a6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 00:48:40 -0400 Subject: [PATCH 560/575] diff logic --- selfdrive/controls/controlsd.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 89c3d89360..6c5cf6ee5a 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -639,10 +639,9 @@ class Controls: if not self.joystick_mode: speeds = long_plan.speeds + resume = False if len(speeds): resume = self.enabled_long and CS.standstill and speeds[-1] > 0.1 and self.CP.carName == "hyundai" - else: - resume = False # accel PID loop pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_helper.v_cruise_kph * CV.KPH_TO_MS) From 039fd97e6e5197bbd386e40226a7ab94d9ee1297 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 00:51:05 -0400 Subject: [PATCH 561/575] more aggro --- selfdrive/controls/lib/longitudinal_planner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index bee5e5636a..8cd3674970 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -82,7 +82,8 @@ class LongitudinalPlanner: self.dt = dt self.a_desired = init_a - self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt) + v_ego_sec = 0.75 if CP.carName == "hyundai" else 2.0 + self.v_desired_filter = FirstOrderFilter(init_v, v_ego_sec, self.dt) self.v_model_error = 0.0 self.v_desired_trajectory = np.zeros(CONTROL_N) From 221f2648a9390ede12a31dd35f0ec76caa0c9dd5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 01:03:24 -0400 Subject: [PATCH 562/575] lower matches a bit more --- selfdrive/controls/lib/longitudinal_planner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 8cd3674970..976944228e 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -82,7 +82,7 @@ class LongitudinalPlanner: self.dt = dt self.a_desired = init_a - v_ego_sec = 0.75 if CP.carName == "hyundai" else 2.0 + v_ego_sec = 0.5 if CP.carName == "hyundai" else 2.0 self.v_desired_filter = FirstOrderFilter(init_v, v_ego_sec, self.dt) self.v_model_error = 0.0 From 8da8986135568c035a2eb17e46996f6783eeeca5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 01:05:07 -0400 Subject: [PATCH 563/575] need to stop this too --- selfdrive/controls/lib/longcontrol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 975556b759..1a9e7c2fb7 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -14,7 +14,7 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, should_stop, brake_pressed, cruise_standstill, resume): # Ignore cruise standstill if car has a gas interceptor cruise_standstill = cruise_standstill and not CP.enableGasInterceptorDEPRECATED - stopping_condition = should_stop + stopping_condition = should_stop and not resume starting_condition = ((not should_stop or resume) and not cruise_standstill and not brake_pressed) From bb3d041bb175e9e112abcb73d8b9d52986bb4acb Mon Sep 17 00:00:00 2001 From: rav4kumar Date: Tue, 6 Aug 2024 07:00:38 -0700 Subject: [PATCH 564/575] rockettttt --- .../sunnypilot/qt/onroad/annotated_camera.cc | 47 +++++-------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc index 142400b8c3..2d89a8068e 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc @@ -1290,46 +1290,25 @@ void AnnotatedCameraWidgetSP::drawDriverState(QPainter &painter, const UIStateSP } void AnnotatedCameraWidgetSP::rocketFuel(QPainter &p) { + UIState *s = uiState(); + float accel = (*s->sm)["carControl"].getCarControl().getActuators().getAccel(); + int widgetHeight = rect().height(); + float halfHeightAbs = std::abs(accel) * widgetHeight / 2.0f; + const float scannerWidth = 15; + QRect scannerRect; - static const int ct_n = 1; - static float ct; - - int rect_w = rect().width(); - int rect_h = rect().height(); - - const int n = 15 + 1; //Add one off screen due to timing issues - static float t[n]; - int dim_n = (sin(ct / 5) + 1) * (n - 0.01); - t[dim_n] = 1.0; - t[(int)(ct/ct_n)] = 1.0; - - UIStateSP *s = uiStateSP(); - float vc_accel0 = (*s->sm)["carState"].getCarState().getAEgo(); - static float vc_accel; - vc_accel = vc_accel + (vc_accel0 - vc_accel) / 5; - float hha = 0; - if (vc_accel > 0) { - hha = 0.85 - 0.1 / vc_accel; // only extend up to 85% + if (accel > 0) { p.setBrush(QColor(0, 245, 0, 200)); - } - if (vc_accel < 0) { - hha = 0.85 + 0.1 / vc_accel; // only extend up to 85% - p.setBrush(QColor(245, 0, 0, 200)); - } - if (hha < 0) { - hha = 0; - } - hha = hha * rect_h; - float wp = 28; - if (vc_accel > 0) { - QRect ra = QRect(rect_w - wp, rect_h / 2 - hha / 2, wp, hha / 2); - p.drawRect(ra); + scannerRect = QRect(0, widgetHeight / 2 - halfHeightAbs, scannerWidth, halfHeightAbs); } else { - QRect ra = QRect(rect_w - wp, rect_h / 2, wp, hha / 2); - p.drawRect(ra); + p.setBrush(QColor(245, 0, 0, 200)); + scannerRect = QRect(0, widgetHeight / 2, scannerWidth, halfHeightAbs); } + + p.drawRect(scannerRect); } + void AnnotatedCameraWidgetSP::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, int num, const cereal::CarState::Reader &car_data, int chevron_data) { painter.save(); From f532c73c6016a51987623f97c98d4e378fa31581 Mon Sep 17 00:00:00 2001 From: rav4kumar Date: Tue, 6 Aug 2024 07:05:50 -0700 Subject: [PATCH 565/575] accel --- selfdrive/controls/lib/sunnypilot/accel_controller.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py index 095c6b3352..ccc797ebc8 100644 --- a/selfdrive/controls/lib/sunnypilot/accel_controller.py +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -32,12 +32,8 @@ AccelPersonality = custom.AccelerationPersonality _DP_CRUISE_MIN_V = [-0.04, -0.04, -0.16, -0.16, -0.26, -0.26, -0.46, -0.46, -0.74] _DP_CRUISE_MIN_V_ECO = [-0.03, -0.03, -0.15, -0.15, -0.25, -0.25, -0.45, -0.45, -0.72] _DP_CRUISE_MIN_V_SPORT = [-0.05, -0.05, -0.17, -0.17, -0.27, -0,27, -0.47, -0.47, -0.76] -_DP_CRUISE_MIN_BP = [0., 3.0, 3.01, 11.1, 11.11, 20., 20.01, 30., 30.01] +_DP_CRUISE_MIN_BP = [0., 3.0, 3.01, 11.1, 11.11, 17., 20.01, 30., 30.01] -#_DP_CRUISE_MIN_V = [-0.045, -0.045, -0.201, -0.201, -0.430, -0.430, -0.66, -0.66, -0.82] -#_DP_CRUISE_MIN_V_ECO = [-0.04, -0.04, -0.200, -0.200, -0.425, -0.425, -0.64, -0.64, -0.80] -#_DP_CRUISE_MIN_V_SPORT = [-0.05, -0.05, -0.204, -0.204, -0.445, -0.445, -0.68, -0.68, -0.84] -#_DP_CRUISE_MIN_BP = [0., 5.00, 5.01, 17., 17.01, 27.77, 27.78, 33., 33.01] _DP_CRUISE_MAX_V = [2.0, 2.0, 2.0, 1.68, 1.07, .72, .53, .42, .13] _DP_CRUISE_MAX_V_ECO = [2.0, 2.0, 2.0, 1.33, 0.86, .53, .42, .31, .085] _DP_CRUISE_MAX_V_SPORT = [2.0, 2.0, 2.0, 2.00, 1.34, .96, .78, .60, .4] From 3413f077fb8e4a3966b1b6ff0d5f1f7927deb6f0 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Tue, 6 Aug 2024 08:20:37 -0700 Subject: [PATCH 566/575] Simplify --- selfdrive/controls/lib/sunnypilot/accel_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py index ccc797ebc8..b9e043aa4f 100644 --- a/selfdrive/controls/lib/sunnypilot/accel_controller.py +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -29,10 +29,10 @@ from openpilot.common.numpy_fast import interp AccelPersonality = custom.AccelerationPersonality # accel personality by @arne182 modified by cgw and kumar -_DP_CRUISE_MIN_V = [-0.04, -0.04, -0.16, -0.16, -0.26, -0.26, -0.46, -0.46, -0.74] -_DP_CRUISE_MIN_V_ECO = [-0.03, -0.03, -0.15, -0.15, -0.25, -0.25, -0.45, -0.45, -0.72] -_DP_CRUISE_MIN_V_SPORT = [-0.05, -0.05, -0.17, -0.17, -0.27, -0,27, -0.47, -0.47, -0.76] -_DP_CRUISE_MIN_BP = [0., 3.0, 3.01, 11.1, 11.11, 17., 20.01, 30., 30.01] +_DP_CRUISE_MIN_V = [-1.0, -1.0, -0.76] +_DP_CRUISE_MIN_V_ECO = [-1.0, -1.0, -0.74] +_DP_CRUISE_MIN_V_SPORT = [-1.0, -1.0, -0.78] +_DP_CRUISE_MIN_BP = [0., 11.1, 20.] _DP_CRUISE_MAX_V = [2.0, 2.0, 2.0, 1.68, 1.07, .72, .53, .42, .13] _DP_CRUISE_MAX_V_ECO = [2.0, 2.0, 2.0, 1.33, 0.86, .53, .42, .31, .085] From 2a383c65b88c1b09e99c60ac6590597424d179a8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 17:26:37 -0400 Subject: [PATCH 567/575] a tad less aggressive --- selfdrive/controls/lib/longitudinal_planner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 976944228e..852a6bd1bf 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -82,7 +82,7 @@ class LongitudinalPlanner: self.dt = dt self.a_desired = init_a - v_ego_sec = 0.5 if CP.carName == "hyundai" else 2.0 + v_ego_sec = 0.6 if CP.carName == "hyundai" else 2.0 self.v_desired_filter = FirstOrderFilter(init_v, v_ego_sec, self.dt) self.v_model_error = 0.0 From c6fa48d166f55c962ba2e71319c08045ade1d5c7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 18:54:46 -0400 Subject: [PATCH 568/575] no starting state for non-ICE --- selfdrive/car/hyundai/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 35ad043a69..d667c2a639 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -102,15 +102,15 @@ class CarInterface(CarInterfaceBase): ret.pcmCruise = not ret.openpilotLongitudinalControl ret.stoppingControl = True - ret.startingState = True ret.vEgoStarting = 0.5 + ret.startAccel = 1.5 ret.stopAccel = -1.0 ret.longitudinalActuatorDelay = 0.5 if ret.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): - ret.startAccel = 1.0 + ret.startingState = False else: - ret.startAccel = 1.5 + ret.startingState = True if DBC[ret.carFingerprint]["radar"] is None: if ret.spFlags & (HyundaiFlagsSP.SP_ENHANCED_SCC | HyundaiFlagsSP.SP_CAMERA_SCC_LEAD): From db684508dad6a43abb795b40ff7bb5420562a56d Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:48:44 -0700 Subject: [PATCH 569/575] Update accel_controller.py --- selfdrive/controls/lib/sunnypilot/accel_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/lib/sunnypilot/accel_controller.py b/selfdrive/controls/lib/sunnypilot/accel_controller.py index b9e043aa4f..8fead3577c 100644 --- a/selfdrive/controls/lib/sunnypilot/accel_controller.py +++ b/selfdrive/controls/lib/sunnypilot/accel_controller.py @@ -29,10 +29,10 @@ from openpilot.common.numpy_fast import interp AccelPersonality = custom.AccelerationPersonality # accel personality by @arne182 modified by cgw and kumar -_DP_CRUISE_MIN_V = [-1.0, -1.0, -0.76] -_DP_CRUISE_MIN_V_ECO = [-1.0, -1.0, -0.74] -_DP_CRUISE_MIN_V_SPORT = [-1.0, -1.0, -0.78] -_DP_CRUISE_MIN_BP = [0., 11.1, 20.] +_DP_CRUISE_MIN_V = [-0.04, -0.04, -0.11, -0.11, -0.44, -0.44, -0.66, -0.66, -0.74] +_DP_CRUISE_MIN_V_ECO = [-0.03, -0.03, -0.10, -0.10, -0.45, -0.45, -0.65, -0.65, -0.72] +_DP_CRUISE_MIN_V_SPORT = [-0.05, -0.05, -0.12, -0.12, -0.47, -0,47, -0.67, -0.67, -0.76] +_DP_CRUISE_MIN_BP = [0., 3.0, 3.01, 11.1, 11.11, 17., 20.01, 30., 30.01] _DP_CRUISE_MAX_V = [2.0, 2.0, 2.0, 1.68, 1.07, .72, .53, .42, .13] _DP_CRUISE_MAX_V_ECO = [2.0, 2.0, 2.0, 1.33, 0.86, .53, .42, .31, .085] From 3c168d0975356c0557b838cd3db99a04b652fc79 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 20:22:10 -0400 Subject: [PATCH 570/575] send it --- cereal/custom.capnp | 1 + common/params.cc | 1 + .../lib/longitudinal_mpc_lib/long_mpc.py | 8 +++++++- .../controls/lib/longitudinal_planner.py | 20 ++++++++++++++++--- selfdrive/controls/plannerd.py | 2 +- .../offroad/settings/sunnypilot_settings.cc | 8 +++++++- 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 8825cf6f07..8bcf50f34e 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -16,6 +16,7 @@ enum LongitudinalPersonalitySP { moderate @1; standard @2; relaxed @3; + overtake @4; } enum AccelerationPersonality { diff --git a/common/params.cc b/common/params.cc index 1f71c36df1..92c2742921 100644 --- a/common/params.cc +++ b/common/params.cc @@ -291,6 +291,7 @@ std::unordered_map keys = { {"OsmLocationUrl", PERSISTENT}, {"OsmWayTest", PERSISTENT}, {"OsmDownloadedDate", PERSISTENT}, + {"OvertakingAccelerationAssist", PERSISTENT}, {"PathOffset", PERSISTENT | BACKUP}, {"PauseLateralSpeed", PERSISTENT | BACKUP}, {"QuietDrive", PERSISTENT | BACKUP}, diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index a2df527a55..0c67119d14 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -66,6 +66,8 @@ def get_jerk_factor(personality=custom.LongitudinalPersonalitySP.standard): return 0.8 elif personality==custom.LongitudinalPersonalitySP.aggressive: return 0.6 + elif personality==custom.LongitudinalPersonalitySP.overtake: + return 0.25 else: raise NotImplementedError("Longitudinal personality not supported") @@ -79,6 +81,8 @@ def get_T_FOLLOW(personality=custom.LongitudinalPersonalitySP.standard): return 1.25 elif personality==custom.LongitudinalPersonalitySP.aggressive: return 1.0 + elif personality==custom.LongitudinalPersonalitySP.overtake: + return 0.5 else: raise NotImplementedError("Longitudinal personality not supported") @@ -358,9 +362,11 @@ class LongitudinalMpc: self.cruise_min_a = min_a self.max_a = max_a - def update(self, radarstate, v_cruise, x, v, a, j, personality=custom.LongitudinalPersonalitySP.standard, dynamic_personality=False): + def update(self, radarstate, v_cruise, x, v, a, j, personality=custom.LongitudinalPersonalitySP.standard, + dynamic_personality=False, overtaking_acceleration_assist=False): v_ego = self.x0[1] t_follow = get_dynamic_personality(v_ego, personality) if dynamic_personality else get_T_FOLLOW(personality) + t_follow = get_T_FOLLOW(custom.LongitudinalPersonalitySP.overtake) if overtaking_acceleration_assist else t_follow self.status = radarstate.leadOne.status or radarstate.leadTwo.status lead_xv_0 = self.process_lead(radarstate.leadOne) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 852a6bd1bf..9c5b770433 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -3,7 +3,7 @@ import math import numpy as np from openpilot.common.numpy_fast import clip, interp from openpilot.common.params import Params -from cereal import car +from cereal import car, custom import cereal.messaging as messaging from openpilot.common.conversions import Conversions as CV @@ -36,6 +36,8 @@ _A_TOTAL_MAX_BP = [20., 40.] EventName = car.CarEvent.EventName +LaneChangeState = log.LaneChangeState +LaneChangeDirection = log.LaneChangeDirection def get_max_accel(v_ego): @@ -102,6 +104,8 @@ class LongitudinalPlanner: self.dynamic_experimental_controller = DynamicExperimentalController() self.accel_controller = AccelController() + self.overtaking_accel = self.params.get_bool("OvertakingAccelerationAssist") + def read_param(self): try: self.dynamic_experimental_controller.set_enabled(self.params.get_bool("DynamicExperimentalControl")) @@ -109,6 +113,8 @@ class LongitudinalPlanner: self.dynamic_experimental_controller = DynamicExperimentalController() self.accel_controller = AccelController() + self.overtaking_accel = self.params.get_bool("OvertakingAccelerationAssist") + @staticmethod def parse_model(model_msg, model_error): if (len(model_msg.position.x) == ModelConstants.IDX_N and @@ -168,6 +174,13 @@ class LongitudinalPlanner: accel_limits = [ACCEL_MIN, ACCEL_MAX] accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] + lat_plan = sm['lateralPlanDEPRECATED'] + dm_state = sm['driverMonitoringState'] + overtaking_accel_allowed = ((lat_plan.laneChangeDirection == LaneChangeDirection.right and dm_state.isRHD) or + (lat_plan.laneChangeDirection == LaneChangeDirection.left and not dm_state.isRHD)) and \ + (lat_plan.laneChangeState in (LaneChangeState.preLaneChange, LaneChangeState.laneChangeStarting)) + overtaking_accel_engaged = self.overtaking_accel and overtaking_accel_allowed and v_ego > 40 * CV.MPH_TO_MS + if reset_state: self.v_desired_filter.x = v_ego # Clip aEgo to cruise limits to prevent large accelerations when becoming active @@ -190,11 +203,12 @@ class LongitudinalPlanner: accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) - self.mpc.set_weights(prev_accel_constraint, personality=sm['controlsStateSP'].personality) + self.mpc.set_weights(prev_accel_constraint, personality=custom.LongitudinalPersonalitySP.overtake if overtaking_accel_engaged else sm['controlsStateSP'].personality) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsStateSP'].personality, dynamic_personality=sm['controlsStateSP'].dynamicPersonality) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsStateSP'].personality, + dynamic_personality=sm['controlsStateSP'].dynamicPersonality, overtaking_acceleration_assist=overtaking_accel_engaged) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution) diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 6c51b38b13..daef841c19 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -32,7 +32,7 @@ def plannerd_thread(): pm = messaging.PubMaster(['longitudinalPlan', 'longitudinalPlanSP'] + lateral_planner_svs) sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2', 'longitudinalPlan', 'navInstruction', 'longitudinalPlanSP', - 'liveMapDataSP', 'e2eLongStateSP', 'controlsStateSP'] + lateral_planner_svs, + 'liveMapDataSP', 'e2eLongStateSP', 'controlsStateSP', 'driverMonitoringState'] + lateral_planner_svs, poll='modelV2', ignore_avg_freq=['radarState']) while True: diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc index c363e78844..2bbbbc8872 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc @@ -49,6 +49,12 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { tr("While in Auto Lane, switch to Laneless for current/future curves."), "../assets/offroad/icon_blank.png", }, + { + "OvertakingAccelerationAssist", + tr("Overtaking Acceleration Assist"), + tr("Overtaking Acceleration Assist will operate when the turn signal indicator is turned on to the left (left-hand drive) or turned on to the right (right-hand drive) while Smart Cruise Control is operating."), + "../assets/offroad/icon_blank.png", + }, { "EnableSlc", tr("Speed Limit Control (SLC)"), @@ -298,7 +304,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { list->addItem(dlp_settings); } - if (param == "VisionCurveLaneless") { + if (param == "OvertakingAccelerationAssist") { list->addItem(laneChangeSettingsLayout); list->addItem(horizontal_line()); From bc6556601753c1e741b33ff54278c919ac42c679 Mon Sep 17 00:00:00 2001 From: rav4kumar Date: Tue, 6 Aug 2024 17:36:53 -0700 Subject: [PATCH 571/575] Revert "dynamically change *coast @cydia2020" This reverts commit c831b5f2af81232cf7896a02cd1cd93da3f32ce7. --- .../lib/longitudinal_mpc_lib/long_mpc.py | 72 ++----------------- .../controls/lib/longitudinal_planner.py | 2 +- 2 files changed, 6 insertions(+), 68 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index e520c3bcb2..3090d4bb51 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -3,8 +3,7 @@ import os import time import numpy as np from cereal import custom -from openpilot.common.numpy_fast import clip, interp -from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import clip from openpilot.common.realtime import DT_MDL from openpilot.common.swaglog import cloudlog # WARNING: imports outside of constants will not trigger a rebuild @@ -101,61 +100,6 @@ def get_dynamic_personality(v_ego, personality=custom.LongitudinalPersonalitySP. raise NotImplementedError("Dynamic personality not supported") return np.interp(v_ego, x_vel, y_dist) -# multiplier for A_CHANGE_COST = 200. -def get_a_change_cost_multiplier(v_ego, v_lead0, v_lead1, personality=custom.LongitudinalPersonalitySP.standard): - if personality==custom.LongitudinalPersonalitySP.relaxed: - a_change_cost_multiplier_follow_distance = 1.0 - elif personality==custom.LongitudinalPersonalitySP.standard: - a_change_cost_multiplier_follow_distance = 0.5 - elif personality==custom.LongitudinalPersonalitySP.moderate: - a_change_cost_multiplier_follow_distance = 0.5 - elif personality==custom.LongitudinalPersonalitySP.aggressive: - a_change_cost_multiplier_follow_distance = 0.1 - else: - raise NotImplementedError("Longitudinal personality not supported") - - # stolen from @KRKeegan - # values used for interpolation - # start with a small a_change_multiplier_values during interpolation to allow for faster change in accel - A_CHANGE_COST_MULTIPLIER_BP = [0., 10.] # vEgo, in m/s - A_CHANGE_COST_MULTIPLIER_V = [.05, 1.] # multiplier values - - # when lead is pulling away, and speed is between 0 and 10 m/s, interpolate a_change_cost_multiplier_v_ego - a_change_cost_multiplier_v_ego = 1. - if (v_lead0 - v_ego > 1e-3) and (v_lead1 - v_ego > 1e-3): - a_change_cost_multiplier_v_ego = interp(v_ego, A_CHANGE_COST_MULTIPLIER_BP, A_CHANGE_COST_MULTIPLIER_V) - - # get the minimum between a_change_multiplier based on driving personality, and a_change_multiplier based - # on v_ego - a_change_multiplier = min(a_change_cost_multiplier_follow_distance, a_change_cost_multiplier_v_ego) - - # and pass it on as the final result - return a_change_multiplier - -# multiplier for DANGER_ZONE_COST = 100. -def get_danger_zone_cost_multiplier(personality=custom.LongitudinalPersonalitySP.standard): - if personality==custom.LongitudinalPersonalitySP.relaxed: - return 1.6 - elif personality==custom.LongitudinalPersonalitySP.standard: - return 1.3 - elif personality==custom.LongitudinalPersonalitySP.moderate: - return 1.3 - elif personality==custom.LongitudinalPersonalitySP.aggressive: - return 1.0 - else: - raise NotImplementedError("Longitudinal personality not supported") - -#def get_STOP_DISTANCE(personality=custom.LongitudinalPersonalitySP.standard): -# if personality==log.LongitudinalPersonality.relaxed: -# return 6.0 -# elif personality==log.LongitudinalPersonality.standard: -# return 5.5 -# elif personality==log.LongitudinalPersonality.aggressive: -# return 4.5 -# else: -# raise NotImplementedError("dynamic stop distance not supported") - - def get_stopped_equivalence_factor(v_lead): return (v_lead**2) / (2 * COMFORT_BRAKE) @@ -354,16 +298,12 @@ class LongitudinalMpc: for i in range(N): self.solver.cost_set(i, 'Zl', Zl) - def set_weights(self, prev_accel_constraint=True, v_lead0 = 0., v_lead1 = 0., personality=custom.LongitudinalPersonalitySP.standard): - v_ego = self.x0[1] + def set_weights(self, prev_accel_constraint=True, personality=custom.LongitudinalPersonalitySP.standard): jerk_factor = get_jerk_factor(personality) - a_change_cost_multiplier = get_a_change_cost_multiplier(v_ego, v_lead0, v_lead1, personality) - danger_zone_cost_multiplier = get_danger_zone_cost_multiplier(personality) if self.mode == 'acc': a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0 - cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, jerk_factor * a_change_cost_multiplier \ - * a_change_cost, jerk_factor * J_EGO_COST] - constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST * danger_zone_cost_multiplier] + cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, jerk_factor * a_change_cost, jerk_factor * J_EGO_COST] + constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] elif self.mode == 'blended': a_change_cost = 40.0 if prev_accel_constraint else 0 cost_weights = [0., 0.1, 0.2, 5.0, a_change_cost, 1.0] @@ -417,7 +357,7 @@ class LongitudinalMpc: self.cruise_min_a = min_a self.max_a = max_a - def update(self, radarstate, v_cruise, prev_accel_constraint, x, v, a, j, personality=custom.LongitudinalPersonalitySP.standard, dynamic_personality=False): + def update(self, radarstate, v_cruise, x, v, a, j, personality=custom.LongitudinalPersonalitySP.standard, dynamic_personality=False): v_ego = self.x0[1] t_follow = get_dynamic_personality(v_ego, personality) if dynamic_personality else get_T_FOLLOW(personality) self.status = radarstate.leadOne.status or radarstate.leadTwo.status @@ -425,8 +365,6 @@ class LongitudinalMpc: lead_xv_0 = self.process_lead(radarstate.leadOne) lead_xv_1 = self.process_lead(radarstate.leadTwo) - self.set_weights(prev_accel_constraint=prev_accel_constraint, v_lead0=lead_xv_0[0, 1], v_lead1=lead_xv_1[0, 1], personality=personality) - # To estimate a safe distance from a moving lead, we calculate how much stopping # distance that lead needs as a minimum. We can add that to the current distance # and then treat that as a stopped car/obstacle at this new distance. diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 4e7e64470e..da8ec847ce 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -198,7 +198,7 @@ class LongitudinalPlanner: self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - self.mpc.update(sm['radarState'], v_cruise, prev_accel_constraint, x, v, a, j, personality=sm['controlsStateSP'].personality, dynamic_personality=sm['controlsStateSP'].dynamicPersonality) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['controlsStateSP'].personality, dynamic_personality=sm['controlsStateSP'].dynamicPersonality) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution) From 1eafceb321025d5a93364bf01c383b597ec53ba7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 20:46:06 -0400 Subject: [PATCH 572/575] fix --- selfdrive/controls/lib/longitudinal_planner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 9c5b770433..5536b0c1c8 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -3,7 +3,7 @@ import math import numpy as np from openpilot.common.numpy_fast import clip, interp from openpilot.common.params import Params -from cereal import car, custom +from cereal import car, log, custom import cereal.messaging as messaging from openpilot.common.conversions import Conversions as CV From fa4ae1367f236252469b528e9d18a773569ca15a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 22:54:13 -0400 Subject: [PATCH 573/575] back to stock --- selfdrive/car/hyundai/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index d667c2a639..43b9e28664 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -103,7 +103,7 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.vEgoStarting = 0.5 - ret.startAccel = 1.5 + ret.startAccel = 1.0 ret.stopAccel = -1.0 ret.longitudinalActuatorDelay = 0.5 From bacb49d8ee5043053288b95f0875827bc14c65b0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 6 Aug 2024 23:42:31 -0400 Subject: [PATCH 574/575] fix --- cereal/custom.capnp | 1 + selfdrive/controls/controlsd.py | 17 +++++++++++++++-- .../lib/longitudinal_mpc_lib/long_mpc.py | 2 +- selfdrive/controls/lib/longitudinal_planner.py | 14 +------------- .../qt/offroad/settings/sunnypilot_settings.cc | 1 + 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 8bcf50f34e..0ef005b07f 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -40,6 +40,7 @@ struct ControlsStateSP @0x81c2f05a394cf4af { personality @8 :LongitudinalPersonalitySP; dynamicPersonality @9 :Bool; accelPersonality @10 :AccelerationPersonality; + overtakingAccelerationAssist @11 :Bool; lateralControlState :union { indiState @1 :LateralINDIState; diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 6c5cf6ee5a..7b5d90cbbc 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -185,6 +185,7 @@ class Controls: self.custom_model_metadata.capabilities & ModelCapabilities.LateralPlannerSolution self.dynamic_personality = self.params.get_bool("DynamicPersonality") + self.overtaking_accel = self.params.get_bool("OvertakingAccelerationAssist") self.accel_personality = self.read_accel_personality_param() @@ -811,11 +812,21 @@ class Controls: # Curvature & Steering angle lp = self.sm['liveParameters'] - lp_mono_time_svs = 'lateralPlanDEPRECATED' if self.model_use_lateral_planner else 'modelV2' + dh = 'lateralPlanDEPRECATED' if self.model_use_lateral_planner else 'modelV2' steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg) curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll) + lc_svs = self.sm[dh] + long_plan = self.sm['longitudinalPlan'].hasLead + dm_state = self.sm['driverMonitoringState'] + overtaking_accel_allowed = ((lc_svs.laneChangeDirection == LaneChangeDirection.right and dm_state.isRHD) or + (lc_svs.laneChangeDirection == LaneChangeDirection.left and not dm_state.isRHD)) and \ + (lc_svs.laneChangeState in (LaneChangeState.preLaneChange, LaneChangeState.laneChangeStarting)) + overtaking_accel_engaged = self.overtaking_accel and overtaking_accel_allowed and \ + CS.vEgo > (60 * CV.KPH_TO_MS) if self.is_metric else (40 * CV.MPH_TO_MS) and long_plan.hasLead and \ + long_plan.aTarget > -0.2 and not (CS.leftBlinker and CS.rightBlinker) + # controlsState dat = messaging.new_message('controlsState') dat.valid = CS.canValid @@ -830,7 +841,7 @@ class Controls: controlsState.alertSound = current_alert.audible_alert controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan'] - controlsState.lateralPlanMonoTime = self.sm.logMonoTime[lp_mono_time_svs] + controlsState.lateralPlanMonoTime = self.sm.logMonoTime[dh] controlsState.enabled = not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and (self.enabled or CS.cruiseState.enabled) and CS.gearShifter not in [GearShifter.park, GearShifter.reverse] controlsState.active = not (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) and (self.active or CS.cruiseState.enabled) controlsState.curvature = curvature @@ -868,6 +879,7 @@ class Controls: controlsStateSP.personality = self.personality controlsStateSP.dynamicPersonality = self.dynamic_personality controlsStateSP.accelPersonality = self.accel_personality + controlsStateSP.overtakingAccelerationAssist = overtaking_accel_engaged if self.enable_nnff and lat_tuning == 'torque': controlsStateSP.lateralControlState.torqueState = self.LaC.pid_long_sp @@ -934,6 +946,7 @@ class Controls: self.reverse_acc_change = self.params.get_bool("ReverseAccChange") self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl") + self.overtaking_accel = self.params.get_bool("OvertakingAccelerationAssist") if self.sm.frame % int(2.5 / DT_CTRL) == 0: self.live_torque = self.params.get_bool("LiveTorque") diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 0c67119d14..cfc1f6cf09 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -67,7 +67,7 @@ def get_jerk_factor(personality=custom.LongitudinalPersonalitySP.standard): elif personality==custom.LongitudinalPersonalitySP.aggressive: return 0.6 elif personality==custom.LongitudinalPersonalitySP.overtake: - return 0.25 + return 0.1 else: raise NotImplementedError("Longitudinal personality not supported") diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 5536b0c1c8..f17f8df8b1 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -36,8 +36,6 @@ _A_TOTAL_MAX_BP = [20., 40.] EventName = car.CarEvent.EventName -LaneChangeState = log.LaneChangeState -LaneChangeDirection = log.LaneChangeDirection def get_max_accel(v_ego): @@ -104,8 +102,6 @@ class LongitudinalPlanner: self.dynamic_experimental_controller = DynamicExperimentalController() self.accel_controller = AccelController() - self.overtaking_accel = self.params.get_bool("OvertakingAccelerationAssist") - def read_param(self): try: self.dynamic_experimental_controller.set_enabled(self.params.get_bool("DynamicExperimentalControl")) @@ -113,8 +109,6 @@ class LongitudinalPlanner: self.dynamic_experimental_controller = DynamicExperimentalController() self.accel_controller = AccelController() - self.overtaking_accel = self.params.get_bool("OvertakingAccelerationAssist") - @staticmethod def parse_model(model_msg, model_error): if (len(model_msg.position.x) == ModelConstants.IDX_N and @@ -174,13 +168,6 @@ class LongitudinalPlanner: accel_limits = [ACCEL_MIN, ACCEL_MAX] accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] - lat_plan = sm['lateralPlanDEPRECATED'] - dm_state = sm['driverMonitoringState'] - overtaking_accel_allowed = ((lat_plan.laneChangeDirection == LaneChangeDirection.right and dm_state.isRHD) or - (lat_plan.laneChangeDirection == LaneChangeDirection.left and not dm_state.isRHD)) and \ - (lat_plan.laneChangeState in (LaneChangeState.preLaneChange, LaneChangeState.laneChangeStarting)) - overtaking_accel_engaged = self.overtaking_accel and overtaking_accel_allowed and v_ego > 40 * CV.MPH_TO_MS - if reset_state: self.v_desired_filter.x = v_ego # Clip aEgo to cruise limits to prevent large accelerations when becoming active @@ -203,6 +190,7 @@ class LongitudinalPlanner: accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) + overtaking_accel_engaged = sm['controlsStateSP'].overtakingAccelerationAssist self.mpc.set_weights(prev_accel_constraint, personality=custom.LongitudinalPersonalitySP.overtake if overtaking_accel_engaged else sm['controlsStateSP'].personality) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc index 2bbbbc8872..17be24764f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_settings.cc @@ -372,6 +372,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { toggles["EnableMads"]->setConfirmation(true, false); toggles["EndToEndLongAlertLight"]->setConfirmation(true, false); toggles["CustomOffsets"]->showDescription(); + toggles["OvertakingAccelerationAssist"]->setConfirmation(true, false); connect(toggles["EnableMads"], &ToggleControlSP::toggleFlipped, mads_settings, &MadsSettings::updateToggles); connect(toggles["EnableMads"], &ToggleControlSP::toggleFlipped, [=](bool state) { From ffd42b02b88539333e99bd0e8e473cf64f2dab5b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 7 Aug 2024 00:16:18 -0400 Subject: [PATCH 575/575] sportier --- selfdrive/controls/lib/longitudinal_planner.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index f17f8df8b1..7e288c1888 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -154,8 +154,10 @@ class LongitudinalPlanner: accel_limits = [ACCEL_MIN, ACCEL_MAX] accel_limits_turns = [ACCEL_MIN, ACCEL_MAX] + overtaking_accel_engaged = sm['controlsStateSP'].overtakingAccelerationAssist # override accel using Accel Controller - if self.accel_controller.is_enabled(accel_personality=sm['controlsStateSP'].accelPersonality): + if self.accel_controller.is_enabled(accel_personality=custom.AccelerationPersonality.sport if overtaking_accel_engaged else + sm['controlsStateSP'].accelPersonality): # get min, max from accel controller min_limit, max_limit = self.accel_controller.get_accel_limits(v_ego, accel_limits) if self.mpc.mode == 'acc': @@ -190,7 +192,6 @@ class LongitudinalPlanner: accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) - overtaking_accel_engaged = sm['controlsStateSP'].overtakingAccelerationAssist self.mpc.set_weights(prev_accel_constraint, personality=custom.LongitudinalPersonalitySP.overtake if overtaking_accel_engaged else sm['controlsStateSP'].personality) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)