Merge remote-tracking branch 'upstream/master' into civic22_long

pull/25364/head
royjr 3 years ago
commit cb5105a95b
  1. 1
      .github/workflows/selfdrive_tests.yaml
  2. 2
      opendbc
  3. 1
      selfdrive/car/chrysler/values.py
  4. 18
      selfdrive/car/ford/values.py
  5. 18
      selfdrive/car/honda/interface.py
  6. 17
      selfdrive/car/honda/values.py
  7. 4
      selfdrive/car/hyundai/carcontroller.py
  8. 39
      selfdrive/car/hyundai/carstate.py
  9. 44
      selfdrive/car/hyundai/hyundaicanfd.py
  10. 6
      selfdrive/car/hyundai/interface.py
  11. 2
      selfdrive/car/hyundai/values.py
  12. 2
      selfdrive/car/tests/routes.py
  13. 5
      selfdrive/car/tests/test_models.py
  14. 1
      selfdrive/car/torque_data/override.yaml
  15. 29
      selfdrive/car/torque_data/params.yaml
  16. 1
      selfdrive/car/toyota/values.py
  17. 3
      selfdrive/controls/controlsd.py
  18. 3
      selfdrive/controls/lib/radar_helpers.py
  19. 7
      selfdrive/locationd/calibrationd.py
  20. 23
      selfdrive/locationd/torqued.py
  21. 2
      selfdrive/modeld/models/supercombo.onnx
  22. 1
      selfdrive/sensord/rawgps/rawgpsd.py
  23. 2
      selfdrive/test/process_replay/model_replay_ref_commit
  24. 3
      selfdrive/test/process_replay/process_replay.py
  25. 2
      selfdrive/test/process_replay/ref_commit
  26. 4
      selfdrive/test/process_replay/test_processes.py
  27. 8
      selfdrive/ui/qt/onroad.cc
  28. 74
      selfdrive/ui/qt/widgets/cameraview.cc
  29. 8
      selfdrive/ui/qt/widgets/cameraview.h
  30. 54
      selfdrive/ui/translations/main_ja.ts
  31. 27
      selfdrive/ui/ui.cc
  32. 4
      selfdrive/ui/ui.h
  33. 2
      tools/cabana/.gitignore
  34. 6
      tools/cabana/SConscript
  35. 12
      tools/cabana/binaryview.cc
  36. 1
      tools/cabana/binaryview.h
  37. 91
      tools/cabana/chartswidget.cc
  38. 16
      tools/cabana/chartswidget.h
  39. 22
      tools/cabana/dbcmanager.cc
  40. 4
      tools/cabana/dbcmanager.h
  41. 109
      tools/cabana/detailwidget.cc
  42. 26
      tools/cabana/detailwidget.h
  43. 19
      tools/cabana/mainwin.cc
  44. 4
      tools/cabana/mainwin.h
  45. 78
      tools/cabana/messageswidget.cc
  46. 11
      tools/cabana/messageswidget.h
  47. 14
      tools/cabana/settings.cc
  48. 3
      tools/cabana/settings.h
  49. 18
      tools/cabana/signaledit.cc
  50. 7
      tools/cabana/signaledit.h
  51. 4
      tools/cabana/tests/test_cabana
  52. 35
      tools/cabana/tests/test_cabana.cc
  53. 10
      tools/cabana/tests/test_runner.cc
  54. 1
      tools/cabana/videowidget.cc
  55. 4
      tools/gpstest/run_unittest.sh
  56. 4
      tools/gpstest/test_gps.py
  57. 2
      tools/plotjuggler/juggle.py
  58. 5
      tools/sim/bridge.py
  59. 46
      tools/sim/rgb_to_nv12.cl

@ -232,6 +232,7 @@ jobs:
./selfdrive/loggerd/tests/test_logger &&\
./system/proclogd/tests/test_proclog && \
./tools/replay/tests/test_replay && \
./tools/cabana/tests/test_cabana && \
./system/camerad/test/ae_gray_test && \
coverage xml"
- name: "Upload coverage to Codecov"

@ -1 +1 @@
Subproject commit a95b0ae8a5244a9d6311bf72aa2a7d63d41b4a9f
Subproject commit b3dc569994fd10e4de04afd650980c51ddfce5e1

@ -241,6 +241,7 @@ FW_VERSIONS = {
b'68334977AH',
b'68504022AB',
b'68530686AB',
b'68504022AC',
],
(Ecu.fwdRadar, 0x753, None): [
b'04672895AB',

@ -61,10 +61,10 @@ class FordCarInfo(CarInfo):
CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
CAR.ESCAPE_MK4: [
FordCarInfo("Ford Escape 2020"),
FordCarInfo("Ford Kuga EU", "Driver Assistance Pack"),
FordCarInfo("Ford Escape 2020-21"),
FordCarInfo("Ford Kuga 2020-21", "Driver Assistance Pack"),
],
CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-21"),
CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-22"),
CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"),
}
@ -87,31 +87,41 @@ FW_QUERY_CONFIG = FwQueryConfig(
FW_VERSIONS = {
CAR.ESCAPE_MK4: {
(Ecu.eps, 0x730, None): [
b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.abs, 0x760, None): [
b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6C-2D053-NY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6C-2D053-SA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x706, None): [
b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7E0, None): [
b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.shiftByWire, 0x732, None): [
b'LX6P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
},
CAR.EXPLORER_MK6: {
(Ecu.eps, 0x730, None): [
b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.abs, 0x760, None): [
b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -123,10 +133,12 @@ FW_VERSIONS = {
(Ecu.engine, 0x7E0, None): [
b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.shiftByWire, 0x732, None): [
b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MP-14G395-JB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
},
CAR.FOCUS_MK4: {

@ -220,23 +220,17 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]]
tire_stiffness_factor = 0.677
elif candidate == CAR.ODYSSEY:
ret.mass = 4471. * CV.LB_TO_KG + STD_CARGO_KG
elif candidate in (CAR.ODYSSEY, CAR.ODYSSEY_CHN):
ret.mass = 1900. + STD_CARGO_KG
ret.wheelbase = 3.00
ret.centerToFront = ret.wheelbase * 0.41
ret.steerRatio = 14.35 # as spec
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.82
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
elif candidate == CAR.ODYSSEY_CHN:
ret.mass = 1849.2 + STD_CARGO_KG # mean of 4 models in kg
ret.wheelbase = 2.90
ret.centerToFront = ret.wheelbase * 0.41 # from CAR.ODYSSEY
ret.steerRatio = 14.35
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.82
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
if candidate == CAR.ODYSSEY_CHN:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end
else:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
elif candidate in (CAR.PILOT, CAR.PASSPORT):
ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight

@ -1031,6 +1031,23 @@ FW_VERSIONS = {
b'54008-THR-A020\x00\x00',
],
},
CAR.ODYSSEY_CHN: {
(Ecu.eps, 0x18da30f1, None): [
b'39990-T6D-H220\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-T6A-J010\x00\x00',
],
(Ecu.combinationMeter, 0x18da60f1, None): [
b'78109-T6A-F310\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-T6A-P040\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-T6A-P110\x00\x00',
],
},
CAR.PILOT: {
(Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-TG7-A520\x00\x00',

@ -49,6 +49,7 @@ class CarController:
self.angle_limit_counter = 0
self.frame = 0
self.accel_last = 0
self.apply_steer_last = 0
self.car_fingerprint = CP.carFingerprint
self.last_button_frame = 0
@ -123,8 +124,9 @@ class CarController:
if self.CP.openpilotLongitudinalControl:
can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame))
if self.frame % 2 == 0:
can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, accel, stopping, CC.cruiseControl.override,
can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
set_speed_in_units))
self.accel_last = accel
else:
# button presses
if (self.frame - self.last_button_frame) * DT_CTRL > 0.25:

@ -196,10 +196,10 @@ class CarState(CarStateBase):
if not self.CP.openpilotLongitudinalControl:
speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam
ret.cruiseState.speed = cp_cruise_info.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor
ret.cruiseState.standstill = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STANDSTILL"] == 1
ret.cruiseState.enabled = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STATUS"] != 0
self.cruise_info = copy.copy(cp_cruise_info.vl["CRUISE_INFO"])
ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor
ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1
ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2)
self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"])
cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS"
self.prev_cruise_buttons = self.cruise_buttons[-1]
@ -464,12 +464,12 @@ class CarState(CarStateBase):
if CP.flags & HyundaiFlags.CANFD_HDA2 and not CP.openpilotLongitudinalControl:
signals += [
("CRUISE_STATUS", "CRUISE_INFO"),
("SET_SPEED", "CRUISE_INFO"),
("CRUISE_STANDSTILL", "CRUISE_INFO"),
("ACCMode", "SCC_CONTROL"),
("VSetDis", "SCC_CONTROL"),
("CRUISE_STANDSTILL", "SCC_CONTROL"),
]
checks += [
("CRUISE_INFO", 50),
("SCC_CONTROL", 50),
]
if CP.carFingerprint in EV_CAR:
@ -497,21 +497,20 @@ class CarState(CarStateBase):
checks = [("CAM_0x2a4", 20)]
else:
signals = [
("COUNTER", "CRUISE_INFO"),
("NEW_SIGNAL_1", "CRUISE_INFO"),
("CRUISE_MAIN", "CRUISE_INFO"),
("CRUISE_STATUS", "CRUISE_INFO"),
("CRUISE_INACTIVE", "CRUISE_INFO"),
("ZEROS_9", "CRUISE_INFO"),
("CRUISE_STANDSTILL", "CRUISE_INFO"),
("ZEROS_5", "CRUISE_INFO"),
("DISTANCE_SETTING", "CRUISE_INFO"),
("SET_SPEED", "CRUISE_INFO"),
("NEW_SIGNAL_4", "CRUISE_INFO"),
("COUNTER", "SCC_CONTROL"),
("NEW_SIGNAL_1", "SCC_CONTROL"),
("MainMode_ACC", "SCC_CONTROL"),
("ACCMode", "SCC_CONTROL"),
("CRUISE_INACTIVE", "SCC_CONTROL"),
("ZEROS_9", "SCC_CONTROL"),
("CRUISE_STANDSTILL", "SCC_CONTROL"),
("ZEROS_5", "SCC_CONTROL"),
("DISTANCE_SETTING", "SCC_CONTROL"),
("VSetDis", "SCC_CONTROL"),
]
checks = [
("CRUISE_INFO", 50),
("SCC_CONTROL", 50),
]
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 6)

