diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index fedeaa734b..f5edfe5929 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -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" @@ -364,7 +367,7 @@ jobs: CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v2 - + model_replay_onnx: name: model replay onnx runs-on: ubuntu-20.04 diff --git a/README.md b/README.md index c426b839d5..34b17625f9 100755 --- a/README.md +++ b/README.md @@ -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 diff --git a/SConstruct b/SConstruct index b243a4dc45..1209894ba4 100644 --- a/SConstruct +++ b/SConstruct @@ -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') diff --git a/cereal b/cereal index c6acc0698a..df08568318 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit c6acc0698a604e715e960250359b6bf97e4987e3 +Subproject commit df08568318da97ed6f87747caee0a5b2c30086c4 diff --git a/common/params.cc b/common/params.cc index b8526bc231..f93c87cd98 100644 --- a/common/params.cc +++ b/common/params.cc @@ -128,7 +128,7 @@ std::unordered_map 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}, diff --git a/docs/c_docs.rst b/docs/c_docs.rst index 4e1e8a247a..5638b40bf0 100644 --- a/docs/c_docs.rst +++ b/docs/c_docs.rst @@ -54,7 +54,7 @@ soundd replay """""" .. autodoxygenindex:: - :project: selfdrive_ui_replay + :project: tools_replay qt "" diff --git a/opendbc b/opendbc index 82be71072c..47b79c4d5a 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 82be71072c52fc78cf0e1eabc396af26c18ddc11 +Subproject commit 47b79c4d5ab5adc0bdd9d228c3d9594da0355c49 diff --git a/panda b/panda index 4bc85ad40a..2652453892 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 4bc85ad40ad032672008eb75567892ba45e0b932 +Subproject commit 265245389208e1e6ada86b169e879c0a2e30426c diff --git a/rednose_repo b/rednose_repo index 7663289f1e..225dbacbaa 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 7663289f1e68860f53dc34337ef080dde69a2586 +Subproject commit 225dbacbaac312f85eaaee0b97a3acc31f9c6b47 diff --git a/release/files_common b/release/files_common index 0ace73623f..7e8dbd37a6 100644 --- a/release/files_common +++ b/release/files_common @@ -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 diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index f71a300263..202526df05 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -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 diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 9d864faac4..6c82c33856 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -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"]) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 7cfce55100..58a67f58ce 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -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: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index cf47501588..645d2f523b 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -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): + 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 diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index ced2ce8e61..136337c5a4 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -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: diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index fb086fd13f..a2200926c0 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -1,7 +1,7 @@ legend: [LAT_ACCEL_FACTOR, MAX_LAT_ACCEL_MEASURED, FRICTION] ### angle control # Nissan appears to have torque -NISSAN X-TRAIL 2017: [.nan, 1.5, .nan] +NISSAN X-TRAIL 2017: [.nan, 1.5, .nan] NISSAN ALTIMA 2020: [.nan, 1.5, .nan] NISSAN LEAF 2018 Instrument Cluster: [.nan, 1.5, .nan] NISSAN LEAF 2018: [.nan, 1.5, .nan] @@ -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] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 75f2ab3033..fdb923f693 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -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 diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py index 3de6daae21..a8b8758d89 100644 --- a/selfdrive/car/toyota/tunes.py +++ b/selfdrive/car/toyota/tunes.py @@ -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] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 32f66b6fa0..c7a26257f3 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -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', ], diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 9b6f6a16c3..0148a138a2 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -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', diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index f72ffc4b88..46caa41a90 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -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, diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py index c3870e0f9e..93dd5bdc47 100755 --- a/selfdrive/debug/count_events.py +++ b/selfdrive/debug/count_events.py @@ -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)) diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index e092c939ae..81bcc6ce1c 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -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, diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index f1997bceb0..5098b25d38 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -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: + + ret = None + + if block: + ret = get_orbit_data(t, *astro_dog_vars) + elif self.orbit_fetch_future is None: + self.orbit_fetch_executor = ProcessPoolExecutor(max_workers=1) self.orbit_fetch_future = self.orbit_fetch_executor.submit(get_orbit_data, t, *astro_dog_vars) - if block: - self.orbit_fetch_future.result() - if self.orbit_fetch_future.done(): - ret = self.orbit_fetch_future.result() + elif self.orbit_fetch_future.done(): self.last_fetch_orbits_t = t - if ret: - self.astro_dog.orbits, self.astro_dog.orbit_fetched_times = ret - self.cache_ephemeris(t=t) - self.orbit_fetch_future = None + 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) 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(): - sm = messaging.SubMaster(['ubloxGnss']) - pm = messaging.PubMaster(['gnssMeasurements']) +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) diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index fe7d2e650b..3faf4f8d4e 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -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__": diff --git a/selfdrive/locationd/models/gnss_kf.py b/selfdrive/locationd/models/gnss_kf.py index ce0ff84dc2..5c9cb4b3d1 100755 --- a/selfdrive/locationd/models/gnss_kf.py +++ b/selfdrive/locationd/models/gnss_kf.py @@ -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,12 +120,13 @@ class GNSSKalman(): gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, maha_test_kinds=maha_test_kinds) - 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, - self.dim_state, maha_test_kinds=self.maha_test_kinds) + 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) @property diff --git a/selfdrive/locationd/test/_test_locationd_lib.py b/selfdrive/locationd/test/_test_locationd_lib.py new file mode 100755 index 0000000000..8a0ed3ef05 --- /dev/null +++ b/selfdrive/locationd/test/_test_locationd_lib.py @@ -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() + diff --git a/selfdrive/locationd/test/test_laikad.py b/selfdrive/locationd/test/test_laikad.py index 3a72303b0c..c353da9624 100755 --- a/selfdrive/locationd/test/test_laikad.py +++ b/selfdrive/locationd/test/test_laikad.py @@ -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 diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index 7f5d752100..29036b8387 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -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 diff --git a/selfdrive/loggerd/encoder/ffmpeg_encoder.cc b/selfdrive/loggerd/encoder/ffmpeg_encoder.cc index 22587819a4..5f8d140e8b 100644 --- a/selfdrive/loggerd/encoder/ffmpeg_encoder.cc +++ b/selfdrive/loggerd/encoder/ffmpeg_encoder.cc @@ -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; } diff --git a/selfdrive/loggerd/logger.cc b/selfdrive/loggerd/logger.cc index 5aed47e291..8038f1926c 100644 --- a/selfdrive/loggerd/logger.cc +++ b/selfdrive/loggerd/logger.cc @@ -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 > *properties = - (std::vector > *)cookie; - - properties->push_back(std::make_pair(std::string(key), std::string(value))); -} - // ***** log metadata ***** kj::Array logger_build_init_data() { MessageBuilder msg; diff --git a/selfdrive/loggerd/loggerd.cc b/selfdrive/loggerd/loggerd.cc index 4086f4991e..a75ab2c92b 100644 --- a/selfdrive/loggerd/loggerd.cc +++ b/selfdrive/loggerd/loggerd.cc @@ -6,7 +6,6 @@ ExitHandler do_exit; struct LoggerdState { LoggerState logger = {}; char segment_path[4096]; - std::mutex rotate_lock; std::atomic rotate_segment; std::atomic last_camera_seen_tms; std::atomic 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(); - } + 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); } diff --git a/selfdrive/loggerd/tests/test_logger.cc b/selfdrive/loggerd/tests/test_logger.cc index 11a50fa2e7..c8f6620924 100644 --- a/selfdrive/loggerd/tests/test_logger.cc +++ b/selfdrive/loggerd/tests/test_logger.cc @@ -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; diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 10b4c0a4b8..12f894061a 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 import os import subprocess -import sys -import time import textwrap from pathlib import Path @@ -28,62 +26,49 @@ 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 - - compile_output = [] - - # Read progress from stderr and update spinner - while scons.poll() is None: - try: - line = scons.stderr.readline() - if line is None: - continue - line = line.rstrip() - - prefix = b'progress: ' - if line.startswith(prefix): - i = int(line[len(prefix):]) - spinner.update_progress(MAX_BUILD_PROGRESS * min(1., i / TOTAL_SCONS_NODES), 100.) - elif len(line): - compile_output.append(line) - print(line.decode('utf8', 'replace')) - except Exception: - pass - - if scons.returncode != 0: - # Read remaining output - 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'])] - error_s = "\n".join(errors) - add_file_handler(cloudlog) - cloudlog.error("scons build failed\n" + error_s) - - # Show TextWindow - spinner.close() - if not os.getenv("CI"): - error_s = "\n \n".join("\n".join(textwrap.wrap(e, 65)) for e in errors) - with TextWindow("openpilot failed to build\n \n" + error_s) as t: - t.wait_for_exit() - exit(1) - else: - break + scons: subprocess.Popen = subprocess.Popen(["scons", j_flag, "--cache-populate"], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) + assert scons.stderr is not None + + compile_output = [] + + # Read progress from stderr and update spinner + while scons.poll() is None: + try: + line = scons.stderr.readline() + if line is None: + continue + line = line.rstrip() + + prefix = b'progress: ' + if line.startswith(prefix): + i = int(line[len(prefix):]) + spinner.update_progress(MAX_BUILD_PROGRESS * min(1., i / TOTAL_SCONS_NODES), 100.) + elif len(line): + compile_output.append(line) + print(line.decode('utf8', 'replace')) + except Exception: + pass + + if scons.returncode != 0: + # Read remaining output + r = scons.stderr.read().split(b'\n') + compile_output += r + + # 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'])] + error_s = "\n".join(errors) + add_file_handler(cloudlog) + cloudlog.error("scons build failed\n" + error_s) + + # Show TextWindow + spinner.close() + if not os.getenv("CI"): + error_s = "\n \n".join("\n".join(textwrap.wrap(e, 65)) for e in errors) + with TextWindow("openpilot failed to build\n \n" + error_s) as t: + t.wait_for_exit() + exit(1) + # enforce max cache size cache_files = [f for f in CACHE_DIR.rglob('*') if f.is_file()] diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index 9fa4ca644a..531ddb3a02 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -5,7 +5,7 @@ Process replay is a regression test designed to identify any changes in the outp If the test fails, make sure that you didn't unintentionally change anything. If there are intentional changes, the reference logs will be updated. Use `test_processes.py` to run the test locally. -Use `FILEREADER_CACHE='1' test_processes.py` to cache log files. +Use `FILEREADER_CACHE='1' test_processes.py` to cache log files. Currently the following processes are tested: @@ -15,6 +15,7 @@ Currently the following processes are tested: * calibrationd * dmonitoringd * locationd +* laikad * paramsd * ubloxd diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 62c5eb4826..def61a10a1 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -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, + ), ] diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 662f8cc5b8..946ddce19e 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -41161c8d151b0c2017214cad0aad3156533ab868 +a0b5ce7b2e0b9c073e51ac8908402d53e1d99722 \ No newline at end of file diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 5f73176fab..91226fc577 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -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] diff --git a/selfdrive/thermald/tests/test_power_monitoring.py b/selfdrive/thermald/tests/test_power_monitoring.py index c0524add69..efe607155a 100755 --- a/selfdrive/thermald/tests/test_power_monitoring.py +++ b/selfdrive/thermald/tests/test_power_monitoring.py @@ -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 diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 6cf5c428a8..f20c649b4e 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -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: - statlog.sample("power_draw", current_power_draw) - msg.deviceState.powerDrawW = current_power_draw - else: - msg.deviceState.powerDrawW = 0 + current_power_draw = HARDWARE.get_current_power_draw() + statlog.sample("power_draw", current_power_draw) + msg.deviceState.powerDrawW = current_power_draw + + 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) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 28fcc5f56f..0835c16372 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -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]) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 3588026ba6..fe31a2dcb1 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -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> 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()); diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index e522d6160d..0edeb252b7 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -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"))); } } diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index 674c7fa7cb..eaa8b1f703 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -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); } diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index dc9f292df1..5bf70584a1 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -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; } diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index 418ccd3179..536ca495ca 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -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); } }); diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index d32cc26e43..77e84293b2 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -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, [=]() { diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 175e6cf5a3..392287d65d 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -29,45 +29,45 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { std::vector> 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> 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); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 2759416236..7032fc7518 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -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]); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 4cd88fc9e3..dc1e69da2a 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -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); diff --git a/selfdrive/ui/qt/setup/reset.cc b/selfdrive/ui/qt/setup/reset.cc index 9ffcf7f6cf..582217c1d7 100644 --- a/selfdrive/ui/qt/setup/reset.cc +++ b/selfdrive/ui/qt/setup/reset.cc @@ -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"( diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 10a2d05f34..69dafcf741 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -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); diff --git a/selfdrive/ui/qt/setup/updater.cc b/selfdrive/ui/qt/setup/updater.cc index 6e8189f4ba..fd7148c534 100644 --- a/selfdrive/ui/qt/setup/updater.cc +++ b/selfdrive/ui/qt/setup/updater.cc @@ -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(); } } diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 00e72b352c..accab67bf0 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -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)); } diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index ac8e7bcd18..21ec5eedcf 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -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); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 366aa76f49..f82d9bf496 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -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(); } diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 8666eb1d4e..0bc90b5307 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -6,6 +6,8 @@ #include #endif +#include + #include #include @@ -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) { diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 42e9043602..cc11ec2c27 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -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 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> frames; uint32_t draw_frame_id = 0; diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc index 61d47db1c3..31009f03ca 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -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"; diff --git a/selfdrive/ui/qt/widgets/drive_stats.h b/selfdrive/ui/qt/widgets/drive_stats.h index 9446294516..5e2d96b240 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.h +++ b/selfdrive/ui/qt/widgets/drive_stats.h @@ -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_; diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 8a5d492804..755ccfe8c5 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -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(); } diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc index 433193df5d..937ea02f86 100644 --- a/selfdrive/ui/qt/widgets/offroad_alerts.cc +++ b/selfdrive/ui/qt/widgets/offroad_alerts.cc @@ -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(); }); diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 8a208bf3cf..d2529821f4 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -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"(
  1. Go to https://connect.comma.ai on your phone
  2. Click "add new device" and scan the QR code on the right
  3. Bookmark connect.comma.ai to your home screen to use it like an app
- )", 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 bullets = {"Remote access", "1 year of storage", "Developer perks"}; + QVector bullets = {tr("Remote access"), tr("1 year of storage"), tr("Developer perks")}; for (auto &b: bullets) { const QString check = " "; 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 { diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc index 46ccf6aee3..1f48c72735 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.cc +++ b/selfdrive/ui/qt/widgets/ssh_keys.cc @@ -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); } } diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h index 596d1d83b9..01e2ab83ce 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.h +++ b/selfdrive/ui/qt/widgets/ssh_keys.h @@ -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); }); diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index 7765bab17d..26335744f3 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -1,3 +1,4 @@ test playsound test_sound +test_translations diff --git a/selfdrive/ui/tests/create_test_translations.sh b/selfdrive/ui/tests/create_test_translations.sh new file mode 100755 index 0000000000..451a3cbfb0 --- /dev/null +++ b/selfdrive/ui/tests/create_test_translations.sh @@ -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>" +TRANSLATED="$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 diff --git a/selfdrive/ui/tests/test_runner.cc b/selfdrive/ui/tests/test_runner.cc index b20ac86c64..ac63139d17 100644 --- a/selfdrive/ui/tests/test_runner.cc +++ b/selfdrive/ui/tests/test_runner.cc @@ -1,10 +1,25 @@ #define CATCH_CONFIG_RUNNER #include "catch2/catch.hpp" -#include + +#include +#include +#include +#include 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); } diff --git a/selfdrive/ui/tests/test_translations.cc b/selfdrive/ui/tests/test_translations.cc new file mode 100644 index 0000000000..fecb9da44a --- /dev/null +++ b/selfdrive/ui/tests/test_translations.cc @@ -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 +void checkWidgetTrWrap(MainWindow &w) { + int i = 0; + for (auto widget : w.findChildren()) { + 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(w); + checkWidgetTrWrap(w); +} diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 4d1e1ab746..c8fc645cf2 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -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")) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index cbf3921e4e..7364b81a40 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -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 diff --git a/system/camerad/snapshot/snapshot.py b/system/camerad/snapshot/snapshot.py index 9a81937eec..946c220a77 100755 --- a/system/camerad/snapshot/snapshot.py +++ b/system/camerad/snapshot/snapshot.py @@ -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) diff --git a/system/hardware/base.py b/system/hardware/base.py index 8684386f62..b052a48c5b 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -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 diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index b2c4a4343b..60d14e4a6b 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -55,6 +55,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!") diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index a69d3eb743..66cb98c606 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -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") diff --git a/tools/CTF.md b/tools/CTF.md index 396e7575a4..a14a41d55d 100644 --- a/tools/CTF.md +++ b/tools/CTF.md @@ -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 diff --git a/tools/replay/.gitignore b/tools/replay/.gitignore new file mode 100644 index 0000000000..83f0e99a8b --- /dev/null +++ b/tools/replay/.gitignore @@ -0,0 +1,5 @@ +moc_* +*.moc + +replay +tests/test_replay diff --git a/tools/replay/README.md b/tools/replay/README.md index 759a448d73..b705fb60db 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -9,12 +9,12 @@ python lib/auth.py # Start a replay -selfdrive/ui/replay/replay +tools/replay/replay # 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 +tools/replay/replay ``` diff --git a/tools/replay/SConscript b/tools/replay/SConscript new file mode 100644 index 0000000000..4a85f46d61 --- /dev/null +++ b/tools/replay/SConscript @@ -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]) diff --git a/selfdrive/ui/replay/camera.cc b/tools/replay/camera.cc similarity index 96% rename from selfdrive/ui/replay/camera.cc rename to tools/replay/camera.cc index 2e8b68a415..87afe63a2a 100644 --- a/selfdrive/ui/replay/camera.cc +++ b/tools/replay/camera.cc @@ -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 diff --git a/selfdrive/ui/replay/camera.h b/tools/replay/camera.h similarity index 92% rename from selfdrive/ui/replay/camera.h rename to tools/replay/camera.h index 7f078511cf..66d33142fb 100644 --- a/selfdrive/ui/replay/camera.h +++ b/tools/replay/camera.h @@ -3,8 +3,8 @@ #include #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: diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index b212abc431..0b8b4fe0a1 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -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,11 +95,10 @@ 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) - CAN_MSGS += [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] - + 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")) diff --git a/selfdrive/ui/replay/consoleui.cc b/tools/replay/consoleui.cc similarity index 99% rename from selfdrive/ui/replay/consoleui.cc rename to tools/replay/consoleui.cc index 05eb09c9ed..e4a3146a69 100644 --- a/selfdrive/ui/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/replay/consoleui.h" +#include "tools/replay/consoleui.h" #include #include diff --git a/selfdrive/ui/replay/consoleui.h b/tools/replay/consoleui.h similarity index 96% rename from selfdrive/ui/replay/consoleui.h rename to tools/replay/consoleui.h index bce1146d46..20e07524dd 100644 --- a/selfdrive/ui/replay/consoleui.h +++ b/tools/replay/consoleui.h @@ -7,7 +7,7 @@ #include #include -#include "selfdrive/ui/replay/replay.h" +#include "tools/replay/replay.h" #include class ConsoleUI : public QObject { diff --git a/selfdrive/ui/replay/filereader.cc b/tools/replay/filereader.cc similarity index 94% rename from selfdrive/ui/replay/filereader.cc rename to tools/replay/filereader.cc index 5f862bec35..88879067c9 100644 --- a/selfdrive/ui/replay/filereader.cc +++ b/tools/replay/filereader.cc @@ -1,9 +1,9 @@ -#include "selfdrive/ui/replay/filereader.h" +#include "tools/replay/filereader.h" #include #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 = [] { diff --git a/selfdrive/ui/replay/filereader.h b/tools/replay/filereader.h similarity index 100% rename from selfdrive/ui/replay/filereader.h rename to tools/replay/filereader.h diff --git a/selfdrive/ui/replay/framereader.cc b/tools/replay/framereader.cc similarity index 95% rename from selfdrive/ui/replay/framereader.cc rename to tools/replay/framereader.cc index 38d98bddc5..ecde9ad5d2 100644 --- a/selfdrive/ui/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -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 #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) { + assert(f != nullptr && yuv != nullptr); + uint8_t *y = yuv; + uint8_t *uv = y + width * height; if (hw_pix_fmt == HW_PIX_FMT) { - uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data(); - uint8_t *uv = y + width * height; for (int i = 0; i < height/2; i++) { 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], diff --git a/selfdrive/ui/replay/framereader.h b/tools/replay/framereader.h similarity index 94% rename from selfdrive/ui/replay/framereader.h rename to tools/replay/framereader.h index 443636e27d..e50b61d7f4 100644 --- a/selfdrive/ui/replay/framereader.h +++ b/tools/replay/framereader.h @@ -4,7 +4,7 @@ #include #include -#include "selfdrive/ui/replay/filereader.h" +#include "tools/replay/filereader.h" extern "C" { #include @@ -46,7 +46,6 @@ private: AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; AVBufferRef *hw_device_ctx = nullptr; - std::vector nv12toyuv_buffer; int prev_idx = -1; inline static std::atomic has_hw_decoder = true; }; diff --git a/selfdrive/ui/replay/logreader.cc b/tools/replay/logreader.cc similarity index 97% rename from selfdrive/ui/replay/logreader.cc rename to tools/replay/logreader.cc index 579fe50644..f27224ac53 100644 --- a/selfdrive/ui/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -1,7 +1,7 @@ -#include "selfdrive/ui/replay/logreader.h" +#include "tools/replay/logreader.h" #include -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/util.h" Event::Event(const kj::ArrayPtr &amsg, bool frame) : reader(amsg), frame(frame) { words = kj::ArrayPtr(amsg.begin(), reader.getEnd()); diff --git a/selfdrive/ui/replay/logreader.h b/tools/replay/logreader.h similarity index 97% rename from selfdrive/ui/replay/logreader.h rename to tools/replay/logreader.h index b4a38a5721..fb63bf3913 100644 --- a/selfdrive/ui/replay/logreader.h +++ b/tools/replay/logreader.h @@ -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); diff --git a/selfdrive/ui/replay/main.cc b/tools/replay/main.cc similarity index 96% rename from selfdrive/ui/replay/main.cc rename to tools/replay/main.cc index e09587023c..d3d6894877 100644 --- a/selfdrive/ui/replay/main.cc +++ b/tools/replay/main.cc @@ -1,8 +1,8 @@ #include #include -#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"; diff --git a/selfdrive/ui/replay/replay.cc b/tools/replay/replay.cc similarity index 99% rename from selfdrive/ui/replay/replay.cc rename to tools/replay/replay.cc index cf7520b361..c886a7e186 100644 --- a/selfdrive/ui/replay/replay.cc +++ b/tools/replay/replay.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/replay/replay.h" +#include "tools/replay/replay.h" #include #include @@ -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) { diff --git a/selfdrive/ui/replay/replay.h b/tools/replay/replay.h similarity index 97% rename from selfdrive/ui/replay/replay.h rename to tools/replay/replay.h index c89a835f64..13269d3ec9 100644 --- a/selfdrive/ui/replay/replay.h +++ b/tools/replay/replay.h @@ -4,8 +4,8 @@ #include -#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; diff --git a/selfdrive/ui/replay/route.cc b/tools/replay/route.cc similarity index 97% rename from selfdrive/ui/replay/route.cc rename to tools/replay/route.cc index 3d595141cd..5b47090229 100644 --- a/selfdrive/ui/replay/route.cc +++ b/tools/replay/route.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/replay/route.h" +#include "tools/replay/route.h" #include #include @@ -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); diff --git a/selfdrive/ui/replay/route.h b/tools/replay/route.h similarity index 92% rename from selfdrive/ui/replay/route.h rename to tools/replay/route.h index ac8fba75ca..6ca9c3b883 100644 --- a/selfdrive/ui/replay/route.h +++ b/tools/replay/route.h @@ -2,9 +2,9 @@ #include -#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; diff --git a/selfdrive/ui/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc similarity index 98% rename from selfdrive/ui/replay/tests/test_replay.cc rename to tools/replay/tests/test_replay.cc index bf984f5f3d..bd5dee013c 100644 --- a/selfdrive/ui/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -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"; diff --git a/selfdrive/ui/replay/tests/test_runner.cc b/tools/replay/tests/test_runner.cc similarity index 100% rename from selfdrive/ui/replay/tests/test_runner.cc rename to tools/replay/tests/test_runner.cc diff --git a/selfdrive/ui/replay/util.cc b/tools/replay/util.cc similarity index 99% rename from selfdrive/ui/replay/util.cc rename to tools/replay/util.cc index 099219ccde..a6ebbec615 100644 --- a/selfdrive/ui/replay/util.cc +++ b/tools/replay/util.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/replay/util.h" +#include "tools/replay/util.h" #include #include diff --git a/selfdrive/ui/replay/util.h b/tools/replay/util.h similarity index 100% rename from selfdrive/ui/replay/util.h rename to tools/replay/util.h diff --git a/tools/serial/connect.sh b/tools/serial/connect.sh index 037152019f..a073310b0a 100755 --- a/tools/serial/connect.sh +++ b/tools/serial/connect.sh @@ -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 diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index faf67c3ef5..17dc2c7a62 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -198,12 +198,12 @@ def gps_callback(gps, vehicle_state): def fake_driver_monitoring(exit_event: threading.Event): - pm = messaging.PubMaster(['driverState', 'driverMonitoringState']) + pm = messaging.PubMaster(['driverStateV2', 'driverMonitoringState']) while not exit_event.is_set(): # dmonitoringmodeld output - dat = messaging.new_message('driverState') - dat.driverState.faceProb = 1.0 - pm.send('driverState', dat) + dat = messaging.new_message('driverStateV2') + dat.driverStateV2.leftDriverData.faceProb = 1.0 + pm.send('driverStateV2', dat) # dmonitoringd output dat = messaging.new_message('driverMonitoringState') diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 60d41a4771..5bc3630766 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -59,6 +59,7 @@ function install_ubuntu_common_requirements() { qtmultimedia5-dev \ qtlocation5-dev \ qtpositioning5-dev \ + qttools5-dev-tools \ libqt5sql5-sqlite \ libqt5svg5-dev \ libqt5x11extras5-dev \