Merge remote-tracking branch 'upstream/master' into enable-planner

pull/24873/head
Shane Smiskol 3 years ago
commit c9e446ad21
  1. 5
      .github/workflows/selfdrive_tests.yaml
  2. 1
      README.md
  3. 23
      SConstruct
  4. 2
      cereal
  5. 2
      common/params.cc
  6. 2
      docs/c_docs.rst
  7. 2
      opendbc
  8. 2
      panda
  9. 2
      rednose_repo
  10. 4
      release/files_common
  11. 4
      selfdrive/car/chrysler/carstate.py
  12. 8
      selfdrive/car/hyundai/carstate.py
  13. 12
      selfdrive/car/hyundai/interface.py
  14. 29
      selfdrive/car/hyundai/values.py
  15. 59
      selfdrive/car/interfaces.py
  16. 2
      selfdrive/car/torque_data/override.yaml
  17. 8
      selfdrive/car/toyota/interface.py
  18. 6
      selfdrive/car/toyota/tunes.py
  19. 2
      selfdrive/car/toyota/values.py
  20. 4
      selfdrive/car/volkswagen/values.py
  21. 16
      selfdrive/controls/lib/latcontrol_torque.py
  22. 16
      selfdrive/debug/count_events.py
  23. 10
      selfdrive/locationd/calibrationd.py
  24. 78
      selfdrive/locationd/laikad.py
  25. 4
      selfdrive/locationd/models/car_kf.py
  26. 16
      selfdrive/locationd/models/gnss_kf.py
  27. 106
      selfdrive/locationd/test/_test_locationd_lib.py
  28. 27
      selfdrive/locationd/test/test_laikad.py
  29. 94
      selfdrive/locationd/test/test_locationd.py
  30. 2
      selfdrive/loggerd/encoder/ffmpeg_encoder.cc
  31. 9
      selfdrive/loggerd/logger.cc
  32. 16
      selfdrive/loggerd/loggerd.cc
  33. 2
      selfdrive/loggerd/tests/test_logger.cc
  34. 101
      selfdrive/manager/build.py
  35. 1
      selfdrive/test/process_replay/README.md
  36. 21
      selfdrive/test/process_replay/process_replay.py
  37. 2
      selfdrive/test/process_replay/ref_commit
  38. 3
      selfdrive/test/profiling/profiler.py
  39. 3
      selfdrive/thermald/tests/test_power_monitoring.py
  40. 13
      selfdrive/thermald/thermald.py
  41. 23
      selfdrive/ui/SConscript
  42. 8
      selfdrive/ui/installer/installer.cc
  43. 6
      selfdrive/ui/qt/home.cc
  44. 16
      selfdrive/ui/qt/maps/map_settings.cc
  45. 2
      selfdrive/ui/qt/offroad/driverview.cc
  46. 32
      selfdrive/ui/qt/offroad/networking.cc
  47. 14
      selfdrive/ui/qt/offroad/onboarding.cc
  48. 110
      selfdrive/ui/qt/offroad/settings.cc
  49. 35
      selfdrive/ui/qt/onroad.cc
  50. 2
      selfdrive/ui/qt/onroad.h
  51. 18
      selfdrive/ui/qt/setup/reset.cc
  52. 42
      selfdrive/ui/qt/setup/setup.cc
  53. 16
      selfdrive/ui/qt/setup/updater.cc
  54. 16
      selfdrive/ui/qt/sidebar.cc
  55. 4
      selfdrive/ui/qt/text.cc
  56. 8
      selfdrive/ui/qt/util.cc
  57. 72
      selfdrive/ui/qt/widgets/cameraview.cc
  58. 12
      selfdrive/ui/qt/widgets/cameraview.h
  59. 8
      selfdrive/ui/qt/widgets/drive_stats.cc
  60. 2
      selfdrive/ui/qt/widgets/drive_stats.h
  61. 10
      selfdrive/ui/qt/widgets/input.cc
  62. 6
      selfdrive/ui/qt/widgets/offroad_alerts.cc
  63. 28
      selfdrive/ui/qt/widgets/prime.cc
  64. 18
      selfdrive/ui/qt/widgets/ssh_keys.cc
  65. 2
      selfdrive/ui/qt/widgets/ssh_keys.h
  66. 1
      selfdrive/ui/tests/.gitignore
  67. 18
      selfdrive/ui/tests/create_test_translations.sh
  68. 19
      selfdrive/ui/tests/test_runner.cc
  69. 51
      selfdrive/ui/tests/test_translations.cc
  70. 1
      selfdrive/ui/ui.cc
  71. 8
      selfdrive/ui/ui.h
  72. 2
      system/camerad/snapshot/snapshot.py
  73. 4
      system/hardware/base.py
  74. 3
      system/hardware/pc/hardware.py
  75. 3
      system/hardware/tici/hardware.py
  76. 2
      tools/CTF.md
  77. 5
      tools/replay/.gitignore
  78. 14
      tools/replay/README.md
  79. 19
      tools/replay/SConscript
  80. 4
      tools/replay/camera.cc
  81. 4
      tools/replay/camera.h
  82. 12
      tools/replay/can_replay.py
  83. 2
      tools/replay/consoleui.cc
  84. 2
      tools/replay/consoleui.h
  85. 4
      tools/replay/filereader.cc
  86. 0
      tools/replay/filereader.h
  87. 13
      tools/replay/framereader.cc
  88. 3
      tools/replay/framereader.h
  89. 4
      tools/replay/logreader.cc
  90. 2
      tools/replay/logreader.h
  91. 4
      tools/replay/main.cc
  92. 4
      tools/replay/replay.cc
  93. 4
      tools/replay/replay.h
  94. 6
      tools/replay/route.cc
  95. 6
      tools/replay/route.h
  96. 4
      tools/replay/tests/test_replay.cc
  97. 0
      tools/replay/tests/test_runner.cc
  98. 2
      tools/replay/util.cc
  99. 0
      tools/replay/util.h
  100. 4
      tools/serial/connect.sh
  101. Some files were not shown because too many files have changed in this diff Show More

@ -299,17 +299,20 @@ jobs:
$UNIT_TEST selfdrive/loggerd && \ $UNIT_TEST selfdrive/loggerd && \
$UNIT_TEST selfdrive/car && \ $UNIT_TEST selfdrive/car && \
$UNIT_TEST selfdrive/locationd && \ $UNIT_TEST selfdrive/locationd && \
selfdrive/locationd/test/_test_locationd_lib.py && \
$UNIT_TEST selfdrive/athena && \ $UNIT_TEST selfdrive/athena && \
$UNIT_TEST selfdrive/thermald && \ $UNIT_TEST selfdrive/thermald && \
$UNIT_TEST selfdrive/hardware/tici && \ $UNIT_TEST selfdrive/hardware/tici && \
$UNIT_TEST selfdrive/modeld && \ $UNIT_TEST selfdrive/modeld && \
$UNIT_TEST tools/lib/tests && \ $UNIT_TEST tools/lib/tests && \
./selfdrive/ui/tests/create_test_translations.sh && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
./common/tests/test_util && \ ./common/tests/test_util && \
./common/tests/test_swaglog && \ ./common/tests/test_swaglog && \
./selfdrive/boardd/tests/test_boardd_usbprotocol && \ ./selfdrive/boardd/tests/test_boardd_usbprotocol && \
./selfdrive/loggerd/tests/test_logger &&\ ./selfdrive/loggerd/tests/test_logger &&\
./system/proclogd/tests/test_proclog && \ ./system/proclogd/tests/test_proclog && \
./selfdrive/ui/replay/tests/test_replay && \ ./tools/replay/tests/test_replay && \
./system/camerad/test/ae_gray_test && \ ./system/camerad/test/ae_gray_test && \
coverage xml" coverage xml"
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"

@ -121,6 +121,7 @@ Directory Structure
├── modeld # Driving and monitoring model runners ├── modeld # Driving and monitoring model runners
├── proclogd # Logs information from proc ├── proclogd # Logs information from proc
├── sensord # IMU interface code ├── sensord # IMU interface code
├── navd # Turn-by-turn navigation
├── test # Unit tests, system tests, and a car simulator ├── test # Unit tests, system tests, and a car simulator
└── ui # The UI └── ui # The UI

@ -356,22 +356,27 @@ Export('cereal', 'messaging', 'visionipc')
# Build rednose library and ekf models # Build rednose library and ekf models
rednose_deps = [
"#selfdrive/locationd/models/constants.py",
"#selfdrive/locationd/models/gnss_helpers.py",
]
rednose_config = { rednose_config = {
'generated_folder': '#selfdrive/locationd/models/generated', 'generated_folder': '#selfdrive/locationd/models/generated',
'to_build': { 'to_build': {
'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, []), 'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, [], rednose_deps),
'live': ('#selfdrive/locationd/models/live_kf.py', True, ['live_kf_constants.h']), 'live': ('#selfdrive/locationd/models/live_kf.py', True, ['live_kf_constants.h'], rednose_deps),
'car': ('#selfdrive/locationd/models/car_kf.py', True, []), 'car': ('#selfdrive/locationd/models/car_kf.py', True, [], rednose_deps),
}, },
} }
if arch != "larch64": if arch != "larch64":
rednose_config['to_build'].update({ rednose_config['to_build'].update({
'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, []), 'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, [], rednose_deps),
'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, []), 'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, [], []),
'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, []), 'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, [], []),
'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, []), 'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, [], []),
'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, []), 'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, [], rednose_deps),
}) })
Export('rednose_config') Export('rednose_config')
@ -411,6 +416,8 @@ SConscript(['selfdrive/locationd/SConscript'])
SConscript(['selfdrive/sensord/SConscript']) SConscript(['selfdrive/sensord/SConscript'])
SConscript(['selfdrive/ui/SConscript']) SConscript(['selfdrive/ui/SConscript'])
SConscript(['tools/replay/SConscript'])
if GetOption('test'): if GetOption('test'):
SConscript('panda/tests/safety/SConscript') SConscript('panda/tests/safety/SConscript')

@ -1 +1 @@
Subproject commit c6acc0698a604e715e960250359b6bf97e4987e3 Subproject commit df08568318da97ed6f87747caee0a5b2c30086c4