@ -1,3 +1,4 @@
from common.numpy_fast import clip
from selfdrive.car.hyundai.values import HyundaiFlags
@ -52,10 +53,9 @@ def create_buttons(packer, CP, cnt, btn):
def create_acc_cancel(packer, CP, cruise_info_copy):
values = cruise_info_copy
values.update({
"CRUISE_STATUS": 0,
"CRUISE_INACTIVE": 1,
"ACCMode": 4,
})
return packer.make_can_msg("CRUISE_INFO", get_e_can_bus(CP), values)
return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values)
def create_lfahda_cluster(packer, CP, enabled):
values = {
@ -65,33 +65,37 @@ def create_lfahda_cluster(packer, CP, enabled):
return packer.make_can_msg("LFAHDA_CLUSTER", get_e_can_bus(CP), values)
def create_acc_control(packer, CP, enabled, accel, stopping, gas_override, set_speed):
cruise_status = 0 if not enabled else (4 if gas_override else 2)
def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_override, set_speed):
jerk = 5
jn = jerk / 50
if not enabled or gas_override:
accel = 0
a_val, a_raw = 0, 0
else:
a_raw = accel
a_val = clip(accel, accel_last - jn, accel_last + jn)
if stopping:
a_raw = 0
values = {
"CRUISE_STATUS": cruise_status,
"CRUISE_INACTIVE": 0 if enabled else 1,
"CRUISE_MAIN": 1,
"CRUISE_STANDSTILL": 0,
"STOP_REQ": 1 if stopping else 0,
"ACCEL_REQ": accel,
"ACCEL_REQ2": accel,
"SET_SPEED": set_speed,
"DISTANCE_SETTING": 4,
"ACCMode": 0 if not enabled else (2 if gas_override else 1),
"MainMode_ACC": 1,
"StopReq": 1 if stopping else 0,
"aReqValue": a_val,
"aReqRaw": a_raw,
"VSetDis": set_speed,
"JerkLowerLimit": jerk if enabled else 1,
"ACC_ObjDist": 1,
"ObjValid": 1,
"ObjValid": 0,
"OBJ_STATUS": 2,
"SET_ME_2": 0x2,
"SET_ME_2": 0x4,
"SET_ME_3": 0x3,
"SET_ME_TMP_64": 0x64,
"NEW_SIGNAL_9": 2,
"NEW_SIGNAL_10": 4,
"DISTANCE_SETTING": 4,
}
return packer.make_can_msg("CRUISE_INFO", get_e_can_bus(CP), values)
return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values)

@ -202,14 +202,10 @@ class CarInterface(CarInterfaceBase):
if candidate in CANFD_CAR:
ret.longitudinalTuning.kpV = [0.1]
ret.longitudinalTuning.kiV = [0.0]
ret.longitudinalActuatorDelayLowerBound = 0.15
ret.longitudinalActuatorDelayUpperBound = 0.5
ret.experimentalLongitudinalAvailable = bool(ret.flags & HyundaiFlags.CANFD_HDA2)
else:
ret.longitudinalTuning.kpV = [0.5]
ret.longitudinalTuning.kiV = [0.0]
ret.longitudinalActuatorDelayLowerBound = 0.5
ret.longitudinalActuatorDelayUpperBound = 0.5
ret.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR)
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
ret.pcmCruise = not ret.openpilotLongitudinalControl
@ -218,6 +214,8 @@ class CarInterface(CarInterfaceBase):
ret.startingState = True
ret.vEgoStarting = 0.1
ret.startAccel = 2.0
ret.longitudinalActuatorDelayLowerBound = 0.5
ret.longitudinalActuatorDelayUpperBound = 0.5
# *** feature detection ***
if candidate in CANFD_CAR:

@ -1232,6 +1232,7 @@ FW_VERSIONS = {
b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106',
b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106',
b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106',
b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819',
@ -1244,6 +1245,7 @@ FW_VERSIONS = {
b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800',
b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800',
b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800',
b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',

@ -25,6 +25,7 @@ non_tested_cars = [
GM.BOLT_EV,
HYUNDAI.GENESIS_G90,
HYUNDAI.KIA_OPTIMA_H,
HONDA.ODYSSEY_CHN,
]
CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,))
@ -61,7 +62,6 @@ routes = [
CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED),
CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV),
CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX),
CarTestRoute("81722949a62ea724|2019-04-06--15-19-25", HONDA.ODYSSEY_CHN),
CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T
CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T
CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs

@ -109,6 +109,10 @@ class TestCarModelBase(unittest.TestCase):
assert cls.CP
assert cls.CP.carFingerprint == cls.car_model
@classmethod
def tearDownClass(cls):
del cls.can_msgs
def setUp(self):
self.CI = self.CarInterface(self.CP, self.CarController, self.CarState)
assert self.CI
@ -233,7 +237,6 @@ class TestCarModelBase(unittest.TestCase):
# TODO: check rest of panda's carstate (steering, ACC main on, etc.)
checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev()
checks['cruiseState'] += CS.cruiseState.enabled and not CS.cruiseState.available
if self.CP.carName not in ("hyundai", "volkswagen", "body"):
# TODO: fix standstill mismatches for other makes
checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving()

@ -21,7 +21,6 @@ FORD FOCUS 4TH GEN: [.nan, 1.5, .nan]
COMMA BODY: [.nan, 1000, .nan]
# Totally new cars
KIA EV6 2022: [3.5, 3.0, 0.0]
RAM 1500 5TH GEN: [2.0, 2.0, 0.0]
RAM HD 5TH GEN: [1.4, 1.4, 0.0]
SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11]

@ -39,17 +39,18 @@ HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.0781366561692759
HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382]
JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185]
JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003]
KIA EV6 2022: [3.2, 3.0, 0.05]
KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716]
KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267]
KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558]
KIA STINGER GT2 2018: [2.7499043387418967, 1.849652021986449, 0.12048334239559202]
LEXUS ES 2019: [2.0203086922726112, 2.134803912579666, 0.12757526789308554]
LEXUS ES HYBRID 2019: [2.392442298703042, 1.863360677810788, 0.17690002108856212]
LEXUS ES 2019: [1.935835, 2.134803912579666, 0.093439]
LEXUS ES HYBRID 2019: [2.135678, 1.863360677810788, 0.109627]
LEXUS NX 2018: [2.302625600642627, 2.1382378491466625, 0.14986840878892838]
LEXUS NX 2020: [2.4331999786982936, 2.1045680431705414, 0.14099899317761067]
LEXUS NX HYBRID 2018: [2.4025593501080955, 1.8080446063815507, 0.15349361249519017]
LEXUS RX 2016: [1.5876816543130423, 1.0427699298523752, 0.21334066732397142]
LEXUS RX 2020: [1.5228812994274734, 1.431102486563665, 0.2093316728710659]
LEXUS RX 2020: [1.5228812994274734, 1.431102486563665, 0.164117]
LEXUS RX HYBRID 2017: [1.6984261557042386, 1.3211501880159107, 0.1820354534928893]
LEXUS RX HYBRID 2020: [1.5522309889823778, 1.255230465866663, 0.2220954003055114]
MAZDA CX-9 2021: [1.7601682915983443, 1.0889677335154337, 0.17713792194297195]
@ -62,31 +63,31 @@ TOYOTA AVALON 2019: [1.7036141952825095, 1.239619084240008, 0.08459830394899492]
TOYOTA AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605]
TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193]
TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509]
TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.13519250664782062]
TOYOTA CAMRY 2021: [2.6922769557433055, 2.3476510120007434, 0.1450430192989234]
TOYOTA CAMRY HYBRID 2018: [2.0974120828287774, 1.7996193116697359, 0.13823613467632756]
TOYOTA CAMRY HYBRID 2021: [2.6426668350384457, 2.3901492458927986, 0.16103875108816076]
TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.105192506]
TOYOTA CAMRY 2021: [2.446083, 2.3476510120007434, 0.121615]
TOYOTA CAMRY HYBRID 2018: [1.996333, 1.7996193116697359, 0.112565]
TOYOTA CAMRY HYBRID 2021: [2.263582, 2.3901492458927986, 0.115257]
TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652]
TOYOTA COROLLA HYBRID TSS2 2019: [1.9079729107361805, 1.8118712531729109, 0.22251440891543514]
TOYOTA COROLLA TSS2 2019: [2.0742917676766712, 1.9258612322678952, 0.16888685704519352]
TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796]
TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457]
TOYOTA HIGHLANDER HYBRID 2018: [1.9421825202382728, 1.6433903296845025, 0.16928956792275918]
TOYOTA HIGHLANDER HYBRID 2020: [2.103373061114133, 2.104015182965606, 0.14447040132184993]
TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600]
TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993]
TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565]
TOYOTA PRIUS 2017: [2.0183401513314294, 1.5023147650693636, 0.20856908464957724]
TOYOTA PRIUS TSS2 2021: [2.327639738920072, 1.9104337425537743, 0.2030762265549664]
TOYOTA PRIUS 2017: [1.746445, 1.5023147650693636, 0.151515]
TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968]
TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975]
TOYOTA RAV4 2019: [2.5038362866776835, 2.0993589721530252, 0.1552425356342368]
TOYOTA RAV4 2019: [2.331293, 2.0993589721530252, 0.129822]
TOYOTA RAV4 2019 8965: [2.5084506298290377, 2.4216520504763475, 0.11992835265067918]
TOYOTA RAV4 2019 x02: [2.7209621987605024, 2.2148637653781593, 0.10862567142268198]
TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406]
TOYOTA RAV4 HYBRID 2019: [2.2271858492309153, 2.074844961405639, 0.14382216826893632]
TOYOTA RAV4 HYBRID 2019 8965: [2.1077397198131336, 1.8162215166877735, 0.13891369391200137]
TOYOTA RAV4 HYBRID 2019 x02: [2.803624333289342, 2.272367966572498, 0.11364569214387774]
TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.1565442715453653]
TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.112174]
TOYOTA RAV4 HYBRID 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736]
TOYOTA SIENNA 2018: [1.8660896232147548, 1.3208264576110418, 0.18799149615227198]
TOYOTA SIENNA 2018: [1.689726, 1.3208264576110418, 0.140456]
VOLKSWAGEN ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032]
VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367]
VOLKSWAGEN GOLF 7TH GEN: [1.3750394140491293, 1.5814743077200641, 0.2018321939386586]

