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. 25
      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. 70
      selfdrive/locationd/laikad.py
  25. 4
      selfdrive/locationd/models/car_kf.py
  26. 14
      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. 4
      selfdrive/loggerd/loggerd.cc
  33. 2
      selfdrive/loggerd/tests/test_logger.cc
  34. 17
      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. 9
      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. 10
      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/car && \
$UNIT_TEST selfdrive/locationd && \
selfdrive/locationd/test/_test_locationd_lib.py && \
$UNIT_TEST selfdrive/athena && \
$UNIT_TEST selfdrive/thermald && \
$UNIT_TEST selfdrive/hardware/tici && \
$UNIT_TEST selfdrive/modeld && \
$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_swaglog && \
./selfdrive/boardd/tests/test_boardd_usbprotocol && \
./selfdrive/loggerd/tests/test_logger &&\
./system/proclogd/tests/test_proclog && \
./selfdrive/ui/replay/tests/test_replay && \
./tools/replay/tests/test_replay && \
./system/camerad/test/ae_gray_test && \
coverage xml"
- name: "Upload coverage to Codecov"

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

@ -356,22 +356,27 @@ Export('cereal', 'messaging', 'visionipc')
# Build rednose library and ekf models
rednose_deps = [
"#selfdrive/locationd/models/constants.py",
"#selfdrive/locationd/models/gnss_helpers.py",
]
rednose_config = {
'generated_folder': '#selfdrive/locationd/models/generated',
'to_build': {
'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, []),
'live': ('#selfdrive/locationd/models/live_kf.py', True, ['live_kf_constants.h']),
'car': ('#selfdrive/locationd/models/car_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'], rednose_deps),
'car': ('#selfdrive/locationd/models/car_kf.py', True, [], rednose_deps),
},
}
if arch != "larch64":
rednose_config['to_build'].update({
'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, []),
'pos_computer_4': ('#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, []),
'lane': ('#xx/pipeline/lib/ekf/lane_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_5': ('#rednose/helpers/lst_sq_computer.py', False, [], []),
'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, [], []),
'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, [], rednose_deps),
})
Export('rednose_config')
@ -411,6 +416,8 @@ SConscript(['selfdrive/locationd/SConscript'])
SConscript(['selfdrive/sensord/SConscript'])
SConscript(['selfdrive/ui/SConscript'])
SConscript(['tools/replay/SConscript'])
if GetOption('test'):
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},
{"IsUpdateAvailable", CLEAR_ON_MANAGER_START},
{"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF},
{"LaikadEphemeris", PERSISTENT},
{"LaikadEphemeris", PERSISTENT | DONT_LOG},
{"LastAthenaPingTime", CLEAR_ON_MANAGER_START},
{"LastGPSPosition", PERSISTENT},
{"LastManagerExitReason", CLEAR_ON_MANAGER_START},

@ -54,7 +54,7 @@ soundd
replay
""""""
.. autodoxygenindex::
:project: selfdrive_ui_replay
:project: tools_replay
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/*.h
selfdrive/ui/replay/*.cc
selfdrive/ui/replay/*.h
tools/replay/*.cc
tools/replay/*.h
selfdrive/ui/qt/maps/*.cc
selfdrive/ui/qt/maps/*.h

@ -47,8 +47,6 @@ class CarState(CarStateBase):
ret.leftBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1
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.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.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.steeringTorqueEps = cp.vl["EPS_STATUS"]["TORQUE_MOTOR"]
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD

@ -5,7 +5,7 @@ from cereal import car
from common.conversions import Conversions as CV
from opendbc.can.parser import CANParser
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
PREV_BUTTON_SAMPLES = 4
@ -32,6 +32,8 @@ class CarState(CarStateBase):
self.park_brake = False
self.buttons_counter = 0
self.params = CarControllerParams(CP)
def update(self, cp, cp_cam):
if self.CP.carFingerprint in HDA2_CAR:
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"])
ret.steeringTorque = cp.vl["MDPS12"]["CR_Mdps_StrColTq"]
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
# cruise state
@ -157,7 +159,7 @@ class CarState(CarStateBase):
ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEERING_ANGLE"] * -1
ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_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"],
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.interfaces import CarInterfaceBase
from selfdrive.car.disable_ecu import disable_ecu
from selfdrive.controls.lib.latcontrol_torque import set_torque_tune
ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
@ -51,7 +50,6 @@ class CarInterface(CarInterfaceBase):
ret.stopAccel = 0.0
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):
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG
@ -66,7 +64,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.84
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
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:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 4497. * CV.LB_TO_KG
@ -96,13 +94,13 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.72
ret.steerRatio = 12.9
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:
ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG
ret.wheelbase = 2.72
ret.steerRatio = 12.9
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:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 2060. + STD_CARGO_KG
@ -204,7 +202,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.80
ret.steerRatio = 13.75
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:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1825. + STD_CARGO_KG
@ -244,7 +242,7 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput),
get_safety_config(car.CarParams.SafetyModel.hyundaiHDA2)]
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
elif candidate == CAR.GENESIS_G70:

@ -13,21 +13,27 @@ class CarControllerParams:
ACCEL_MAX = 2.0 # m/s
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.
# 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,
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):
self.STEER_MAX = 255
else:
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:
@ -1140,9 +1146,6 @@ FW_VERSIONS = {
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 ',
],
(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): [
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',
@ -1268,5 +1271,3 @@ DBC = {
CAR.KIA_EV6: dbc_dict('kia_ev6', None),
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
ACCEL_MAX = 2.0
ACCEL_MIN = -3.5
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_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
class CarInterfaceBase(ABC):
@ -85,7 +110,7 @@ class CarInterfaceBase(ABC):
ret.steerControlType = car.CarParams.SteerControlType.torque
ret.minSteerSpeed = 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.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this
@ -110,28 +135,16 @@ class CarInterfaceBase(ABC):
return ret
@staticmethod
def get_torque_params(candidate, default=float('NaN')):
with open(TORQUE_SUBSTITUTE_PATH) as f:
sub = yaml.load(f, Loader=yaml.FullLoader)
if candidate in sub:
candidate = sub[candidate]
with open(TORQUE_PARAMS_PATH) as f:
params = yaml.load(f, Loader=yaml.FullLoader)
with open(TORQUE_OVERRIDE_PATH) as f:
override = yaml.load(f, Loader=yaml.FullLoader)
# 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'])}
def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0):
params = get_torque_params(candidate)
tune.init('torque')
tune.torque.useSteeringAngle = True
tune.torque.kp = 1.0 / params['LAT_ACCEL_FACTOR']
tune.torque.kf = 1.0 / params['LAT_ACCEL_FACTOR']
tune.torque.ki = 0.1 / params['LAT_ACCEL_FACTOR']
tune.torque.friction = params['FRICTION']
tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg
@abstractmethod
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]
# 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
mock: [10.0, 10, 0.0]

@ -2,7 +2,6 @@
from cereal import car
from common.conversions import Conversions as CV
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.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
@ -32,9 +31,8 @@ class CarInterface(CarInterfaceBase):
ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop
stop_and_go = False
torque_params = CarInterfaceBase.get_torque_params(candidate)
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:
stop_and_go = True
@ -46,7 +44,7 @@ class CarInterface(CarInterfaceBase):
for fw in car_fw:
if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00':
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:
stop_and_go = True
@ -54,7 +52,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 17.4
tire_stiffness_factor = 0.5533
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):
stop_and_go = True if (candidate in CAR.RAV4H) else False

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

@ -1262,6 +1262,7 @@ FW_VERSIONS = {
b'\x01F152642711\x00\x00\x00\x00\x00\x00',
b'\x01F152642750\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): [
b'8965B42170\x00\x00\x00\x00\x00\x00',
@ -1296,6 +1297,7 @@ FW_VERSIONS = {
b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
b'\x01896634AA0000\x00\x00\x00\x00',
b'\x01896634AA1000\x00\x00\x00\x00',
b'\x01896634A88000\x00\x00\x00\x00',
],

@ -438,6 +438,7 @@ FW_VERSIONS = {
},
CAR.PASSAT_MK8: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8703N906026E \xf1\x892114',
b'\xf1\x8704E906023AH\xf1\x893379',
b'\xf1\x8704L906026ET\xf1\x891990',
b'\xf1\x8704L906026GA\xf1\x892013',
@ -450,17 +451,20 @@ FW_VERSIONS = {
b'\xf1\x870D9300014L \xf1\x895002',
b'\xf1\x870D9300041A \xf1\x894801',
b'\xf1\x870DD300045T \xf1\x891601',
b'\xf1\x870DL300011H \xf1\x895201',
b'\xf1\x870GC300042H \xf1\x891404',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111',
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\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900',
b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900',
b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111',
],
(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\x820522B0080803',
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
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):
def __init__(self, CP, CI):
super().__init__(CP, CI)
@ -66,15 +56,15 @@ class LatControlTorque(LatControl):
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
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
error = apply_deadzone(setpoint - measurement, lateral_accel_deadzone)
error = setpoint - measurement
pid_log.error = error
ff = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY
# 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
freeze_integrator = CS.steeringRateLimited or CS.steeringPressed or CS.vEgo < 5
output_torque = self.pid.update(error,

@ -1,8 +1,11 @@
#!/usr/bin/env python3
import sys
import math
import datetime
from collections import Counter
from pprint import pprint
from tqdm import tqdm
from typing import cast
from cereal.services import service_list
from tools.lib.route import Route
@ -17,6 +20,8 @@ if __name__ == "__main__":
cams = [s for s in service_list if s.endswith('CameraState')]
cnt_cameras = dict.fromkeys(cams, 0)
start_time = math.inf
end_time = -math.inf
for q in tqdm(r.qlog_paths()):
if q is None:
continue
@ -31,6 +36,10 @@ if __name__ == "__main__":
if not msg.valid:
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")
pprint(cnt_events)
@ -42,4 +51,9 @@ if __name__ == "__main__":
print("\n")
print("Cameras")
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
from typing import List, NoReturn, Optional
from cereal import car, log
from cereal import log
import cereal.messaging as messaging
from common.conversions import Conversions as CV
from common.params import Params, put_nonblocking
@ -62,7 +62,7 @@ class Calibrator:
def __init__(self, param_put: bool = False):
self.param_put = param_put
self.CP = car.CarParams.from_bytes(Params().get("CarParams", block=True))
self.not_car = False
# Read saved calibration
params = Params()
@ -192,7 +192,7 @@ class Calibrator:
liveCalibration.rpyCalib = smooth_rpy.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)
liveCalibration.validBlocks = INPUTS_NEEDED
liveCalibration.calStatus = Calibration.CALIBRATED
@ -212,7 +212,7 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m
set_realtime_priority(1)
if sm is None:
sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll=['cameraOdometry'])
sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry'])
if pm is None:
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
sm.update(timeout)
calibrator.not_car = sm['carParams'].notCar
if sm.updated['cameraOdometry']:
calibrator.handle_v_ego(sm['carState'].vEgo)
new_rpy = calibrator.handle_cam_odom(sm['cameraOdometry'].trans,

@ -1,5 +1,6 @@
#!/usr/bin/env python3
import json
import os
import time
from collections import defaultdict
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),
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.gnss_kf = GNSSKalman(GENERATED_DIR)
self.orbit_fetch_executor = ProcessPoolExecutor()
self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True)
self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None
self.orbit_fetch_future: Optional[Future] = None
self.last_fetch_orbits_t = None
self.last_cached_t = None
self.save_ephemeris = save_ephemeris
@ -44,9 +47,13 @@ class Laikad:
self.last_pos_fix_t = None
def load_cache(self):
if not self.save_ephemeris:
return
cache = Params().get(EPHEMERIS_CACHE)
if not cache:
return
try:
cache = json.loads(cache, object_hook=deserialize_hook)
self.astro_dog.add_orbits(cache['orbits'])
@ -62,6 +69,16 @@ class Laikad:
cls=CacheSerializer))
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):
if ublox_msg.which == 'measurementReport':
t = ublox_mono_time * 1e-9
@ -73,17 +90,11 @@ class Laikad:
new_meas = read_raw_ublox(report)
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:
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
est_pos = self.get_est_pos(t, processed_measurements)
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))
ecef_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist()
ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist()
@ -116,7 +127,7 @@ class Laikad:
valid = self.kf_valid(t)
if not all(valid):
if not valid[0]:
cloudlog.info("Init gnss kalman filter")
cloudlog.info("Kalman filter uninitialized")
elif not valid[1]:
cloudlog.error("Time gap of over 10s detected, gnss kalman reset")
elif not valid[2]:
@ -133,8 +144,8 @@ class Laikad:
# Ensure gnss filter is updated even with no new measurements
self.gnss_kf.predict(t)
def kf_valid(self, t: float):
filter_time = self.gnss_kf.filter.filter_time
def kf_valid(self, t: float) -> List[bool]:
filter_time = self.gnss_kf.filter.get_filter_time()
return [filter_time is not None,
filter_time is not None and abs(t - filter_time) < MAX_TIME_GAP,
all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))]
@ -148,17 +159,22 @@ class Laikad:
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):
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:
self.orbit_fetch_future = self.orbit_fetch_executor.submit(get_orbit_data, t, *astro_dog_vars)
ret = None
if block:
self.orbit_fetch_future.result()
if self.orbit_fetch_future.done():
ret = self.orbit_fetch_future.result()
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)
elif self.orbit_fetch_future.done():
self.last_fetch_orbits_t = t
if ret:
ret = self.orbit_fetch_future.result()
self.orbit_fetch_executor = 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)
self.orbit_fetch_future = None
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)
data = (astro_dog.orbits, astro_dog.orbit_fetched_times)
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")
return data
@ -251,17 +267,21 @@ class EphemerisSourceType(IntEnum):
glonassIacUltraRapid = 2
def main():
def main(sm=None, pm=None):
if sm is None:
sm = messaging.SubMaster(['ubloxGnss'])
if pm is None:
pm = messaging.PubMaster(['gnssMeasurements'])
replay = "REPLAY" in os.environ
# todo get last_known_position
laikad = Laikad(save_ephemeris=True)
laikad = Laikad(save_ephemeris=not replay)
while True:
sm.update()
if sm.updated['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:
pm.send('gnssMeasurements', msg)

@ -15,7 +15,7 @@ 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 # 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
@ -171,7 +171,7 @@ class CarKalman(KalmanFilter):
if P_initial is not None:
self.P_initial = P_initial
# 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__":

@ -3,12 +3,17 @@ import sys
from typing import List
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.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():
ECEF_POS = slice(0, 3) # x, y and z in ECEF in meters
@ -115,11 +120,12 @@ class GNSSKalman():
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]
# 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.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)

@ -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 common.params import Params
from laika.constants import SECS_IN_DAY
from laika.ephemeris import EphemerisType, GPSEphemeris
from laika.gps_time import GPSTime
from laika.helpers import ConstellationId, TimeRangeHolder
@ -62,6 +63,26 @@ class TestLaikad(unittest.TestCase):
def setUp(self):
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):
data_mock = defaultdict(str)
data_mock['sv_id'] = 1
@ -155,7 +176,7 @@ class TestLaikad(unittest.TestCase):
while Params().get(EPHEMERIS_CACHE) is None:
time.sleep(0.1)
max_time -= 0.1
if max_time == 0:
if max_time < 0:
self.fail("Cache has not been written after 2 seconds")
# Test cache with no ephemeris
@ -170,7 +191,7 @@ class TestLaikad(unittest.TestCase):
wait_for_cache()
# 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
self.dict_has_values(laikad.astro_dog.orbits)
self.dict_has_values(laikad.astro_dog.nav)
@ -185,7 +206,7 @@ class TestLaikad(unittest.TestCase):
mock_method.assert_not_called()
# 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)
self.assertIsNotNone(msg)
# Verify orbit data is not downloaded

@ -1,110 +1,16 @@
#!/usr/bin/env python3
import os
import json
import random
import unittest
import time
import capnp
from cffi import FFI
from cereal import log
import cereal.messaging as messaging
from cereal.services import service_list
from common.params import Params
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):
MAX_WAITS = 1000

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

@ -19,15 +19,6 @@
#include "common/swaglog.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 *****
kj::Array<capnp::word> logger_build_init_data() {
MessageBuilder msg;

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

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

@ -1,8 +1,6 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
import time
import textwrap
from pathlib import Path
@ -28,7 +26,6 @@ def build(spinner: Spinner, dirty: bool = False) -> None:
nproc = os.cpu_count()
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)
assert scons.stderr is not None
@ -57,17 +54,6 @@ def build(spinner: Spinner, dirty: bool = False) -> None:
r = scons.stderr.read().split(b'\n')
compile_output += r
if retry and (not dirty):
if not os.getenv("CI"):
print("scons build failed, cleaning in")
for i in range(3, -1, -1):
print("....%d" % i)
time.sleep(1)
subprocess.check_call(["scons", "-c"], cwd=BASEDIR, env=env)
else:
print("scons build failed after retry")
sys.exit(1)
else:
# Build failed log errors
errors = [line.decode('utf8', 'replace') for line in compile_output
if any(err in line for err in [b'error: ', b'not found, needed by target'])]
@ -82,8 +68,7 @@ def build(spinner: Spinner, dirty: bool = False) -> None:
with TextWindow("openpilot failed to build\n \n" + error_s) as t:
t.wait_for_exit()
exit(1)
else:
break
# enforce max cache size
cache_files = [f for f in CACHE_DIR.rglob('*') if f.is_file()]

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

@ -236,6 +236,13 @@ def ublox_rcv_callback(msg):
return []
def laika_rcv_callback(msg, CP, cfg, fsm):
if msg.ubloxGnss.which() == "measurementReport":
return ["gnssMeasurements"], True
else:
return [], False
CONFIGS = [
ProcessConfig(
proc_name="controlsd",
@ -282,7 +289,8 @@ CONFIGS = [
proc_name="calibrationd",
pub_sub={
"carState": ["liveCalibration"],
"cameraOdometry": []
"cameraOdometry": [],
"carParams": [],
},
ignore=["logMonoTime", "valid"],
init_callback=get_car_params,
@ -337,6 +345,17 @@ CONFIGS = [
tolerance=None,
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"))
os.environ['FINGERPRINT'] = fingerprint
os.environ['REPLAY'] = "1"
def run(sm, pm, can_sock):
try:
@ -81,12 +82,14 @@ if __name__ == '__main__':
from selfdrive.controls.radard import radard_thread
from selfdrive.locationd.paramsd import main as paramsd_thread
from selfdrive.controls.plannerd import main as plannerd_thread
from selfdrive.locationd.laikad import main as laikad_thread
procs = {
'radard': radard_thread,
'controlsd': controlsd_thread,
'paramsd': paramsd_thread,
'plannerd': plannerd_thread,
'laikad': laikad_thread,
}
proc = sys.argv[1]

@ -119,7 +119,8 @@ class TestPowerMonitoring(unittest.TestCase):
@parameterized.expand(ALL_PANDA_TYPES)
def test_max_time_offroad(self, hw_type):
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.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
start_time = ssb

@ -348,12 +348,13 @@ def thermald_thread(end_event, hw_queue):
power_monitor.calculate(peripheralState, onroad_conditions["ignition"])
msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used()
msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity())
current_power_draw = HARDWARE.get_current_power_draw() # pylint: disable=assignment-from-none
if current_power_draw is not None:
current_power_draw = HARDWARE.get_current_power_draw()
statlog.sample("power_draw", current_power_draw)
msg.deviceState.powerDrawW = current_power_draw
else:
msg.deviceState.powerDrawW = 0
som_power_draw = HARDWARE.get_som_power_draw()
statlog.sample("som_power_draw", som_power_draw)
msg.deviceState.somPowerDrawW = som_power_draw
# 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)

@ -18,10 +18,11 @@ if arch == "Darwin":
del base_libs[base_libs.index('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/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_env['CPPDEFINES'] = []
@ -31,7 +32,7 @@ if maps:
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
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
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/offroad/onboarding.cc", "qt/offroad/driverview.cc"]
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
@ -107,17 +111,6 @@ if GetOption('extras'):
# keep installers small
assert f[0].get_size() < 300*1e3
# build headless replay
# build watch3
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'])
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->setSpacing(0);
QLabel *title = new QLabel("Installing...");
QLabel *title = new QLabel(tr("Installing..."));
title->setStyleSheet("font-size: 90px; font-weight: 600;");
layout->addWidget(title, 0, Qt::AlignTop);
@ -141,9 +141,9 @@ void Installer::cachedFetch(const QString &cache) {
void Installer::readProgress() {
const QVector<QPair<QString, int>> stages = {
// prefix, weight in percentage
{"Receiving objects: ", 91},
{"Resolving deltas: ", 2},
{"Updating files: ", 7},
{tr("Receiving objects: "), 91},
{tr("Resolving deltas: "), 2},
{tr("Updating files: "), 7},
};
auto line = QString(proc.readAllStandardError());

@ -85,6 +85,7 @@ void HomeWindow::mousePressEvent(QMouseEvent* e) {
}
void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
HomeWindow::mousePressEvent(e);
const SubMaster &sm = *(uiState()->sm);
if (sm["carParams"].getCarParams().getNotCar()) {
if (onroad->isVisible()) {
@ -92,6 +93,7 @@ void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
} else if (body->isVisible()) {
slayout->setCurrentWidget(onroad);
}
showSidebar(false);
}
}
@ -109,7 +111,7 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
date = new QLabel();
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->setStyleSheet("background-color: #364DEF;");
QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); });
@ -200,6 +202,6 @@ void OffroadHome::refresh() {
update_notif->setVisible(updateAvailable);
alert_notif->setVisible(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);
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");
current_layout->addWidget(title);
current_route = new ButtonControl("", "CLEAR");
current_route = new ButtonControl("", tr("CLEAR"));
current_route->setStyleSheet("padding-left: 40px;");
current_layout->addWidget(current_route);
QObject::connect(current_route, &ButtonControl::clicked, [=]() {
@ -78,7 +78,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
main_layout->addWidget(current_widget);
// Recents
QLabel *recents_title = new QLabel("Recent Destinations");
QLabel *recents_title = new QLabel(tr("Recent Destinations"));
recents_title->setStyleSheet("font-size: 55px");
main_layout->addWidget(recents_title);
main_layout->addSpacing(20);
@ -92,7 +92,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
QWidget * no_prime_widget = new QWidget;
{
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->setAlignment(Qt::AlignCenter);
@ -104,7 +104,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation));
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->setAlignment(Qt::AlignCenter);
@ -161,12 +161,12 @@ void MapPanel::showEvent(QShowEvent *event) {
void MapPanel::clear() {
home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png"));
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();
work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png"));
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();
clearLayout(recent_layout);
@ -279,7 +279,7 @@ void MapPanel::parseResponse(const QString &response, bool success) {
}
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)");
recent_layout->addWidget(no_recents);
}

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

@ -27,7 +27,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen);
vlayout->setContentsMargins(20, 20, 20, 20);
if (show_advanced) {
QPushButton* advancedSettings = new QPushButton("Advanced");
QPushButton* advancedSettings = new QPushButton(tr("Advanced"));
advancedSettings->setObjectName("advanced_btn");
advancedSettings->setStyleSheet("margin-right: 30px;");
advancedSettings->setFixedSize(400, 100);
@ -84,7 +84,7 @@ void Networking::connectToNetwork(const Network &n) {
} else if (n.security_type == SecurityType::OPEN) {
wifi->connect(n);
} 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()) {
wifi->connect(n, pass);
}
@ -94,7 +94,7 @@ void Networking::connectToNetwork(const Network &n) {
void Networking::wrongPassword(const QString &ssid) {
if (wifi->seenNetworks.contains(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()) {
wifi->connect(n, pass);
}
@ -118,7 +118,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
main_layout->setSpacing(20);
// Back button
QPushButton* back = new QPushButton("Back");
QPushButton* back = new QPushButton(tr("Back"));
back->setObjectName("back_btn");
back->setFixedSize(400, 100);
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
@ -126,14 +126,14 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
ListWidget *list = new ListWidget(this);
// Enable tethering layout
tetheringToggle = new ToggleControl("Enable Tethering", "", "", wifi->isTetheringEnabled());
tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled());
list->addItem(tetheringToggle);
QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering);
// Change tethering password
ButtonControl *editPasswordButton = new ButtonControl("Tethering Password", "EDIT");
ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT"));
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()) {
wifi->changeTetheringPassword(pass);
}
@ -141,7 +141,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(editPasswordButton);
// IP address
ipLabel = new LabelControl("IP Address", wifi->ipv4_address);
ipLabel = new LabelControl(tr("IP Address"), wifi->ipv4_address);
list->addItem(ipLabel);
// SSH keys
@ -150,7 +150,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
// Roaming toggle
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) {
params.putBool("GsmRoaming", state);
wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")));
@ -158,11 +158,11 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(roamingToggle);
// APN settings
ButtonControl *editApnButton = new ButtonControl("APN Setting", "EDIT");
ButtonControl *editApnButton = new ButtonControl(tr("APN Setting"), tr("EDIT"));
connect(editApnButton, &ButtonControl::clicked, [=]() {
const bool roamingEnabled = params.getBool("GsmRoaming");
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()) {
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);
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;");
main_layout->addWidget(scanning, 0, Qt::AlignCenter);
@ -260,7 +260,7 @@ void WifiUI::refresh() {
clearLayout(main_layout);
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;");
main_layout->addWidget(scanning, 0, Qt::AlignCenter);
return;
@ -286,17 +286,17 @@ void WifiUI::refresh() {
hlayout->addWidget(ssidLabel, network.connected == ConnectedType::CONNECTING ? 0 : 1);
if (network.connected == ConnectedType::CONNECTING) {
QPushButton *connecting = new QPushButton("CONNECTING...");
QPushButton *connecting = new QPushButton(tr("CONNECTING..."));
connecting->setObjectName("connecting");
hlayout->addWidget(connecting, 2, Qt::AlignLeft);
}
// Forget button
if (wifi->isKnownConnection(network.ssid) && !wifi->isTetheringEnabled()) {
QPushButton *forgetBtn = new QPushButton("FORGET");
QPushButton *forgetBtn = new QPushButton(tr("FORGET"));
forgetBtn->setObjectName("forgetBtn");
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);
}
});

@ -76,7 +76,7 @@ void TermsPage::showEvent(QShowEvent *event) {
main_layout->setContentsMargins(45, 35, 45, 45);
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;");
main_layout->addWidget(title);
@ -104,11 +104,11 @@ void TermsPage::showEvent(QShowEvent *event) {
buttons->setSpacing(45);
main_layout->addLayout(buttons);
QPushButton *decline_btn = new QPushButton("Decline");
QPushButton *decline_btn = new QPushButton(tr("Decline"));
buttons->addWidget(decline_btn);
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->setStyleSheet(R"(
QPushButton {
@ -123,7 +123,7 @@ void TermsPage::showEvent(QShowEvent *event) {
}
void TermsPage::enableAccept() {
accept_btn->setText("Agree");
accept_btn->setText(tr("Agree"));
accept_btn->setEnabled(true);
}
@ -137,7 +137,7 @@ void DeclinePage::showEvent(QShowEvent *event) {
main_layout->setSpacing(40);
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->setWordWrap(true);
main_layout->addWidget(text, 0, Qt::AlignCenter);
@ -146,12 +146,12 @@ void DeclinePage::showEvent(QShowEvent *event) {
buttons->setSpacing(45);
main_layout->addLayout(buttons);
QPushButton *back_btn = new QPushButton("Back");
QPushButton *back_btn = new QPushButton(tr("Back"));
buttons->addWidget(back_btn);
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");
buttons->addWidget(uninstall_btn);
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{
{
"OpenpilotEnabledToggle",
"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("Enable openpilot"),
tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."),
"../assets/offroad/icon_openpilot.png",
},
{
"IsLdwEnabled",
"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("Enable Lane Departure Warnings"),
tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."),
"../assets/offroad/icon_warning.png",
},
{
"IsRHD",
"Enable Right-Hand Drive",
"Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat.",
tr("Enable Right-Hand Drive"),
tr("Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat."),
"../assets/offroad/icon_openpilot_mirrored.png",
},
{
"IsMetric",
"Use Metric System",
"Display speed in km/h instead of mph.",
tr("Use Metric System"),
tr("Display speed in km/h instead of mph."),
"../assets/offroad/icon_metric.png",
},
{
"RecordFront",
"Record and Upload Driver Camera",
"Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
tr("Record and Upload Driver Camera"),
tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
"../assets/offroad/icon_monitoring.png",
},
{
"DisengageOnAccelerator",
"Disengage On Accelerator Pedal",
"When enabled, pressing the accelerator pedal will disengage openpilot.",
tr("Disengage On Accelerator Pedal"),
tr("When enabled, pressing the accelerator pedal will disengage openpilot."),
"../assets/offroad/icon_disengage_on_accelerator.svg",
},
#ifdef ENABLE_MAPS
{
"NavSettingTime24h",
"Show ETA in 24h format",
"Use 24h format instead of am/pm",
tr("Show ETA in 24h format"),
tr("Use 24h format instead of am/pm"),
"../assets/offroad/icon_metric.png",
},
#endif
@ -79,8 +79,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
if (params.getBool("DisableRadar_Allow")) {
toggles.push_back({
"DisableRadar",
"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 Longitudinal Control"),
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",
});
}
@ -95,29 +95,29 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
setSpacing(50);
addItem(new LabelControl("Dongle ID", getDongleId().value_or("N/A")));
addItem(new LabelControl("Serial", params.get("HardwareSerial").c_str()));
addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A"))));
addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str()));
// offroad-only buttons
auto dcamBtn = new ButtonControl("Driver Camera", "PREVIEW",
"Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)");
auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"),
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(); });
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::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");
}
});
addItem(resetCalibBtn);
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, [=]() {
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();
}
});
@ -125,7 +125,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
}
if (Hardware::TICI()) {
auto regulatoryBtn = new ButtonControl("Regulatory", "VIEW", "");
auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), "");
connect(regulatoryBtn, &ButtonControl::clicked, [=]() {
const std::string txt = util::read_file("../assets/offroad/fcc.html");
RichTextDialog::alert(QString::fromStdString(txt), this);
@ -143,12 +143,12 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
QHBoxLayout *power_layout = new QHBoxLayout();
power_layout->setSpacing(30);
QPushButton *reboot_btn = new QPushButton("Reboot");
QPushButton *reboot_btn = new QPushButton(tr("Reboot"));
reboot_btn->setObjectName("reboot_btn");
power_layout->addWidget(reboot_btn);
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");
power_layout->addWidget(poweroff_btn);
QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff);
@ -168,8 +168,8 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
void DevicePanel::updateCalibDescription() {
QString desc =
"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.";
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.");
std::string calib_bytes = Params().get("CalibrationParams");
if (!calib_bytes.empty()) {
try {
@ -179,9 +179,9 @@ void DevicePanel::updateCalibDescription() {
if (calib.getCalStatus() != 0) {
double pitch = calib.getRpyCalib()[1] * (180 / M_PI);
double yaw = calib.getRpyCalib()[2] * (180 / M_PI);
desc += QString(" Your device is pointed %1° %2 and %3° %4.")
.arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "down" : "up",
QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "left" : "right");
desc += QString(tr(" Your device is pointed %1° %2 and %3° %4."))
.arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? tr("down") : tr("up"),
QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? tr("left") : tr("right"));
}
} catch (kj::Exception) {
qInfo() << "invalid CalibrationParams";
@ -192,51 +192,51 @@ void DevicePanel::updateCalibDescription() {
void DevicePanel::reboot() {
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
if (!uiState()->engaged()) {
Params().putBool("DoReboot", true);
}
}
} else {
ConfirmationDialog::alert("Disengage to Reboot", this);
ConfirmationDialog::alert(tr("Disengage to Reboot"), this);
}
}
void DevicePanel::poweroff() {
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
if (!uiState()->engaged()) {
Params().putBool("DoShutdown", true);
}
}
} else {
ConfirmationDialog::alert("Disengage to Power Off", this);
ConfirmationDialog::alert(tr("Disengage to Power Off"), this);
}
}
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
gitBranchLbl = new LabelControl("Git Branch");
gitCommitLbl = new LabelControl("Git Commit");
osVersionLbl = new LabelControl("OS Version");
versionLbl = new LabelControl("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.");
updateBtn = new ButtonControl("Check for Update", "");
gitBranchLbl = new LabelControl(tr("Git Branch"));
gitCommitLbl = new LabelControl(tr("Git Commit"));
osVersionLbl = new LabelControl(tr("OS Version"));
versionLbl = new LabelControl(tr("Version"), "", QString::fromStdString(params.get("ReleaseNotes")).trimmed());
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(tr("Check for Update"), "");
connect(updateBtn, &ButtonControl::clicked, [=]() {
if (params.getBool("IsOffroad")) {
fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime")));
fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount")));
updateBtn->setText("CHECKING");
updateBtn->setText(tr("CHECKING"));
updateBtn->setEnabled(false);
}
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, [&]() {
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);
}
});
@ -250,8 +250,8 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
fs_watch = new QFileSystemWatcher(this);
QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) {
if (path.contains("UpdateFailedCount") && std::atoi(params.get("UpdateFailedCount").c_str()) > 0) {
lastUpdateLbl->setText("failed to fetch update");
updateBtn->setText("CHECK");
lastUpdateLbl->setText(tr("failed to fetch update"));
updateBtn->setText(tr("CHECK"));
updateBtn->setEnabled(true);
} else if (path.contains("LastUpdateTime")) {
updateLabels();
@ -272,7 +272,7 @@ void SoftwarePanel::updateLabels() {
versionLbl->setText(getBrandVersion());
lastUpdateLbl->setText(lastUpdate);
updateBtn->setText("CHECK");
updateBtn->setText(tr("CHECK"));
updateBtn->setEnabled(true);
gitBranchLbl->setText(QString::fromStdString(params.get("GitBranch")));
gitCommitLbl->setText(QString::fromStdString(params.get("GitCommit")).left(10));
@ -301,7 +301,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
)");
// close button
QPushButton *close_btn = new QPushButton("×");
QPushButton *close_btn = new QPushButton(tr("×"));
close_btn->setStyleSheet(R"(
QPushButton {
font-size: 140px;
@ -327,15 +327,15 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView);
QList<QPair<QString, QWidget *>> panels = {
{"Device", device},
{"Network", network_panel(this)},
{"Toggles", new TogglesPanel(this)},
{"Software", new SoftwarePanel(this)},
{tr("Device"), device},
{tr("Network"), network_panel(this)},
{tr("Toggles"), new TogglesPanel(this)},
{tr("Software"), new SoftwarePanel(this)},
};
#ifdef ENABLE_MAPS
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);
#endif
@ -367,7 +367,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
nav_btns->addButton(btn);
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);
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("speed", cur_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("status", s.status);
@ -208,6 +208,12 @@ void NvgWindow::updateState(const UIState &s) {
setProperty("engageable", cs.getEngageable() || cs.getEnabled());
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) {
@ -260,10 +266,10 @@ void NvgWindow::drawHud(QPainter &p) {
p.setPen(QColor(0xa6, 0xa6, 0xa6, 0xff));
}
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.moveTop(set_speed_rect.top() + 27);
p.drawText(max_rect, Qt::AlignCenter, "MAX");
p.drawText(max_rect, Qt::AlignCenter, tr("MAX"));
// Draw set speed
if (is_cruise_set) {
@ -307,16 +313,16 @@ void NvgWindow::drawHud(QPainter &p) {
// "SPEED"
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.moveTop(sign_rect_outer.top() + 22);
p.drawText(text_speed_rect, Qt::AlignCenter, "SPEED");
p.drawText(text_speed_rect, Qt::AlignCenter, tr("SPEED"));
// "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.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
configFont(p, "Inter", 70, "Bold");
@ -399,23 +405,20 @@ void NvgWindow::initializeGL() {
setBackgroundColor(bg_colors[STATUS_DISENGAGED]);
}
void NvgWindow::updateFrameMat(int w, int h) {
CameraViewWidget::updateFrameMat(w, h);
void NvgWindow::updateFrameMat() {
CameraViewWidget::updateFrameMat();
UIState *s = uiState();
int w = width(), h = height();
s->fb_w = w;
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
// 1) Put (0, 0) in the middle of the video
// 2) Apply same scaling as video
// 3) Put (0, 0) in top left corner of video
s->car_space_transform.reset();
s->car_space_transform.translate(w / 2, h / 2 + y_offset)
s->car_space_transform.translate(w / 2 - x_offset, h / 2 - y_offset)
.scale(zoom, zoom)
.translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]);
}

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

@ -26,16 +26,16 @@ void Reset::doReset() {
if (rm == 0 || fmt == 0) {
std::system("sudo reboot");
}
body->setText("Reset failed. Reboot to try again.");
body->setText(tr("Reset failed. Reboot to try again."));
rebootBtn->show();
}
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) {
body->setText(confirm_txt);
} else {
body->setText("Resetting device...");
body->setText(tr("Resetting device..."));
rejectBtn->hide();
rebootBtn->hide();
confirmBtn->hide();
@ -50,13 +50,13 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
main_layout->setContentsMargins(45, 220, 45, 45);
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;");
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
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->setStyleSheet("font-size: 80px; font-weight: light;");
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);
blayout->setSpacing(50);
rejectBtn = new QPushButton("Cancel");
rejectBtn = new QPushButton(tr("Cancel"));
blayout->addWidget(rejectBtn);
QObject::connect(rejectBtn, &QPushButton::clicked, QCoreApplication::instance(), &QCoreApplication::quit);
rebootBtn = new QPushButton("Reboot");
rebootBtn = new QPushButton(tr("Reboot"));
blayout->addWidget(rebootBtn);
#ifdef __aarch64__
QObject::connect(rebootBtn, &QPushButton::clicked, [=]{
@ -77,7 +77,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
});
#endif
confirmBtn = new QPushButton("Confirm");
confirmBtn = new QPushButton(tr("Confirm"));
confirmBtn->setStyleSheet("background-color: #465BEA;");
blayout->addWidget(confirmBtn);
QObject::connect(confirmBtn, &QPushButton::clicked, this, &Reset::confirm);
@ -85,7 +85,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
rejectBtn->setVisible(!recover);
rebootBtn->setVisible(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"(

@ -70,13 +70,13 @@ QWidget * Setup::low_voltage() {
inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft);
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;");
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
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->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 80px; font-weight: 300;");
@ -89,14 +89,14 @@ QWidget * Setup::low_voltage() {
blayout->setSpacing(50);
main_layout->addLayout(blayout, 0);
QPushButton *poweroff = new QPushButton("Power off");
QPushButton *poweroff = new QPushButton(tr("Power off"));
poweroff->setObjectName("navBtn");
blayout->addWidget(poweroff);
QObject::connect(poweroff, &QPushButton::clicked, this, [=]() {
Hardware::poweroff();
});
QPushButton *cont = new QPushButton("Continue");
QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn");
blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
@ -114,12 +114,12 @@ QWidget * Setup::getting_started() {
vlayout->setContentsMargins(165, 280, 100, 0);
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;");
vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
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->setStyleSheet("font-size: 80px; font-weight: 300;");
vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft);
@ -144,7 +144,7 @@ QWidget * Setup::network_setup() {
main_layout->setContentsMargins(55, 50, 55, 50);
// 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;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
@ -162,7 +162,7 @@ QWidget * Setup::network_setup() {
main_layout->addLayout(blayout);
blayout->setSpacing(50);
QPushButton *back = new QPushButton("Back");
QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back);
@ -179,9 +179,9 @@ QWidget * Setup::network_setup() {
cont->setEnabled(success);
if (success) {
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 {
cont->setText("Waiting for internet");
cont->setText(tr("Waiting for internet"));
}
repaint();
});
@ -235,7 +235,7 @@ QWidget * Setup::software_selection() {
main_layout->setSpacing(0);
// 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;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
@ -245,12 +245,12 @@ QWidget * Setup::software_selection() {
QButtonGroup *group = new QButtonGroup(widget);
group->setExclusive(true);
QWidget *dashcam = radio_button("Dashcam", group);
QWidget *dashcam = radio_button(tr("Dashcam"), group);
main_layout->addWidget(dashcam);
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->addStretch();
@ -260,12 +260,12 @@ QWidget * Setup::software_selection() {
main_layout->addLayout(blayout);
blayout->setSpacing(50);
QPushButton *back = new QPushButton("Back");
QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back);
QPushButton *cont = new QPushButton("Continue");
QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn");
cont->setEnabled(false);
cont->setProperty("primary", true);
@ -278,7 +278,7 @@ QWidget * Setup::software_selection() {
});
QString url = DASHCAM_URL;
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()) {
QTimer::singleShot(1000, this, [=]() {
@ -300,7 +300,7 @@ QWidget * Setup::software_selection() {
QWidget * Setup::downloading() {
QWidget *widget = new QWidget();
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;");
main_layout->addWidget(txt, 0, Qt::AlignCenter);
return widget;
@ -312,13 +312,13 @@ QWidget * Setup::download_failed() {
main_layout->setContentsMargins(55, 225, 55, 55);
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;");
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
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->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;");
@ -331,14 +331,14 @@ QWidget * Setup::download_failed() {
blayout->setSpacing(50);
main_layout->addLayout(blayout, 0);
QPushButton *reboot = new QPushButton("Reboot device");
QPushButton *reboot = new QPushButton(tr("Reboot device"));
reboot->setObjectName("navBtn");
blayout->addWidget(reboot);
QObject::connect(reboot, &QPushButton::clicked, this, [=]() {
Hardware::reboot();
});
QPushButton *restart = new QPushButton("Start over");
QPushButton *restart = new QPushButton(tr("Start over"));
restart->setObjectName("navBtn");
restart->setProperty("primary", true);
blayout->addWidget(restart);

@ -20,13 +20,13 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
QVBoxLayout *layout = new QVBoxLayout(prompt);
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;");
layout->addWidget(title);
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->setStyleSheet("font-size: 65px;");
layout->addWidget(desc);
@ -37,14 +37,14 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
hlayout->setSpacing(30);
layout->addLayout(hlayout);
QPushButton *connect = new QPushButton("Connect to Wi-Fi");
QPushButton *connect = new QPushButton(tr("Connect to Wi-Fi"));
connect->setObjectName("navBtn");
QObject::connect(connect, &QPushButton::clicked, [=]() {
setCurrentWidget(wifi);
});
hlayout->addWidget(connect);
QPushButton *install = new QPushButton("Install");
QPushButton *install = new QPushButton(tr("Install"));
install->setObjectName("navBtn");
install->setStyleSheet("background-color: #465BEA;");
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; }");
layout->addWidget(networking, 1);
QPushButton *back = new QPushButton("Back");
QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn");
back->setStyleSheet("padding-left: 60px; padding-right: 60px;");
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->setSpacing(0);
text = new QLabel("Loading...");
text = new QLabel(tr("Loading..."));
text->setStyleSheet("font-size: 90px; font-weight: 600;");
layout->addWidget(text, 0, Qt::AlignTop);
@ -91,7 +91,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
layout->addStretch();
reboot = new QPushButton("Reboot");
reboot = new QPushButton(tr("Reboot"));
reboot->setObjectName("navBtn");
reboot->setStyleSheet("padding-left: 60px; padding-right: 60px;");
QObject::connect(reboot, &QPushButton::clicked, [=]() {
@ -161,7 +161,7 @@ void Updater::updateFinished(int exitCode, QProcess::ExitStatus exitStatus) {
if (exitCode == 0) {
Hardware::reboot();
} else {
text->setText("Update failed");
text->setText(tr("Update failed"));
reboot->show();
}
}

@ -64,26 +64,26 @@ void Sidebar::updateState(const UIState &s) {
ItemStatus connectStatus;
auto last_ping = deviceState.getLastAthenaPingTime();
if (last_ping == 0) {
connectStatus = ItemStatus{{"CONNECT", "OFFLINE"}, warning_color};
connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color};
} 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));
ItemStatus tempStatus = {{"TEMP", "HIGH"}, danger_color};
ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color};
auto ts = deviceState.getThermalStatus();
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) {
tempStatus = {{"TEMP", "OK"}, warning_color};
tempStatus = {{tr("TEMP"), tr("OK")}, warning_color};
}
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) {
pandaStatus = {{"NO", "PANDA"}, danger_color};
pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color};
} 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));
}

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

@ -15,7 +15,7 @@ QString getVersion() {
}
QString getBrand() {
return Params().getBool("Passive") ? "dashcam" : "openpilot";
return Params().getBool("Passive") ? QObject::tr("dashcam") : QObject::tr("openpilot");
}
QString getBrandVersion() {
@ -63,13 +63,13 @@ QString timeAgo(const QDateTime &date) {
s = "now";
} else if (diff < 60 * 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) {
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) {
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 {
s = date.date().toString();
}

@ -6,6 +6,8 @@
#include <GLES3/gl3.h>
#endif
#include <cmath>
#include <QOpenGLBuffer>
#include <QOffscreenSurface>
@ -59,13 +61,6 @@ const char frame_fragment_shader[] =
"}\n";
#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) {
const float driver_view_ratio = 2.0;
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 (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 {
auto intrinsic_matrix = stream_type == VISION_STREAM_WIDE_ROAD ? ecam_intrinsic_matrix : fcam_intrinsic_matrix;
float zoom = ZOOM / intrinsic_matrix.v[0];
if (stream_type == VISION_STREAM_WIDE_ROAD) {
zoom *= 0.5;
}
intrinsic_matrix = (stream_type == VISION_STREAM_WIDE_ROAD) ? ecam_intrinsic_matrix : fcam_intrinsic_matrix;
zoom = (stream_type == VISION_STREAM_WIDE_ROAD) ? 2.5 : 1.1;
// 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 zy = zoom * 2 * intrinsic_matrix.v[5] / height();
const mat4 frame_transform = {{
zx, 0.0, 0.0, 0.0,
0.0, zy, 0.0, -y_offset / height() * 2,
zx, 0.0, 0.0, -x_offset / width() * 2,
0.0, zy, 0.0, y_offset / height() * 2,
0.0, 0.0, 1.0, 0.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) {
// fit frame to widget size
float widget_aspect_ratio = (float)width() / 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() {
glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF());
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
if (frames.empty()) return;
int frame_idx;
for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) {
if (frames[frame_idx].first == draw_frame_id) break;
int frame_idx = frames.size() - 1;
// 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());
glBindVertexArray(frame_vao);
@ -311,7 +337,7 @@ void CameraViewWidget::vipcConnected(VisionIpcClient *vipc_client) {
assert(glGetError() == GL_NO_ERROR);
#endif
updateFrameMat(width(), height());
updateFrameMat();
}
void CameraViewWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) {

@ -42,11 +42,12 @@ signals:
protected:
void paintGL() 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 hideEvent(QHideEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); }
virtual void updateFrameMat(int w, int h);
virtual void updateFrameMat();
void updateCalibration(const mat3 &calib);
void vipcThread();
bool zoomed_view;
@ -68,6 +69,13 @@ protected:
std::atomic<VisionStreamType> stream_type;
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;
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.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(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);
};
add_stats_layouts("ALL TIME", all_);
add_stats_layouts(tr("ALL TIME"), all_);
main_layout->addStretch();
add_stats_layouts("PAST WEEK", week_);
add_stats_layouts(tr("PAST WEEK"), week_);
if (auto dongleId = getDongleId()) {
QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats";

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

@ -67,7 +67,7 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s
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->setStyleSheet(R"(
font-size: 48px;
@ -164,7 +164,7 @@ void InputDialog::handleEnter() {
done(QDialog::Accepted);
emitText(line->text());
} 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) {
ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "", parent);
ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", parent);
return d.exec();
}
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();
}
@ -254,6 +254,6 @@ RichTextDialog::RichTextDialog(const QString &prompt_text, const QString &btn_te
}
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();
}

@ -22,12 +22,12 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent
QHBoxLayout *footer_layout = new QHBoxLayout();
main_layout->addLayout(footer_layout);
QPushButton *dismiss_btn = new QPushButton("Close");
QPushButton *dismiss_btn = new QPushButton(tr("Close"));
dismiss_btn->setFixedSize(400, 125);
footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft);
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->setFixedSize(550, 125);
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;)");
if (hasRebootBtn) {
QPushButton *rebootBtn = new QPushButton("Reboot and Update");
QPushButton *rebootBtn = new QPushButton(tr("Reboot and Update"));
rebootBtn->setFixedSize(600, 125);
footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight);
QObject::connect(rebootBtn, &QPushButton::clicked, [=]() { Hardware::reboot(); });

@ -83,18 +83,18 @@ PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) {
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->setWordWrap(true);
vlayout->addWidget(title);
QLabel *instructions = new QLabel(R"(
QLabel *instructions = new QLabel(tr(R"(
<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;'>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>
</ol>
)", this);
)"), this);
instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
instructions->setWordWrap(true);
vlayout->addWidget(instructions);
@ -120,19 +120,19 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
primeLayout->setMargin(0);
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;");
primeLayout->addWidget(subscribed, 0, Qt::AlignTop);
primeLayout->addSpacing(60);
QLabel* commaPrime = new QLabel("comma prime");
QLabel* commaPrime = new QLabel(tr("comma prime"));
commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;");
primeLayout->addWidget(commaPrime, 0, Qt::AlignTop);
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;");
primeLayout->addWidget(connectUrl, 0, Qt::AlignTop);
@ -145,7 +145,7 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
pointsLayout->setMargin(0);
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;");
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->setSpacing(0);
QLabel *upgrade = new QLabel("Upgrade Now");
QLabel *upgrade = new QLabel(tr("Upgrade Now"));
upgrade->setStyleSheet("font-size: 75px; font-weight: bold;");
main_layout->addWidget(upgrade, 0, Qt::AlignTop);
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->setWordWrap(true);
main_layout->addWidget(description, 0, Qt::AlignTop);
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;");
main_layout->addWidget(features, 0, Qt::AlignBottom);
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) {
const QString check = "<b><font color='#465BEA'>✓</font></b> ";
QLabel *l = new QLabel(check + b);
@ -227,20 +227,20 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
finishRegistationLayout->setContentsMargins(30, 75, 30, 45);
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;");
finishRegistationLayout->addWidget(registrationTitle);
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->setStyleSheet("font-size: 55px; font-weight: light; margin-left: 55px;");
finishRegistationLayout->addWidget(registrationDescription);
finishRegistationLayout->addStretch();
QPushButton* pair = new QPushButton("Pair device");
QPushButton* pair = new QPushButton(tr("Pair device"));
pair->setFixedHeight(220);
pair->setStyleSheet(R"(
QPushButton {

@ -4,16 +4,16 @@
#include "selfdrive/ui/qt/api.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.setStyleSheet("color: #aaaaaa");
hlayout->insertWidget(1, &username_label);
QObject::connect(this, &ButtonControl::clicked, [=]() {
if (text() == "ADD") {
QString username = InputDialog::getText("Enter your GitHub username", this);
if (text() == tr("ADD")) {
QString username = InputDialog::getText(tr("Enter your GitHub username"), this);
if (username.length() > 0) {
setText("LOADING");
setText(tr("LOADING"));
setEnabled(false);
getUserKeys(username);
}
@ -31,10 +31,10 @@ void SshControl::refresh() {
QString param = QString::fromStdString(params.get("GithubSshKeys"));
if (param.length()) {
username_label.setText(QString::fromStdString(params.get("GithubUsername")));
setText("REMOVE");
setText(tr("REMOVE"));
} else {
username_label.setText("");
setText("ADD");
setText(tr("ADD"));
}
setEnabled(true);
}
@ -47,13 +47,13 @@ void SshControl::getUserKeys(const QString &username) {
params.put("GithubUsername", username.toStdString());
params.put("GithubSshKeys", resp.toStdString());
} 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 {
if (request->timeout()) {
ConfirmationDialog::alert("Request timed out", this);
ConfirmationDialog::alert(tr("Request timed out"), this);
} 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
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) {
Hardware::set_ssh_enabled(state);
});

@ -1,3 +1,4 @@
test
playsound
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
#include "catch2/catch.hpp"
#include <QCoreApplication>
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QTranslator>
int main(int argc, char **argv) {
// 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);
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.calibration_valid = sm["liveCalibration"].getLiveCalibration().getCalStatus() == 1;
}
if (s->worldObjectsVisible()) {
if (sm.updated("modelV2")) {

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

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

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

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

@ -362,6 +362,9 @@ class Tici(HardwareBase):
def get_current_power_draw(self):
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):
# 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")

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

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

@ -9,12 +9,12 @@
python lib/auth.py
# Start a replay
selfdrive/ui/replay/replay <route-name>
tools/replay/replay <route-name>
# 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:
# selfdrive/ui/replay/replay --demo
# tools/replay/replay --demo
# watch the replay with the normal openpilot UI
cd selfdrive/ui && ./ui
@ -25,8 +25,8 @@ python replay/ui.py
## usage
``` bash
$ selfdrive/ui/replay/replay -h
Usage: selfdrive/ui/replay/replay [options] route
$ tools/replay/replay -h
Usage: tools/replay/replay [options] route
Mock openpilot components by publishing logged messages.
Options:
@ -51,7 +51,7 @@ simply replay a route using the `--dcam` and `--ecam` flags:
```bash
# start a replay
cd selfdrive/ui/replay && ./replay --demo --dcam --ecam
cd tools/replay && ./replay --demo --dcam --ecam
# then start watch3
cd selfdrive/ui && ./watch3
@ -70,5 +70,5 @@ In order to replay specific route:
MOCK=1 selfdrive/boardd/tests/boardd_old.py
# 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 "selfdrive/ui/replay/util.h"
#include "tools/replay/camera.h"
#include "tools/replay/util.h"
#include <cassert>

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

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

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

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

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

@ -1,5 +1,5 @@
#include "selfdrive/ui/replay/framereader.h"
#include "selfdrive/ui/replay/util.h"
#include "tools/replay/framereader.h"
#include "tools/replay/util.h"
#include <cassert>
#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 (!initHardwareDecoder(HW_DEVICE_TYPE)) {
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) {
if (hw_pix_fmt == HW_PIX_FMT) {
uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data();
assert(f != nullptr && yuv != nullptr);
uint8_t *y = yuv;
uint8_t *uv = y + width * height;
if (hw_pix_fmt == HW_PIX_FMT) {
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 + 1)*width, f->data[0] + (i*2 + 1)*f->linesize[0], width);
memcpy(uv + i*width, f->data[1] + i*f->linesize[1], width);
}
} else {
uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data();
uint8_t *uv = y + width * height;
libyuv::I420ToNV12(f->data[0], f->linesize[0],
f->data[1], f->linesize[1],
f->data[2], f->linesize[2],

@ -4,7 +4,7 @@
#include <string>
#include <vector>
#include "selfdrive/ui/replay/filereader.h"
#include "tools/replay/filereader.h"
extern "C" {
#include <libavcodec/avcodec.h>
@ -46,7 +46,6 @@ private:
AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;
AVBufferRef *hw_device_ctx = nullptr;
std::vector<uint8_t> nv12toyuv_buffer;
int prev_idx = -1;
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 "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) {
words = kj::ArrayPtr<const capnp::word>(amsg.begin(), reader.getEnd());

@ -7,7 +7,7 @@
#include "cereal/gen/cpp/log.capnp.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 int MAX_CAMERAS = std::size(ALL_CAMERAS);

@ -1,8 +1,8 @@
#include <QApplication>
#include <QCommandLineParser>
#include "selfdrive/ui/replay/consoleui.h"
#include "selfdrive/ui/replay/replay.h"
#include "tools/replay/consoleui.h"
#include "tools/replay/replay.h"
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 <QtConcurrent>
@ -8,7 +8,7 @@
#include "common/params.h"
#include "common/timing.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)
: sm(sm_), flags_(flags), QObject(parent) {

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

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

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

@ -6,8 +6,8 @@
#include "catch2/catch.hpp"
#include "common/util.h"
#include "selfdrive/ui/replay/replay.h"
#include "selfdrive/ui/replay/util.h"
#include "tools/replay/replay.h"
#include "tools/replay/util.h"
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";

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

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

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

Loading…
Cancel
Save