@ -128,7 +128,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"IsTakingSnapshot", CLEAR_ON_MANAGER_START}, {"IsTakingSnapshot", CLEAR_ON_MANAGER_START},
{"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, {"IsUpdateAvailable", CLEAR_ON_MANAGER_START},
{"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF},
{"LaikadEphemeris", PERSISTENT}, {"LaikadEphemeris", PERSISTENT | DONT_LOG},
{"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START},
{"LastGPSPosition", PERSISTENT}, {"LastGPSPosition", PERSISTENT},
{"LastManagerExitReason", CLEAR_ON_MANAGER_START}, {"LastManagerExitReason", CLEAR_ON_MANAGER_START},

@ -54,7 +54,7 @@ soundd
replay replay
"""""" """"""
.. autodoxygenindex:: .. autodoxygenindex::
:project: selfdrive_ui_replay :project: tools_replay
qt qt
"" ""

@ -1 +1 @@
Subproject commit 82be71072c52fc78cf0e1eabc396af26c18ddc11 Subproject commit 47b79c4d5ab5adc0bdd9d228c3d9594da0355c49

@ -1 +1 @@
Subproject commit 4bc85ad40ad032672008eb75567892ba45e0b932 Subproject commit 265245389208e1e6ada86b169e879c0a2e30426c

@ -1 +1 @@
Subproject commit 7663289f1e68860f53dc34337ef080dde69a2586 Subproject commit 225dbacbaac312f85eaaee0b97a3acc31f9c6b47

@ -296,8 +296,8 @@ selfdrive/ui/qt/offroad/*.qml
selfdrive/ui/qt/widgets/*.cc selfdrive/ui/qt/widgets/*.cc
selfdrive/ui/qt/widgets/*.h selfdrive/ui/qt/widgets/*.h
selfdrive/ui/replay/*.cc tools/replay/*.cc
selfdrive/ui/replay/*.h tools/replay/*.h
selfdrive/ui/qt/maps/*.cc selfdrive/ui/qt/maps/*.cc
selfdrive/ui/qt/maps/*.h selfdrive/ui/qt/maps/*.h

@ -47,8 +47,6 @@ class CarState(CarStateBase):
ret.leftBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1 ret.leftBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1
ret.rightBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 2 ret.rightBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 2
ret.steeringAngleDeg = cp.vl["STEERING"]["STEER_ANGLE"]
ret.steeringRateDeg = cp.vl["STEERING"]["STEERING_RATE"]
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["GEAR"]["PRNDL"], None)) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["GEAR"]["PRNDL"], None))
ret.cruiseState.available = cp.vl["DAS_3"]["ACC_AVAILABLE"] == 1 # ACC is white ret.cruiseState.available = cp.vl["DAS_3"]["ACC_AVAILABLE"] == 1 # ACC is white
@ -58,6 +56,8 @@ class CarState(CarStateBase):
ret.cruiseState.nonAdaptive = cp.vl["DASHBOARD"]["CRUISE_STATE"] in (1, 2) ret.cruiseState.nonAdaptive = cp.vl["DASHBOARD"]["CRUISE_STATE"] in (1, 2)
ret.accFaulted = cp.vl["DAS_3"]["ACC_FAULTED"] != 0 ret.accFaulted = cp.vl["DAS_3"]["ACC_FAULTED"] != 0
ret.steeringAngleDeg = cp.vl["STEERING"]["STEER_ANGLE"]
ret.steeringRateDeg = cp.vl["STEERING"]["STEERING_RATE"]
ret.steeringTorque = cp.vl["EPS_STATUS"]["TORQUE_DRIVER"] ret.steeringTorque = cp.vl["EPS_STATUS"]["TORQUE_DRIVER"]
ret.steeringTorqueEps = cp.vl["EPS_STATUS"]["TORQUE_MOTOR"] ret.steeringTorqueEps = cp.vl["EPS_STATUS"]["TORQUE_MOTOR"]
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD

@ -5,7 +5,7 @@ from cereal import car
from common.conversions import Conversions as CV from common.conversions import Conversions as CV
from opendbc.can.parser import CANParser from opendbc.can.parser import CANParser
from opendbc.can.can_define import CANDefine from opendbc.can.can_define import CANDefine
from selfdrive.car.hyundai.values import DBC, STEER_THRESHOLD, FEATURES, HDA2_CAR, EV_CAR, HYBRID_CAR, Buttons from selfdrive.car.hyundai.values import DBC, FEATURES, HDA2_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams
from selfdrive.car.interfaces import CarStateBase from selfdrive.car.interfaces import CarStateBase
PREV_BUTTON_SAMPLES = 4 PREV_BUTTON_SAMPLES = 4
@ -32,6 +32,8 @@ class CarState(CarStateBase):
self.park_brake = False self.park_brake = False
self.buttons_counter = 0 self.buttons_counter = 0
self.params = CarControllerParams(CP)
def update(self, cp, cp_cam): def update(self, cp, cp_cam):
if self.CP.carFingerprint in HDA2_CAR: if self.CP.carFingerprint in HDA2_CAR:
return self.update_hda2(cp, cp_cam) return self.update_hda2(cp, cp_cam)
@ -61,7 +63,7 @@ class CarState(CarStateBase):
50, cp.vl["CGW1"]["CF_Gway_TurnSigLh"], cp.vl["CGW1"]["CF_Gway_TurnSigRh"]) 50, cp.vl["CGW1"]["CF_Gway_TurnSigLh"], cp.vl["CGW1"]["CF_Gway_TurnSigRh"])
ret.steeringTorque = cp.vl["MDPS12"]["CR_Mdps_StrColTq"] ret.steeringTorque = cp.vl["MDPS12"]["CR_Mdps_StrColTq"]
ret.steeringTorqueEps = cp.vl["MDPS12"]["CR_Mdps_OutTq"] ret.steeringTorqueEps = cp.vl["MDPS12"]["CR_Mdps_OutTq"]
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD
ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0 ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0
# cruise state # cruise state
@ -157,7 +159,7 @@ class CarState(CarStateBase):
ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEERING_ANGLE"] * -1 ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEERING_ANGLE"] * -1
ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"] ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"]
ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"] ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"]
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"], ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"],
cp.vl["BLINKERS"]["RIGHT_LAMP"]) cp.vl["BLINKERS"]["RIGHT_LAMP"])

@ -7,7 +7,6 @@ from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR
from selfdrive.car import STD_CARGO_KG, create_button_enable_events, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car import STD_CARGO_KG, create_button_enable_events, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
from selfdrive.car.disable_ecu import disable_ecu from selfdrive.car.disable_ecu import disable_ecu
from selfdrive.controls.lib.latcontrol_torque import set_torque_tune
ButtonType = car.CarState.ButtonEvent.Type ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName EventName = car.CarEvent.EventName
@ -51,7 +50,6 @@ class CarInterface(CarInterfaceBase):
ret.stopAccel = 0.0 ret.stopAccel = 0.0
ret.longitudinalActuatorDelayUpperBound = 1.0 # s ret.longitudinalActuatorDelayUpperBound = 1.0 # s
torque_params = CarInterfaceBase.get_torque_params(candidate)
if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022):
ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG
@ -66,7 +64,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.84 ret.wheelbase = 2.84
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.SONATA_LF: elif candidate == CAR.SONATA_LF:
ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kf = 0.00005
ret.mass = 4497. * CV.LB_TO_KG ret.mass = 4497. * CV.LB_TO_KG
@ -96,13 +94,13 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.72 ret.wheelbase = 2.72
ret.steerRatio = 12.9 ret.steerRatio = 12.9
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.ELANTRA_HEV_2021: elif candidate == CAR.ELANTRA_HEV_2021:
ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG
ret.wheelbase = 2.72 ret.wheelbase = 2.72
ret.steerRatio = 12.9 ret.steerRatio = 12.9
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.HYUNDAI_GENESIS: elif candidate == CAR.HYUNDAI_GENESIS:
ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kf = 0.00005
ret.mass = 2060. + STD_CARGO_KG ret.mass = 2060. + STD_CARGO_KG
@ -204,7 +202,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.80 ret.wheelbase = 2.80
ret.steerRatio = 13.75 ret.steerRatio = 13.75
tire_stiffness_factor = 0.5 tire_stiffness_factor = 0.5
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.KIA_STINGER: elif candidate == CAR.KIA_STINGER:
ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1825. + STD_CARGO_KG ret.mass = 1825. + STD_CARGO_KG
@ -244,7 +242,7 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput), ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput),
get_safety_config(car.CarParams.SafetyModel.hyundaiHDA2)] get_safety_config(car.CarParams.SafetyModel.hyundaiHDA2)]
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION']) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
# Genesis # Genesis
elif candidate == CAR.GENESIS_G70: elif candidate == CAR.GENESIS_G70:

@ -13,21 +13,27 @@ class CarControllerParams:
ACCEL_MAX = 2.0 # m/s ACCEL_MAX = 2.0 # m/s
def __init__(self, CP): def __init__(self, CP):
self.STEER_DELTA_UP = 3
self.STEER_DELTA_DOWN = 7
self.STEER_DRIVER_ALLOWANCE = 50
self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_DRIVER_FACTOR = 1
self.STEER_THRESHOLD = 150
if CP.carFingerprint in HDA2_CAR:
self.STEER_MAX = 270
self.STEER_DRIVER_ALLOWANCE = 250
self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_THRESHOLD = 250
# To determine the limit for your car, find the maximum value that the stock LKAS will request. # 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. # If the max stock LKAS request is <384, add your car to this list.
if CP.carFingerprint in HDA2_CAR:
self.STEER_MAX = 150
elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ, elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ,
CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_HEV, CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_HEV,
CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER): CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER):
self.STEER_MAX = 255 self.STEER_MAX = 255
else: else:
self.STEER_MAX = 384 self.STEER_MAX = 384
self.STEER_DELTA_UP = 3
self.STEER_DELTA_DOWN = 7
self.STEER_DRIVER_ALLOWANCE = 50
self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_DRIVER_FACTOR = 1
class CAR: class CAR:
@ -1140,9 +1146,6 @@ FW_VERSIONS = {
b'\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', b'\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ',
b'\xf1\x8799110L5000\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', b'\xf1\x8799110L5000\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ',
], ],
(Ecu.esp, 0x7b0, None): [
b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102',
b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102', b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102',
@ -1268,5 +1271,3 @@ DBC = {
CAR.KIA_EV6: dbc_dict('kia_ev6', None), CAR.KIA_EV6: dbc_dict('kia_ev6', None),
CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
} }
STEER_THRESHOLD = 150

@ -20,11 +20,36 @@ EventName = car.CarEvent.EventName
MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS
ACCEL_MAX = 2.0 ACCEL_MAX = 2.0
ACCEL_MIN = -3.5 ACCEL_MIN = -3.5
TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml') TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml')
TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml') TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml')
TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.yaml') TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.yaml')
def get_torque_params(candidate):
with open(TORQUE_SUBSTITUTE_PATH) as f:
sub = yaml.load(f, Loader=yaml.CSafeLoader)
if candidate in sub:
candidate = sub[candidate]
with open(TORQUE_PARAMS_PATH) as f:
params = yaml.load(f, Loader=yaml.CSafeLoader)
with open(TORQUE_OVERRIDE_PATH) as f:
override = yaml.load(f, Loader=yaml.CSafeLoader)
# Ensure no overlap
if sum([candidate in x for x in [sub, params, override]]) > 1:
raise RuntimeError(f'{candidate} is defined twice in torque config')
if candidate in override:
out = override[candidate]
elif candidate in params:
out = params[candidate]
else:
raise NotImplementedError(f"Did not find torque params for {candidate}")
return {key: out[i] for i, key in enumerate(params['legend'])}
# generic car and radar interfaces # generic car and radar interfaces
class CarInterfaceBase(ABC): class CarInterfaceBase(ABC):
@ -85,7 +110,7 @@ class CarInterfaceBase(ABC):
ret.steerControlType = car.CarParams.SteerControlType.torque ret.steerControlType = car.CarParams.SteerControlType.torque
ret.minSteerSpeed = 0. ret.minSteerSpeed = 0.
ret.wheelSpeedFactor = 1.0 ret.wheelSpeedFactor = 1.0
ret.maxLateralAccel = CarInterfaceBase.get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED'] ret.maxLateralAccel = get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED']
ret.pcmCruise = True # openpilot's state is tied to the PCM's cruise state on most cars ret.pcmCruise = True # openpilot's state is tied to the PCM's cruise state on most cars
ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this
@ -110,28 +135,16 @@ class CarInterfaceBase(ABC):
return ret return ret
@staticmethod @staticmethod
def get_torque_params(candidate, default=float('NaN')): def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0):
with open(TORQUE_SUBSTITUTE_PATH) as f: params = get_torque_params(candidate)
sub = yaml.load(f, Loader=yaml.FullLoader)
if candidate in sub: tune.init('torque')
candidate = sub[candidate] tune.torque.useSteeringAngle = True
tune.torque.kp = 1.0 / params['LAT_ACCEL_FACTOR']
with open(TORQUE_PARAMS_PATH) as f: tune.torque.kf = 1.0 / params['LAT_ACCEL_FACTOR']
params = yaml.load(f, Loader=yaml.FullLoader) tune.torque.ki = 0.1 / params['LAT_ACCEL_FACTOR']
with open(TORQUE_OVERRIDE_PATH) as f: tune.torque.friction = params['FRICTION']
override = yaml.load(f, Loader=yaml.FullLoader) tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg
# Ensure no overlap
if sum([candidate in x for x in [sub, params, override]]) > 1:
raise RuntimeError(f'{candidate} is defined twice in torque config')
if candidate in override:
out = override[candidate]
elif candidate in params:
out = params[candidate]
else:
raise NotImplementedError(f"Did not find torque params for {candidate}")
return {key:out[i] for i, key in enumerate(params['legend'])}
@abstractmethod @abstractmethod
def _update(self, c: car.CarControl) -> car.CarState: def _update(self, c: car.CarControl) -> car.CarState:

@ -20,7 +20,7 @@ FORD FOCUS 4TH GEN: [.nan, 1.5, .nan]
COMMA BODY: [.nan, 1000, .nan] COMMA BODY: [.nan, 1000, .nan]
# Totally new car # Totally new car
KIA EV6 2022: [3.0, 2.5, 0.05] KIA EV6 2022: [3.0, 2.5, 0.0]
# Dashcam or fallback configured as ideal car # Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0] mock: [10.0, 10, 0.0]

@ -2,7 +2,6 @@
from cereal import car from cereal import car
from common.conversions import Conversions as CV from common.conversions import Conversions as CV
from panda import Panda from panda import Panda
from selfdrive.controls.lib.latcontrol_torque import set_torque_tune
from selfdrive.car.toyota.tunes import LatTunes, LongTunes, set_long_tune, set_lat_tune from selfdrive.car.toyota.tunes import LatTunes, LongTunes, set_long_tune, set_lat_tune
from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, CarControllerParams from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, CarControllerParams
from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
@ -32,9 +31,8 @@ class CarInterface(CarInterfaceBase):
ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop
stop_and_go = False stop_and_go = False
torque_params = CarInterfaceBase.get_torque_params(candidate)
steering_angle_deadzone_deg = 0.0 steering_angle_deadzone_deg = 0.0
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'], steering_angle_deadzone_deg) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg)
if candidate == CAR.PRIUS: if candidate == CAR.PRIUS:
stop_and_go = True stop_and_go = True
@ -46,7 +44,7 @@ class CarInterface(CarInterfaceBase):
for fw in car_fw: for fw in car_fw:
if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00':
steering_angle_deadzone_deg = 1.0 steering_angle_deadzone_deg = 1.0
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'], steering_angle_deadzone_deg) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg)
elif candidate == CAR.PRIUS_V: elif candidate == CAR.PRIUS_V:
stop_and_go = True stop_and_go = True
@ -54,7 +52,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 17.4 ret.steerRatio = 17.4
tire_stiffness_factor = 0.5533 tire_stiffness_factor = 0.5533
ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'], steering_angle_deadzone_deg) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg)
elif candidate in (CAR.RAV4, CAR.RAV4H): elif candidate in (CAR.RAV4, CAR.RAV4H):
stop_and_go = True if (candidate in CAR.RAV4H) else False stop_and_go = True if (candidate in CAR.RAV4H) else False

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from enum import Enum from enum import Enum
from selfdrive.controls.lib.latcontrol_torque import set_torque_tune
class LongTunes(Enum): class LongTunes(Enum):
PEDAL = 0 PEDAL = 0
@ -24,7 +23,6 @@ class LatTunes(Enum):
PID_L = 13 PID_L = 13
PID_M = 14 PID_M = 14
PID_N = 15 PID_N = 15
TORQUE = 16
###### LONG ###### ###### LONG ######
@ -51,9 +49,7 @@ def set_long_tune(tune, name):
###### LAT ###### ###### LAT ######
def set_lat_tune(tune, name, MAX_LAT_ACCEL=2.5, FRICTION=0.01, steering_angle_deadzone_deg=0.0, use_steering_angle=True): def set_lat_tune(tune, name, MAX_LAT_ACCEL=2.5, FRICTION=0.01, steering_angle_deadzone_deg=0.0, use_steering_angle=True):
if name == LatTunes.TORQUE: if 'PID' in str(name):
set_torque_tune(tune, MAX_LAT_ACCEL, FRICTION, steering_angle_deadzone_deg)
elif 'PID' in str(name):
tune.init('pid') tune.init('pid')
tune.pid.kiBP = [0.0] tune.pid.kiBP = [0.0]
tune.pid.kpBP = [0.0] tune.pid.kpBP = [0.0]

@ -1262,6 +1262,7 @@ FW_VERSIONS = {
b'\x01F152642711\x00\x00\x00\x00\x00\x00', b'\x01F152642711\x00\x00\x00\x00\x00\x00',
b'\x01F152642750\x00\x00\x00\x00\x00\x00', b'\x01F152642750\x00\x00\x00\x00\x00\x00',
b'\x01F152642751\x00\x00\x00\x00\x00\x00', b'\x01F152642751\x00\x00\x00\x00\x00\x00',
b'\x01F15260R292\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.eps, 0x7a1, None): [ (Ecu.eps, 0x7a1, None): [
b'8965B42170\x00\x00\x00\x00\x00\x00', b'8965B42170\x00\x00\x00\x00\x00\x00',
@ -1296,6 +1297,7 @@ FW_VERSIONS = {
b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00', b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x700, None): [ (Ecu.engine, 0x700, None): [
b'\x01896634AA0000\x00\x00\x00\x00',
b'\x01896634AA1000\x00\x00\x00\x00', b'\x01896634AA1000\x00\x00\x00\x00',
b'\x01896634A88000\x00\x00\x00\x00', b'\x01896634A88000\x00\x00\x00\x00',
], ],

@ -438,6 +438,7 @@ FW_VERSIONS = {
}, },
CAR.PASSAT_MK8: { CAR.PASSAT_MK8: {
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xf1\x8703N906026E \xf1\x892114',
b'\xf1\x8704E906023AH\xf1\x893379', b'\xf1\x8704E906023AH\xf1\x893379',
b'\xf1\x8704L906026ET\xf1\x891990', b'\xf1\x8704L906026ET\xf1\x891990',
b'\xf1\x8704L906026GA\xf1\x892013', b'\xf1\x8704L906026GA\xf1\x892013',
@ -450,17 +451,20 @@ FW_VERSIONS = {
b'\xf1\x870D9300014L \xf1\x895002', b'\xf1\x870D9300014L \xf1\x895002',
b'\xf1\x870D9300041A \xf1\x894801', b'\xf1\x870D9300041A \xf1\x894801',
b'\xf1\x870DD300045T \xf1\x891601', b'\xf1\x870DD300045T \xf1\x891601',
b'\xf1\x870DL300011H \xf1\x895201',
b'\xf1\x870GC300042H \xf1\x891404', b'\xf1\x870GC300042H \xf1\x891404',
], ],
(Ecu.srs, 0x715, None): [ (Ecu.srs, 0x715, None): [
b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111', b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111',
b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311', b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311',
b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111',
b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211',
b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900',
b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900',
b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111',
], ],
(Ecu.eps, 0x712, None): [ (Ecu.eps, 0x712, None): [
b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1',
b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803',
b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803',
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1',

@ -22,16 +22,6 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
FRICTION_THRESHOLD = 0.2 FRICTION_THRESHOLD = 0.2
def set_torque_tune(tune, MAX_LAT_ACCEL=2.5, FRICTION=0.01, steering_angle_deadzone_deg=0.0):
tune.init('torque')
tune.torque.useSteeringAngle = True
tune.torque.kp = 1.0 / MAX_LAT_ACCEL
tune.torque.kf = 1.0 / MAX_LAT_ACCEL
tune.torque.ki = 0.1 / MAX_LAT_ACCEL
tune.torque.friction = FRICTION
tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg
class LatControlTorque(LatControl): class LatControlTorque(LatControl):
def __init__(self, CP, CI): def __init__(self, CP, CI):
super().__init__(CP, CI) super().__init__(CP, CI)
@ -66,15 +56,15 @@ class LatControlTorque(LatControl):
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
low_speed_factor = interp(CS.vEgo, [0, 15], [500, 0]) low_speed_factor = interp(CS.vEgo, [0, 10, 20], [500, 500, 200])
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
measurement = actual_lateral_accel + low_speed_factor * actual_curvature measurement = actual_lateral_accel + low_speed_factor * actual_curvature
error = apply_deadzone(setpoint - measurement, lateral_accel_deadzone) error = setpoint - measurement
pid_log.error = error pid_log.error = error
ff = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY ff = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY
# convert friction into lateral accel units for feedforward # convert friction into lateral accel units for feedforward
friction_compensation = interp(error, [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], [-self.friction, self.friction]) friction_compensation = interp(apply_deadzone(error, lateral_accel_deadzone), [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], [-self.friction, self.friction])
ff += friction_compensation / self.kf ff += friction_compensation / self.kf
freeze_integrator = CS.steeringRateLimited or CS.steeringPressed or CS.vEgo < 5 freeze_integrator = CS.steeringRateLimited or CS.steeringPressed or CS.vEgo < 5
output_torque = self.pid.update(error, output_torque = self.pid.update(error,

@ -1,8 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import sys
import math
import datetime
from collections import Counter from collections import Counter
from pprint import pprint from pprint import pprint
from tqdm import tqdm from tqdm import tqdm
from typing import cast
from cereal.services import service_list from cereal.services import service_list
from tools.lib.route import Route from tools.lib.route import Route
@ -17,6 +20,8 @@ if __name__ == "__main__":
cams = [s for s in service_list if s.endswith('CameraState')] cams = [s for s in service_list if s.endswith('CameraState')]
cnt_cameras = dict.fromkeys(cams, 0) cnt_cameras = dict.fromkeys(cams, 0)
start_time = math.inf
end_time = -math.inf
for q in tqdm(r.qlog_paths()): for q in tqdm(r.qlog_paths()):
if q is None: if q is None:
continue continue
@ -31,6 +36,10 @@ if __name__ == "__main__":
if not msg.valid: if not msg.valid:
cnt_valid[msg.which()] += 1 cnt_valid[msg.which()] += 1
end_time = max(end_time, msg.logMonoTime)
start_time = min(start_time, msg.logMonoTime)
duration = (end_time - start_time) / 1e9
print("Events") print("Events")
pprint(cnt_events) pprint(cnt_events)
@ -42,4 +51,9 @@ if __name__ == "__main__":
print("\n") print("\n")
print("Cameras") print("Cameras")
for k, v in cnt_cameras.items(): for k, v in cnt_cameras.items():
print(" ", k.ljust(20), v) s = service_list[k]
expected_frames = int(s.frequency * duration / cast(float, s.decimation))
print(" ", k.ljust(20), f"{v}, {v/expected_frames:.1%} of expected")
print("\n")
print("Route duration", datetime.timedelta(seconds=duration))

@ -12,7 +12,7 @@ import capnp
import numpy as np import numpy as np
from typing import List, NoReturn, Optional from typing import List, NoReturn, Optional
from cereal import car, log from cereal import log
import cereal.messaging as messaging import cereal.messaging as messaging
from common.conversions import Conversions as CV from common.conversions import Conversions as CV
from common.params import Params, put_nonblocking from common.params import Params, put_nonblocking
@ -62,7 +62,7 @@ class Calibrator:
def __init__(self, param_put: bool = False): def __init__(self, param_put: bool = False):
self.param_put = param_put self.param_put = param_put
self.CP = car.CarParams.from_bytes(Params().get("CarParams", block=True)) self.not_car = False
# Read saved calibration # Read saved calibration
params = Params() params = Params()
@ -192,7 +192,7 @@ class Calibrator:
liveCalibration.rpyCalib = smooth_rpy.tolist() liveCalibration.rpyCalib = smooth_rpy.tolist()
liveCalibration.rpyCalibSpread = self.calib_spread.tolist() liveCalibration.rpyCalibSpread = self.calib_spread.tolist()
if self.CP.notCar: if self.not_car:
extrinsic_matrix = get_view_frame_from_road_frame(0, 0, 0, model_height) extrinsic_matrix = get_view_frame_from_road_frame(0, 0, 0, model_height)
liveCalibration.validBlocks = INPUTS_NEEDED liveCalibration.validBlocks = INPUTS_NEEDED
liveCalibration.calStatus = Calibration.CALIBRATED liveCalibration.calStatus = Calibration.CALIBRATED
@ -212,7 +212,7 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m
set_realtime_priority(1) set_realtime_priority(1)
if sm is None: if sm is None:
sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll=['cameraOdometry']) sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry'])
if pm is None: if pm is None:
pm = messaging.PubMaster(['liveCalibration']) pm = messaging.PubMaster(['liveCalibration'])
@ -223,6 +223,8 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m
timeout = 0 if sm.frame == -1 else 100 timeout = 0 if sm.frame == -1 else 100
sm.update(timeout) sm.update(timeout)
calibrator.not_car = sm['carParams'].notCar
if sm.updated['cameraOdometry']: if sm.updated['cameraOdometry']:
calibrator.handle_v_ego(sm['carState'].vEgo) calibrator.handle_v_ego(sm['carState'].vEgo)
new_rpy = calibrator.handle_cam_odom(sm['cameraOdometry'].trans, new_rpy = calibrator.handle_cam_odom(sm['cameraOdometry'].trans,

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import json import json
import os
import time import time
from collections import defaultdict from collections import defaultdict
from concurrent.futures import Future, ProcessPoolExecutor from concurrent.futures import Future, ProcessPoolExecutor
@ -31,9 +32,11 @@ class Laikad:
def __init__(self, valid_const=("GPS", "GLONASS"), auto_update=False, valid_ephem_types=(EphemerisType.ULTRA_RAPID_ORBIT, EphemerisType.NAV), def __init__(self, valid_const=("GPS", "GLONASS"), auto_update=False, valid_ephem_types=(EphemerisType.ULTRA_RAPID_ORBIT, EphemerisType.NAV),
save_ephemeris=False, last_known_position=None): save_ephemeris=False, last_known_position=None):
self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True) self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True)
self.gnss_kf = GNSSKalman(GENERATED_DIR) self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True)
self.orbit_fetch_executor = ProcessPoolExecutor()
self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None
self.orbit_fetch_future: Optional[Future] = None self.orbit_fetch_future: Optional[Future] = None
self.last_fetch_orbits_t = None self.last_fetch_orbits_t = None
self.last_cached_t = None self.last_cached_t = None
self.save_ephemeris = save_ephemeris self.save_ephemeris = save_ephemeris
@ -44,9 +47,13 @@ class Laikad:
self.last_pos_fix_t = None self.last_pos_fix_t = None
def load_cache(self): def load_cache(self):
if not self.save_ephemeris:
return
cache = Params().get(EPHEMERIS_CACHE) cache = Params().get(EPHEMERIS_CACHE)
if not cache: if not cache:
return return
try: try:
cache = json.loads(cache, object_hook=deserialize_hook) cache = json.loads(cache, object_hook=deserialize_hook)
self.astro_dog.add_orbits(cache['orbits']) self.astro_dog.add_orbits(cache['orbits'])
@ -62,6 +69,16 @@ class Laikad:
cls=CacheSerializer)) cls=CacheSerializer))
self.last_cached_t = t self.last_cached_t = t
def get_est_pos(self, t, processed_measurements):
if self.last_pos_fix_t is None or abs(self.last_pos_fix_t - t) >= 2:
min_measurements = 5 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 4
pos_fix, pos_fix_residual = calc_pos_fix_gauss_newton(processed_measurements, self.posfix_functions, min_measurements=min_measurements)
if len(pos_fix) > 0:
self.last_pos_fix = pos_fix[:3]
self.last_pos_residual = pos_fix_residual
self.last_pos_fix_t = t
return self.last_pos_fix
def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False): def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False):
if ublox_msg.which == 'measurementReport': if ublox_msg.which == 'measurementReport':
t = ublox_mono_time * 1e-9 t = ublox_mono_time * 1e-9
@ -73,17 +90,11 @@ class Laikad:
new_meas = read_raw_ublox(report) new_meas = read_raw_ublox(report)
processed_measurements = process_measurements(new_meas, self.astro_dog) processed_measurements = process_measurements(new_meas, self.astro_dog)
if self.last_pos_fix_t is None or abs(self.last_pos_fix_t - t) >= 2: est_pos = self.get_est_pos(t, processed_measurements)
min_measurements = 5 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 4
pos_fix, pos_fix_residual = calc_pos_fix_gauss_newton(processed_measurements, self.posfix_functions, min_measurements=min_measurements)
if len(pos_fix) > 0:
self.last_pos_fix = pos_fix[:3]
self.last_pos_residual = pos_fix_residual
self.last_pos_fix_t = t
corrected_measurements = correct_measurements(processed_measurements, self.last_pos_fix, self.astro_dog) if self.last_pos_fix_t is not None else [] corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) if len(est_pos) > 0 else []
self.update_localizer(self.last_pos_fix, t, corrected_measurements) self.update_localizer(est_pos, t, corrected_measurements)
kf_valid = all(self.kf_valid(t)) kf_valid = all(self.kf_valid(t))
ecef_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist() ecef_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist()
ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist() ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist()
@ -116,7 +127,7 @@ class Laikad:
valid = self.kf_valid(t) valid = self.kf_valid(t)
if not all(valid): if not all(valid):
if not valid[0]: if not valid[0]:
cloudlog.info("Init gnss kalman filter") cloudlog.info("Kalman filter uninitialized")
elif not valid[1]: elif not valid[1]:
cloudlog.error("Time gap of over 10s detected, gnss kalman reset") cloudlog.error("Time gap of over 10s detected, gnss kalman reset")
elif not valid[2]: elif not valid[2]:
@ -133,8 +144,8 @@ class Laikad:
# Ensure gnss filter is updated even with no new measurements # Ensure gnss filter is updated even with no new measurements
self.gnss_kf.predict(t) self.gnss_kf.predict(t)
def kf_valid(self, t: float): def kf_valid(self, t: float) -> List[bool]:
filter_time = self.gnss_kf.filter.filter_time filter_time = self.gnss_kf.filter.get_filter_time()
return [filter_time is not None, return [filter_time is not None,
filter_time is not None and abs(t - filter_time) < MAX_TIME_GAP, filter_time is not None and abs(t - filter_time) < MAX_TIME_GAP,
all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))] all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))]
@ -148,17 +159,22 @@ class Laikad:
def fetch_orbits(self, t: GPSTime, block): def fetch_orbits(self, t: GPSTime, block):
if t not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or t - self.last_fetch_orbits_t > SECS_IN_HR): if t not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or t - self.last_fetch_orbits_t > SECS_IN_HR):
astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types
if self.orbit_fetch_future is None:
ret = None
if block:
ret = get_orbit_data(t, *astro_dog_vars)
elif self.orbit_fetch_future is None:
self.orbit_fetch_executor = ProcessPoolExecutor(max_workers=1)
self.orbit_fetch_future = self.orbit_fetch_executor.submit(get_orbit_data, t, *astro_dog_vars) self.orbit_fetch_future = self.orbit_fetch_executor.submit(get_orbit_data, t, *astro_dog_vars)
if block: elif self.orbit_fetch_future.done():
self.orbit_fetch_future.result()
if self.orbit_fetch_future.done():
ret = self.orbit_fetch_future.result()
self.last_fetch_orbits_t = t self.last_fetch_orbits_t = t
if ret: ret = self.orbit_fetch_future.result()
self.astro_dog.orbits, self.astro_dog.orbit_fetched_times = ret self.orbit_fetch_executor = self.orbit_fetch_future = None
self.cache_ephemeris(t=t)
self.orbit_fetch_future = None if ret is not None:
self.astro_dog.orbits, self.astro_dog.orbit_fetched_times = ret
self.cache_ephemeris(t=t)
def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types): def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types):
@ -170,7 +186,7 @@ def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types):
astro_dog.get_orbit_data(t, only_predictions=True) astro_dog.get_orbit_data(t, only_predictions=True)
data = (astro_dog.orbits, astro_dog.orbit_fetched_times) data = (astro_dog.orbits, astro_dog.orbit_fetched_times)
except RuntimeError as e: except RuntimeError as e:
cloudlog.info(f"No orbit data found. {e}") cloudlog.warning(f"No orbit data found. {e}")
cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s") cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s")
return data return data
@ -251,17 +267,21 @@ class EphemerisSourceType(IntEnum):
glonassIacUltraRapid = 2 glonassIacUltraRapid = 2
def main(): def main(sm=None, pm=None):
sm = messaging.SubMaster(['ubloxGnss']) if sm is None:
pm = messaging.PubMaster(['gnssMeasurements']) sm = messaging.SubMaster(['ubloxGnss'])
if pm is None:
pm = messaging.PubMaster(['gnssMeasurements'])
replay = "REPLAY" in os.environ
# todo get last_known_position # todo get last_known_position
laikad = Laikad(save_ephemeris=True) laikad = Laikad(save_ephemeris=not replay)
while True: while True:
sm.update() sm.update()
if sm.updated['ubloxGnss']: if sm.updated['ubloxGnss']:
ublox_msg = sm['ubloxGnss'] ublox_msg = sm['ubloxGnss']
msg = laikad.process_ublox_msg(ublox_msg, sm.logMonoTime['ubloxGnss']) msg = laikad.process_ublox_msg(ublox_msg, sm.logMonoTime['ubloxGnss'], block=replay)
if msg is not None: if msg is not None:
pm.send('gnssMeasurements', msg) pm.send('gnssMeasurements', msg)

@ -15,7 +15,7 @@ if __name__ == '__main__': # Generating sympy
import sympy as sp import sympy as sp
from rednose.helpers.ekf_sym import gen_code from rednose.helpers.ekf_sym import gen_code
else: else:
from rednose.helpers.ekf_sym_pyx import EKF_sym # pylint: disable=no-name-in-module, import-error from rednose.helpers.ekf_sym_pyx import EKF_sym_pyx # pylint: disable=no-name-in-module, import-error
i = 0 i = 0
@ -171,7 +171,7 @@ class CarKalman(KalmanFilter):
if P_initial is not None: if P_initial is not None:
self.P_initial = P_initial self.P_initial = P_initial
# init filter # init filter
self.filter = EKF_sym(generated_dir, self.name, self.Q, self.initial_x, self.P_initial, dim_state, dim_state_err, global_vars=self.global_vars, logger=cloudlog) self.filter = EKF_sym_pyx(generated_dir, self.name, self.Q, self.initial_x, self.P_initial, dim_state, dim_state_err, global_vars=self.global_vars, logger=cloudlog)
if __name__ == "__main__": if __name__ == "__main__":

@ -3,12 +3,17 @@ import sys
from typing import List from typing import List
import numpy as np import numpy as np
import sympy as sp
from rednose.helpers.ekf_sym import EKF_sym, gen_code
from selfdrive.locationd.models.constants import ObservationKind from selfdrive.locationd.models.constants import ObservationKind
from selfdrive.locationd.models.gnss_helpers import parse_pr, parse_prr from selfdrive.locationd.models.gnss_helpers import parse_pr, parse_prr
if __name__ == '__main__': # Generating sympy
import sympy as sp
from rednose.helpers.ekf_sym import gen_code
else:
from rednose.helpers.ekf_sym_pyx import EKF_sym_pyx # pylint: disable=no-name-in-module,import-error
from rednose.helpers.ekf_sym import EKF_sym # pylint: disable=no-name-in-module,import-error
class States(): class States():
ECEF_POS = slice(0, 3) # x, y and z in ECEF in meters ECEF_POS = slice(0, 3) # x, y and z in ECEF in meters
@ -115,12 +120,13 @@ class GNSSKalman():
gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, maha_test_kinds=maha_test_kinds) gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, maha_test_kinds=maha_test_kinds)
def __init__(self, generated_dir): def __init__(self, generated_dir, cython=False):
self.dim_state = self.x_initial.shape[0] self.dim_state = self.x_initial.shape[0]
# init filter # init filter
self.filter = EKF_sym(generated_dir, self.name, self.Q, self.x_initial, self.P_initial, self.dim_state, filter_cls = EKF_sym_pyx if cython else EKF_sym
self.dim_state, maha_test_kinds=self.maha_test_kinds) self.filter = filter_cls(generated_dir, self.name, self.Q, self.x_initial, self.P_initial, self.dim_state,
self.dim_state, maha_test_kinds=self.maha_test_kinds)
self.init_state(GNSSKalman.x_initial, covs=GNSSKalman.P_initial) self.init_state(GNSSKalman.x_initial, covs=GNSSKalman.P_initial)
@property @property

@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""This test can't be run together with other locationd tests.
cffi.dlopen breaks the list of registered filters."""
import os
import random
import unittest
from cffi import FFI
import cereal.messaging as messaging
from cereal import log
SENSOR_DECIMATION = 1
VISION_DECIMATION = 1
LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../liblocationd.so'))
class TestLocationdLib(unittest.TestCase):
def setUp(self):
header = '''typedef ...* Localizer_t;
Localizer_t localizer_init();
void localizer_get_message_bytes(Localizer_t localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid, char *buff, size_t buff_size);
void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t size);'''
self.ffi = FFI()
self.ffi.cdef(header)
self.lib = self.ffi.dlopen(LIBLOCATIOND_PATH)
self.localizer = self.lib.localizer_init()
self.buff_size = 2048
self.msg_buff = self.ffi.new(f'char[{self.buff_size}]')
def localizer_handle_msg(self, msg_builder):
bytstr = msg_builder.to_bytes()
self.lib.localizer_handle_msg_bytes(self.localizer, self.ffi.from_buffer(bytstr), len(bytstr))
def localizer_get_msg(self, t=0, inputsOK=True, sensorsOK=True, gpsOK=True, msgValid=True):
self.lib.localizer_get_message_bytes(self.localizer, inputsOK, sensorsOK, gpsOK, msgValid, self.ffi.addressof(self.msg_buff, 0), self.buff_size)
return log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8)
def test_liblocalizer(self):
msg = messaging.new_message('liveCalibration')
msg.liveCalibration.validBlocks = random.randint(1, 10)
msg.liveCalibration.rpyCalib = [random.random() / 10 for _ in range(3)]
self.localizer_handle_msg(msg)
liveloc = self.localizer_get_msg()
self.assertTrue(liveloc is not None)
@unittest.skip("temporarily disabled due to false positives")
def test_device_fell(self):
msg = messaging.new_message('sensorEvents', 1)
msg.sensorEvents[0].sensor = 1
msg.sensorEvents[0].timestamp = msg.logMonoTime
msg.sensorEvents[0].type = 1
msg.sensorEvents[0].init('acceleration')
msg.sensorEvents[0].acceleration.v = [10.0, 0.0, 0.0] # zero with gravity
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertTrue(ret.liveLocationKalman.deviceStable)
msg = messaging.new_message('sensorEvents', 1)
msg.sensorEvents[0].sensor = 1
msg.sensorEvents[0].timestamp = msg.logMonoTime
msg.sensorEvents[0].type = 1
msg.sensorEvents[0].init('acceleration')
msg.sensorEvents[0].acceleration.v = [50.1, 0.0, 0.0] # more than 40 m/s**2
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertFalse(ret.liveLocationKalman.deviceStable)
def test_posenet_spike(self):
for _ in range(SENSOR_DECIMATION):
msg = messaging.new_message('carState')
msg.carState.vEgo = 6.0 # more than 5 m/s
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertTrue(ret.liveLocationKalman.posenetOK)
for _ in range(20 * VISION_DECIMATION): # size of hist_old
msg = messaging.new_message('cameraOdometry')
msg.cameraOdometry.rot = [0.0, 0.0, 0.0]
msg.cameraOdometry.rotStd = [0.1, 0.1, 0.1]
msg.cameraOdometry.trans = [0.0, 0.0, 0.0]
msg.cameraOdometry.transStd = [2.0, 0.1, 0.1]
self.localizer_handle_msg(msg)
for _ in range(20 * VISION_DECIMATION): # size of hist_new
msg = messaging.new_message('cameraOdometry')
msg.cameraOdometry.rot = [0.0, 0.0, 0.0]
msg.cameraOdometry.rotStd = [1.0, 1.0, 1.0]
msg.cameraOdometry.trans = [0.0, 0.0, 0.0]
msg.cameraOdometry.transStd = [10.1, 0.1, 0.1] # more than 4 times larger
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertFalse(ret.liveLocationKalman.posenetOK)
if __name__ == "__main__":
unittest.main()

@ -7,6 +7,7 @@ from unittest import mock
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from common.params import Params from common.params import Params
from laika.constants import SECS_IN_DAY
from laika.ephemeris import EphemerisType, GPSEphemeris from laika.ephemeris import EphemerisType, GPSEphemeris
from laika.gps_time import GPSTime from laika.gps_time import GPSTime
from laika.helpers import ConstellationId, TimeRangeHolder from laika.helpers import ConstellationId, TimeRangeHolder
@ -62,6 +63,26 @@ class TestLaikad(unittest.TestCase):
def setUp(self): def setUp(self):
Params().delete(EPHEMERIS_CACHE) Params().delete(EPHEMERIS_CACHE)
def test_fetch_orbits_non_blocking(self):
gpstime = GPSTime.from_datetime(datetime(2021, month=3, day=1))
laikad = Laikad()
laikad.fetch_orbits(gpstime, block=False)
laikad.orbit_fetch_future.result(5)
# Get results and save orbits to laikad:
laikad.fetch_orbits(gpstime, block=False)
ephem = laikad.astro_dog.orbits['G01'][0]
self.assertIsNotNone(ephem)
laikad.fetch_orbits(gpstime+2*SECS_IN_DAY, block=False)
laikad.orbit_fetch_future.result(5)
# Get results and save orbits to laikad:
laikad.fetch_orbits(gpstime + 2 * SECS_IN_DAY, block=False)
ephem2 = laikad.astro_dog.orbits['G01'][0]
self.assertIsNotNone(ephem)
self.assertNotEqual(ephem, ephem2)
def test_ephemeris_source_in_msg(self): def test_ephemeris_source_in_msg(self):
data_mock = defaultdict(str) data_mock = defaultdict(str)
data_mock['sv_id'] = 1 data_mock['sv_id'] = 1
@ -155,7 +176,7 @@ class TestLaikad(unittest.TestCase):
while Params().get(EPHEMERIS_CACHE) is None: while Params().get(EPHEMERIS_CACHE) is None:
time.sleep(0.1) time.sleep(0.1)
max_time -= 0.1 max_time -= 0.1
if max_time == 0: if max_time < 0:
self.fail("Cache has not been written after 2 seconds") self.fail("Cache has not been written after 2 seconds")
# Test cache with no ephemeris # Test cache with no ephemeris
@ -170,7 +191,7 @@ class TestLaikad(unittest.TestCase):
wait_for_cache() wait_for_cache()
# Check both nav and orbits separate # Check both nav and orbits separate
laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV) laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV, save_ephemeris=True)
# Verify orbits and nav are loaded from cache # Verify orbits and nav are loaded from cache
self.dict_has_values(laikad.astro_dog.orbits) self.dict_has_values(laikad.astro_dog.orbits)
self.dict_has_values(laikad.astro_dog.nav) self.dict_has_values(laikad.astro_dog.nav)
@ -185,7 +206,7 @@ class TestLaikad(unittest.TestCase):
mock_method.assert_not_called() mock_method.assert_not_called()
# Verify cache is working for only orbits by running a segment # Verify cache is working for only orbits by running a segment
laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT) laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT, save_ephemeris=True)
msg = verify_messages(self.logs, laikad, return_one_success=True) msg = verify_messages(self.logs, laikad, return_one_success=True)
self.assertIsNotNone(msg) self.assertIsNotNone(msg)
# Verify orbit data is not downloaded # Verify orbit data is not downloaded

@ -1,110 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os
import json import json
import random import random
import unittest import unittest
import time import time
import capnp import capnp
from cffi import FFI
from cereal import log
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal.services import service_list from cereal.services import service_list
from common.params import Params from common.params import Params
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
SENSOR_DECIMATION = 1
VISION_DECIMATION = 1
LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../liblocationd.so'))
class TestLocationdLib(unittest.TestCase):
def setUp(self):
header = '''typedef ...* Localizer_t;
Localizer_t localizer_init();
void localizer_get_message_bytes(Localizer_t localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid, char *buff, size_t buff_size);
void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t size);'''
self.ffi = FFI()
self.ffi.cdef(header)
self.lib = self.ffi.dlopen(LIBLOCATIOND_PATH)
self.localizer = self.lib.localizer_init()
self.buff_size = 2048
self.msg_buff = self.ffi.new(f'char[{self.buff_size}]')
def localizer_handle_msg(self, msg_builder):
bytstr = msg_builder.to_bytes()
self.lib.localizer_handle_msg_bytes(self.localizer, self.ffi.from_buffer(bytstr), len(bytstr))
def localizer_get_msg(self, t=0, inputsOK=True, sensorsOK=True, gpsOK=True, msgValid=True):
self.lib.localizer_get_message_bytes(self.localizer, inputsOK, sensorsOK, gpsOK, msgValid, self.ffi.addressof(self.msg_buff, 0), self.buff_size)
return log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8)
def test_liblocalizer(self):
msg = messaging.new_message('liveCalibration')
msg.liveCalibration.validBlocks = random.randint(1, 10)
msg.liveCalibration.rpyCalib = [random.random() / 10 for _ in range(3)]
self.localizer_handle_msg(msg)
liveloc = self.localizer_get_msg()
self.assertTrue(liveloc is not None)
@unittest.skip("temporarily disabled due to false positives")
def test_device_fell(self):
msg = messaging.new_message('sensorEvents', 1)
msg.sensorEvents[0].sensor = 1
msg.sensorEvents[0].timestamp = msg.logMonoTime
msg.sensorEvents[0].type = 1
msg.sensorEvents[0].init('acceleration')
msg.sensorEvents[0].acceleration.v = [10.0, 0.0, 0.0] # zero with gravity
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertTrue(ret.liveLocationKalman.deviceStable)
msg = messaging.new_message('sensorEvents', 1)
msg.sensorEvents[0].sensor = 1
msg.sensorEvents[0].timestamp = msg.logMonoTime
msg.sensorEvents[0].type = 1
msg.sensorEvents[0].init('acceleration')
msg.sensorEvents[0].acceleration.v = [50.1, 0.0, 0.0] # more than 40 m/s**2
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertFalse(ret.liveLocationKalman.deviceStable)
def test_posenet_spike(self):
for _ in range(SENSOR_DECIMATION):
msg = messaging.new_message('carState')
msg.carState.vEgo = 6.0 # more than 5 m/s
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertTrue(ret.liveLocationKalman.posenetOK)
for _ in range(20 * VISION_DECIMATION): # size of hist_old
msg = messaging.new_message('cameraOdometry')
msg.cameraOdometry.rot = [0.0, 0.0, 0.0]
msg.cameraOdometry.rotStd = [0.1, 0.1, 0.1]
msg.cameraOdometry.trans = [0.0, 0.0, 0.0]
msg.cameraOdometry.transStd = [2.0, 0.1, 0.1]
self.localizer_handle_msg(msg)
for _ in range(20 * VISION_DECIMATION): # size of hist_new
msg = messaging.new_message('cameraOdometry')
msg.cameraOdometry.rot = [0.0, 0.0, 0.0]
msg.cameraOdometry.rotStd = [1.0, 1.0, 1.0]
msg.cameraOdometry.trans = [0.0, 0.0, 0.0]
msg.cameraOdometry.transStd = [10.1, 0.1, 0.1] # more than 4 times larger
self.localizer_handle_msg(msg)
ret = self.localizer_get_msg()
self.assertFalse(ret.liveLocationKalman.posenetOK)
class TestLocationdProc(unittest.TestCase): class TestLocationdProc(unittest.TestCase):
MAX_WAITS = 1000 MAX_WAITS = 1000

@ -68,7 +68,9 @@ void FfmpegEncoder::encoder_open(const char* path) {
void FfmpegEncoder::encoder_close() { void FfmpegEncoder::encoder_close() {
if (!is_open) return; if (!is_open) return;
writer_close(); writer_close();
avcodec_free_context(&codec_ctx);
is_open = false; is_open = false;
} }

@ -19,15 +19,6 @@
#include "common/swaglog.h" #include "common/swaglog.h"
#include "common/version.h" #include "common/version.h"
// ***** logging helpers *****
void append_property(const char* key, const char* value, void *cookie) {
std::vector<std::pair<std::string, std::string> > *properties =
(std::vector<std::pair<std::string, std::string> > *)cookie;
properties->push_back(std::make_pair(std::string(key), std::string(value)));
}
// ***** log metadata ***** // ***** log metadata *****
kj::Array<capnp::word> logger_build_init_data() { kj::Array<capnp::word> logger_build_init_data() {
MessageBuilder msg; MessageBuilder msg;

@ -6,7 +6,6 @@ ExitHandler do_exit;
struct LoggerdState { struct LoggerdState {
LoggerState logger = {}; LoggerState logger = {};
char segment_path[4096]; char segment_path[4096];
std::mutex rotate_lock;
std::atomic<int> rotate_segment; std::atomic<int> rotate_segment;
std::atomic<double> last_camera_seen_tms; std::atomic<double> last_camera_seen_tms;
std::atomic<int> ready_to_rotate; // count of encoders ready to rotate std::atomic<int> ready_to_rotate; // count of encoders ready to rotate
@ -15,15 +14,12 @@ struct LoggerdState {
}; };
void logger_rotate(LoggerdState *s) { void logger_rotate(LoggerdState *s) {
{ int segment = -1;
std::unique_lock lk(s->rotate_lock); int err = logger_next(&s->logger, LOG_ROOT.c_str(), s->segment_path, sizeof(s->segment_path), &segment);
int segment = -1; assert(err == 0);
int err = logger_next(&s->logger, LOG_ROOT.c_str(), s->segment_path, sizeof(s->segment_path), &segment); s->rotate_segment = segment;
assert(err == 0); s->ready_to_rotate = 0;
s->rotate_segment = segment; s->last_rotate_tms = millis_since_boot();
s->ready_to_rotate = 0;
s->last_rotate_tms = millis_since_boot();
}
LOGW((s->logger.part == 0) ? "logging to %s" : "rotated to %s", s->segment_path); LOGW((s->logger.part == 0) ? "logging to %s" : "rotated to %s", s->segment_path);
} }

@ -9,7 +9,7 @@
#include "cereal/messaging/messaging.h" #include "cereal/messaging/messaging.h"
#include "common/util.h" #include "common/util.h"
#include "selfdrive/loggerd/logger.h" #include "selfdrive/loggerd/logger.h"
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
typedef cereal::Sentinel::SentinelType SentinelType; typedef cereal::Sentinel::SentinelType SentinelType;

@ -1,8 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import subprocess import subprocess
import sys
import time
import textwrap import textwrap
from pathlib import Path from pathlib import Path
@ -28,62 +26,49 @@ def build(spinner: Spinner, dirty: bool = False) -> None:
nproc = os.cpu_count() nproc = os.cpu_count()
j_flag = "" if nproc is None else f"-j{nproc - 1}" j_flag = "" if nproc is None else f"-j{nproc - 1}"
for retry in [True, False]: scons: subprocess.Popen = subprocess.Popen(["scons", j_flag, "--cache-populate"], cwd=BASEDIR, env=env, stderr=subprocess.PIPE)
scons: subprocess.Popen = subprocess.Popen(["scons", j_flag, "--cache-populate"], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) assert scons.stderr is not None
assert scons.stderr is not None
compile_output = []
compile_output = []
# Read progress from stderr and update spinner
# Read progress from stderr and update spinner while scons.poll() is None:
while scons.poll() is None: try:
try: line = scons.stderr.readline()
line = scons.stderr.readline() if line is None:
if line is None: continue
continue line = line.rstrip()
line = line.rstrip()
prefix = b'progress: '
prefix = b'progress: ' if line.startswith(prefix):
if line.startswith(prefix): i = int(line[len(prefix):])
i = int(line[len(prefix):]) spinner.update_progress(MAX_BUILD_PROGRESS * min(1., i / TOTAL_SCONS_NODES), 100.)
spinner.update_progress(MAX_BUILD_PROGRESS * min(1., i / TOTAL_SCONS_NODES), 100.) elif len(line):
elif len(line): compile_output.append(line)
compile_output.append(line) print(line.decode('utf8', 'replace'))
print(line.decode('utf8', 'replace')) except Exception:
except Exception: pass
pass
if scons.returncode != 0:
if scons.returncode != 0: # Read remaining output
# Read remaining output r = scons.stderr.read().split(b'\n')
r = scons.stderr.read().split(b'\n') compile_output += r
compile_output += r
# Build failed log errors
if retry and (not dirty): errors = [line.decode('utf8', 'replace') for line in compile_output
if not os.getenv("CI"): if any(err in line for err in [b'error: ', b'not found, needed by target'])]
print("scons build failed, cleaning in") error_s = "\n".join(errors)
for i in range(3, -1, -1): add_file_handler(cloudlog)
print("....%d" % i) cloudlog.error("scons build failed\n" + error_s)
time.sleep(1)
subprocess.check_call(["scons", "-c"], cwd=BASEDIR, env=env) # Show TextWindow
else: spinner.close()
print("scons build failed after retry") if not os.getenv("CI"):
sys.exit(1) error_s = "\n \n".join("\n".join(textwrap.wrap(e, 65)) for e in errors)
else: with TextWindow("openpilot failed to build\n \n" + error_s) as t:
# Build failed log errors t.wait_for_exit()
errors = [line.decode('utf8', 'replace') for line in compile_output exit(1)
if any(err in line for err in [b'error: ', b'not found, needed by target'])]
error_s = "\n".join(errors)
add_file_handler(cloudlog)
cloudlog.error("scons build failed\n" + error_s)
# Show TextWindow
spinner.close()
if not os.getenv("CI"):
error_s = "\n \n".join("\n".join(textwrap.wrap(e, 65)) for e in errors)
with TextWindow("openpilot failed to build\n \n" + error_s) as t:
t.wait_for_exit()
exit(1)
else:
break
# enforce max cache size # enforce max cache size
cache_files = [f for f in CACHE_DIR.rglob('*') if f.is_file()] cache_files = [f for f in CACHE_DIR.rglob('*') if f.is_file()]

@ -15,6 +15,7 @@ Currently the following processes are tested:
* calibrationd * calibrationd
* dmonitoringd * dmonitoringd
* locationd * locationd
* laikad
* paramsd * paramsd
* ubloxd * ubloxd

@ -236,6 +236,13 @@ def ublox_rcv_callback(msg):
return [] return []
def laika_rcv_callback(msg, CP, cfg, fsm):
if msg.ubloxGnss.which() == "measurementReport":
return ["gnssMeasurements"], True
else:
return [], False
CONFIGS = [ CONFIGS = [
ProcessConfig( ProcessConfig(
proc_name="controlsd", proc_name="controlsd",
@ -282,7 +289,8 @@ CONFIGS = [
proc_name="calibrationd", proc_name="calibrationd",
pub_sub={ pub_sub={
"carState": ["liveCalibration"], "carState": ["liveCalibration"],
"cameraOdometry": [] "cameraOdometry": [],
"carParams": [],
}, },
ignore=["logMonoTime", "valid"], ignore=["logMonoTime", "valid"],
init_callback=get_car_params, init_callback=get_car_params,
@ -337,6 +345,17 @@ CONFIGS = [
tolerance=None, tolerance=None,
fake_pubsubmaster=False, fake_pubsubmaster=False,
), ),
ProcessConfig(
proc_name="laikad",
pub_sub={
"ubloxGnss": ["gnssMeasurements"],
},
ignore=["logMonoTime"],
init_callback=get_car_params,
should_recv_callback=laika_rcv_callback,
tolerance=NUMPY_TOLERANCE,
fake_pubsubmaster=True,
),
] ]

@ -1 +1 @@
41161c8d151b0c2017214cad0aad3156533ab868 a0b5ce7b2e0b9c073e51ac8908402d53e1d99722

@ -53,6 +53,7 @@ def profile(proc, func, car='toyota'):
msgs = list(LogReader(rlog_url)) * int(os.getenv("LOOP", "1")) msgs = list(LogReader(rlog_url)) * int(os.getenv("LOOP", "1"))
os.environ['FINGERPRINT'] = fingerprint os.environ['FINGERPRINT'] = fingerprint
os.environ['REPLAY'] = "1"
def run(sm, pm, can_sock): def run(sm, pm, can_sock):
try: try:
@ -81,12 +82,14 @@ if __name__ == '__main__':
from selfdrive.controls.radard import radard_thread from selfdrive.controls.radard import radard_thread
from selfdrive.locationd.paramsd import main as paramsd_thread from selfdrive.locationd.paramsd import main as paramsd_thread
from selfdrive.controls.plannerd import main as plannerd_thread from selfdrive.controls.plannerd import main as plannerd_thread
from selfdrive.locationd.laikad import main as laikad_thread
procs = { procs = {
'radard': radard_thread, 'radard': radard_thread,
'controlsd': controlsd_thread, 'controlsd': controlsd_thread,
'paramsd': paramsd_thread, 'paramsd': paramsd_thread,
'plannerd': plannerd_thread, 'plannerd': plannerd_thread,
'laikad': laikad_thread,
} }
proc = sys.argv[1] proc = sys.argv[1]

@ -119,7 +119,8 @@ class TestPowerMonitoring(unittest.TestCase):
@parameterized.expand(ALL_PANDA_TYPES) @parameterized.expand(ALL_PANDA_TYPES)
def test_max_time_offroad(self, hw_type): def test_max_time_offroad(self, hw_type):
MOCKED_MAX_OFFROAD_TIME = 3600 MOCKED_MAX_OFFROAD_TIME = 3600
with pm_patch("MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True), pm_patch("HARDWARE.get_current_power_draw", None): POWER_DRAW = 0 # To stop shutting down for other reasons
with pm_patch("MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True), pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
pm = PowerMonitoring() pm = PowerMonitoring()
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
start_time = ssb start_time = ssb

@ -348,12 +348,13 @@ def thermald_thread(end_event, hw_queue):
power_monitor.calculate(peripheralState, onroad_conditions["ignition"]) power_monitor.calculate(peripheralState, onroad_conditions["ignition"])
msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used() msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used()
msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity()) msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity())
current_power_draw = HARDWARE.get_current_power_draw() # pylint: disable=assignment-from-none current_power_draw = HARDWARE.get_current_power_draw()
if current_power_draw is not None: statlog.sample("power_draw", current_power_draw)
statlog.sample("power_draw", current_power_draw) msg.deviceState.powerDrawW = current_power_draw
msg.deviceState.powerDrawW = current_power_draw
else: som_power_draw = HARDWARE.get_som_power_draw()
msg.deviceState.powerDrawW = 0 statlog.sample("som_power_draw", som_power_draw)
msg.deviceState.somPowerDrawW = som_power_draw
# Check if we need to disable charging (handled by boardd) # Check if we need to disable charging (handled by boardd)
msg.deviceState.chargingDisabled = power_monitor.should_disable_charging(onroad_conditions["ignition"], in_car, off_ts) msg.deviceState.chargingDisabled = power_monitor.should_disable_charging(onroad_conditions["ignition"], in_car, off_ts)

@ -18,10 +18,11 @@ if arch == "Darwin":
del base_libs[base_libs.index('OpenCL')] del base_libs[base_libs.index('OpenCL')]
qt_env['FRAMEWORKS'] += ['OpenCL'] qt_env['FRAMEWORKS'] += ['OpenCL']
widgets_src = ["ui.cc", "qt/util.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", 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/drive_stats.cc",
"qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.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/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/api.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc",
"qt/request_repeater.cc", "qt/qt_window.cc", "qt/offroad/networking.cc", "qt/offroad/wifiManager.cc"] "qt/request_repeater.cc", "qt/qt_window.cc", "qt/offroad/networking.cc", "qt/offroad/wifiManager.cc"]
qt_env['CPPDEFINES'] = [] qt_env['CPPDEFINES'] = []
@ -31,7 +32,7 @@ if maps:
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)
qt_libs = [widgets] + base_libs qt_libs = [widgets, qt_util] + base_libs
# build assets # build assets
assets = "#selfdrive/assets/assets.cc" assets = "#selfdrive/assets/assets.cc"
@ -57,6 +58,9 @@ qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc",
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
"qt/offroad/onboarding.cc", "qt/offroad/driverview.cc"] "qt/offroad/onboarding.cc", "qt/offroad/driverview.cc"]
qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs)
if GetOption('test'):
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)
# setup and factory resetter # setup and factory resetter
@ -107,17 +111,6 @@ if GetOption('extras'):
# keep installers small # keep installers small
assert f[0].get_size() < 300*1e3 assert f[0].get_size() < 300*1e3
# build watch3
# build headless replay
if arch in ['x86_64', 'Darwin'] or GetOption('extras'): if arch in ['x86_64', 'Darwin'] or GetOption('extras'):
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
replay_lib_src = ["replay/replay.cc", "replay/consoleui.cc", "replay/camera.cc", "replay/filereader.cc", "replay/logreader.cc", "replay/framereader.cc", "replay/route.cc", "replay/util.cc"]
replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs)
replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + qt_libs
qt_env.Program("replay/replay", ["replay/main.cc"], LIBS=replay_libs)
qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'zmq', 'visionipc', 'messaging']) qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'zmq', 'visionipc', 'messaging'])
if GetOption('test'):
qt_env.Program('replay/tests/test_replay', ['replay/tests/test_runner.cc', 'replay/tests/test_replay.cc'], LIBS=[replay_libs])

@ -53,7 +53,7 @@ Installer::Installer(QWidget *parent) : QWidget(parent) {
layout->setContentsMargins(150, 290, 150, 150); layout->setContentsMargins(150, 290, 150, 150);
layout->setSpacing(0); layout->setSpacing(0);
QLabel *title = new QLabel("Installing..."); QLabel *title = new QLabel(tr("Installing..."));
title->setStyleSheet("font-size: 90px; font-weight: 600;"); title->setStyleSheet("font-size: 90px; font-weight: 600;");
layout->addWidget(title, 0, Qt::AlignTop); layout->addWidget(title, 0, Qt::AlignTop);
@ -141,9 +141,9 @@ void Installer::cachedFetch(const QString &cache) {
void Installer::readProgress() { void Installer::readProgress() {
const QVector<QPair<QString, int>> stages = { const QVector<QPair<QString, int>> stages = {
// prefix, weight in percentage // prefix, weight in percentage
{"Receiving objects: ", 91}, {tr("Receiving objects: "), 91},
{"Resolving deltas: ", 2}, {tr("Resolving deltas: "), 2},
{"Updating files: ", 7}, {tr("Updating files: "), 7},
}; };
auto line = QString(proc.readAllStandardError()); auto line = QString(proc.readAllStandardError());

@ -85,6 +85,7 @@ void HomeWindow::mousePressEvent(QMouseEvent* e) {
} }
void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) { void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
HomeWindow::mousePressEvent(e);
const SubMaster &sm = *(uiState()->sm); const SubMaster &sm = *(uiState()->sm);
if (sm["carParams"].getCarParams().getNotCar()) { if (sm["carParams"].getCarParams().getNotCar()) {
if (onroad->isVisible()) { if (onroad->isVisible()) {
@ -92,6 +93,7 @@ void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
} else if (body->isVisible()) { } else if (body->isVisible()) {
slayout->setCurrentWidget(onroad); slayout->setCurrentWidget(onroad);
} }
showSidebar(false);
} }
} }
@ -109,7 +111,7 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
date = new QLabel(); date = new QLabel();
header_layout->addWidget(date, 1, Qt::AlignHCenter | Qt::AlignLeft); header_layout->addWidget(date, 1, Qt::AlignHCenter | Qt::AlignLeft);
update_notif = new QPushButton("UPDATE"); update_notif = new QPushButton(tr("UPDATE"));
update_notif->setVisible(false); update_notif->setVisible(false);
update_notif->setStyleSheet("background-color: #364DEF;"); update_notif->setStyleSheet("background-color: #364DEF;");
QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); });
@ -200,6 +202,6 @@ void OffroadHome::refresh() {
update_notif->setVisible(updateAvailable); update_notif->setVisible(updateAvailable);
alert_notif->setVisible(alerts); alert_notif->setVisible(alerts);
if (alerts) { if (alerts) {
alert_notif->setText(QString::number(alerts) + (alerts > 1 ? " ALERTS" : " ALERT")); alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT")));
} }
} }

@ -59,11 +59,11 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
current_widget = new QWidget(this); current_widget = new QWidget(this);
QVBoxLayout *current_layout = new QVBoxLayout(current_widget); QVBoxLayout *current_layout = new QVBoxLayout(current_widget);
QLabel *title = new QLabel("Current Destination"); QLabel *title = new QLabel(tr("Current Destination"));
title->setStyleSheet("font-size: 55px"); title->setStyleSheet("font-size: 55px");
current_layout->addWidget(title); current_layout->addWidget(title);
current_route = new ButtonControl("", "CLEAR"); current_route = new ButtonControl("", tr("CLEAR"));
current_route->setStyleSheet("padding-left: 40px;"); current_route->setStyleSheet("padding-left: 40px;");
current_layout->addWidget(current_route); current_layout->addWidget(current_route);
QObject::connect(current_route, &ButtonControl::clicked, [=]() { QObject::connect(current_route, &ButtonControl::clicked, [=]() {
@ -78,7 +78,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
main_layout->addWidget(current_widget); main_layout->addWidget(current_widget);
// Recents // Recents
QLabel *recents_title = new QLabel("Recent Destinations"); QLabel *recents_title = new QLabel(tr("Recent Destinations"));
recents_title->setStyleSheet("font-size: 55px"); recents_title->setStyleSheet("font-size: 55px");
main_layout->addWidget(recents_title); main_layout->addWidget(recents_title);
main_layout->addSpacing(20); main_layout->addSpacing(20);
@ -92,7 +92,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
QWidget * no_prime_widget = new QWidget; QWidget * no_prime_widget = new QWidget;
{ {
QVBoxLayout *no_prime_layout = new QVBoxLayout(no_prime_widget); QVBoxLayout *no_prime_layout = new QVBoxLayout(no_prime_widget);
QLabel *signup_header = new QLabel("Try the Navigation Beta"); QLabel *signup_header = new QLabel(tr("Try the Navigation Beta"));
signup_header->setStyleSheet(R"(font-size: 75px; color: white; font-weight:600;)"); signup_header->setStyleSheet(R"(font-size: 75px; color: white; font-weight:600;)");
signup_header->setAlignment(Qt::AlignCenter); signup_header->setAlignment(Qt::AlignCenter);
@ -104,7 +104,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation)); screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation));
no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter); no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter);
QLabel *signup = new QLabel("Get turn-by-turn directions displayed and more with a comma \nprime subscription. Sign up now: https://connect.comma.ai"); QLabel *signup = new QLabel(tr("Get turn-by-turn directions displayed and more with a comma \nprime subscription. Sign up now: https://connect.comma.ai"));
signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)"); signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)");
signup->setAlignment(Qt::AlignCenter); signup->setAlignment(Qt::AlignCenter);
@ -161,12 +161,12 @@ void MapPanel::showEvent(QShowEvent *event) {
void MapPanel::clear() { void MapPanel::clear() {
home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png")); home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png"));
home_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); home_address->setStyleSheet(R"(font-size: 50px; color: grey;)");
home_address->setText("No home\nlocation set"); home_address->setText(tr("No home\nlocation set"));
home_button->disconnect(); home_button->disconnect();
work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png")); work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png"));
work_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); work_address->setStyleSheet(R"(font-size: 50px; color: grey;)");
work_address->setText("No work\nlocation set"); work_address->setText(tr("No work\nlocation set"));
work_button->disconnect(); work_button->disconnect();
clearLayout(recent_layout); clearLayout(recent_layout);
@ -279,7 +279,7 @@ void MapPanel::parseResponse(const QString &response, bool success) {
} }
if (!has_recents) { if (!has_recents) {
QLabel *no_recents = new QLabel("no recent destinations"); QLabel *no_recents = new QLabel(tr("no recent destinations"));
no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)"); no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)");
recent_layout->addWidget(no_recents); recent_layout->addWidget(no_recents);
} }

@ -53,7 +53,7 @@ void DriverViewScene::paintEvent(QPaintEvent* event) {
p.setPen(Qt::white); p.setPen(Qt::white);
p.setRenderHint(QPainter::TextAntialiasing); p.setRenderHint(QPainter::TextAntialiasing);
configFont(p, "Inter", 100, "Bold"); configFont(p, "Inter", 100, "Bold");
p.drawText(geometry(), Qt::AlignCenter, "camera starting"); p.drawText(geometry(), Qt::AlignCenter, tr("camera starting"));
return; return;
} }

@ -27,7 +27,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen); QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen);
vlayout->setContentsMargins(20, 20, 20, 20); vlayout->setContentsMargins(20, 20, 20, 20);
if (show_advanced) { if (show_advanced) {
QPushButton* advancedSettings = new QPushButton("Advanced"); QPushButton* advancedSettings = new QPushButton(tr("Advanced"));
advancedSettings->setObjectName("advanced_btn"); advancedSettings->setObjectName("advanced_btn");
advancedSettings->setStyleSheet("margin-right: 30px;"); advancedSettings->setStyleSheet("margin-right: 30px;");
advancedSettings->setFixedSize(400, 100); advancedSettings->setFixedSize(400, 100);
@ -84,7 +84,7 @@ void Networking::connectToNetwork(const Network &n) {
} else if (n.security_type == SecurityType::OPEN) { } else if (n.security_type == SecurityType::OPEN) {
wifi->connect(n); wifi->connect(n);
} else if (n.security_type == SecurityType::WPA) { } else if (n.security_type == SecurityType::WPA) {
QString pass = InputDialog::getText("Enter password", this, "for \"" + n.ssid + "\"", true, 8); QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"") + n.ssid + "\"", true, 8);
if (!pass.isEmpty()) { if (!pass.isEmpty()) {
wifi->connect(n, pass); wifi->connect(n, pass);
} }
@ -94,7 +94,7 @@ void Networking::connectToNetwork(const Network &n) {
void Networking::wrongPassword(const QString &ssid) { void Networking::wrongPassword(const QString &ssid) {
if (wifi->seenNetworks.contains(ssid)) { if (wifi->seenNetworks.contains(ssid)) {
const Network &n = wifi->seenNetworks.value(ssid); const Network &n = wifi->seenNetworks.value(ssid);
QString pass = InputDialog::getText("Wrong password", this, "for \"" + n.ssid +"\"", true, 8); QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"") + n.ssid +"\"", true, 8);
if (!pass.isEmpty()) { if (!pass.isEmpty()) {
wifi->connect(n, pass); wifi->connect(n, pass);
} }
@ -118,7 +118,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
main_layout->setSpacing(20); main_layout->setSpacing(20);
// Back button // Back button
QPushButton* back = new QPushButton("Back"); QPushButton* back = new QPushButton(tr("Back"));
back->setObjectName("back_btn"); back->setObjectName("back_btn");
back->setFixedSize(400, 100); back->setFixedSize(400, 100);
connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
@ -126,14 +126,14 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
ListWidget *list = new ListWidget(this); ListWidget *list = new ListWidget(this);
// Enable tethering layout // Enable tethering layout
tetheringToggle = new ToggleControl("Enable Tethering", "", "", wifi->isTetheringEnabled()); tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled());
list->addItem(tetheringToggle); list->addItem(tetheringToggle);
QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering); QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering);
// Change tethering password // Change tethering password
ButtonControl *editPasswordButton = new ButtonControl("Tethering Password", "EDIT"); ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT"));
connect(editPasswordButton, &ButtonControl::clicked, [=]() { connect(editPasswordButton, &ButtonControl::clicked, [=]() {
QString pass = InputDialog::getText("Enter new tethering password", this, "", true, 8, wifi->getTetheringPassword()); QString pass = InputDialog::getText(tr("Enter new tethering password"), this, "", true, 8, wifi->getTetheringPassword());
if (!pass.isEmpty()) { if (!pass.isEmpty()) {
wifi->changeTetheringPassword(pass); wifi->changeTetheringPassword(pass);
} }
@ -141,7 +141,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(editPasswordButton); list->addItem(editPasswordButton);
// IP address // IP address
ipLabel = new LabelControl("IP Address", wifi->ipv4_address); ipLabel = new LabelControl(tr("IP Address"), wifi->ipv4_address);
list->addItem(ipLabel); list->addItem(ipLabel);
// SSH keys // SSH keys
@ -150,7 +150,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
// Roaming toggle // Roaming toggle
const bool roamingEnabled = params.getBool("GsmRoaming"); const bool roamingEnabled = params.getBool("GsmRoaming");
ToggleControl *roamingToggle = new ToggleControl("Enable Roaming", "", "", roamingEnabled); ToggleControl *roamingToggle = new ToggleControl(tr("Enable Roaming"), "", "", roamingEnabled);
QObject::connect(roamingToggle, &SshToggle::toggleFlipped, [=](bool state) { QObject::connect(roamingToggle, &SshToggle::toggleFlipped, [=](bool state) {
params.putBool("GsmRoaming", state); params.putBool("GsmRoaming", state);
wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn"))); wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")));
@ -158,11 +158,11 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(roamingToggle); list->addItem(roamingToggle);
// APN settings // APN settings
ButtonControl *editApnButton = new ButtonControl("APN Setting", "EDIT"); ButtonControl *editApnButton = new ButtonControl(tr("APN Setting"), tr("EDIT"));
connect(editApnButton, &ButtonControl::clicked, [=]() { connect(editApnButton, &ButtonControl::clicked, [=]() {
const bool roamingEnabled = params.getBool("GsmRoaming"); const bool roamingEnabled = params.getBool("GsmRoaming");
const QString cur_apn = QString::fromStdString(params.get("GsmApn")); const QString cur_apn = QString::fromStdString(params.get("GsmApn"));
QString apn = InputDialog::getText("Enter APN", this, "leave blank for automatic configuration", false, -1, cur_apn).trimmed(); QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed();
if (apn.isEmpty()) { if (apn.isEmpty()) {
params.remove("GsmApn"); params.remove("GsmApn");
@ -207,7 +207,7 @@ WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi)
checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(49, Qt::SmoothTransformation); checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(49, Qt::SmoothTransformation);
circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(49, Qt::SmoothTransformation); circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(49, Qt::SmoothTransformation);
QLabel *scanning = new QLabel("Scanning for networks..."); QLabel *scanning = new QLabel(tr("Scanning for networks..."));
scanning->setStyleSheet("font-size: 65px;"); scanning->setStyleSheet("font-size: 65px;");
main_layout->addWidget(scanning, 0, Qt::AlignCenter); main_layout->addWidget(scanning, 0, Qt::AlignCenter);
@ -260,7 +260,7 @@ void WifiUI::refresh() {
clearLayout(main_layout); clearLayout(main_layout);
if (wifi->seenNetworks.size() == 0) { if (wifi->seenNetworks.size() == 0) {
QLabel *scanning = new QLabel("Scanning for networks..."); QLabel *scanning = new QLabel(tr("Scanning for networks..."));
scanning->setStyleSheet("font-size: 65px;"); scanning->setStyleSheet("font-size: 65px;");
main_layout->addWidget(scanning, 0, Qt::AlignCenter); main_layout->addWidget(scanning, 0, Qt::AlignCenter);
return; return;
@ -286,17 +286,17 @@ void WifiUI::refresh() {
hlayout->addWidget(ssidLabel, network.connected == ConnectedType::CONNECTING ? 0 : 1); hlayout->addWidget(ssidLabel, network.connected == ConnectedType::CONNECTING ? 0 : 1);
if (network.connected == ConnectedType::CONNECTING) { if (network.connected == ConnectedType::CONNECTING) {
QPushButton *connecting = new QPushButton("CONNECTING..."); QPushButton *connecting = new QPushButton(tr("CONNECTING..."));
connecting->setObjectName("connecting"); connecting->setObjectName("connecting");
hlayout->addWidget(connecting, 2, Qt::AlignLeft); hlayout->addWidget(connecting, 2, Qt::AlignLeft);
} }
// Forget button // Forget button
if (wifi->isKnownConnection(network.ssid) && !wifi->isTetheringEnabled()) { if (wifi->isKnownConnection(network.ssid) && !wifi->isTetheringEnabled()) {
QPushButton *forgetBtn = new QPushButton("FORGET"); QPushButton *forgetBtn = new QPushButton(tr("FORGET"));
forgetBtn->setObjectName("forgetBtn"); forgetBtn->setObjectName("forgetBtn");
QObject::connect(forgetBtn, &QPushButton::clicked, [=]() { QObject::connect(forgetBtn, &QPushButton::clicked, [=]() {
if (ConfirmationDialog::confirm("Forget Wi-Fi Network \"" + QString::fromUtf8(network.ssid) + "\"?", this)) { if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"") + QString::fromUtf8(network.ssid) + "\"?", this)) {
wifi->forgetConnection(network.ssid); wifi->forgetConnection(network.ssid);
} }
}); });

@ -76,7 +76,7 @@ void TermsPage::showEvent(QShowEvent *event) {
main_layout->setContentsMargins(45, 35, 45, 45); main_layout->setContentsMargins(45, 35, 45, 45);
main_layout->setSpacing(0); main_layout->setSpacing(0);
QLabel *title = new QLabel("Terms & Conditions"); QLabel *title = new QLabel(tr("Terms & Conditions"));
title->setStyleSheet("font-size: 90px; font-weight: 600;"); title->setStyleSheet("font-size: 90px; font-weight: 600;");
main_layout->addWidget(title); main_layout->addWidget(title);
@ -104,11 +104,11 @@ void TermsPage::showEvent(QShowEvent *event) {
buttons->setSpacing(45); buttons->setSpacing(45);
main_layout->addLayout(buttons); main_layout->addLayout(buttons);
QPushButton *decline_btn = new QPushButton("Decline"); QPushButton *decline_btn = new QPushButton(tr("Decline"));
buttons->addWidget(decline_btn); buttons->addWidget(decline_btn);
QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms); QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms);
accept_btn = new QPushButton("Scroll to accept"); accept_btn = new QPushButton(tr("Scroll to accept"));
accept_btn->setEnabled(false); accept_btn->setEnabled(false);
accept_btn->setStyleSheet(R"( accept_btn->setStyleSheet(R"(
QPushButton { QPushButton {
@ -123,7 +123,7 @@ void TermsPage::showEvent(QShowEvent *event) {
} }
void TermsPage::enableAccept() { void TermsPage::enableAccept() {
accept_btn->setText("Agree"); accept_btn->setText(tr("Agree"));
accept_btn->setEnabled(true); accept_btn->setEnabled(true);
} }
@ -137,7 +137,7 @@ void DeclinePage::showEvent(QShowEvent *event) {
main_layout->setSpacing(40); main_layout->setSpacing(40);
QLabel *text = new QLabel(this); QLabel *text = new QLabel(this);
text->setText("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 openpilot."));
text->setStyleSheet(R"(font-size: 80px; font-weight: 300; margin: 200px;)"); text->setStyleSheet(R"(font-size: 80px; font-weight: 300; margin: 200px;)");
text->setWordWrap(true); text->setWordWrap(true);
main_layout->addWidget(text, 0, Qt::AlignCenter); main_layout->addWidget(text, 0, Qt::AlignCenter);
@ -146,12 +146,12 @@ void DeclinePage::showEvent(QShowEvent *event) {
buttons->setSpacing(45); buttons->setSpacing(45);
main_layout->addLayout(buttons); main_layout->addLayout(buttons);
QPushButton *back_btn = new QPushButton("Back"); QPushButton *back_btn = new QPushButton(tr("Back"));
buttons->addWidget(back_btn); buttons->addWidget(back_btn);
QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack); QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack);
QPushButton *uninstall_btn = new QPushButton(QString("Decline, uninstall %1").arg(getBrand())); QPushButton *uninstall_btn = new QPushButton(QString(tr("Decline, uninstall %1")).arg(getBrand()));
uninstall_btn->setStyleSheet("background-color: #B73D3D"); uninstall_btn->setStyleSheet("background-color: #B73D3D");
buttons->addWidget(uninstall_btn); buttons->addWidget(uninstall_btn);
QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() { QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() {

@ -29,45 +29,45 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
std::vector<std::tuple<QString, QString, QString, QString>> toggles{ std::vector<std::tuple<QString, QString, QString, QString>> toggles{
{ {
"OpenpilotEnabledToggle", "OpenpilotEnabledToggle",
"Enable openpilot", tr("Enable 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.", 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", "../assets/offroad/icon_openpilot.png",
}, },
{ {
"IsLdwEnabled", "IsLdwEnabled",
"Enable Lane Departure Warnings", tr("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).", 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_warning.png", "../assets/offroad/icon_warning.png",
}, },
{ {
"IsRHD", "IsRHD",
"Enable Right-Hand Drive", tr("Enable Right-Hand Drive"),
"Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat.", tr("Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat."),
"../assets/offroad/icon_openpilot_mirrored.png", "../assets/offroad/icon_openpilot_mirrored.png",
}, },
{ {
"IsMetric", "IsMetric",
"Use Metric System", tr("Use Metric System"),
"Display speed in km/h instead of mph.", tr("Display speed in km/h instead of mph."),
"../assets/offroad/icon_metric.png", "../assets/offroad/icon_metric.png",
}, },
{ {
"RecordFront", "RecordFront",
"Record and Upload Driver Camera", tr("Record and Upload Driver Camera"),
"Upload data from the driver facing camera and help improve the driver monitoring algorithm.", tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
"../assets/offroad/icon_monitoring.png", "../assets/offroad/icon_monitoring.png",
}, },
{ {
"DisengageOnAccelerator", "DisengageOnAccelerator",
"Disengage On Accelerator Pedal", tr("Disengage On Accelerator Pedal"),
"When enabled, pressing the accelerator pedal will disengage openpilot.", tr("When enabled, pressing the accelerator pedal will disengage openpilot."),
"../assets/offroad/icon_disengage_on_accelerator.svg", "../assets/offroad/icon_disengage_on_accelerator.svg",
}, },
#ifdef ENABLE_MAPS #ifdef ENABLE_MAPS
{ {
"NavSettingTime24h", "NavSettingTime24h",
"Show ETA in 24h format", tr("Show ETA in 24h format"),
"Use 24h format instead of am/pm", tr("Use 24h format instead of am/pm"),
"../assets/offroad/icon_metric.png", "../assets/offroad/icon_metric.png",
}, },
#endif #endif
@ -79,8 +79,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
if (params.getBool("DisableRadar_Allow")) { if (params.getBool("DisableRadar_Allow")) {
toggles.push_back({ toggles.push_back({
"DisableRadar", "DisableRadar",
"openpilot Longitudinal Control", tr("openpilot Longitudinal Control"),
"openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB!", tr("openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB!"),
"../assets/offroad/icon_speed_limit.png", "../assets/offroad/icon_speed_limit.png",
}); });
} }
@ -95,29 +95,29 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
setSpacing(50); setSpacing(50);
addItem(new LabelControl("Dongle ID", getDongleId().value_or("N/A"))); addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A"))));
addItem(new LabelControl("Serial", params.get("HardwareSerial").c_str())); addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str()));
// offroad-only buttons // offroad-only buttons
auto dcamBtn = new ButtonControl("Driver Camera", "PREVIEW", auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"),
"Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"); tr("Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"));
connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); });
addItem(dcamBtn); addItem(dcamBtn);
auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", " "); auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
connect(resetCalibBtn, &ButtonControl::showDescription, this, &DevicePanel::updateCalibDescription); connect(resetCalibBtn, &ButtonControl::showDescription, this, &DevicePanel::updateCalibDescription);
connect(resetCalibBtn, &ButtonControl::clicked, [&]() { connect(resetCalibBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), this)) {
params.remove("CalibrationParams"); params.remove("CalibrationParams");
} }
}); });
addItem(resetCalibBtn); addItem(resetCalibBtn);
if (!params.getBool("Passive")) { if (!params.getBool("Passive")) {
auto retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot"); auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot"));
connect(retrainingBtn, &ButtonControl::clicked, [=]() { connect(retrainingBtn, &ButtonControl::clicked, [=]() {
if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), this)) {
emit reviewTrainingGuide(); emit reviewTrainingGuide();
} }
}); });
@ -125,7 +125,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
} }
if (Hardware::TICI()) { if (Hardware::TICI()) {
auto regulatoryBtn = new ButtonControl("Regulatory", "VIEW", ""); auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), "");
connect(regulatoryBtn, &ButtonControl::clicked, [=]() { connect(regulatoryBtn, &ButtonControl::clicked, [=]() {
const std::string txt = util::read_file("../assets/offroad/fcc.html"); const std::string txt = util::read_file("../assets/offroad/fcc.html");
RichTextDialog::alert(QString::fromStdString(txt), this); RichTextDialog::alert(QString::fromStdString(txt), this);
@ -143,12 +143,12 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
QHBoxLayout *power_layout = new QHBoxLayout(); QHBoxLayout *power_layout = new QHBoxLayout();
power_layout->setSpacing(30); power_layout->setSpacing(30);
QPushButton *reboot_btn = new QPushButton("Reboot"); QPushButton *reboot_btn = new QPushButton(tr("Reboot"));
reboot_btn->setObjectName("reboot_btn"); reboot_btn->setObjectName("reboot_btn");
power_layout->addWidget(reboot_btn); power_layout->addWidget(reboot_btn);
QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot); QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot);
QPushButton *poweroff_btn = new QPushButton("Power Off"); QPushButton *poweroff_btn = new QPushButton(tr("Power Off"));
poweroff_btn->setObjectName("poweroff_btn"); poweroff_btn->setObjectName("poweroff_btn");
power_layout->addWidget(poweroff_btn); power_layout->addWidget(poweroff_btn);
QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff);
@ -168,8 +168,8 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
void DevicePanel::updateCalibDescription() { void DevicePanel::updateCalibDescription() {
QString desc = QString desc =
"openpilot requires the device to be mounted within 4° left or right and " tr("openpilot requires the device to be mounted within 4° left or right and "
"within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required."; "within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.");
std::string calib_bytes = Params().get("CalibrationParams"); std::string calib_bytes = Params().get("CalibrationParams");
if (!calib_bytes.empty()) { if (!calib_bytes.empty()) {
try { try {
@ -179,9 +179,9 @@ void DevicePanel::updateCalibDescription() {
if (calib.getCalStatus() != 0) { if (calib.getCalStatus() != 0) {
double pitch = calib.getRpyCalib()[1] * (180 / M_PI); double pitch = calib.getRpyCalib()[1] * (180 / M_PI);
double yaw = calib.getRpyCalib()[2] * (180 / M_PI); double yaw = calib.getRpyCalib()[2] * (180 / M_PI);
desc += QString(" Your device is pointed %1° %2 and %3° %4.") desc += QString(tr(" Your device is pointed %1° %2 and %3° %4."))
.arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "down" : "up", .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? tr("down") : tr("up"),
QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "left" : "right"); QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? tr("left") : tr("right"));
} }
} catch (kj::Exception) { } catch (kj::Exception) {
qInfo() << "invalid CalibrationParams"; qInfo() << "invalid CalibrationParams";
@ -192,51 +192,51 @@ void DevicePanel::updateCalibDescription() {
void DevicePanel::reboot() { void DevicePanel::reboot() {
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), this)) {
// Check engaged again in case it changed while the dialog was open // Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
Params().putBool("DoReboot", true); Params().putBool("DoReboot", true);
} }
} }
} else { } else {
ConfirmationDialog::alert("Disengage to Reboot", this); ConfirmationDialog::alert(tr("Disengage to Reboot"), this);
} }
} }
void DevicePanel::poweroff() { void DevicePanel::poweroff() {
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), this)) {
// Check engaged again in case it changed while the dialog was open // Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
Params().putBool("DoShutdown", true); Params().putBool("DoShutdown", true);
} }
} }
} else { } else {
ConfirmationDialog::alert("Disengage to Power Off", this); ConfirmationDialog::alert(tr("Disengage to Power Off"), this);
} }
} }
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
gitBranchLbl = new LabelControl("Git Branch"); gitBranchLbl = new LabelControl(tr("Git Branch"));
gitCommitLbl = new LabelControl("Git Commit"); gitCommitLbl = new LabelControl(tr("Git Commit"));
osVersionLbl = new LabelControl("OS Version"); osVersionLbl = new LabelControl(tr("OS Version"));
versionLbl = new LabelControl("Version", "", QString::fromStdString(params.get("ReleaseNotes")).trimmed()); versionLbl = new LabelControl(tr("Version"), "", QString::fromStdString(params.get("ReleaseNotes")).trimmed());
lastUpdateLbl = new LabelControl("Last Update Check", "", "The last time openpilot successfully checked for an update. The updater only runs while the car is off."); lastUpdateLbl = new LabelControl(tr("Last Update Check"), "", tr("The last time openpilot successfully checked for an update. The updater only runs while the car is off."));
updateBtn = new ButtonControl("Check for Update", ""); updateBtn = new ButtonControl(tr("Check for Update"), "");
connect(updateBtn, &ButtonControl::clicked, [=]() { connect(updateBtn, &ButtonControl::clicked, [=]() {
if (params.getBool("IsOffroad")) { if (params.getBool("IsOffroad")) {
fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime"))); fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime")));
fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount"))); fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount")));
updateBtn->setText("CHECKING"); updateBtn->setText(tr("CHECKING"));
updateBtn->setEnabled(false); updateBtn->setEnabled(false);
} }
std::system("pkill -1 -f selfdrive.updated"); std::system("pkill -1 -f selfdrive.updated");
}); });
auto uninstallBtn = new ButtonControl("Uninstall " + getBrand(), "UNINSTALL"); auto uninstallBtn = new ButtonControl(tr("Uninstall ") + getBrand(), tr("UNINSTALL"));
connect(uninstallBtn, &ButtonControl::clicked, [&]() { connect(uninstallBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) {
params.putBool("DoUninstall", true); params.putBool("DoUninstall", true);
} }
}); });
@ -250,8 +250,8 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
fs_watch = new QFileSystemWatcher(this); fs_watch = new QFileSystemWatcher(this);
QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) { QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) {
if (path.contains("UpdateFailedCount") && std::atoi(params.get("UpdateFailedCount").c_str()) > 0) { if (path.contains("UpdateFailedCount") && std::atoi(params.get("UpdateFailedCount").c_str()) > 0) {
lastUpdateLbl->setText("failed to fetch update"); lastUpdateLbl->setText(tr("failed to fetch update"));
updateBtn->setText("CHECK"); updateBtn->setText(tr("CHECK"));
updateBtn->setEnabled(true); updateBtn->setEnabled(true);
} else if (path.contains("LastUpdateTime")) { } else if (path.contains("LastUpdateTime")) {
updateLabels(); updateLabels();
@ -272,7 +272,7 @@ void SoftwarePanel::updateLabels() {
versionLbl->setText(getBrandVersion()); versionLbl->setText(getBrandVersion());
lastUpdateLbl->setText(lastUpdate); lastUpdateLbl->setText(lastUpdate);
updateBtn->setText("CHECK"); updateBtn->setText(tr("CHECK"));
updateBtn->setEnabled(true); updateBtn->setEnabled(true);
gitBranchLbl->setText(QString::fromStdString(params.get("GitBranch"))); gitBranchLbl->setText(QString::fromStdString(params.get("GitBranch")));
gitCommitLbl->setText(QString::fromStdString(params.get("GitCommit")).left(10)); gitCommitLbl->setText(QString::fromStdString(params.get("GitCommit")).left(10));
@ -301,7 +301,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
)"); )");
// close button // close button
QPushButton *close_btn = new QPushButton("×"); QPushButton *close_btn = new QPushButton(tr("×"));
close_btn->setStyleSheet(R"( close_btn->setStyleSheet(R"(
QPushButton { QPushButton {
font-size: 140px; font-size: 140px;
@ -327,15 +327,15 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView);
QList<QPair<QString, QWidget *>> panels = { QList<QPair<QString, QWidget *>> panels = {
{"Device", device}, {tr("Device"), device},
{"Network", network_panel(this)}, {tr("Network"), network_panel(this)},
{"Toggles", new TogglesPanel(this)}, {tr("Toggles"), new TogglesPanel(this)},
{"Software", new SoftwarePanel(this)}, {tr("Software"), new SoftwarePanel(this)},
}; };
#ifdef ENABLE_MAPS #ifdef ENABLE_MAPS
auto map_panel = new MapPanel(this); auto map_panel = new MapPanel(this);
panels.push_back({"Navigation", map_panel}); panels.push_back({tr("Navigation"), map_panel});
QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings); QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings);
#endif #endif
@ -367,7 +367,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
nav_btns->addButton(btn); nav_btns->addButton(btn);
sidebar_layout->addWidget(btn, 0, Qt::AlignRight); sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
const int lr_margin = name != "Network" ? 50 : 0; // Network 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); panel->setContentsMargins(lr_margin, 25, lr_margin, 25);
ScrollView *panel_frame = new ScrollView(panel, this); ScrollView *panel_frame = new ScrollView(panel, this);

@ -199,7 +199,7 @@ void NvgWindow::updateState(const UIState &s) {
setProperty("is_metric", s.scene.is_metric); setProperty("is_metric", s.scene.is_metric);
setProperty("speed", cur_speed); setProperty("speed", cur_speed);
setProperty("setSpeed", set_speed); setProperty("setSpeed", set_speed);
setProperty("speedUnit", s.scene.is_metric ? "km/h" : "mph"); setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph"));
setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
setProperty("status", s.status); setProperty("status", s.status);
@ -208,6 +208,12 @@ void NvgWindow::updateState(const UIState &s) {
setProperty("engageable", cs.getEngageable() || cs.getEnabled()); setProperty("engageable", cs.getEngageable() || cs.getEnabled());
setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode()); setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode());
} }
if (s.scene.calibration_valid) {
CameraViewWidget::updateCalibration(s.scene.view_from_calib);
} else {
CameraViewWidget::updateCalibration(DEFAULT_CALIBRATION);
}
} }
void NvgWindow::drawHud(QPainter &p) { void NvgWindow::drawHud(QPainter &p) {
@ -260,10 +266,10 @@ void NvgWindow::drawHud(QPainter &p) {
p.setPen(QColor(0xa6, 0xa6, 0xa6, 0xff)); p.setPen(QColor(0xa6, 0xa6, 0xa6, 0xff));
} }
configFont(p, "Inter", 40, "SemiBold"); configFont(p, "Inter", 40, "SemiBold");
QRect max_rect = getTextRect(p, Qt::AlignCenter, "MAX"); QRect max_rect = getTextRect(p, Qt::AlignCenter, tr("MAX"));
max_rect.moveCenter({set_speed_rect.center().x(), 0}); max_rect.moveCenter({set_speed_rect.center().x(), 0});
max_rect.moveTop(set_speed_rect.top() + 27); max_rect.moveTop(set_speed_rect.top() + 27);
p.drawText(max_rect, Qt::AlignCenter, "MAX"); p.drawText(max_rect, Qt::AlignCenter, tr("MAX"));
// Draw set speed // Draw set speed
if (is_cruise_set) { if (is_cruise_set) {
@ -307,16 +313,16 @@ void NvgWindow::drawHud(QPainter &p) {
// "SPEED" // "SPEED"
configFont(p, "Inter", 28, "SemiBold"); configFont(p, "Inter", 28, "SemiBold");
QRect text_speed_rect = getTextRect(p, Qt::AlignCenter, "SPEED"); QRect text_speed_rect = getTextRect(p, Qt::AlignCenter, tr("SPEED"));
text_speed_rect.moveCenter({sign_rect.center().x(), 0}); text_speed_rect.moveCenter({sign_rect.center().x(), 0});
text_speed_rect.moveTop(sign_rect_outer.top() + 22); text_speed_rect.moveTop(sign_rect_outer.top() + 22);
p.drawText(text_speed_rect, Qt::AlignCenter, "SPEED"); p.drawText(text_speed_rect, Qt::AlignCenter, tr("SPEED"));
// "LIMIT" // "LIMIT"
QRect text_limit_rect = getTextRect(p, Qt::AlignCenter, "LIMIT"); QRect text_limit_rect = getTextRect(p, Qt::AlignCenter, tr("LIMIT"));
text_limit_rect.moveCenter({sign_rect.center().x(), 0}); text_limit_rect.moveCenter({sign_rect.center().x(), 0});
text_limit_rect.moveTop(sign_rect_outer.top() + 51); text_limit_rect.moveTop(sign_rect_outer.top() + 51);
p.drawText(text_limit_rect, Qt::AlignCenter, "LIMIT"); p.drawText(text_limit_rect, Qt::AlignCenter, tr("LIMIT"));
// Speed limit value // Speed limit value
configFont(p, "Inter", 70, "Bold"); configFont(p, "Inter", 70, "Bold");
@ -399,23 +405,20 @@ void NvgWindow::initializeGL() {
setBackgroundColor(bg_colors[STATUS_DISENGAGED]); setBackgroundColor(bg_colors[STATUS_DISENGAGED]);
} }
void NvgWindow::updateFrameMat(int w, int h) { void NvgWindow::updateFrameMat() {
CameraViewWidget::updateFrameMat(w, h); CameraViewWidget::updateFrameMat();
UIState *s = uiState(); UIState *s = uiState();
int w = width(), h = height();
s->fb_w = w; s->fb_w = w;
s->fb_h = h; s->fb_h = h;
auto intrinsic_matrix = s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix;
float zoom = ZOOM / intrinsic_matrix.v[0];
if (s->wide_camera) {
zoom *= 0.5;
}
// Apply transformation such that video pixel coordinates match video // Apply transformation such that video pixel coordinates match video
// 1) Put (0, 0) in the middle of the video // 1) Put (0, 0) in the middle of the video
// 2) Apply same scaling as video // 2) Apply same scaling as video
// 3) Put (0, 0) in top left corner of video // 3) Put (0, 0) in top left corner of video
s->car_space_transform.reset(); s->car_space_transform.reset();
s->car_space_transform.translate(w / 2, h / 2 + y_offset) s->car_space_transform.translate(w / 2 - x_offset, h / 2 - y_offset)
.scale(zoom, zoom) .scale(zoom, zoom)
.translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]);
} }

@ -70,7 +70,7 @@ protected:
void paintGL() override; void paintGL() override;
void initializeGL() override; void initializeGL() override;
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void updateFrameMat(int w, int h) override; void updateFrameMat() override;
void drawLaneLines(QPainter &painter, const UIState *s); void drawLaneLines(QPainter &painter, const UIState *s);
void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd); void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd);
void drawHud(QPainter &p); void drawHud(QPainter &p);

@ -26,16 +26,16 @@ void Reset::doReset() {
if (rm == 0 || fmt == 0) { if (rm == 0 || fmt == 0) {
std::system("sudo reboot"); std::system("sudo reboot");
} }
body->setText("Reset failed. Reboot to try again."); body->setText(tr("Reset failed. Reboot to try again."));
rebootBtn->show(); rebootBtn->show();
} }
void Reset::confirm() { void Reset::confirm() {
const QString confirm_txt = "Are you sure you want to reset your device?"; const QString confirm_txt = tr("Are you sure you want to reset your device?");
if (body->text() != confirm_txt) { if (body->text() != confirm_txt) {
body->setText(confirm_txt); body->setText(confirm_txt);
} else { } else {
body->setText("Resetting device..."); body->setText(tr("Resetting device..."));
rejectBtn->hide(); rejectBtn->hide();
rebootBtn->hide(); rebootBtn->hide();
confirmBtn->hide(); confirmBtn->hide();
@ -50,13 +50,13 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
main_layout->setContentsMargins(45, 220, 45, 45); main_layout->setContentsMargins(45, 220, 45, 45);
main_layout->setSpacing(0); main_layout->setSpacing(0);
QLabel *title = new QLabel("System Reset"); QLabel *title = new QLabel(tr("System Reset"));
title->setStyleSheet("font-size: 90px; font-weight: 600;"); title->setStyleSheet("font-size: 90px; font-weight: 600;");
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
main_layout->addSpacing(60); main_layout->addSpacing(60);
body = new QLabel("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot."); body = new QLabel(tr("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot."));
body->setWordWrap(true); body->setWordWrap(true);
body->setStyleSheet("font-size: 80px; font-weight: light;"); body->setStyleSheet("font-size: 80px; font-weight: light;");
main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft); main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft);
@ -65,11 +65,11 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
main_layout->addLayout(blayout); main_layout->addLayout(blayout);
blayout->setSpacing(50); blayout->setSpacing(50);
rejectBtn = new QPushButton("Cancel"); rejectBtn = new QPushButton(tr("Cancel"));
blayout->addWidget(rejectBtn); blayout->addWidget(rejectBtn);
QObject::connect(rejectBtn, &QPushButton::clicked, QCoreApplication::instance(), &QCoreApplication::quit); QObject::connect(rejectBtn, &QPushButton::clicked, QCoreApplication::instance(), &QCoreApplication::quit);
rebootBtn = new QPushButton("Reboot"); rebootBtn = new QPushButton(tr("Reboot"));
blayout->addWidget(rebootBtn); blayout->addWidget(rebootBtn);
#ifdef __aarch64__ #ifdef __aarch64__
QObject::connect(rebootBtn, &QPushButton::clicked, [=]{ QObject::connect(rebootBtn, &QPushButton::clicked, [=]{
@ -77,7 +77,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
}); });
#endif #endif
confirmBtn = new QPushButton("Confirm"); confirmBtn = new QPushButton(tr("Confirm"));
confirmBtn->setStyleSheet("background-color: #465BEA;"); confirmBtn->setStyleSheet("background-color: #465BEA;");
blayout->addWidget(confirmBtn); blayout->addWidget(confirmBtn);
QObject::connect(confirmBtn, &QPushButton::clicked, this, &Reset::confirm); QObject::connect(confirmBtn, &QPushButton::clicked, this, &Reset::confirm);
@ -85,7 +85,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
rejectBtn->setVisible(!recover); rejectBtn->setVisible(!recover);
rebootBtn->setVisible(recover); rebootBtn->setVisible(recover);
if (recover) { if (recover) {
body->setText("Unable to mount data partition. Press confirm to reset your device."); body->setText(tr("Unable to mount data partition. Press confirm to reset your device."));
} }
setStyleSheet(R"( setStyleSheet(R"(

@ -70,13 +70,13 @@ QWidget * Setup::low_voltage() {
inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft); inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(80); inner_layout->addSpacing(80);
QLabel *title = new QLabel("WARNING: Low Voltage"); QLabel *title = new QLabel(tr("WARNING: Low Voltage"));
title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;"); title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;");
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(25); inner_layout->addSpacing(25);
QLabel *body = new QLabel("Power your device in a car with a harness or proceed at your own risk."); QLabel *body = new QLabel(tr("Power your device in a car with a harness or proceed at your own risk."));
body->setWordWrap(true); body->setWordWrap(true);
body->setAlignment(Qt::AlignTop | Qt::AlignLeft); body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 80px; font-weight: 300;"); body->setStyleSheet("font-size: 80px; font-weight: 300;");
@ -89,14 +89,14 @@ QWidget * Setup::low_voltage() {
blayout->setSpacing(50); blayout->setSpacing(50);
main_layout->addLayout(blayout, 0); main_layout->addLayout(blayout, 0);
QPushButton *poweroff = new QPushButton("Power off"); QPushButton *poweroff = new QPushButton(tr("Power off"));
poweroff->setObjectName("navBtn"); poweroff->setObjectName("navBtn");
blayout->addWidget(poweroff); blayout->addWidget(poweroff);
QObject::connect(poweroff, &QPushButton::clicked, this, [=]() { QObject::connect(poweroff, &QPushButton::clicked, this, [=]() {
Hardware::poweroff(); Hardware::poweroff();
}); });
QPushButton *cont = new QPushButton("Continue"); QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn"); cont->setObjectName("navBtn");
blayout->addWidget(cont); blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
@ -114,12 +114,12 @@ QWidget * Setup::getting_started() {
vlayout->setContentsMargins(165, 280, 100, 0); vlayout->setContentsMargins(165, 280, 100, 0);
main_layout->addLayout(vlayout); main_layout->addLayout(vlayout);
QLabel *title = new QLabel("Getting Started"); QLabel *title = new QLabel(tr("Getting Started"));
title->setStyleSheet("font-size: 90px; font-weight: 500;"); title->setStyleSheet("font-size: 90px; font-weight: 500;");
vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
vlayout->addSpacing(90); vlayout->addSpacing(90);
QLabel *desc = new QLabel("Before we get on the road, let’s finish installation and cover some details."); QLabel *desc = new QLabel(tr("Before we get on the road, let’s finish installation and cover some details."));
desc->setWordWrap(true); desc->setWordWrap(true);
desc->setStyleSheet("font-size: 80px; font-weight: 300;"); desc->setStyleSheet("font-size: 80px; font-weight: 300;");
vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft); vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft);
@ -144,7 +144,7 @@ QWidget * Setup::network_setup() {
main_layout->setContentsMargins(55, 50, 55, 50); main_layout->setContentsMargins(55, 50, 55, 50);
// title // title
QLabel *title = new QLabel("Connect to Wi-Fi"); QLabel *title = new QLabel(tr("Connect to Wi-Fi"));
title->setStyleSheet("font-size: 90px; font-weight: 500;"); title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
@ -162,7 +162,7 @@ QWidget * Setup::network_setup() {
main_layout->addLayout(blayout); main_layout->addLayout(blayout);
blayout->setSpacing(50); blayout->setSpacing(50);
QPushButton *back = new QPushButton("Back"); QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn"); back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back); blayout->addWidget(back);
@ -179,9 +179,9 @@ QWidget * Setup::network_setup() {
cont->setEnabled(success); cont->setEnabled(success);
if (success) { if (success) {
const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL; const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL;
cont->setText(cell ? "Continue without Wi-Fi" : "Continue"); cont->setText(cell ? tr("Continue without Wi-Fi") : tr("Continue"));
} else { } else {
cont->setText("Waiting for internet"); cont->setText(tr("Waiting for internet"));
} }
repaint(); repaint();
}); });
@ -235,7 +235,7 @@ QWidget * Setup::software_selection() {
main_layout->setSpacing(0); main_layout->setSpacing(0);
// title // title
QLabel *title = new QLabel("Choose Software to Install"); QLabel *title = new QLabel(tr("Choose Software to Install"));
title->setStyleSheet("font-size: 90px; font-weight: 500;"); title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
@ -245,12 +245,12 @@ QWidget * Setup::software_selection() {
QButtonGroup *group = new QButtonGroup(widget); QButtonGroup *group = new QButtonGroup(widget);
group->setExclusive(true); group->setExclusive(true);
QWidget *dashcam = radio_button("Dashcam", group); QWidget *dashcam = radio_button(tr("Dashcam"), group);
main_layout->addWidget(dashcam); main_layout->addWidget(dashcam);
main_layout->addSpacing(30); main_layout->addSpacing(30);
QWidget *custom = radio_button("Custom Software", group); QWidget *custom = radio_button(tr("Custom Software"), group);
main_layout->addWidget(custom); main_layout->addWidget(custom);
main_layout->addStretch(); main_layout->addStretch();
@ -260,12 +260,12 @@ QWidget * Setup::software_selection() {
main_layout->addLayout(blayout); main_layout->addLayout(blayout);
blayout->setSpacing(50); blayout->setSpacing(50);
QPushButton *back = new QPushButton("Back"); QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn"); back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back); blayout->addWidget(back);
QPushButton *cont = new QPushButton("Continue"); QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn"); cont->setObjectName("navBtn");
cont->setEnabled(false); cont->setEnabled(false);
cont->setProperty("primary", true); cont->setProperty("primary", true);
@ -278,7 +278,7 @@ QWidget * Setup::software_selection() {
}); });
QString url = DASHCAM_URL; QString url = DASHCAM_URL;
if (group->checkedButton() != dashcam) { if (group->checkedButton() != dashcam) {
url = InputDialog::getText("Enter URL", this, "for Custom Software"); url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
} }
if (!url.isEmpty()) { if (!url.isEmpty()) {
QTimer::singleShot(1000, this, [=]() { QTimer::singleShot(1000, this, [=]() {
@ -300,7 +300,7 @@ QWidget * Setup::software_selection() {
QWidget * Setup::downloading() { QWidget * Setup::downloading() {
QWidget *widget = new QWidget(); QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget); QVBoxLayout *main_layout = new QVBoxLayout(widget);
QLabel *txt = new QLabel("Downloading..."); QLabel *txt = new QLabel(tr("Downloading..."));
txt->setStyleSheet("font-size: 90px; font-weight: 500;"); txt->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(txt, 0, Qt::AlignCenter); main_layout->addWidget(txt, 0, Qt::AlignCenter);
return widget; return widget;
@ -312,13 +312,13 @@ QWidget * Setup::download_failed() {
main_layout->setContentsMargins(55, 225, 55, 55); main_layout->setContentsMargins(55, 225, 55, 55);
main_layout->setSpacing(0); main_layout->setSpacing(0);
QLabel *title = new QLabel("Download Failed"); QLabel *title = new QLabel(tr("Download Failed"));
title->setStyleSheet("font-size: 90px; font-weight: 500;"); title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
main_layout->addSpacing(67); main_layout->addSpacing(67);
QLabel *body = new QLabel("Ensure the entered URL is valid, and the device’s internet connection is good."); QLabel *body = new QLabel(tr("Ensure the entered URL is valid, and the device’s internet connection is good."));
body->setWordWrap(true); body->setWordWrap(true);
body->setAlignment(Qt::AlignTop | Qt::AlignLeft); body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;"); body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;");
@ -331,14 +331,14 @@ QWidget * Setup::download_failed() {
blayout->setSpacing(50); blayout->setSpacing(50);
main_layout->addLayout(blayout, 0); main_layout->addLayout(blayout, 0);
QPushButton *reboot = new QPushButton("Reboot device"); QPushButton *reboot = new QPushButton(tr("Reboot device"));
reboot->setObjectName("navBtn"); reboot->setObjectName("navBtn");
blayout->addWidget(reboot); blayout->addWidget(reboot);
QObject::connect(reboot, &QPushButton::clicked, this, [=]() { QObject::connect(reboot, &QPushButton::clicked, this, [=]() {
Hardware::reboot(); Hardware::reboot();
}); });
QPushButton *restart = new QPushButton("Start over"); QPushButton *restart = new QPushButton(tr("Start over"));
restart->setObjectName("navBtn"); restart->setObjectName("navBtn");
restart->setProperty("primary", true); restart->setProperty("primary", true);
blayout->addWidget(restart); blayout->addWidget(restart);

@ -20,13 +20,13 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
QVBoxLayout *layout = new QVBoxLayout(prompt); QVBoxLayout *layout = new QVBoxLayout(prompt);
layout->setContentsMargins(100, 250, 100, 100); layout->setContentsMargins(100, 250, 100, 100);
QLabel *title = new QLabel("Update Required"); QLabel *title = new QLabel(tr("Update Required"));
title->setStyleSheet("font-size: 80px; font-weight: bold;"); title->setStyleSheet("font-size: 80px; font-weight: bold;");
layout->addWidget(title); layout->addWidget(title);
layout->addSpacing(75); layout->addSpacing(75);
QLabel *desc = new QLabel("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB."); QLabel *desc = new QLabel(tr("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB."));
desc->setWordWrap(true); desc->setWordWrap(true);
desc->setStyleSheet("font-size: 65px;"); desc->setStyleSheet("font-size: 65px;");
layout->addWidget(desc); layout->addWidget(desc);
@ -37,14 +37,14 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
hlayout->setSpacing(30); hlayout->setSpacing(30);
layout->addLayout(hlayout); layout->addLayout(hlayout);
QPushButton *connect = new QPushButton("Connect to Wi-Fi"); QPushButton *connect = new QPushButton(tr("Connect to Wi-Fi"));
connect->setObjectName("navBtn"); connect->setObjectName("navBtn");
QObject::connect(connect, &QPushButton::clicked, [=]() { QObject::connect(connect, &QPushButton::clicked, [=]() {
setCurrentWidget(wifi); setCurrentWidget(wifi);
}); });
hlayout->addWidget(connect); hlayout->addWidget(connect);
QPushButton *install = new QPushButton("Install"); QPushButton *install = new QPushButton(tr("Install"));
install->setObjectName("navBtn"); install->setObjectName("navBtn");
install->setStyleSheet("background-color: #465BEA;"); install->setStyleSheet("background-color: #465BEA;");
QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate); QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate);
@ -61,7 +61,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }"); networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }");
layout->addWidget(networking, 1); layout->addWidget(networking, 1);
QPushButton *back = new QPushButton("Back"); QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn"); back->setObjectName("navBtn");
back->setStyleSheet("padding-left: 60px; padding-right: 60px;"); back->setStyleSheet("padding-left: 60px; padding-right: 60px;");
QObject::connect(back, &QPushButton::clicked, [=]() { QObject::connect(back, &QPushButton::clicked, [=]() {
@ -77,7 +77,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
layout->setContentsMargins(150, 330, 150, 150); layout->setContentsMargins(150, 330, 150, 150);
layout->setSpacing(0); layout->setSpacing(0);
text = new QLabel("Loading..."); text = new QLabel(tr("Loading..."));
text->setStyleSheet("font-size: 90px; font-weight: 600;"); text->setStyleSheet("font-size: 90px; font-weight: 600;");
layout->addWidget(text, 0, Qt::AlignTop); layout->addWidget(text, 0, Qt::AlignTop);
@ -91,7 +91,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
layout->addStretch(); layout->addStretch();
reboot = new QPushButton("Reboot"); reboot = new QPushButton(tr("Reboot"));
reboot->setObjectName("navBtn"); reboot->setObjectName("navBtn");
reboot->setStyleSheet("padding-left: 60px; padding-right: 60px;"); reboot->setStyleSheet("padding-left: 60px; padding-right: 60px;");
QObject::connect(reboot, &QPushButton::clicked, [=]() { QObject::connect(reboot, &QPushButton::clicked, [=]() {
@ -161,7 +161,7 @@ void Updater::updateFinished(int exitCode, QProcess::ExitStatus exitStatus) {
if (exitCode == 0) { if (exitCode == 0) {
Hardware::reboot(); Hardware::reboot();
} else { } else {
text->setText("Update failed"); text->setText(tr("Update failed"));
reboot->show(); reboot->show();
} }
} }

@ -64,26 +64,26 @@ void Sidebar::updateState(const UIState &s) {
ItemStatus connectStatus; ItemStatus connectStatus;
auto last_ping = deviceState.getLastAthenaPingTime(); auto last_ping = deviceState.getLastAthenaPingTime();
if (last_ping == 0) { if (last_ping == 0) {
connectStatus = ItemStatus{{"CONNECT", "OFFLINE"}, warning_color}; connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color};
} else { } else {
connectStatus = nanos_since_boot() - last_ping < 80e9 ? ItemStatus{{"CONNECT", "ONLINE"}, good_color} : ItemStatus{{"CONNECT", "ERROR"}, danger_color}; connectStatus = nanos_since_boot() - last_ping < 80e9 ? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, good_color} : ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color};
} }
setProperty("connectStatus", QVariant::fromValue(connectStatus)); setProperty("connectStatus", QVariant::fromValue(connectStatus));
ItemStatus tempStatus = {{"TEMP", "HIGH"}, danger_color}; ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color};
auto ts = deviceState.getThermalStatus(); auto ts = deviceState.getThermalStatus();
if (ts == cereal::DeviceState::ThermalStatus::GREEN) { if (ts == cereal::DeviceState::ThermalStatus::GREEN) {
tempStatus = {{"TEMP", "GOOD"}, good_color}; tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color};
} else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) {
tempStatus = {{"TEMP", "OK"}, warning_color}; tempStatus = {{tr("TEMP"), tr("OK")}, warning_color};
} }
setProperty("tempStatus", QVariant::fromValue(tempStatus)); setProperty("tempStatus", QVariant::fromValue(tempStatus));
ItemStatus pandaStatus = {{"VEHICLE", "ONLINE"}, good_color}; ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color};
if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
pandaStatus = {{"NO", "PANDA"}, danger_color}; pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color};
} else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) { } else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) {
pandaStatus = {{"GPS", "SEARCH"}, warning_color}; pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color};
} }
setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); setProperty("pandaStatus", QVariant::fromValue(pandaStatus));
} }

@ -33,12 +33,12 @@ int main(int argc, char *argv[]) {
QPushButton *btn = new QPushButton(); QPushButton *btn = new QPushButton();
#ifdef __aarch64__ #ifdef __aarch64__
btn->setText("Reboot"); btn->setText(QObject::tr("Reboot"));
QObject::connect(btn, &QPushButton::clicked, [=]() { QObject::connect(btn, &QPushButton::clicked, [=]() {
Hardware::reboot(); Hardware::reboot();
}); });
#else #else
btn->setText("Exit"); btn->setText(QObject::tr("Exit"));
QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit);
#endif #endif
main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom);

@ -15,7 +15,7 @@ QString getVersion() {
} }
QString getBrand() { QString getBrand() {
return Params().getBool("Passive") ? "dashcam" : "openpilot"; return Params().getBool("Passive") ? QObject::tr("dashcam") : QObject::tr("openpilot");
} }
QString getBrandVersion() { QString getBrandVersion() {
@ -63,13 +63,13 @@ QString timeAgo(const QDateTime &date) {
s = "now"; s = "now";
} else if (diff < 60 * 60) { } else if (diff < 60 * 60) {
int minutes = diff / 60; int minutes = diff / 60;
s = QString("%1 minute%2 ago").arg(minutes).arg(minutes > 1 ? "s" : ""); s = QString(QObject::tr("%1 minute%2 ago")).arg(minutes).arg(minutes > 1 ? "s" : "");
} else if (diff < 60 * 60 * 24) { } else if (diff < 60 * 60 * 24) {
int hours = diff / (60 * 60); int hours = diff / (60 * 60);
s = QString("%1 hour%2 ago").arg(hours).arg(hours > 1 ? "s" : ""); s = QString(QObject::tr("%1 hour%2 ago")).arg(hours).arg(hours > 1 ? "s" : "");
} else if (diff < 3600 * 24 * 7) { } else if (diff < 3600 * 24 * 7) {
int days = diff / (60 * 60 * 24); int days = diff / (60 * 60 * 24);
s = QString("%1 day%2 ago").arg(days).arg(days > 1 ? "s" : ""); s = QString(QObject::tr("%1 day%2 ago")).arg(days).arg(days > 1 ? "s" : "");
} else { } else {
s = date.date().toString(); s = date.date().toString();
} }

@ -6,6 +6,8 @@
#include <GLES3/gl3.h> #include <GLES3/gl3.h>
#endif #endif
#include <cmath>
#include <QOpenGLBuffer> #include <QOpenGLBuffer>
#include <QOffscreenSurface> #include <QOffscreenSurface>
@ -59,13 +61,6 @@ const char frame_fragment_shader[] =
"}\n"; "}\n";
#endif #endif
const mat4 device_transform = {{
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
}};
mat4 get_driver_view_transform(int screen_width, int screen_height, int stream_width, int stream_height) { mat4 get_driver_view_transform(int screen_width, int screen_height, int stream_width, int stream_height) {
const float driver_view_ratio = 2.0; const float driver_view_ratio = 2.0;
const float yscale = stream_height * driver_view_ratio / stream_width; const float yscale = stream_height * driver_view_ratio / stream_width;
@ -185,45 +180,76 @@ void CameraViewWidget::hideEvent(QHideEvent *event) {
} }
} }
void CameraViewWidget::updateFrameMat(int w, int h) { void CameraViewWidget::updateFrameMat() {
int w = width(), h = height();
if (zoomed_view) { if (zoomed_view) {
if (stream_type == VISION_STREAM_DRIVER) { if (stream_type == VISION_STREAM_DRIVER) {
frame_mat = matmul(device_transform, get_driver_view_transform(w, h, stream_width, stream_height)); frame_mat = get_driver_view_transform(w, h, stream_width, stream_height);
} else { } else {
auto intrinsic_matrix = stream_type == VISION_STREAM_WIDE_ROAD ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; intrinsic_matrix = (stream_type == VISION_STREAM_WIDE_ROAD) ? ecam_intrinsic_matrix : fcam_intrinsic_matrix;
float zoom = ZOOM / intrinsic_matrix.v[0]; zoom = (stream_type == VISION_STREAM_WIDE_ROAD) ? 2.5 : 1.1;
if (stream_type == VISION_STREAM_WIDE_ROAD) {
zoom *= 0.5; // Project point at "infinity" to compute x and y offsets
} // to ensure this ends up in the middle of the screen
// TODO: use proper perspective transform?
const vec3 inf = {{1000., 0., 0.}};
const vec3 Ep = matvecmul3(calibration, inf);
const vec3 Kep = matvecmul3(intrinsic_matrix, Ep);
float x_offset_ = (Kep.v[0] / Kep.v[2] - intrinsic_matrix.v[2]) * zoom;
float y_offset_ = (Kep.v[1] / Kep.v[2] - intrinsic_matrix.v[5]) * zoom;
float max_x_offset = intrinsic_matrix.v[2] * zoom - w / 2 - 5;
float max_y_offset = intrinsic_matrix.v[5] * zoom - h / 2 - 5;
x_offset = std::clamp(x_offset_, -max_x_offset, max_x_offset);
y_offset = std::clamp(y_offset_, -max_y_offset, max_y_offset);
float zx = zoom * 2 * intrinsic_matrix.v[2] / width(); float zx = zoom * 2 * intrinsic_matrix.v[2] / width();
float zy = zoom * 2 * intrinsic_matrix.v[5] / height(); float zy = zoom * 2 * intrinsic_matrix.v[5] / height();
const mat4 frame_transform = {{ const mat4 frame_transform = {{
zx, 0.0, 0.0, 0.0, zx, 0.0, 0.0, -x_offset / width() * 2,
0.0, zy, 0.0, -y_offset / height() * 2, 0.0, zy, 0.0, y_offset / height() * 2,
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
}}; }};
frame_mat = matmul(device_transform, frame_transform); frame_mat = frame_transform;
} }
} else if (stream_width > 0 && stream_height > 0) { } else if (stream_width > 0 && stream_height > 0) {
// fit frame to widget size // fit frame to widget size
float widget_aspect_ratio = (float)width() / height(); float widget_aspect_ratio = (float)width() / height();
float frame_aspect_ratio = (float)stream_width / stream_height; float frame_aspect_ratio = (float)stream_width / stream_height;
frame_mat = matmul(device_transform, get_fit_view_transform(widget_aspect_ratio, frame_aspect_ratio)); frame_mat = get_fit_view_transform(widget_aspect_ratio, frame_aspect_ratio);
} }
} }
void CameraViewWidget::updateCalibration(const mat3 &calib) {
calibration = calib;
updateFrameMat();
}
void CameraViewWidget::paintGL() { void CameraViewWidget::paintGL() {
glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF());
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
if (frames.empty()) return; if (frames.empty()) return;
int frame_idx; int frame_idx = frames.size() - 1;
for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) {
if (frames[frame_idx].first == draw_frame_id) break; // Always draw latest frame until sync logic is more stable
// for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) {
// if (frames[frame_idx].first == draw_frame_id) break;
// }
// Log duplicate/dropped frames
static int prev_id = 0;
if (frames[frame_idx].first == prev_id) {
qInfo() << "Drawing same frame twice" << frames[frame_idx].first;
} else if (frames[frame_idx].first != prev_id + 1) {
qInfo() << "Skipped frame" << frames[frame_idx].first;
} }
prev_id = frames[frame_idx].first;
glViewport(0, 0, width(), height()); glViewport(0, 0, width(), height());
glBindVertexArray(frame_vao); glBindVertexArray(frame_vao);
@ -311,7 +337,7 @@ void CameraViewWidget::vipcConnected(VisionIpcClient *vipc_client) {
assert(glGetError() == GL_NO_ERROR); assert(glGetError() == GL_NO_ERROR);
#endif #endif
updateFrameMat(width(), height()); updateFrameMat();
} }
void CameraViewWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) { void CameraViewWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) {

@ -42,11 +42,12 @@ signals:
protected: protected:
void paintGL() override; void paintGL() override;
void initializeGL() override; void initializeGL() override;
void resizeGL(int w, int h) override { updateFrameMat(w, h); } void resizeGL(int w, int h) override { updateFrameMat(); }
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override; void hideEvent(QHideEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); } void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); }
virtual void updateFrameMat(int w, int h); virtual void updateFrameMat();
void updateCalibration(const mat3 &calib);
void vipcThread(); void vipcThread();
bool zoomed_view; bool zoomed_view;
@ -68,6 +69,13 @@ protected:
std::atomic<VisionStreamType> stream_type; std::atomic<VisionStreamType> stream_type;
QThread *vipc_thread = nullptr; QThread *vipc_thread = nullptr;
// Calibration
float x_offset = 0;
float y_offset = 0;
float zoom = 1.0;
mat3 calibration = DEFAULT_CALIBRATION;
mat3 intrinsic_matrix = fcam_intrinsic_matrix;
std::deque<std::pair<uint32_t, VisionBuf*>> frames; std::deque<std::pair<uint32_t, VisionBuf*>> frames;
uint32_t draw_frame_id = 0; uint32_t draw_frame_id = 0;

@ -34,16 +34,16 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) {
grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft);
grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft); grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft);
grid_layout->addWidget(newLabel("Drives", "unit"), row + 1, 0, Qt::AlignLeft); grid_layout->addWidget(newLabel((tr("Drives")), "unit"), row + 1, 0, Qt::AlignLeft);
grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft); grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft);
grid_layout->addWidget(newLabel("Hours ", "unit"), row + 1, 2, Qt::AlignLeft); grid_layout->addWidget(newLabel(tr("Hours"), "unit"), row + 1, 2, Qt::AlignLeft);
main_layout->addLayout(grid_layout); main_layout->addLayout(grid_layout);
}; };
add_stats_layouts("ALL TIME", all_); add_stats_layouts(tr("ALL TIME"), all_);
main_layout->addStretch(); main_layout->addStretch();
add_stats_layouts("PAST WEEK", week_); add_stats_layouts(tr("PAST WEEK"), week_);
if (auto dongleId = getDongleId()) { if (auto dongleId = getDongleId()) {
QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats";

@ -12,7 +12,7 @@ public:
private: private:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void updateStats(); void updateStats();
inline QString getDistanceUnit() const { return metric_ ? "KM" : "Miles"; } inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); }
bool metric_; bool metric_;
QJsonDocument stats_; QJsonDocument stats_;

@ -67,7 +67,7 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s
vlayout->addWidget(sublabel, 1, Qt::AlignTop | Qt::AlignLeft); vlayout->addWidget(sublabel, 1, Qt::AlignTop | Qt::AlignLeft);
} }
QPushButton* cancel_btn = new QPushButton("Cancel"); QPushButton* cancel_btn = new QPushButton(tr("Cancel"));
cancel_btn->setFixedSize(386, 125); cancel_btn->setFixedSize(386, 125);
cancel_btn->setStyleSheet(R"( cancel_btn->setStyleSheet(R"(
font-size: 48px; font-size: 48px;
@ -164,7 +164,7 @@ void InputDialog::handleEnter() {
done(QDialog::Accepted); done(QDialog::Accepted);
emitText(line->text()); emitText(line->text());
} else { } else {
setMessage("Need at least "+QString::number(minLength)+" characters!", false); setMessage(tr("Need at least ") + QString::number(minLength) + tr(" characters!"), false);
} }
} }
@ -217,12 +217,12 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString
} }
bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) { bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) {
ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "", parent); ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", parent);
return d.exec(); return d.exec();
} }
bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) { bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) {
ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "Cancel", parent); ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), tr("Cancel"), parent);
return d.exec(); return d.exec();
} }
@ -254,6 +254,6 @@ RichTextDialog::RichTextDialog(const QString &prompt_text, const QString &btn_te
} }
bool RichTextDialog::alert(const QString &prompt_text, QWidget *parent) { bool RichTextDialog::alert(const QString &prompt_text, QWidget *parent) {
auto d = RichTextDialog(prompt_text, "Ok", parent); auto d = RichTextDialog(prompt_text, tr("Ok"), parent);
return d.exec(); return d.exec();
} }

@ -22,12 +22,12 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent
QHBoxLayout *footer_layout = new QHBoxLayout(); QHBoxLayout *footer_layout = new QHBoxLayout();
main_layout->addLayout(footer_layout); main_layout->addLayout(footer_layout);
QPushButton *dismiss_btn = new QPushButton("Close"); QPushButton *dismiss_btn = new QPushButton(tr("Close"));
dismiss_btn->setFixedSize(400, 125); dismiss_btn->setFixedSize(400, 125);
footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft); footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft);
QObject::connect(dismiss_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); QObject::connect(dismiss_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss);
snooze_btn = new QPushButton("Snooze Update"); snooze_btn = new QPushButton(tr("Snooze Update"));
snooze_btn->setVisible(false); snooze_btn->setVisible(false);
snooze_btn->setFixedSize(550, 125); snooze_btn->setFixedSize(550, 125);
footer_layout->addWidget(snooze_btn, 0, Qt::AlignBottom | Qt::AlignRight); footer_layout->addWidget(snooze_btn, 0, Qt::AlignBottom | Qt::AlignRight);
@ -38,7 +38,7 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent
snooze_btn->setStyleSheet(R"(color: white; background-color: #4F4F4F;)"); snooze_btn->setStyleSheet(R"(color: white; background-color: #4F4F4F;)");
if (hasRebootBtn) { if (hasRebootBtn) {
QPushButton *rebootBtn = new QPushButton("Reboot and Update"); QPushButton *rebootBtn = new QPushButton(tr("Reboot and Update"));
rebootBtn->setFixedSize(600, 125); rebootBtn->setFixedSize(600, 125);
footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight); footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight);
QObject::connect(rebootBtn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); QObject::connect(rebootBtn, &QPushButton::clicked, [=]() { Hardware::reboot(); });

@ -83,18 +83,18 @@ PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) {
vlayout->addSpacing(30); vlayout->addSpacing(30);
QLabel *title = new QLabel("Pair your device to your comma account", this); QLabel *title = new QLabel(tr("Pair your device to your comma account"), this);
title->setStyleSheet("font-size: 75px; color: black;"); title->setStyleSheet("font-size: 75px; color: black;");
title->setWordWrap(true); title->setWordWrap(true);
vlayout->addWidget(title); vlayout->addWidget(title);
QLabel *instructions = new QLabel(R"( QLabel *instructions = new QLabel(tr(R"(
<ol type='1' style='margin-left: 15px;'> <ol type='1' style='margin-left: 15px;'>
<li style='margin-bottom: 50px;'>Go to https://connect.comma.ai on your phone</li> <li style='margin-bottom: 50px;'>Go to https://connect.comma.ai on your phone</li>
<li style='margin-bottom: 50px;'>Click "add new device" and scan the QR code on the right</li> <li style='margin-bottom: 50px;'>Click "add new device" and scan the QR code on the right</li>
<li style='margin-bottom: 50px;'>Bookmark connect.comma.ai to your home screen to use it like an app</li> <li style='margin-bottom: 50px;'>Bookmark connect.comma.ai to your home screen to use it like an app</li>
</ol> </ol>
)", this); )"), this);
instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;"); instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
instructions->setWordWrap(true); instructions->setWordWrap(true);
vlayout->addWidget(instructions); vlayout->addWidget(instructions);
@ -120,19 +120,19 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
primeLayout->setMargin(0); primeLayout->setMargin(0);
primeWidget->setContentsMargins(60, 50, 60, 50); primeWidget->setContentsMargins(60, 50, 60, 50);
QLabel* subscribed = new QLabel("✓ SUBSCRIBED"); QLabel* subscribed = new QLabel(tr("✓ SUBSCRIBED"));
subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;"); subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;");
primeLayout->addWidget(subscribed, 0, Qt::AlignTop); primeLayout->addWidget(subscribed, 0, Qt::AlignTop);
primeLayout->addSpacing(60); primeLayout->addSpacing(60);
QLabel* commaPrime = new QLabel("comma prime"); QLabel* commaPrime = new QLabel(tr("comma prime"));
commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;"); commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;");
primeLayout->addWidget(commaPrime, 0, Qt::AlignTop); primeLayout->addWidget(commaPrime, 0, Qt::AlignTop);
primeLayout->addSpacing(20); primeLayout->addSpacing(20);
QLabel* connectUrl = new QLabel("CONNECT.COMMA.AI"); QLabel* connectUrl = new QLabel(tr("CONNECT.COMMA.AI"));
connectUrl->setStyleSheet("font-size: 41px; font-family: Inter SemiBold; color: #A0A0A0;"); connectUrl->setStyleSheet("font-size: 41px; font-family: Inter SemiBold; color: #A0A0A0;");
primeLayout->addWidget(connectUrl, 0, Qt::AlignTop); primeLayout->addWidget(connectUrl, 0, Qt::AlignTop);
@ -145,7 +145,7 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
pointsLayout->setMargin(0); pointsLayout->setMargin(0);
pointsWidget->setContentsMargins(60, 50, 60, 50); pointsWidget->setContentsMargins(60, 50, 60, 50);
QLabel* commaPoints = new QLabel("COMMA POINTS"); QLabel* commaPoints = new QLabel(tr("COMMA POINTS"));
commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;"); commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;");
pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop); pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop);
@ -181,24 +181,24 @@ PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QFrame(parent) {
main_layout->setContentsMargins(80, 90, 80, 60); main_layout->setContentsMargins(80, 90, 80, 60);
main_layout->setSpacing(0); main_layout->setSpacing(0);
QLabel *upgrade = new QLabel("Upgrade Now"); QLabel *upgrade = new QLabel(tr("Upgrade Now"));
upgrade->setStyleSheet("font-size: 75px; font-weight: bold;"); upgrade->setStyleSheet("font-size: 75px; font-weight: bold;");
main_layout->addWidget(upgrade, 0, Qt::AlignTop); main_layout->addWidget(upgrade, 0, Qt::AlignTop);
main_layout->addSpacing(50); main_layout->addSpacing(50);
QLabel *description = new QLabel("Become a comma prime member at connect.comma.ai"); QLabel *description = new QLabel(tr("Become a comma prime member at connect.comma.ai"));
description->setStyleSheet("font-size: 60px; font-weight: light; color: white;"); description->setStyleSheet("font-size: 60px; font-weight: light; color: white;");
description->setWordWrap(true); description->setWordWrap(true);
main_layout->addWidget(description, 0, Qt::AlignTop); main_layout->addWidget(description, 0, Qt::AlignTop);
main_layout->addStretch(); main_layout->addStretch();
QLabel *features = new QLabel("PRIME FEATURES:"); QLabel *features = new QLabel(tr("PRIME FEATURES:"));
features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;"); features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;");
main_layout->addWidget(features, 0, Qt::AlignBottom); main_layout->addWidget(features, 0, Qt::AlignBottom);
main_layout->addSpacing(30); main_layout->addSpacing(30);
QVector<QString> bullets = {"Remote access", "1 year of storage", "Developer perks"}; QVector<QString> bullets = {tr("Remote access"), tr("1 year of storage"), tr("Developer perks")};
for (auto &b: bullets) { for (auto &b: bullets) {
const QString check = "<b><font color='#465BEA'>✓</font></b> "; const QString check = "<b><font color='#465BEA'>✓</font></b> ";
QLabel *l = new QLabel(check + b); QLabel *l = new QLabel(check + b);
@ -227,20 +227,20 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
finishRegistationLayout->setContentsMargins(30, 75, 30, 45); finishRegistationLayout->setContentsMargins(30, 75, 30, 45);
finishRegistationLayout->setSpacing(0); finishRegistationLayout->setSpacing(0);
QLabel* registrationTitle = new QLabel("Finish Setup"); QLabel* registrationTitle = new QLabel(tr("Finish Setup"));
registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold; margin-left: 55px;"); registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold; margin-left: 55px;");
finishRegistationLayout->addWidget(registrationTitle); finishRegistationLayout->addWidget(registrationTitle);
finishRegistationLayout->addSpacing(30); finishRegistationLayout->addSpacing(30);
QLabel* registrationDescription = new QLabel("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."); QLabel* registrationDescription = new QLabel(tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
registrationDescription->setWordWrap(true); registrationDescription->setWordWrap(true);
registrationDescription->setStyleSheet("font-size: 55px; font-weight: light; margin-left: 55px;"); registrationDescription->setStyleSheet("font-size: 55px; font-weight: light; margin-left: 55px;");
finishRegistationLayout->addWidget(registrationDescription); finishRegistationLayout->addWidget(registrationDescription);
finishRegistationLayout->addStretch(); finishRegistationLayout->addStretch();
QPushButton* pair = new QPushButton("Pair device"); QPushButton* pair = new QPushButton(tr("Pair device"));
pair->setFixedHeight(220); pair->setFixedHeight(220);
pair->setStyleSheet(R"( pair->setStyleSheet(R"(
QPushButton { QPushButton {

@ -4,16 +4,16 @@
#include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/qt/api.h"
#include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/input.h"
SshControl::SshControl() : ButtonControl("SSH Keys", "", "Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.") { SshControl::SshControl() : ButtonControl(tr("SSH Keys"), "", tr("Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.")) {
username_label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); username_label.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
username_label.setStyleSheet("color: #aaaaaa"); username_label.setStyleSheet("color: #aaaaaa");
hlayout->insertWidget(1, &username_label); hlayout->insertWidget(1, &username_label);
QObject::connect(this, &ButtonControl::clicked, [=]() { QObject::connect(this, &ButtonControl::clicked, [=]() {
if (text() == "ADD") { if (text() == tr("ADD")) {
QString username = InputDialog::getText("Enter your GitHub username", this); QString username = InputDialog::getText(tr("Enter your GitHub username"), this);
if (username.length() > 0) { if (username.length() > 0) {
setText("LOADING"); setText(tr("LOADING"));
setEnabled(false); setEnabled(false);
getUserKeys(username); getUserKeys(username);
} }
@ -31,10 +31,10 @@ void SshControl::refresh() {
QString param = QString::fromStdString(params.get("GithubSshKeys")); QString param = QString::fromStdString(params.get("GithubSshKeys"));
if (param.length()) { if (param.length()) {
username_label.setText(QString::fromStdString(params.get("GithubUsername"))); username_label.setText(QString::fromStdString(params.get("GithubUsername")));
setText("REMOVE"); setText(tr("REMOVE"));
} else { } else {
username_label.setText(""); username_label.setText("");
setText("ADD"); setText(tr("ADD"));
} }
setEnabled(true); setEnabled(true);
} }
@ -47,13 +47,13 @@ void SshControl::getUserKeys(const QString &username) {
params.put("GithubUsername", username.toStdString()); params.put("GithubUsername", username.toStdString());
params.put("GithubSshKeys", resp.toStdString()); params.put("GithubSshKeys", resp.toStdString());
} else { } else {
ConfirmationDialog::alert(QString("Username '%1' has no keys on GitHub").arg(username), this); ConfirmationDialog::alert(QString(tr("Username '%1' has no keys on GitHub")).arg(username), this);
} }
} else { } else {
if (request->timeout()) { if (request->timeout()) {
ConfirmationDialog::alert("Request timed out", this); ConfirmationDialog::alert(tr("Request timed out"), this);
} else { } else {
ConfirmationDialog::alert(QString("Username '%1' doesn't exist on GitHub").arg(username), this); ConfirmationDialog::alert(QString(tr("Username '%1' doesn't exist on GitHub")).arg(username), this);
} }
} }

@ -10,7 +10,7 @@ class SshToggle : public ToggleControl {
Q_OBJECT Q_OBJECT
public: public:
SshToggle() : ToggleControl("Enable SSH", "", "", Hardware::get_ssh_enabled()) { SshToggle() : ToggleControl(tr("Enable SSH"), "", "", Hardware::get_ssh_enabled()) {
QObject::connect(this, &SshToggle::toggleFlipped, [=](bool state) { QObject::connect(this, &SshToggle::toggleFlipped, [=](bool state) {
Hardware::set_ssh_enabled(state); Hardware::set_ssh_enabled(state);
}); });

@ -1,3 +1,4 @@
test test
playsound playsound
test_sound test_sound
test_translations

@ -0,0 +1,18 @@
#!/bin/bash
set -e
UI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"/..
TEST_TEXT="(WRAPPED_SOURCE_TEXT)"
TEST_TS_FILE=$UI_DIR/translations/main_test_en.ts
TEST_QM_FILE=$UI_DIR/translations/main_test_en.qm
# translation strings
UNFINISHED="<translation type=\"unfinished\"><\/translation>"
TRANSLATED="<translation>$TEST_TEXT<\/translation>"
mkdir -p $UI_DIR/translations
rm -f $TEST_TS_FILE $TEST_QM_FILE
lupdate -recursive "$UI_DIR" -ts $TEST_TS_FILE
sed -i "s/$UNFINISHED/$TRANSLATED/" $TEST_TS_FILE
lrelease $TEST_TS_FILE

@ -1,10 +1,25 @@
#define CATCH_CONFIG_RUNNER #define CATCH_CONFIG_RUNNER
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include <QCoreApplication>
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QTranslator>
int main(int argc, char **argv) { int main(int argc, char **argv) {
// unit tests for Qt // unit tests for Qt
QCoreApplication app(argc, argv); QApplication app(argc, argv);
QString language_file = "main_test_en";
qDebug() << "Loading language:" << language_file;
QTranslator translator;
QString translationsPath = QDir::cleanPath(qApp->applicationDirPath() + "/../translations");
if (!translator.load(language_file, translationsPath)) {
qDebug() << "Failed to load translation file!";
}
app.installTranslator(&translator);
const int res = Catch::Session().run(argc, argv); const int res = Catch::Session().run(argc, argv);
return (res < 0xff ? res : 0xff); return (res < 0xff ? res : 0xff);
} }

@ -0,0 +1,51 @@
#include "catch2/catch.hpp"
#include "common/params.h"
#include "selfdrive/ui/qt/window.h"
const QString TEST_TEXT = "(WRAPPED_SOURCE_TEXT)"; // what each string should be translated to
QRegExp RE_NUM("\\d*");
QStringList getParentWidgets(QWidget* widget){
QStringList parentWidgets;
while (widget->parentWidget() != Q_NULLPTR) {
widget = widget->parentWidget();
parentWidgets.append(widget->metaObject()->className());
}
return parentWidgets;
}
template <typename T>
void checkWidgetTrWrap(MainWindow &w) {
int i = 0;
for (auto widget : w.findChildren<T>()) {
const QString text = widget->text();
SECTION(text.toStdString() + "-" + std::to_string(i)) {
bool isNumber = RE_NUM.exactMatch(text);
bool wrapped = text.contains(TEST_TEXT);
QString parentWidgets = getParentWidgets(widget).join("->");
if (!text.isEmpty() && !isNumber && !wrapped) {
FAIL(("\"" + text + "\" must be wrapped. Parent widgets: " + parentWidgets).toStdString());
}
// warn if source string wrapped, but UI adds text
// TODO: add way to ignore this
if (wrapped && text != TEST_TEXT) {
WARN(("\"" + text + "\" is dynamic and needs a custom retranslate function. Parent widgets: " + parentWidgets).toStdString());
}
}
i++;
}
}
// Tests all strings in the UI are wrapped with tr()
TEST_CASE("UI: test all strings wrapped") {
Params().remove("HardwareSerial");
Params().remove("DongleId");
qputenv("TICI", "1");
MainWindow w;
checkWidgetTrWrap<QPushButton*>(w);
checkWidgetTrWrap<QLabel*>(w);
}

@ -140,6 +140,7 @@ static void update_state(UIState *s) {
scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j); scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j);
} }
} }
scene.calibration_valid = sm["liveCalibration"].getLiveCalibration().getCalStatus() == 1;
} }
if (s->worldObjectsVisible()) { if (s->worldObjectsVisible()) {
if (sm.updated("modelV2")) { if (sm.updated("modelV2")) {

@ -22,10 +22,7 @@ const int footer_h = 280;
const int UI_FREQ = 20; // Hz const int UI_FREQ = 20; // Hz
typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert;
// TODO: this is also hardcoded in common/transformations/camera.py const mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }};
// TODO: choose based on frame input size
const float y_offset = 150.0;
const float ZOOM = 2912.8;
struct Alert { struct Alert {
QString text1; QString text1;
@ -93,7 +90,8 @@ typedef struct {
} line_vertices_data; } line_vertices_data;
typedef struct UIScene { typedef struct UIScene {
mat3 view_from_calib; bool calibration_valid = false;
mat3 view_from_calib = DEFAULT_CALIBRATION;
cereal::PandaState::PandaType pandaType; cereal::PandaState::PandaType pandaType;
// modelV2 // modelV2

@ -39,7 +39,7 @@ def yuv_to_rgb(y, u, v):
[0.00000, -0.39465, 2.03211], [0.00000, -0.39465, 2.03211],
[1.13983, -0.58060, 0.00000], [1.13983, -0.58060, 0.00000],
]) ])
rgb = np.dot(yuv, m) rgb = np.dot(yuv, m).clip(0, 255)
return rgb.astype(np.uint8) return rgb.astype(np.uint8)

@ -86,6 +86,10 @@ class HardwareBase(ABC):
def get_current_power_draw(self): def get_current_power_draw(self):
pass pass
@abstractmethod
def get_som_power_draw(self):
pass
@abstractmethod @abstractmethod
def shutdown(self): def shutdown(self):
pass pass

@ -56,6 +56,9 @@ class Pc(HardwareBase):
def get_current_power_draw(self): def get_current_power_draw(self):
return 0 return 0
def get_som_power_draw(self):
return 0
def shutdown(self): def shutdown(self):
print("SHUTDOWN!") print("SHUTDOWN!")

@ -362,6 +362,9 @@ class Tici(HardwareBase):
def get_current_power_draw(self): def get_current_power_draw(self):
return (self.read_param_file("/sys/class/hwmon/hwmon1/power1_input", int) / 1e6) return (self.read_param_file("/sys/class/hwmon/hwmon1/power1_input", int) / 1e6)
def get_som_power_draw(self):
return (self.read_param_file("/sys/class/power_supply/bms/voltage_now", int) * self.read_param_file("/sys/class/power_supply/bms/current_now", int) / 1e12)
def shutdown(self): def shutdown(self):
# Note that for this to work and have the device stay powered off, the panda needs to be in UsbPowerMode::CLIENT! # Note that for this to work and have the device stay powered off, the panda needs to be in UsbPowerMode::CLIENT!
os.system("sudo poweroff") os.system("sudo poweroff")

@ -12,7 +12,7 @@ Welcome to the first part of the comma CTF!
getting started getting started
```bash ```bash
# start the route reply # start the route reply
cd selfdrive/ui/replay cd tools/replay
./replay '0c7f0c7f0c7f0c7f|2021-10-13--13-00-00' --dcam --ecam ./replay '0c7f0c7f0c7f0c7f|2021-10-13--13-00-00' --dcam --ecam
# start the UI in another terminal # start the UI in another terminal

@ -0,0 +1,5 @@
moc_*
*.moc
replay
tests/test_replay

@ -9,12 +9,12 @@
python lib/auth.py python lib/auth.py
# Start a replay # Start a replay
selfdrive/ui/replay/replay <route-name> tools/replay/replay <route-name>
# Example: # Example:
# selfdrive/ui/replay/replay '4cf7a6ad03080c90|2021-09-29--13-46-36' # tools/replay/replay '4cf7a6ad03080c90|2021-09-29--13-46-36'
# or use --demo to replay the default demo route: # or use --demo to replay the default demo route:
# selfdrive/ui/replay/replay --demo # tools/replay/replay --demo
# watch the replay with the normal openpilot UI # watch the replay with the normal openpilot UI
cd selfdrive/ui && ./ui cd selfdrive/ui && ./ui
@ -25,8 +25,8 @@ python replay/ui.py
## usage ## usage
``` bash ``` bash
$ selfdrive/ui/replay/replay -h $ tools/replay/replay -h
Usage: selfdrive/ui/replay/replay [options] route Usage: tools/replay/replay [options] route
Mock openpilot components by publishing logged messages. Mock openpilot components by publishing logged messages.
Options: Options:
@ -51,7 +51,7 @@ simply replay a route using the `--dcam` and `--ecam` flags:
```bash ```bash
# start a replay # start a replay
cd selfdrive/ui/replay && ./replay --demo --dcam --ecam cd tools/replay && ./replay --demo --dcam --ecam
# then start watch3 # then start watch3
cd selfdrive/ui && ./watch3 cd selfdrive/ui && ./watch3
@ -70,5 +70,5 @@ In order to replay specific route:
MOCK=1 selfdrive/boardd/tests/boardd_old.py MOCK=1 selfdrive/boardd/tests/boardd_old.py
# In another terminal: # In another terminal:
selfdrive/ui/replay/replay <route-name> tools/replay/replay <route-name>
``` ```

@ -0,0 +1,19 @@
import os
Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc',
'cereal', 'transformations')
base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq',
'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"]
qt_libs = ['qt_util'] + base_libs
if arch in ['x86_64', 'Darwin'] or GetOption('extras'):
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc"]
replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=qt_libs)
replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + qt_libs
qt_env.Program("replay", ["main.cc"], LIBS=replay_libs)
if GetOption('test'):
qt_env.Program('tests/test_replay', ['tests/test_runner.cc', 'tests/test_replay.cc'], LIBS=[replay_libs])

@ -1,5 +1,5 @@
#include "selfdrive/ui/replay/camera.h" #include "tools/replay/camera.h"
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
#include <cassert> #include <cassert>

@ -3,8 +3,8 @@
#include <unistd.h> #include <unistd.h>
#include "cereal/visionipc/visionipc_server.h" #include "cereal/visionipc/visionipc_server.h"
#include "common/queue.h" #include "common/queue.h"
#include "selfdrive/ui/replay/framereader.h" #include "tools/replay/framereader.h"
#include "selfdrive/ui/replay/logreader.h" #include "tools/replay/logreader.h"
class CameraServer { class CameraServer {
public: public:

@ -2,6 +2,7 @@
import os import os
import time import time
import threading import threading
import multiprocessing
from tqdm import tqdm from tqdm import tqdm
os.environ['FILEREADER_CACHE'] = '1' os.environ['FILEREADER_CACHE'] = '1'
@ -9,7 +10,7 @@ os.environ['FILEREADER_CACHE'] = '1'
from common.basedir import BASEDIR from common.basedir import BASEDIR
from common.realtime import config_realtime_process, Ratekeeper, DT_CTRL from common.realtime import config_realtime_process, Ratekeeper, DT_CTRL
from selfdrive.boardd.boardd import can_capnp_to_can_list from selfdrive.boardd.boardd import can_capnp_to_can_list
from tools.lib.logreader import LogReader from tools.plotjuggler.juggle import load_segment
from panda import Panda from panda import Panda
try: try:
@ -94,11 +95,10 @@ if __name__ == "__main__":
ROUTE = "77611a1fac303767/2020-03-24--09-50-38" ROUTE = "77611a1fac303767/2020-03-24--09-50-38"
REPLAY_SEGS = list(range(10, 16)) # route has 82 segments available REPLAY_SEGS = list(range(10, 16)) # route has 82 segments available
CAN_MSGS = [] CAN_MSGS = []
for i in tqdm(REPLAY_SEGS): logs = [f"https://commadataci.blob.core.windows.net/openpilotci/{ROUTE}/{i}/rlog.bz2" for i in REPLAY_SEGS]
log_url = f"https://commadataci.blob.core.windows.net/openpilotci/{ROUTE}/{i}/rlog.bz2" with multiprocessing.Pool(24) as pool:
lr = LogReader(log_url) for lr in tqdm(pool.map(load_segment, logs)):
CAN_MSGS += [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] CAN_MSGS += [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can']
# set both to cycle ignition # set both to cycle ignition
IGN_ON = int(os.getenv("ON", "0")) IGN_ON = int(os.getenv("ON", "0"))

@ -1,4 +1,4 @@
#include "selfdrive/ui/replay/consoleui.h" #include "tools/replay/consoleui.h"
#include <QApplication> #include <QApplication>
#include <initializer_list> #include <initializer_list>

@ -7,7 +7,7 @@
#include <QTimer> #include <QTimer>
#include <QTimerEvent> #include <QTimerEvent>
#include "selfdrive/ui/replay/replay.h" #include "tools/replay/replay.h"
#include <ncurses.h> #include <ncurses.h>
class ConsoleUI : public QObject { class ConsoleUI : public QObject {

@ -1,9 +1,9 @@
#include "selfdrive/ui/replay/filereader.h" #include "tools/replay/filereader.h"
#include <fstream> #include <fstream>
#include "common/util.h" #include "common/util.h"
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
std::string cacheFilePath(const std::string &url) { std::string cacheFilePath(const std::string &url) {
static std::string cache_path = [] { static std::string cache_path = [] {

@ -1,5 +1,5 @@
#include "selfdrive/ui/replay/framereader.h" #include "tools/replay/framereader.h"
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
#include <cassert> #include <cassert>
#include "libyuv.h" #include "libyuv.h"
@ -117,8 +117,6 @@ bool FrameReader::load(const std::byte *data, size_t size, bool no_hw_decoder, s
if (has_hw_decoder && !no_hw_decoder) { if (has_hw_decoder && !no_hw_decoder) {
if (!initHardwareDecoder(HW_DEVICE_TYPE)) { if (!initHardwareDecoder(HW_DEVICE_TYPE)) {
rWarning("No device with hardware decoder found. fallback to CPU decoding."); rWarning("No device with hardware decoder found. fallback to CPU decoding.");
} else {
nv12toyuv_buffer.resize(getYUVSize());
} }
} }
@ -227,17 +225,16 @@ AVFrame *FrameReader::decodeFrame(AVPacket *pkt) {
} }
bool FrameReader::copyBuffers(AVFrame *f, uint8_t *yuv) { bool FrameReader::copyBuffers(AVFrame *f, uint8_t *yuv) {
assert(f != nullptr && yuv != nullptr);
uint8_t *y = yuv;
uint8_t *uv = y + width * height;
if (hw_pix_fmt == HW_PIX_FMT) { if (hw_pix_fmt == HW_PIX_FMT) {
uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data();
uint8_t *uv = y + width * height;
for (int i = 0; i < height/2; i++) { for (int i = 0; i < height/2; i++) {
memcpy(y + (i*2 + 0)*width, f->data[0] + (i*2 + 0)*f->linesize[0], width); memcpy(y + (i*2 + 0)*width, f->data[0] + (i*2 + 0)*f->linesize[0], width);
memcpy(y + (i*2 + 1)*width, f->data[0] + (i*2 + 1)*f->linesize[0], width); memcpy(y + (i*2 + 1)*width, f->data[0] + (i*2 + 1)*f->linesize[0], width);
memcpy(uv + i*width, f->data[1] + i*f->linesize[1], width); memcpy(uv + i*width, f->data[1] + i*f->linesize[1], width);
} }
} else { } else {
uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data();
uint8_t *uv = y + width * height;
libyuv::I420ToNV12(f->data[0], f->linesize[0], libyuv::I420ToNV12(f->data[0], f->linesize[0],
f->data[1], f->linesize[1], f->data[1], f->linesize[1],
f->data[2], f->linesize[2], f->data[2], f->linesize[2],

@ -4,7 +4,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "selfdrive/ui/replay/filereader.h" #include "tools/replay/filereader.h"
extern "C" { extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
@ -46,7 +46,6 @@ private:
AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;
AVBufferRef *hw_device_ctx = nullptr; AVBufferRef *hw_device_ctx = nullptr;
std::vector<uint8_t> nv12toyuv_buffer;
int prev_idx = -1; int prev_idx = -1;
inline static std::atomic<bool> has_hw_decoder = true; inline static std::atomic<bool> has_hw_decoder = true;
}; };

@ -1,7 +1,7 @@
#include "selfdrive/ui/replay/logreader.h" #include "tools/replay/logreader.h"
#include <algorithm> #include <algorithm>
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
Event::Event(const kj::ArrayPtr<const capnp::word> &amsg, bool frame) : reader(amsg), frame(frame) { Event::Event(const kj::ArrayPtr<const capnp::word> &amsg, bool frame) : reader(amsg), frame(frame) {
words = kj::ArrayPtr<const capnp::word>(amsg.begin(), reader.getEnd()); words = kj::ArrayPtr<const capnp::word>(amsg.begin(), reader.getEnd());

@ -7,7 +7,7 @@
#include "cereal/gen/cpp/log.capnp.h" #include "cereal/gen/cpp/log.capnp.h"
#include "system/camerad/cameras/camera_common.h" #include "system/camerad/cameras/camera_common.h"
#include "selfdrive/ui/replay/filereader.h" #include "tools/replay/filereader.h"
const CameraType ALL_CAMERAS[] = {RoadCam, DriverCam, WideRoadCam}; const CameraType ALL_CAMERAS[] = {RoadCam, DriverCam, WideRoadCam};
const int MAX_CAMERAS = std::size(ALL_CAMERAS); const int MAX_CAMERAS = std::size(ALL_CAMERAS);

@ -1,8 +1,8 @@
#include <QApplication> #include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include "selfdrive/ui/replay/consoleui.h" #include "tools/replay/consoleui.h"
#include "selfdrive/ui/replay/replay.h" #include "tools/replay/replay.h"
const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36";

@ -1,4 +1,4 @@
#include "selfdrive/ui/replay/replay.h" #include "tools/replay/replay.h"
#include <QDebug> #include <QDebug>
#include <QtConcurrent> #include <QtConcurrent>
@ -8,7 +8,7 @@
#include "common/params.h" #include "common/params.h"
#include "common/timing.h" #include "common/timing.h"
#include "system/hardware/hw.h" #include "system/hardware/hw.h"
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *sm_, uint32_t flags, QString data_dir, QObject *parent) Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *sm_, uint32_t flags, QString data_dir, QObject *parent)
: sm(sm_), flags_(flags), QObject(parent) { : sm(sm_), flags_(flags), QObject(parent) {

@ -4,8 +4,8 @@
#include <QThread> #include <QThread>
#include "selfdrive/ui/replay/camera.h" #include "tools/replay/camera.h"
#include "selfdrive/ui/replay/route.h" #include "tools/replay/route.h"
// one segment uses about 100M of memory // one segment uses about 100M of memory
constexpr int FORWARD_SEGS = 5; constexpr int FORWARD_SEGS = 5;

@ -1,4 +1,4 @@
#include "selfdrive/ui/replay/route.h" #include "tools/replay/route.h"
#include <QDir> #include <QDir>
#include <QEventLoop> #include <QEventLoop>
@ -11,8 +11,8 @@
#include "system/hardware/hw.h" #include "system/hardware/hw.h"
#include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/qt/api.h"
#include "selfdrive/ui/replay/replay.h" #include "tools/replay/replay.h"
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
Route::Route(const QString &route, const QString &data_dir) : data_dir_(data_dir) { Route::Route(const QString &route, const QString &data_dir) : data_dir_(data_dir) {
route_ = parseRoute(route); route_ = parseRoute(route);

@ -2,9 +2,9 @@
#include <QFutureSynchronizer> #include <QFutureSynchronizer>
#include "selfdrive/ui/replay/framereader.h" #include "tools/replay/framereader.h"
#include "selfdrive/ui/replay/logreader.h" #include "tools/replay/logreader.h"
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
struct RouteIdentifier { struct RouteIdentifier {
QString dongle_id; QString dongle_id;

@ -6,8 +6,8 @@
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include "common/util.h" #include "common/util.h"
#include "selfdrive/ui/replay/replay.h" #include "tools/replay/replay.h"
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36";
const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2";

@ -1,4 +1,4 @@
#include "selfdrive/ui/replay/util.h" #include "tools/replay/util.h"
#include <bzlib.h> #include <bzlib.h>
#include <curl/curl.h> #include <curl/curl.h>

@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
while true; do while true; do
if ls /dev/ttyUSB* 2> /dev/null; then if ls /dev/serial/by-id/usb-FTDI_FT230X* 2> /dev/null; then
sudo screen /dev/ttyUSB* 115200 sudo screen /dev/serial/by-id/usb-FTDI_FT230X* 115200
fi fi
sleep 0.005 sleep 0.005
done done

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save