@ -1367,6 +1367,7 @@ FW_VERSIONS = {
b'\x01896634AA1000\x00\x00\x00\x00',
b'\x01896634A88000\x00\x00\x00\x00',
b'\x01896634A89000\x00\x00\x00\x00',
b'\x01896634A89100\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F0R01100\x00\x00\x00\x00',

@ -254,8 +254,7 @@ class Controls:
self.events.add(EventName.pedalPressed)
if CS.gasPressed:
self.events.add(EventName.pedalPressedPreEnable if self.disengage_on_accelerator else
EventName.gasPressedOverride)
self.events.add(EventName.gasPressedOverride)
if not self.CP.notCar:
self.events.add_from_msg(self.sm['driverMonitoringState'].events)

@ -151,7 +151,8 @@ class Cluster():
def potential_low_speed_lead(self, v_ego):
# stop for stuff in front of you and low speed, even without model confirmation
return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and self.dRel < 25
# Radar points closer than 0.75, are almost always glitches on toyota radars
return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and (0.75 < self.dRel < 25)
def is_potential_fcw(self, model_prob):
return model_prob > .9

@ -66,7 +66,6 @@ class Calibrator:
# Read saved calibration
params = Params()
calibration_params = params.get("CalibrationParams")
self.wide_camera = params.get_bool('WideCameraOnly')
rpy_init = RPY_INIT
wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
valid_blocks = 0
@ -166,10 +165,7 @@ class Calibrator:
self.old_rpy_weight = min(0.0, self.old_rpy_weight - 1/SMOOTH_CYCLES)
straight_and_fast = ((self.v_ego > MIN_SPEED_FILTER) and (trans[0] > MIN_SPEED_FILTER) and (abs(rot[2]) < MAX_YAW_RATE_FILTER))
if self.wide_camera:
angle_std_threshold = 4*MAX_VEL_ANGLE_STD
else:
angle_std_threshold = MAX_VEL_ANGLE_STD
angle_std_threshold = MAX_VEL_ANGLE_STD
certain_if_calib = ((np.arctan2(trans_std[1], trans[0]) < angle_std_threshold) or
(self.valid_blocks < INPUTS_NEEDED))
if not (straight_and_fast and certain_if_calib):
@ -185,7 +181,6 @@ class Calibrator:
new_wide_from_device_euler = np.array(wide_from_device_euler)
else:
new_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
self.rpys[self.block_idx] = (self.idx*self.rpys[self.block_idx] +
(BLOCK_SIZE - self.idx) * new_rpy) / float(BLOCK_SIZE)
self.wide_from_device_eulers[self.block_idx] = (self.idx*self.wide_from_device_eulers[self.block_idx] +

@ -16,7 +16,9 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
HISTORY = 5 # secs
POINTS_PER_BUCKET = 1500
MIN_POINTS_TOTAL = 4000
MIN_POINTS_TOTAL_QLOG = 800
FIT_POINTS_TOTAL = 2000
FIT_POINTS_TOTAL_QLOG = 800
MIN_VEL = 15 # m/s
FRICTION_FACTOR = 1.5 # ~85% of data coverage
FACTOR_SANITY = 0.3
@ -26,7 +28,7 @@ MIN_FILTER_DECAY = 50
MAX_FILTER_DECAY = 250
LAT_ACC_THRESHOLD = 1
STEER_BUCKET_BOUNDS = [(-0.5, -0.3), (-0.3, -0.2), (-0.2, -0.1), (-0.1, 0), (0, 0.1), (0.1, 0.2), (0.2, 0.3), (0.3, 0.5)]
MIN_BUCKET_POINTS = [100, 300, 500, 500, 500, 500, 300, 100]
MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100])
MAX_RESETS = 5.0
MAX_INVALID_THRESHOLD = 10
MIN_ENGAGE_BUFFER = 2 # secs
@ -58,10 +60,11 @@ class NPQueue:
class PointBuckets:
def __init__(self, x_bounds, min_points):
def __init__(self, x_bounds, min_points, min_points_total):
self.x_bounds = x_bounds
self.buckets = {bounds: NPQueue(maxlen=POINTS_PER_BUCKET, rowsize=3) for bounds in x_bounds}
self.buckets_min_points = {bounds: min_point for bounds, min_point in zip(x_bounds, min_points)}
self.min_points_total = min_points_total
def bucket_lengths(self):
return [len(v) for v in self.buckets.values()]
@ -70,7 +73,7 @@ class PointBuckets:
return sum(self.bucket_lengths())
def is_valid(self):
return all(len(v) >= min_pts for v, min_pts in zip(self.buckets.values(), self.buckets_min_points.values())) and (self.__len__() >= MIN_POINTS_TOTAL)
return all(len(v) >= min_pts for v, min_pts in zip(self.buckets.values(), self.buckets_min_points.values())) and (self.__len__() >= self.min_points_total)
def add_point(self, x, y):
for bound_min, bound_max in self.x_bounds:
@ -90,9 +93,17 @@ class PointBuckets:
class TorqueEstimator:
def __init__(self, CP):
def __init__(self, CP, decimated=False):
self.hist_len = int(HISTORY / DT_MDL)
self.lag = CP.steerActuatorDelay + .2 # from controlsd
if decimated:
self.min_bucket_points = MIN_BUCKET_POINTS / 10
self.min_points_total = MIN_POINTS_TOTAL_QLOG
self.fit_points = FIT_POINTS_TOTAL_QLOG
else:
self.min_bucket_points = MIN_BUCKET_POINTS
self.min_points_total = MIN_POINTS_TOTAL
self.fit_points = FIT_POINTS_TOTAL
self.offline_friction = 0.0
self.offline_latAccelFactor = 0.0
@ -157,10 +168,10 @@ class TorqueEstimator:
self.invalid_values_tracker = 0.0
self.decay = MIN_FILTER_DECAY
self.raw_points = defaultdict(lambda: deque(maxlen=self.hist_len))
self.filtered_points = PointBuckets(x_bounds=STEER_BUCKET_BOUNDS, min_points=MIN_BUCKET_POINTS)
self.filtered_points = PointBuckets(x_bounds=STEER_BUCKET_BOUNDS, min_points=self.min_bucket_points, min_points_total=self.min_points_total)
def estimate_params(self):
points = self.filtered_points.get_points(FIT_POINTS_TOTAL)
points = self.filtered_points.get_points(self.fit_points)
# total least square solution as both x and y are noisy observations
# this is empirically the slope of the hysteresis parallelogram as opposed to the line through the diagonals
try:

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a41d42f92913e6cc3909e505b1220b16d31f7cfca5f8a4e82109577b4151b645
oid sha256:dcfad22cecf37275d01a339d96174800c109e9a70f853fdef3e4ef62ed3f4bbe
size 45922983

@ -124,6 +124,7 @@ def setup_quectel(diag: ModemDiag):
# don't automatically turn on GNSS on powerup
at_cmd("AT+QGPSCFG=\"autogps\",0")
at_cmd("AT+QGPSSUPLURL=\"supl.google.com:7275\"")
at_cmd("AT+QGPSCFG=\"outport\",\"usbnmea\"")
at_cmd("AT+QGPS=1")

@ -1 +1 @@
d2f1711fd58d4f2c25b81bd332270da60ff9636d
49ea844254883ac61caa2ac425f453799aeb28a6

@ -456,6 +456,9 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None):
os.environ['SKIP_FW_QUERY'] = "1"
os.environ['FINGERPRINT'] = CP.carFingerprint
if CP.openpilotLongitudinalControl:
params.put_bool("ExperimentalLongitudinalEnabled", True)
def python_replay_process(cfg, lr, fingerprint=None):
sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub]

@ -1 +1 @@
8f2919ba4d8509432e93ff0a44f951254c1ff3fe
fc3a044c567a8702ed1500d745170c365dd6b3d4

@ -24,10 +24,11 @@ source_segments = [
("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2
("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC)
("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH)
("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA
("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA_2018_HYBRID
("RAM", "2f4452b03ccb98f0|2022-09-07--13-55-08--10"), # CHRYSLER.RAM_1500
("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.OUTBACK
("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT
("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.BOLT_EUV
("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL
("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.GOLF
("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.CX9_2021
@ -50,6 +51,7 @@ segments = [
("RAM", "regen20490083AE7|2022-09-27--15-53-15--0"),
("SUBARU", "regen1E72BBDCED5|2022-09-27--15-55-31--0"),
("GM", "regen45B05A80EF6|2022-09-27--15-57-22--0"),
("GM2", "376bf99325883932|2022-10-27--13-41-22--1"),
("NISSAN", "regenC19D899B46D|2022-09-27--15-59-13--0"),
("VOLKSWAGEN", "regenD8F7AC4BD0D|2022-09-27--16-41-45--0"),
("MAZDA", "regenFC3F9ECBB64|2022-09-27--16-03-09--0"),

@ -100,10 +100,6 @@ void OnroadWindow::offroadTransition(bool offroad) {
#endif
alerts->updateAlert({}, bg);
// update stream type
bool wide_cam = Params().getBool("WideCameraOnly");
nvg->setStreamType(wide_cam ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD);
}
void OnroadWindow::paintEvent(QPaintEvent *event) {
@ -232,8 +228,10 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD());
}
setStreamType(s.scene.wide_cam ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD);
if (s.scene.calibration_valid) {
CameraWidget::updateCalibration(s.scene.view_from_calib);
auto calib = s.scene.wide_cam ? s.scene.view_from_wide_calib : s.scene.view_from_calib;
CameraWidget::updateCalibration(calib);
} else {
CameraWidget::updateCalibration(DEFAULT_CALIBRATION);
}

@ -96,8 +96,8 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio)
CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) :
stream_name(stream_name), stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection);
connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived);
QObject::connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection);
QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection);
}
CameraWidget::~CameraWidget() {
@ -162,13 +162,13 @@ void CameraWidget::initializeGL() {
}
void CameraWidget::showEvent(QShowEvent *event) {
frames.clear();
if (!vipc_thread) {
vipc_thread = new QThread();
connect(vipc_thread, &QThread::started, [=]() { vipcThread(); });
connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater);
vipc_thread->start();
}
clearFrames();
}
void CameraWidget::hideEvent(QHideEvent *event) {
@ -178,6 +178,7 @@ void CameraWidget::hideEvent(QHideEvent *event) {
vipc_thread->wait();
vipc_thread = nullptr;
}
clearFrames();
}
void CameraWidget::updateFrameMat() {
@ -187,12 +188,17 @@ void CameraWidget::updateFrameMat() {
if (stream_type == VISION_STREAM_DRIVER) {
frame_mat = get_driver_view_transform(w, h, stream_width, stream_height);
} else {
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
// for narrow come and a little lower for wide cam.
// TODO: use proper perspective transform?
if (stream_type == VISION_STREAM_WIDE_ROAD) {
intrinsic_matrix = ecam_intrinsic_matrix;
zoom = 2.0;
} else {
intrinsic_matrix = fcam_intrinsic_matrix;
zoom = 1.1;
}
const vec3 inf = {{1000., 0., 0.}};
const vec3 Ep = matvecmul3(calibration, inf);
const vec3 Kep = matvecmul3(intrinsic_matrix, Ep);
@ -233,45 +239,37 @@ void CameraWidget::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 = frames.size() - 1;
std::lock_guard lk(frame_lock);
// 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
if (frames[frame_idx].first == prev_frame_id) {
qDebug() << "Drawing same frame twice" << frames[frame_idx].first;
} else if (frames[frame_idx].first != prev_frame_id + 1) {
qDebug() << "Skipped frame" << frames[frame_idx].first;
// use previous texture if update() is called without new frame.
VisionBuf *frame = nullptr;
if (!frames.empty()) {
frame = frames.front().second;
frames.pop_front();
}
prev_frame_id = frames[frame_idx].first;
glViewport(0, 0, width(), height());
glBindVertexArray(frame_vao);
glUseProgram(program->programId());
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
VisionBuf *frame = frames[frame_idx].second;
#ifdef QCOM2
glActiveTexture(GL_TEXTURE0);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]);
assert(glGetError() == GL_NO_ERROR);
if (frame) {
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]);
assert(glGetError() == GL_NO_ERROR);
}
#else
glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width, stream_height, GL_RED, GL_UNSIGNED_BYTE, frame->y);
if (frame) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width, stream_height, GL_RED, GL_UNSIGNED_BYTE, frame->y);
assert(glGetError() == GL_NO_ERROR);
glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride/2);
glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_2D, textures[1]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width/2, stream_height/2, GL_RG, GL_UNSIGNED_BYTE, frame->uv);
if (frame) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width/2, stream_height/2, GL_RG, GL_UNSIGNED_BYTE, frame->uv);
assert(glGetError() == GL_NO_ERROR);
#endif
@ -288,7 +286,6 @@ void CameraWidget::paintGL() {
void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) {
makeCurrent();
frames.clear();
stream_width = vipc_client->buffers[0].width;
stream_height = vipc_client->buffers[0].height;
stream_stride = vipc_client->buffers[0].stride;
@ -339,11 +336,7 @@ void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) {
updateFrameMat();
}
void CameraWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) {
frames.push_back(std::make_pair(frame_id, buf));
while (frames.size() > FRAME_BUFFER_SIZE) {
frames.pop_front();
}
void CameraWidget::vipcFrameReceived() {
update();
}
@ -354,11 +347,13 @@ void CameraWidget::vipcThread() {
while (!QThread::currentThread()->isInterruptionRequested()) {
if (!vipc_client || cur_stream_type != stream_type) {
clearFrames();
cur_stream_type = stream_type;
vipc_client.reset(new VisionIpcClient(stream_name, cur_stream_type, false));
}
if (!vipc_client->connected) {
clearFrames();
if (!vipc_client->connect(false)) {
QThread::msleep(100);
continue;
@ -367,7 +362,15 @@ void CameraWidget::vipcThread() {
}
if (VisionBuf *buf = vipc_client->recv(&meta_main, 1000)) {
emit vipcThreadFrameReceived(buf, meta_main.frame_id);
{
std::lock_guard lk(frame_lock);
frames.push_back(std::make_pair(meta_main.frame_id, buf));
while (frames.size() > FRAME_BUFFER_SIZE) {
qDebug() << "Skipped frame" << frames.front().first;
frames.pop_front();
}
}
emit vipcThreadFrameReceived();
}
}
@ -378,3 +381,8 @@ void CameraWidget::vipcThread() {
egl_images.clear();
#endif
}
void CameraWidget::clearFrames() {
std::lock_guard lk(frame_lock);
frames.clear();
}

@ -1,6 +1,7 @@
#pragma once
#include <memory>
#include <mutex>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
@ -37,7 +38,7 @@ public:
signals:
void clicked();
void vipcThreadConnected(VisionIpcClient *);
void vipcThreadFrameReceived(VisionBuf *, quint32);
void vipcThreadFrameReceived();
protected:
void paintGL() override;
@ -49,6 +50,7 @@ protected:
virtual void updateFrameMat();
void updateCalibration(const mat3 &calib);
void vipcThread();
void clearFrames();
bool zoomed_view;
GLuint frame_vao, frame_vbo, frame_ibo;
@ -76,11 +78,11 @@ protected:
mat3 calibration = DEFAULT_CALIBRATION;
mat3 intrinsic_matrix = fcam_intrinsic_matrix;
std::mutex frame_lock;
std::deque<std::pair<uint32_t, VisionBuf*>> frames;
uint32_t draw_frame_id = 0;
uint32_t prev_frame_id = 0;
protected slots:
void vipcConnected(VisionIpcClient *vipc_client);
void vipcFrameReceived(VisionBuf *vipc_client, uint32_t frame_id);
void vipcFrameReceived();
};

@ -56,7 +56,7 @@
</message>
<message>
<source>leave blank for automatic configuration</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Cellular Metered</source>
@ -136,7 +136,7 @@
</message>
<message>
<source>PREVIEW</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)</source>
@ -156,7 +156,7 @@
</message>
<message>
<source>Review Training Guide</source>
<translation></translation>
<translation>使</translation>
</message>
<message>
<source>REVIEW</source>
@ -168,7 +168,7 @@
</message>
<message>
<source>Are you sure you want to review the training guide?</source>
<translation></translation>
<translation>使</translation>
</message>
<message>
<source>Regulatory</source>
@ -200,11 +200,11 @@
</message>
<message>
<source>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.</source>
<translation>openpilot 4°5°8°</translation>
<translation>openpilotの本体は4°5°8°</translation>
</message>
<message>
<source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> %2%1°%4%3°</translation>
<translation> %2 %1°%4 %3°</translation>
</message>
<message>
<source>down</source>
@ -309,7 +309,7 @@
<name>MapETA</name>
<message>
<source>eta</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>min</source>
@ -452,15 +452,15 @@ location set</source>
</message>
<message>
<source>Go to https://connect.comma.ai on your phone</source>
<translation>connect.comma.ai</translation>
<translation>https://connect.comma.ai」にアクセスしてください。</translation>
</message>
<message>
<source>Click &quot;add new device&quot; and scan the QR code on the right</source>
<translation>QRコードをスキャンしてください</translation>
<translation>QRコードをスキャンしてください</translation>
</message>
<message>
<source>Bookmark connect.comma.ai to your home screen to use it like an app</source>
<translation>connect.comma.ai使</translation>
<translation>connect.comma.ai使</translation>
</message>
</context>
<context>
@ -608,7 +608,7 @@ location set</source>
</message>
<message>
<source>Toggles</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Software</source>
@ -627,7 +627,7 @@ location set</source>
</message>
<message>
<source>Power your device in a car with a harness or proceed at your own risk.</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Power off</source>
@ -643,7 +643,7 @@ location set</source>
</message>
<message>
<source>Before we get on the road, lets finish installation and cover some details.</source>
<translation></translation>
<translation>使</translation>
</message>
<message>
<source>Connect to Wi-Fi</source>
@ -655,7 +655,7 @@ location set</source>
</message>
<message>
<source>Continue without Wi-Fi</source>
<translation>Wi-Fi </translation>
<translation>Wi-Fi </translation>
</message>
<message>
<source>Waiting for internet</source>
@ -663,7 +663,7 @@ location set</source>
</message>
<message>
<source>Choose Software to Install</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Dashcam</source>
@ -710,7 +710,7 @@ location set</source>
</message>
<message>
<source>Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.</source>
<translation> comma connect (connect.comma.ai) comma prime </translation>
<translation> comma connect (connect.comma.ai)comma primeの特典を申請してくださ</translation>
</message>
<message>
<source>Pair device</source>
@ -836,7 +836,7 @@ location set</source>
</message>
<message>
<source>UNINSTALL</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Uninstall %1</source>
@ -859,7 +859,7 @@ location set</source>
</message>
<message>
<source>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.</source>
<translation>警告: これはGitHub SSH GitHub GitHub </translation>
<translation>警告: これはGitHub SSH GitHub commaのスタッフ GitHub </translation>
</message>
<message>
<source>ADD</source>
@ -871,7 +871,7 @@ location set</source>
</message>
<message>
<source>LOADING</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>REMOVE</source>
@ -924,7 +924,7 @@ location set</source>
</message>
<message>
<source>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.</source>
<translation>openpilotシステム使</translation>
<translation>openpilotによるアダプティブクルーズコントロールとレーンキーピングドライバーアシストを利用します</translation>
</message>
<message>
<source>Enable Lane Departure Warnings</source>
@ -932,11 +932,11 @@ location set</source>
</message>
<message>
<source>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).</source>
<translation>3150km</translation>
<translation>3150km</translation>
</message>
<message>
<source>Use Metric System</source>
<translation></translation>
<translation>使</translation>
</message>
<message>
<source>Display speed in km/h instead of mph.</source>
@ -952,7 +952,7 @@ location set</source>
</message>
<message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source>
<translation>🌮 () 🌮</translation>
<translation>🌮 (α) 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
@ -976,11 +976,11 @@ location set</source>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<translation> openpilot </translation>
<translation> openpilot </translation>
</message>
<message>
<source>When enabled, pressing the accelerator pedal will disengage openpilot.</source>
<translation> openpilot </translation>
<translation>openpilotを利用中にアクセルを踏むとopenpilotによる運転サポートを中断しま</translation>
</message>
<message>
<source>Show ETA in 24h Format</source>
@ -1003,11 +1003,11 @@ location set</source>
<name>Updater</name>
<message>
<source>Update Required</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB.</source>
<translation>Wi-Fi 1GB </translation>
<translation>Wi-Fi 1GB </translation>
</message>
<message>
<source>Connect to Wi-Fi</source>

@ -23,8 +23,8 @@ static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y,
const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin};
const vec3 pt = (vec3){{in_x, in_y, in_z}};
const vec3 Ep = matvecmul3(s->scene.view_from_calib, pt);
const vec3 KEp = matvecmul3(s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep);
const vec3 Ep = matvecmul3(s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib, pt);
const vec3 KEp = matvecmul3(s->scene.wide_cam ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep);
// Project.
QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]});
@ -121,17 +121,23 @@ static void update_state(UIState *s) {
if (sm.updated("liveCalibration")) {
auto rpy_list = sm["liveCalibration"].getLiveCalibration().getRpyCalib();
auto wfde_list = sm["liveCalibration"].getLiveCalibration().getWideFromDeviceEuler();
Eigen::Vector3d rpy;
rpy << rpy_list[0], rpy_list[1], rpy_list[2];
Eigen::Vector3d wfde;
if (rpy_list.size() == 3) rpy << rpy_list[0], rpy_list[1], rpy_list[2];
if (wfde_list.size() == 3) wfde << wfde_list[0], wfde_list[1], wfde_list[2];
Eigen::Matrix3d device_from_calib = euler2rot(rpy);
Eigen::Matrix3d wide_from_device = euler2rot(wfde);
Eigen::Matrix3d view_from_device;
view_from_device << 0,1,0,
0,0,1,
1,0,0;
Eigen::Matrix3d view_from_calib = view_from_device * device_from_calib;
Eigen::Matrix3d view_from_wide_calib = view_from_device * wide_from_device * device_from_calib ;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j);
scene.view_from_wide_calib.v[i*3 + j] = view_from_wide_calib(i,j);
}
}
scene.calibration_valid = sm["liveCalibration"].getLiveCalibration().getCalStatus() == 1;
@ -167,6 +173,17 @@ static void update_state(UIState *s) {
scene.light_sensor = std::max(100.0f - scale * sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(), 0.0f);
}
scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition;
if (sm.updated("carState")) {
float v_ego = sm["carState"].getCarState().getVEgo();
// TODO: support replays without ecam by using fcam
// Wide or narrow cam dependent on speed
if ((v_ego < 10) || s->wide_cam_only) {
scene.wide_cam = true;
} else if (v_ego > 15) {
scene.wide_cam = false;
}
}
}
void ui_update_params(UIState *s) {
@ -197,7 +214,7 @@ void UIState::updateStatus() {
if (scene.started) {
status = STATUS_DISENGAGED;
scene.started_frame = sm->frame;
wide_camera = Params().getBool("WideCameraOnly");
wide_cam_only = Params().getBool("WideCameraOnly");
}
started_prev = scene.started;
emit offroadTransition(!scene.started);
@ -219,7 +236,7 @@ UIState::UIState(QObject *parent) : QObject(parent) {
});
Params params;
wide_camera = params.getBool("WideCameraOnly");
wide_cam_only = params.getBool("WideCameraOnly");
prime_type = std::atoi(params.get("PrimeType").c_str());
language = QString::fromStdString(params.get("LanguageSetting"));

@ -87,7 +87,9 @@ const QColor bg_colors [] = {
typedef struct UIScene {
bool calibration_valid = false;
bool wide_cam = true;
mat3 view_from_calib = DEFAULT_CALIBRATION;
mat3 view_from_wide_calib = DEFAULT_CALIBRATION;
cereal::PandaState::PandaType pandaType;
// modelV2
@ -130,7 +132,7 @@ public:
QString language;
QTransform car_space_transform;
bool wide_camera;
bool wide_cam_only;
signals:
void uiUpdate(const UIState &s);

@ -4,4 +4,4 @@ moc_*
_cabana
settings
car_fingerprint_to_dbc.json
tests/_test_cabana

@ -18,5 +18,9 @@ cabana_env = qt_env.Clone()
prev_moc_path = cabana_env['QT_MOCHPREFIX']
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
cabana_env.Execute('./generate_dbc_json.py --out car_fingerprint_to_dbc.json')
cabana_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
'canmessages.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
if GetOption('test'):
cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs])

@ -20,13 +20,15 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
setItemDelegate(delegate);
horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
horizontalHeader()->hide();
verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setMouseTracking(true);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
}
QObject::connect(model, &QAbstractItemModel::modelReset, [this]() {
setFixedHeight((CELL_HEIGHT + 1) * std::min(model->rowCount(), 8) + 2);
});
QSize BinaryView::sizeHint() const {
QSize sz = QTableView::sizeHint();
return {sz.width(), model->rowCount() <= 8 ? ((CELL_HEIGHT + 1) * model->rowCount() + 2) : sz.height()};
}
void BinaryView::highlight(const Signal *sig) {
@ -108,9 +110,9 @@ void BinaryView::leaveEvent(QEvent *event) {
void BinaryView::setMessage(const QString &message_id) {
msg_id = message_id;
model->setMessage(message_id);
resizeRowsToContents();
clearSelection();
updateState();
updateGeometry();
}
void BinaryView::updateState() {

@ -61,6 +61,7 @@ public:
void highlight(const Signal *sig);
const Signal *hoveredSignal() const { return hovered_sig; }
QSet<const Signal*> getOverlappingSignals() const;
QSize sizeHint() const override;
signals:
void signalHovered(const Signal *sig);

@ -142,24 +142,33 @@ void ChartsWidget::updateTitleBar() {
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
}
void ChartsWidget::addChart(const QString &id, const Signal *sig) {
void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show) {
auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; });
if (it == charts.end()) {
if (it != charts.end()) {
if (!show) removeChart((*it));
} else if (show) {
auto chart = new ChartWidget(id, sig, this);
chart->chart_view->updateSeries(display_range);
QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(chart); });;
QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(chart); });
QObject::connect(chart->chart_view, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
QObject::connect(chart->chart_view, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
charts_layout->insertWidget(0, chart);
charts.push_back(chart);
emit chartOpened(chart->id, chart->signal);
}
updateTitleBar();
}
bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) {
auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; });
return it != charts.end();
}
void ChartsWidget::removeChart(ChartWidget *chart) {
charts.removeOne(chart);
chart->deleteLater();
updateTitleBar();
emit chartClosed(chart->id, chart->signal);
}
void ChartsWidget::removeAll(const Signal *sig) {
@ -168,6 +177,7 @@ void ChartsWidget::removeAll(const Signal *sig) {
auto c = it.next();
if (sig == nullptr || c->signal == sig) {
c->deleteLater();
emit chartClosed(c->id, c->signal);
it.remove();
}
}
@ -184,7 +194,6 @@ void ChartsWidget::signalUpdated(const Signal *sig) {
}
}
bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
if (obj != this && event->type() == QEvent::Close) {
emit dock_btn->clicked();
@ -200,33 +209,31 @@ ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent)
main_layout->setSpacing(0);
main_layout->setContentsMargins(0, 0, 0, 0);
QWidget *header = new QWidget(this);
header->setStyleSheet("background-color:white");
header = new QWidget(this);
QGridLayout *header_layout = new QGridLayout(header);
header_layout->setContentsMargins(11, 11, 11, 0);
msg_name_label = new QLabel(this);
msg_name_label->setTextFormat(Qt::RichText);
header_layout->addWidget(msg_name_label, 0, 0, Qt::AlignLeft);
sig_name_label = new QLabel(this);
sig_name_label->setStyleSheet("font-weight:bold");
header_layout->addWidget(sig_name_label, 0, 1, Qt::AlignCenter); //, 0, Qt::AlignCenter);
QPushButton *remove_btn = new QPushButton("", this);
remove_btn = new QPushButton("", this);
remove_btn->setFixedSize(20, 20);
remove_btn->setToolTip(tr("Remove chart"));
header_layout->addWidget(remove_btn, 0, 2, Qt::AlignRight);
main_layout->addWidget(header);
chart_view = new ChartView(id, sig, this);
chart_view->setFixedHeight(settings.chart_height);
main_layout->addWidget(chart_view);
main_layout->addStretch();
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
updateTitle();
updateFromSettings();
QObject::connect(remove_btn, &QPushButton::clicked, [=]() { emit remove(id, sig); });
QObject::connect(&settings, &Settings::changed, [this]() { chart_view->setFixedHeight(settings.chart_height); });
QObject::connect(&settings, &Settings::changed, this, &ChartWidget::updateFromSettings);
}
void ChartWidget::updateTitle() {
@ -234,12 +241,22 @@ void ChartWidget::updateTitle() {
sig_name_label->setText(signal->name.c_str());
}
void ChartWidget::updateFromSettings() {
header->setStyleSheet(settings.chart_theme == 0 ? "background-color:white" : "background-color:#23242c");
QString color_style = settings.chart_theme == 0 ? "color:black" : "color:white";
sig_name_label->setStyleSheet("font-weight:bold;" + color_style);
msg_name_label->setStyleSheet(color_style);
remove_btn->setStyleSheet(color_style);
chart_view->updateFromSettings();
}
// ChartView
ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
: id(id), signal(sig), QChartView(nullptr, parent) {
QLineSeries *series = new QLineSeries();
QChart *chart = new QChart();
chart->setBackgroundRoundness(0);
chart->addSeries(series);
chart->createDefaultAxes();
chart->legend()->hide();
@ -247,11 +264,15 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
chart->layout()->setContentsMargins(0, 0, 0, 0);
track_line = new QGraphicsLineItem(chart);
track_line->setPen(QPen(Qt::gray, 1, Qt::DashLine));
value_text = new QGraphicsSimpleTextItem(chart);
value_text->setBrush(Qt::gray);
track_line->setZValue(chart->zValue() + 10);
track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine));
track_ellipse = new QGraphicsEllipseItem(chart);
track_ellipse->setZValue(chart->zValue() + 10);
track_ellipse->setBrush(Qt::darkGray);
value_text = new QGraphicsTextItem(chart);
value_text->setZValue(chart->zValue() + 10);
line_marker = new QGraphicsLineItem(chart);
line_marker->setPen(QPen(Qt::black, 2));
line_marker->setZValue(chart->zValue() + 10);
setChart(chart);
@ -274,6 +295,12 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
});
}
void ChartView::updateFromSettings() {
setFixedHeight(settings.chart_height);
chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark);
line_marker->setPen(QPen(settings.chart_theme == 0 ? Qt::black : Qt::white, 2));
}
void ChartView::setRange(double min, double max, bool force_update) {
auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
if (force_update || (min != axis_x->min() || max != axis_x->max())) {
@ -348,12 +375,14 @@ void ChartView::updateAxisY() {
void ChartView::enterEvent(QEvent *event) {
track_line->setVisible(true);
value_text->setVisible(true);
track_ellipse->setVisible(true);
QChartView::enterEvent(event);
}
void ChartView::leaveEvent(QEvent *event) {
track_line->setVisible(false);
value_text->setVisible(false);
track_ellipse->setVisible(false);
QChartView::leaveEvent(event);
}
@ -373,29 +402,39 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
// zoom in if selected range is greater than 0.5s
emit zoomIn(min, max);
}
event->accept();
} else if (event->button() == Qt::RightButton) {
emit zoomReset();
event->accept();
} else {
QGraphicsView::mouseReleaseEvent(event);
}
event->accept();
}
void ChartView::mouseMoveEvent(QMouseEvent *ev) {
auto rubber = findChild<QRubberBand *>();
bool dragging = rubber && rubber->isVisible();
if (!dragging) {
bool is_zooming = rubber && rubber->isVisible();
if (!is_zooming) {
const auto plot_area = chart()->plotArea();
float x = std::clamp((float)ev->pos().x(), (float)plot_area.left(), (float)plot_area.right());
track_line->setLine(x, plot_area.top(), x, plot_area.bottom());
auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
double sec = axis_x->min() + ((x - plot_area.x()) / plot_area.width()) * (axis_x->max() - axis_x->min());
auto value = std::lower_bound(vals.begin(), vals.end(), sec, [](auto &p, double x) { return p.x() < x; });
value_text->setPos(x + 6, plot_area.bottom() - 25);
double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right()-1);
double sec = axis_x->min() + (axis_x->max() - axis_x->min()) * (x - plot_area.left()) / plot_area.width();
auto value = std::upper_bound(vals.begin(), vals.end(), sec, [](double x, auto &p) { return x < p.x(); });
if (value != vals.end()) {
value_text->setText(QString("(%1, %2)").arg(value->x(), 0, 'f', 3).arg(value->y()));
} else {
value_text->setText("(--, --)");
QPointF pos = chart()->mapToPosition((*value));
track_line->setLine(pos.x(), plot_area.top(), pos.x(), plot_area.bottom());
track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10);
value_text->setHtml(tr("<div style='background-color:darkGray'><font color='white'>%1, %2)</font></div>")
.arg(value->x(), 0, 'f', 3).arg(value->y()));
int text_x = pos.x() + 8;
if ((text_x + value_text->boundingRect().width()) > plot_area.right()) {
text_x = pos.x() - value_text->boundingRect().width() - 8;
}
value_text->setPos(text_x, pos.y() - 10);
}
track_line->setVisible(value != vals.end());
value_text->setVisible(value != vals.end());
track_ellipse->setVisible(value != vals.end());
}
QChartView::mouseMoveEvent(ev);
}

@ -3,8 +3,9 @@
#include <map>
#include <QLabel>
#include <QGraphicsEllipseItem>
#include <QGraphicsLineItem>
#include <QGraphicsSimpleTextItem>
#include <QGraphicsTextItem>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
@ -23,6 +24,7 @@ public:
void updateSeries(const std::pair<double, double> &range);
void setRange(double min, double max, bool force_update = false);
void updateLineMarker(double current_sec);
void updateFromSettings();
signals:
void zoomIn(double min, double max);
@ -34,11 +36,11 @@ private:
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void adjustChartMargins();
void updateAxisY();
QGraphicsLineItem *track_line;
QGraphicsSimpleTextItem *value_text;
QGraphicsEllipseItem *track_ellipse;
QGraphicsTextItem *value_text;
QGraphicsLineItem *line_marker;
QList<QPointF> vals;
QString id;
@ -51,6 +53,7 @@ Q_OBJECT
public:
ChartWidget(const QString &id, const Signal *sig, QWidget *parent);
void updateTitle();
void updateFromSettings();
signals:
void remove(const QString &msg_id, const Signal *sig);
@ -58,8 +61,10 @@ signals:
public:
QString id;
const Signal *signal;
QWidget *header;
QLabel *msg_name_label;
QLabel *sig_name_label;
QPushButton *remove_btn;
ChartView *chart_view = nullptr;
};
@ -68,12 +73,15 @@ class ChartsWidget : public QWidget {
public:
ChartsWidget(QWidget *parent = nullptr);
void addChart(const QString &id, const Signal *sig);
void showChart(const QString &id, const Signal *sig, bool show);
void removeChart(ChartWidget *chart);
bool isChartOpened(const QString &id, const Signal *sig);
signals:
void dock(bool floating);
void rangeChanged(double min, double max, bool is_zommed);
void chartOpened(const QString &id, const Signal *sig);
void chartClosed(const QString &id, const Signal *sig);
private:
void eventsMerged();

@ -1,5 +1,6 @@
#include "tools/cabana/dbcmanager.h"
#include <limits>
#include <sstream>
#include <QVector>
@ -28,8 +29,25 @@ void DBCManager::open(const QString &name, const QString &content) {
emit DBCFileChanged();
}
void save(const QString &dbc_file_name) {
// TODO: save DBC to file
QString DBCManager::generateDBC() {
if (!dbc) return {};
QString dbc_string;
for (auto &m : dbc->msgs) {
dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(m.address).arg(m.name.c_str()).arg(m.size);
for (auto &sig : m.sigs) {
dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [0|0] \"\" XXX\n")
.arg(sig.name.c_str())
.arg(sig.start_bit)
.arg(sig.size)
.arg(sig.is_little_endian ? '1' : '0')
.arg(sig.is_signed ? '-' : '+')
.arg(sig.factor, 0, 'g', std::numeric_limits<double>::digits10)
.arg(sig.offset, 0, 'g', std::numeric_limits<double>::digits10);
}
dbc_string += "\n";
}
return dbc_string;
}
void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) {

@ -13,8 +13,7 @@ public:
void open(const QString &dbc_file_name);
void open(const QString &name, const QString &content);
void save(const QString &dbc_file_name);
QString generateDBC();
void addSignal(const QString &id, const Signal &sig);
void updateSignal(const QString &id, const QString &sig_name, const Signal &sig);
void removeSignal(const QString &id, const QString &sig_name);
@ -24,6 +23,7 @@ public:
inline QString name() const { return dbc_name; }
void updateMsg(const QString &id, const QString &name, uint32_t size);
inline const DBC *getDBC() const { return dbc; }
inline const Msg *msg(const QString &id) const { return msg(addressFromId(id)); }
inline const Msg *msg(uint32_t address) const {
auto it = msg_map.find(address);

@ -2,6 +2,7 @@
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
@ -11,25 +12,39 @@
// DetailWidget
DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) {
main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
main_layout->setSpacing(0);
right_column = new QVBoxLayout();
main_layout->addLayout(right_column);
binary_view_container = new QWidget(this);
binary_view_container->setMinimumWidth(500);
binary_view_container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
QVBoxLayout *bin_layout = new QVBoxLayout(binary_view_container);
bin_layout->setContentsMargins(0, 0, 0, 0);
bin_layout->setSpacing(0);
// tabbar
tabbar = new QTabBar(this);
tabbar->setTabsClosable(true);
tabbar->setDrawBase(false);
tabbar->setUsesScrollButtons(true);
tabbar->setAutoHide(true);
main_layout->addWidget(tabbar);
tabbar->setContextMenuPolicy(Qt::CustomContextMenu);
bin_layout->addWidget(tabbar);
// message title
QFrame *title_frame = new QFrame();
main_layout->addWidget(title_frame);
QVBoxLayout *frame_layout = new QVBoxLayout(title_frame);
TitleFrame *title_frame = new TitleFrame(this);
title_frame->setFrameShape(QFrame::StyledPanel);
title_frame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
QVBoxLayout *frame_layout = new QVBoxLayout(title_frame);
// message title
QHBoxLayout *title_layout = new QHBoxLayout();
split_btn = new QPushButton("", this);
split_btn->setFixedSize(20, 20);
split_btn->setToolTip(tr("Split to two columns"));
title_layout->addWidget(split_btn);
title_layout->addWidget(new QLabel("time:"));
time_label = new QLabel(this);
time_label->setStyleSheet("font-weight:bold");
@ -54,10 +69,12 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) {
warning_hlayout->addWidget(warning_label, 1, Qt::AlignLeft);
warning_widget->hide();
frame_layout->addWidget(warning_widget);
bin_layout->addWidget(title_frame);
// binary view
binary_view = new BinaryView(this);
main_layout->addWidget(binary_view, 0, Qt::AlignTop);
bin_layout->addWidget(binary_view);
right_column->addWidget(binary_view_container);
// signals
signals_container = new QWidget(this);
@ -68,33 +85,61 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) {
scroll->setWidget(signals_container);
scroll->setWidgetResizable(true);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
main_layout->addWidget(scroll);
right_column->addWidget(scroll);
// history log
history_log = new HistoryLog(this);
main_layout->addWidget(history_log);
right_column->addWidget(history_log);
QObject::connect(split_btn, &QPushButton::clicked, this, &DetailWidget::moveBinaryView);
QObject::connect(title_frame, &TitleFrame::doubleClicked, this, &DetailWidget::moveBinaryView);
QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg);
QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal);
QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal);
QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); });
QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) { setMessage(messages[index]); });
QObject::connect(tabbar, &QTabBar::tabCloseRequested, [=](int index) {
messages.removeAt(index);
tabbar->removeTab(index);
setMessage(messages.isEmpty() ? "" : messages[0]);
QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu);
QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) {
if (index != -1 && tabbar->tabText(index) != msg_id) {
setMessage(tabbar->tabText(index));
}
});
QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab);
QObject::connect(charts, &ChartsWidget::chartOpened, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, true); });
QObject::connect(charts, &ChartsWidget::chartClosed, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, false); });
}
void DetailWidget::showTabBarContextMenu(const QPoint &pt) {
int index = tabbar->tabAt(pt);
if (index >= 0) {
QMenu menu(this);
menu.addAction(tr("Close Other Tabs"));
if (menu.exec(tabbar->mapToGlobal(pt))) {
tabbar->setCurrentIndex(index);
// remove all tabs before the one to keep
for (int i = 0; i < index; ++i) {
tabbar->removeTab(0);
}
// remove all tabs after the one to keep
while (tabbar->count() > 1) {
tabbar->removeTab(1);
}
}
}
}
void DetailWidget::setMessage(const QString &message_id) {
if (message_id.isEmpty()) return;
int index = messages.indexOf(message_id);
int index = -1;
for (int i = 0; i < tabbar->count(); ++i) {
if (tabbar->tabText(i) == message_id) {
index = i;
break;
}
}
if (index == -1) {
messages.push_back(message_id);
tabbar->addTab(message_id);
index = tabbar->count() - 1;
index = tabbar->addTab(message_id);
auto msg = dbc()->msg(message_id);
tabbar->setTabToolTip(index, msg ? msg->name.c_str() : "untitled");
}
@ -114,13 +159,14 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) {
if (auto msg = dbc()->msg(msg_id)) {
for (int i = 0; i < msg->sigs.size(); ++i) {
auto form = new SignalEdit(i, msg_id, &(msg->sigs[i]));
form->setChartOpened(charts->isChartOpened(msg_id, &(msg->sigs[i])));
signals_container->layout()->addWidget(form);
QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]]() { emit showChart(msg_id, sig); });
QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm);
QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal);
QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal);
QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight);
QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered);
QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]](bool show) { charts->showChart(msg_id, sig, show); });
if (i == show_form_idx) {
QTimer::singleShot(0, [=]() { emit form->showFormClicked(); });
}
@ -155,6 +201,20 @@ void DetailWidget::updateState() {
history_log->updateState();
}
void DetailWidget::moveBinaryView() {
if (binview_in_left_col) {
right_column->insertWidget(0, binary_view_container);
emit binaryViewMoved(true);
} else {
main_layout->insertWidget(0, binary_view_container);
emit binaryViewMoved(false);
}
split_btn->setText(binview_in_left_col ? "" : "");
split_btn->setToolTip(binview_in_left_col ? tr("Split to two columns") : tr("Move back"));
binary_view->updateGeometry();
binview_in_left_col = !binview_in_left_col;
}
void DetailWidget::showForm() {
SignalEdit *sender = qobject_cast<SignalEdit *>(QObject::sender());
for (auto f : signals_container->findChildren<SignalEdit *>()) {
@ -165,6 +225,13 @@ void DetailWidget::showForm() {
}
}
void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool opened) {
if (id == msg_id) {
for (auto f : signals_container->findChildren<SignalEdit *>())
if (f->sig == sig) f->setChartOpened(opened);
}
}
void DetailWidget::editMsg() {
auto msg = dbc()->msg(msg_id);
QString name = msg ? msg->name.c_str() : "untitled";

@ -2,11 +2,22 @@
#include <QScrollArea>
#include <QTabBar>
#include <QVBoxLayout>
#include "tools/cabana/binaryview.h"
#include "tools/cabana/chartswidget.h"
#include "tools/cabana/historylog.h"
#include "tools/cabana/signaledit.h"
class TitleFrame : public QFrame {
Q_OBJECT
public:
TitleFrame(QWidget *parent) : QFrame(parent) {}
void mouseDoubleClickEvent(QMouseEvent *e) { emit doubleClicked(); }
signals:
void doubleClicked();
};
class EditMessageDialog : public QDialog {
Q_OBJECT
@ -30,15 +41,16 @@ class DetailWidget : public QWidget {
Q_OBJECT
public:
DetailWidget(QWidget *parent);
DetailWidget(ChartsWidget *charts, QWidget *parent);
void setMessage(const QString &message_id);
void dbcMsgChanged(int show_form_idx = -1);
signals:
void showChart(const QString &msg_id, const Signal *sig);
void removeChart(const Signal *sig);
void binaryViewMoved(bool in);
private:
void updateChartState(const QString &id, const Signal *sig, bool opened);
void showTabBarContextMenu(const QPoint &pt);
void addSignal(int start_bit, int to);
void resizeSignal(const Signal *sig, int from, int to);
void saveSignal(const Signal *sig, const Signal &new_sig);
@ -46,6 +58,7 @@ private:
void editMsg();
void showForm();
void updateState();
void moveBinaryView();
QString msg_id;
QLabel *name_label, *time_label, *warning_label;
@ -53,8 +66,13 @@ private:
QPushButton *edit_btn;
QWidget *signals_container;
QTabBar *tabbar;
QStringList messages;
QHBoxLayout *main_layout;
QVBoxLayout *right_column;
bool binview_in_left_col = false;
QWidget *binary_view_container;
QPushButton *split_btn;
HistoryLog *history_log;
BinaryView *binary_view;
ScrollArea *scroll;
ChartsWidget *charts;
};

@ -3,14 +3,13 @@
#include <QApplication>
#include <QHBoxLayout>
#include <QScreen>
#include <QSplitter>
#include <QVBoxLayout>
#include "tools/replay/util.h"
static MainWindow *main_win = nullptr;
void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
if (main_win) main_win->showStatusMessage(msg);
if (main_win) emit main_win->showMessage(msg, 0);
}
MainWindow::MainWindow() : QWidget() {
@ -22,14 +21,15 @@ MainWindow::MainWindow() : QWidget() {
h_layout->setContentsMargins(0, 0, 0, 0);
main_layout->addLayout(h_layout);
QSplitter *splitter = new QSplitter(Qt::Horizontal, this);
splitter = new QSplitter(Qt::Horizontal, this);
splitter->setHandleWidth(11);
messages_widget = new MessagesWidget(this);
splitter->addWidget(messages_widget);
detail_widget = new DetailWidget(this);
charts_widget = new ChartsWidget(this);
detail_widget = new DetailWidget(charts_widget, this);
splitter->addWidget(detail_widget);
splitter->setSizes({100, 500});
h_layout->addWidget(splitter);
// right widgets
@ -51,13 +51,13 @@ MainWindow::MainWindow() : QWidget() {
video_widget = new VideoWidget(this);
r_layout->addWidget(video_widget, 0, Qt::AlignTop);
charts_widget = new ChartsWidget(this);
r_layout->addWidget(charts_widget);
h_layout->addWidget(right_container);
// status bar
status_bar = new QStatusBar(this);
status_bar->setFixedHeight(20);
status_bar->setContentsMargins(0, 0, 0, 0);
status_bar->setSizeGripEnabled(true);
progress_bar = new QProgressBar();
@ -72,16 +72,16 @@ MainWindow::MainWindow() : QWidget() {
qRegisterMetaType<ReplyMsgType>("ReplyMsgType");
installMessageHandler([this](ReplyMsgType type, const std::string msg) {
// use queued connection to recv the log messages from replay.
emit logMessageFromReplay(QString::fromStdString(msg), 3000);
emit showMessage(QString::fromStdString(msg), 3000);
});
installDownloadProgressHandler([this](uint64_t cur, uint64_t total, bool success) {
emit updateProgressBar(cur, total, success);
});
QObject::connect(this, &MainWindow::logMessageFromReplay, status_bar, &QStatusBar::showMessage);
QObject::connect(this, &MainWindow::showMessage, status_bar, &QStatusBar::showMessage);
QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage);
QObject::connect(detail_widget, &DetailWidget::showChart, charts_widget, &ChartsWidget::addChart);
QObject::connect(detail_widget, &DetailWidget::binaryViewMoved, [this](bool in) { splitter->setSizes({in ? 100 : 0, 500}); });
QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts);
QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged);
QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption);
@ -101,7 +101,6 @@ void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool succe
}
}
void MainWindow::dockCharts(bool dock) {
if (dock && floating_window) {
floating_window->removeEventFilter(charts_widget);

@ -1,6 +1,7 @@
#pragma once
#include <QProgressBar>
#include <QSplitter>
#include <QStatusBar>
#include "tools/cabana/chartswidget.h"
@ -17,7 +18,7 @@ public:
void showStatusMessage(const QString &msg, int timeout = 0) { status_bar->showMessage(msg, timeout); }
signals:
void logMessageFromReplay(const QString &msg, int timeout);
void showMessage(const QString &msg, int timeout);
void updateProgressBar(uint64_t cur, uint64_t total, bool success);
protected:
@ -29,6 +30,7 @@ protected:
MessagesWidget *messages_widget;
DetailWidget *detail_widget;
ChartsWidget *charts_widget;
QSplitter *splitter;
QWidget *floating_window = nullptr;
QVBoxLayout *r_layout;
QProgressBar *progress_bar;

@ -3,6 +3,8 @@
#include <QCompleter>
#include <QDialogButtonBox>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QFontDatabase>
#include <QHeaderView>
#include <QLineEdit>
@ -69,9 +71,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
QObject::connect(can, &CANMessages::updated, [this]() { model->updateState(); });
QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &)));
QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste);
QObject::connect(save_btn, &QPushButton::clicked, [=]() {
// TODO: save DBC to file
});
QObject::connect(save_btn, &QPushButton::clicked, this, &MessagesWidget::saveDBC);
QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) {
if (current.isValid()) {
emit msgSelectionChanged(current.data(Qt::UserRole).toString());
@ -79,16 +79,18 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
});
QFile json_file("./car_fingerprint_to_dbc.json");
if(json_file.open(QIODevice::ReadOnly)) {
if (json_file.open(QIODevice::ReadOnly)) {
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
}
}
void MessagesWidget::loadDBCFromName(const QString &name) {
dbc()->open(name);
dbc_combo->setCurrentText(name);
// refresh model
model->updateState();
if (name != dbc()->name()) {
dbc()->open(name);
dbc_combo->setCurrentText(name);
// re-sort model to refresh column 'Name'
model->updateState(true);
}
}
void MessagesWidget::loadDBCFromPaste() {
@ -96,19 +98,26 @@ void MessagesWidget::loadDBCFromPaste() {
if (dlg.exec()) {
dbc()->open("from paste", dlg.dbc_edit->toPlainText());
dbc_combo->setCurrentText("loaded from paste");
model->updateState(true);
}
}
void MessagesWidget::loadDBCFromFingerprint() {
auto fingerprint = can->carFingerprint();
if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) {
if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) {
auto dbc_name = fingerprint_to_dbc[fingerprint];
if (dbc_name != QJsonValue::Undefined) {
if (dbc_name != QJsonValue::Undefined) {
loadDBCFromName(dbc_name.toString());
}
}
}
void MessagesWidget::saveDBC() {
SaveDBCDialog dlg(this);
dlg.dbc_edit->setText(dbc()->generateDBC());
dlg.exec();
}
// MessageListModel
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const {
@ -222,6 +231,8 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
}
}
// LoadDBCDialog
LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
dbc_edit = new QTextEdit(this);
@ -231,7 +242,48 @@ LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) {
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox);
setFixedWidth(640);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
setMinimumSize({640, 480});
QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
// SaveDBCDialog
SaveDBCDialog::SaveDBCDialog(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Save DBC"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
dbc_edit = new QTextEdit(this);
dbc_edit->setAcceptRichText(false);
main_layout->addWidget(dbc_edit);
QPushButton *copy_to_clipboard = new QPushButton(tr("Copy To Clipboard"), this);
QPushButton *save_as = new QPushButton(tr("Save As"), this);
QHBoxLayout *btn_layout = new QHBoxLayout();
btn_layout->addStretch();
btn_layout->addWidget(copy_to_clipboard);
btn_layout->addWidget(save_as);
main_layout->addLayout(btn_layout);
setMinimumSize({640, 480});
QObject::connect(copy_to_clipboard, &QPushButton::clicked, this, &SaveDBCDialog::copytoClipboard);
QObject::connect(save_as, &QPushButton::clicked, this, &SaveDBCDialog::saveAs);
}
void SaveDBCDialog::copytoClipboard() {
dbc_edit->selectAll();
dbc_edit->copy();
QDialog::accept();
}
void SaveDBCDialog::saveAs() {
QString file_name = QFileDialog::getSaveFileName(this, tr("Save File"),
QDir::homePath() + "/untitled.dbc", tr("DBC (*.dbc)"));
if (!file_name.isEmpty()) {
QFile file(file_name);
if (file.open(QIODevice::WriteOnly)) {
file.write(dbc_edit->toPlainText().toUtf8());
}
QDialog::accept();
}
}

@ -17,6 +17,16 @@ public:
QTextEdit *dbc_edit;
};
class SaveDBCDialog : public QDialog {
Q_OBJECT
public:
SaveDBCDialog(QWidget *parent);
void copytoClipboard();
void saveAs();
QTextEdit *dbc_edit;
};
class MessageListModel : public QAbstractTableModel {
Q_OBJECT
@ -52,6 +62,7 @@ public slots:
void loadDBCFromName(const QString &name);
void loadDBCFromFingerprint();
void loadDBCFromPaste();
void saveDBC();
signals:
void msgSelectionChanged(const QString &message_id);

@ -17,6 +17,7 @@ void Settings::save() {
s.setValue("log_size", can_msg_log_size);
s.setValue("cached_segment", cached_segment_limit);
s.setValue("chart_height", chart_height);
s.setValue("chart_theme", chart_theme);
s.setValue("max_chart_x_range", max_chart_x_range);
emit changed();
}
@ -27,6 +28,7 @@ void Settings::load() {
can_msg_log_size = s.value("log_size", 100).toInt();
cached_segment_limit = s.value("cached_segment", 3).toInt();
chart_height = s.value("chart_height", 200).toInt();
chart_theme = s.value("chart_theme", 0).toInt();
max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt();
}
@ -53,19 +55,24 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
cached_segment->setRange(3, 60);
cached_segment->setSingleStep(1);
cached_segment->setValue(settings.cached_segment_limit);
form_layout->addRow(tr("Cached segments limit(minute)"), cached_segment);
form_layout->addRow(tr("Cached segments limit"), cached_segment);
max_chart_x_range = new QSpinBox(this);
max_chart_x_range->setRange(1, 60);
max_chart_x_range->setSingleStep(1);
max_chart_x_range->setValue(settings.max_chart_x_range / 60);
form_layout->addRow(tr("Chart's max X-axis range(minute)"), max_chart_x_range);
form_layout->addRow(tr("Chart range (minutes)"), max_chart_x_range);
chart_height = new QSpinBox(this);
chart_height->setRange(100, 500);
chart_height->setSingleStep(10);
chart_height->setValue(settings.chart_height);
form_layout->addRow(tr("Chart's height"), chart_height);
form_layout->addRow(tr("Chart height"), chart_height);
chart_theme = new QComboBox();
chart_theme->addItems({"Light", "Dark"});
chart_theme->setCurrentIndex(settings.chart_theme == 1 ? 1 : 0);
form_layout->addRow(tr("Chart theme"), chart_theme);
main_layout->addLayout(form_layout);
@ -82,6 +89,7 @@ void SettingsDlg::save() {
settings.can_msg_log_size = log_size->value();
settings.cached_segment_limit = cached_segment->value();
settings.chart_height = chart_height->value();
settings.chart_theme = chart_theme->currentIndex();
settings.max_chart_x_range = max_chart_x_range->value() * 60;
settings.save();
accept();

@ -1,5 +1,6 @@
#pragma once
#include <QComboBox>
#include <QDialog>
#include <QSpinBox>
@ -15,6 +16,7 @@ public:
int can_msg_log_size = 100;
int cached_segment_limit = 3;
int chart_height = 200;
int chart_theme = 0;
int max_chart_x_range = 3 * 60; // 3 minutes
signals:
@ -31,6 +33,7 @@ public:
QSpinBox *log_size ;
QSpinBox *cached_segment;
QSpinBox *chart_height;
QComboBox *chart_theme;
QSpinBox *max_chart_x_range;
};

@ -82,15 +82,14 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal *sig, QWid
title_layout->addWidget(title, 1);
QPushButton *seek_btn = new QPushButton("");
seek_btn->setStyleSheet("font-weight:bold;font-size:20px");
seek_btn->setStyleSheet("QPushButton{font-weight:bold;font-size:18px}");
seek_btn->setToolTip(tr("Find signal values"));
seek_btn->setFixedSize(20, 20);
seek_btn->setFixedSize(25, 25);
title_layout->addWidget(seek_btn);
QPushButton *plot_btn = new QPushButton("📈");
plot_btn->setToolTip(tr("Show Plot"));
plot_btn->setFixedSize(20, 20);
QObject::connect(plot_btn, &QPushButton::clicked, this, &SignalEdit::showChart);
plot_btn = new QPushButton(this);
plot_btn->setStyleSheet("QPushButton {font-size:18px}");
plot_btn->setFixedSize(25, 25);
title_layout->addWidget(plot_btn);
main_layout->addLayout(title_layout);
@ -120,6 +119,7 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal *sig, QWid
QObject::connect(remove_btn, &QPushButton::clicked, [this]() { emit remove(this->sig); });
QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked);
QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::saveSignal);
QObject::connect(plot_btn, &QPushButton::clicked, [this]() { emit showChart(!chart_opened); });
QObject::connect(seek_btn, &QPushButton::clicked, [this, msg_id]() {
SignalFindDlg dlg(msg_id, this->sig, this);
dlg.exec();
@ -145,6 +145,12 @@ void SignalEdit::saveSignal() {
emit save(this->sig, s);
}
void SignalEdit::setChartOpened(bool opened) {
plot_btn->setText(opened ? "" : "📈");
plot_btn->setToolTip(opened ? tr("Close Plot") :tr("Show Plot"));
chart_opened = opened;
}
void SignalEdit::setFormVisible(bool visible) {
form_container->setVisible(visible);
icon->setText(visible ? "" : ">");

@ -26,13 +26,15 @@ class SignalEdit : public QWidget {
public:
SignalEdit(int index, const QString &msg_id, const Signal *sig, QWidget *parent = nullptr);
void setChartOpened(bool opened);
void setFormVisible(bool show);
void signalHovered(const Signal *sig);
inline bool isFormVisible() const { return form_container->isVisible(); }
const Signal *sig = nullptr;
signals:
void highlight(const Signal *sig);
void showChart();
void showChart(bool show);
void showFormClicked();
void remove(const Signal *sig);
void save(const Signal *sig, const Signal &new_sig);
@ -48,7 +50,8 @@ protected:
QLabel *icon;
int form_idx = 0;
QString msg_id;
const Signal *sig = nullptr;
bool chart_opened = false;
QPushButton *plot_btn;
};
class SignalFindDlg : public QDialog {

@ -0,0 +1,4 @@
#!/bin/sh
cd "$(dirname "$0")"
export LD_LIBRARY_PATH="../../../opendbc/can:$LD_LIBRARY_PATH"
exec ./_test_cabana "$1"

@ -0,0 +1,35 @@
#include "catch2/catch.hpp"
#include "tools/cabana/dbcmanager.h"
TEST_CASE("DBCManager::generateDBC") {
DBCManager dbc_origin(nullptr);
dbc_origin.open("toyota_new_mc_pt_generated");
QString dbc_string = dbc_origin.generateDBC();
DBCManager dbc_from_generated(nullptr);
dbc_from_generated.open("", dbc_string);
auto dbc = dbc_origin.getDBC();
auto new_dbc = dbc_from_generated.getDBC();
REQUIRE(dbc->msgs.size() == new_dbc->msgs.size());
for (int i = 0; i < dbc->msgs.size(); ++i) {
REQUIRE(dbc->msgs[i].name == new_dbc->msgs[i].name);
REQUIRE(dbc->msgs[i].address == new_dbc->msgs[i].address);
REQUIRE(dbc->msgs[i].size == new_dbc->msgs[i].size);
REQUIRE(dbc->msgs[i].sigs.size() == new_dbc->msgs[i].sigs.size());
auto &sig = dbc->msgs[i].sigs;
auto &new_sig = new_dbc->msgs[i].sigs;
for (int j = 0; j < sig.size(); ++j) {
REQUIRE(sig[j].name == new_sig[j].name);
REQUIRE(sig[j].start_bit == new_sig[j].start_bit);
REQUIRE(sig[j].msb == new_sig[j].msb);
REQUIRE(sig[j].lsb == new_sig[j].lsb);
REQUIRE(sig[j].size == new_sig[j].size);
REQUIRE(sig[j].is_signed == new_sig[j].is_signed);
REQUIRE(sig[j].factor == new_sig[j].factor);
REQUIRE(sig[j].offset == new_sig[j].offset);
REQUIRE(sig[j].is_little_endian == new_sig[j].is_little_endian);
}
}
}

@ -0,0 +1,10 @@
#define CATCH_CONFIG_RUNNER
#include "catch2/catch.hpp"
#include <QCoreApplication>
int main(int argc, char **argv) {
// unit tests for Qt
QCoreApplication app(argc, argv);
const int res = Catch::Session().run(argc, argv);
return (res < 0xff ? res : 0xff);
}

@ -17,7 +17,6 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
// TODO: figure out why the CameraWidget crashed occasionally.
cam_widget = new CameraWidget("camerad", VISION_STREAM_ROAD, false, this);
cam_widget->setFixedSize(parent->width(), parent->width() / 1.596);
main_layout->addWidget(cam_widget);

@ -3,8 +3,8 @@
# NOTE: can only run inside limeGPS test box!
# run limeGPS with random static location
timeout 300 ./simulate_gps_signal.py &
gps_PID=$?
timeout 300 ./simulate_gps_signal.py 32.7518 -117.1962 &
gps_PID=$(ps -aux | grep -m 1 "timeout 300" | cut -d ' ' -f 7)
echo "starting limeGPS..."
sleep 10

@ -65,7 +65,7 @@ def verify_ubloxgnss_data(socket: messaging.SubSocket, max_time: int):
sat_count.append(event.ubloxGnss.measurementReport.numMeas)
num_sat = int(sum(sat_count)/len(sat_count))
assert num_sat > 5, f"Not enough satellites {num_sat} (TestBox setup!)"
assert num_sat >= 5, f"Not enough satellites {num_sat} (TestBox setup!)"
def verify_gps_location(socket: messaging.SubSocket, max_time: int):
@ -188,4 +188,4 @@ class TestGPS(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
unittest.main()

@ -89,7 +89,7 @@ def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=No
query = parse_qs(urlparse(route_or_segment_name).query)
route_or_segment_name = query["route"][0]
if route_or_segment_name.startswith(("http://", "https://")) or os.path.isfile(route_or_segment_name):
if route_or_segment_name.startswith(("http://", "https://", "cd:/")) or os.path.isfile(route_or_segment_name):
logs = [route_or_segment_name]
elif ci:
route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True)

@ -82,11 +82,10 @@ class Camerad:
self.queue = cl.CommandQueue(self.ctx)
cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W * 3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG "
# TODO: move rgb_to_yuv.cl to local dir once the frame stream camera is removed
kernel_fn = os.path.join(BASEDIR, "system", "camerad", "transforms", "rgb_to_yuv.cl")
kernel_fn = os.path.join(BASEDIR, "tools/sim/rgb_to_nv12.cl")
with open(kernel_fn) as f:
prg = cl.Program(self.ctx, f.read()).build(cl_arg)
self.krnl = prg.rgb_to_yuv
self.krnl = prg.rgb_to_nv12
self.Wdiv4 = W // 4 if (W % 4 == 0) else (W + (4 - W % 4)) // 4
self.Hdiv4 = H // 4 if (H % 4 == 0) else (H + (4 - H % 4)) // 4

@ -29,23 +29,21 @@ inline void convert_4_ys(__global uchar * out_yuv, int yi, const uchar8 rgbs1, c
vstore4(yy, 0, out_yuv + yi);
}
inline void convert_uv(__global uchar * out_yuv, int ui, int vi,
inline void convert_uv(__global uchar * out_yuv, int uvi,
const uchar8 rgbs1, const uchar8 rgbs2) {
// U & V: average of 2x2 pixels square
const short ab = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3);
const short ag = AVERAGE(rgbs1.s1, rgbs1.s4, rgbs2.s1, rgbs2.s4);
const short ar = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5);
#ifdef CL_DEBUG
if(ui >= RGB_SIZE + RGB_SIZE / 4)
printf("U overflow, %d >= %d\n", ui, RGB_SIZE + RGB_SIZE / 4);
if(vi >= RGB_SIZE + RGB_SIZE / 2)
printf("V overflow, %d >= %d\n", vi, RGB_SIZE + RGB_SIZE / 2);
if(uvi >= RGB_SIZE + RGB_SIZE / 2)
printf("UV overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2);
#endif
out_yuv[ui] = RGB_TO_U(ar, ag, ab);
out_yuv[vi] = RGB_TO_V(ar, ag, ab);
out_yuv[uvi] = RGB_TO_U(ar, ag, ab);
out_yuv[uvi+1] = RGB_TO_V(ar, ag, ab);
}
inline void convert_2_uvs(__global uchar * out_yuv, int ui, int vi,
inline void convert_2_uvs(__global uchar * out_yuv, int uvi,
const uchar8 rgbs1, const uchar8 rgbs2, const uchar8 rgbs3, const uchar8 rgbs4) {
// U & V: average of 2x2 pixels square
const short ab1 = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3);
@ -54,25 +52,20 @@ inline void convert_2_uvs(__global uchar * out_yuv, int ui, int vi,
const short ab2 = AVERAGE(rgbs1.s6, rgbs3.s1, rgbs2.s6, rgbs4.s1);
const short ag2 = AVERAGE(rgbs1.s7, rgbs3.s2, rgbs2.s7, rgbs4.s2);
const short ar2 = AVERAGE(rgbs3.s0, rgbs3.s3, rgbs4.s0, rgbs4.s3);
uchar2 u2 = (uchar2)(
uchar4 uv = (uchar4)(
RGB_TO_U(ar1, ag1, ab1),
RGB_TO_U(ar2, ag2, ab2)
);
uchar2 v2 = (uchar2)(
RGB_TO_V(ar1, ag1, ab1),
RGB_TO_U(ar2, ag2, ab2),
RGB_TO_V(ar2, ag2, ab2)
);
#ifdef CL_DEBUG1
if(ui > RGB_SIZE + RGB_SIZE / 4 - 2)
printf("U 2 overflow, %d >= %d\n", ui, RGB_SIZE + RGB_SIZE / 4 - 2);
if(vi > RGB_SIZE + RGB_SIZE / 2 - 2)
printf("V 2 overflow, %d >= %d\n", vi, RGB_SIZE + RGB_SIZE / 2 - 2);
if(uvi > RGB_SIZE + RGB_SIZE / 2 - 4)
printf("UV2 overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2 - 2);
#endif
vstore2(u2, 0, out_yuv + ui);
vstore2(v2, 0, out_yuv + vi);
vstore4(uv, 0, out_yuv + uvi);
}
__kernel void rgb_to_yuv(__global uchar const * const rgb,
__kernel void rgb_to_nv12(__global uchar const * const rgb,
__global uchar * out_yuv)
{
const int dx = get_global_id(0);
@ -81,8 +74,7 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb,
const int row = mul24(dy, 4); // Current row in rgb image
const int bgri_start = mad24(row, RGB_STRIDE, mul24(col, 3)); // Start offset of rgb data being converted
const int yi_start = mad24(row, WIDTH, col); // Start offset in the target yuv buffer
int ui = mad24(row / 2, UV_WIDTH, RGB_SIZE + col / 2);
int vi = mad24(row / 2 , UV_WIDTH, RGB_SIZE + UV_WIDTH * UV_HEIGHT + col / 2);
int uvi = mad24(row / 2, WIDTH, RGB_SIZE + col);
int num_col = min(WIDTH - col, 4);
int num_row = min(HEIGHT - row, 4);
if(num_row == 4) {
@ -99,15 +91,15 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb,
convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1);
convert_4_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0, rgbs2_1);
convert_4_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0, rgbs3_1);
convert_2_uvs(out_yuv, ui, vi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1);
convert_2_uvs(out_yuv, ui + UV_WIDTH, vi + UV_WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1);
convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1);
convert_2_uvs(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1);
} else if(num_col == 2) {
convert_2_ys(out_yuv, yi_start, rgbs0_0);
convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0);
convert_2_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0);
convert_2_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0);
convert_uv(out_yuv, ui, vi, rgbs0_0, rgbs1_0);
convert_uv(out_yuv, ui + UV_WIDTH, vi + UV_WIDTH, rgbs2_0, rgbs3_0);
convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0);
convert_uv(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0);
}
} else {
const uchar8 rgbs0_0 = vload8(0, rgb + bgri_start);
@ -117,11 +109,11 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb,
if(num_col == 4) {
convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1);
convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1);
convert_2_uvs(out_yuv, ui, vi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1);
convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1);
} else if(num_col == 2) {
convert_2_ys(out_yuv, yi_start, rgbs0_0);
convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0);
convert_uv(out_yuv, ui, vi, rgbs0_0, rgbs1_0);
convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0);
}
}
}
Loading…
Cancel
Save