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 &&\ ./selfdrive/loggerd/tests/test_logger &&\
./system/proclogd/tests/test_proclog && \ ./system/proclogd/tests/test_proclog && \
./tools/replay/tests/test_replay && \ ./tools/replay/tests/test_replay && \
./tools/cabana/tests/test_cabana && \
./system/camerad/test/ae_gray_test && \ ./system/camerad/test/ae_gray_test && \
coverage xml" coverage xml"
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"

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

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

@ -61,10 +61,10 @@ class FordCarInfo(CarInfo):
CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
CAR.ESCAPE_MK4: [ CAR.ESCAPE_MK4: [
FordCarInfo("Ford Escape 2020"), FordCarInfo("Ford Escape 2020-21"),
FordCarInfo("Ford Kuga EU", "Driver Assistance Pack"), 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"), CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"),
} }
@ -87,31 +87,41 @@ FW_QUERY_CONFIG = FwQueryConfig(
FW_VERSIONS = { FW_VERSIONS = {
CAR.ESCAPE_MK4: { CAR.ESCAPE_MK4: {
(Ecu.eps, 0x730, None): [ (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', b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x760, None): [ (Ecu.abs, 0x760, None): [
b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 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): [ (Ecu.fwdRadar, 0x764, None): [
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdCamera, 0x706, None): [ (Ecu.fwdCamera, 0x706, None): [
b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 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): [ (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'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): [ (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: { CAR.EXPLORER_MK6: {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [
b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 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'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): [ (Ecu.abs, 0x760, None): [
b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 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-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): [ (Ecu.fwdRadar, 0x764, None): [
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -123,10 +133,12 @@ FW_VERSIONS = {
(Ecu.engine, 0x7E0, None): [ (Ecu.engine, 0x7E0, None): [
b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', 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'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): [ (Ecu.shiftByWire, 0x732, None): [
b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 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-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: { CAR.FOCUS_MK4: {

@ -220,23 +220,17 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]]
tire_stiffness_factor = 0.677 tire_stiffness_factor = 0.677
elif candidate == CAR.ODYSSEY: elif candidate in (CAR.ODYSSEY, CAR.ODYSSEY_CHN):
ret.mass = 4471. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 1900. + STD_CARGO_KG
ret.wheelbase = 3.00 ret.wheelbase = 3.00
ret.centerToFront = ret.wheelbase * 0.41 ret.centerToFront = ret.wheelbase * 0.41
ret.steerRatio = 14.35 # as spec 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 tire_stiffness_factor = 0.82
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] 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): elif candidate in (CAR.PILOT, CAR.PASSPORT):
ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight

@ -1031,6 +1031,23 @@ FW_VERSIONS = {
b'54008-THR-A020\x00\x00', 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: { CAR.PILOT: {
(Ecu.shiftByWire, 0x18da0bf1, None): [ (Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-TG7-A520\x00\x00', b'54008-TG7-A520\x00\x00',

@ -49,6 +49,7 @@ class CarController:
self.angle_limit_counter = 0 self.angle_limit_counter = 0
self.frame = 0 self.frame = 0
self.accel_last = 0
self.apply_steer_last = 0 self.apply_steer_last = 0
self.car_fingerprint = CP.carFingerprint self.car_fingerprint = CP.carFingerprint
self.last_button_frame = 0 self.last_button_frame = 0
@ -123,8 +124,9 @@ class CarController:
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl:
can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame))
if self.frame % 2 == 0: 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)) set_speed_in_units))
self.accel_last = accel
else: else:
# button presses # button presses
if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: if (self.frame - self.last_button_frame) * DT_CTRL > 0.25:

@ -196,10 +196,10 @@ class CarState(CarStateBase):
if not self.CP.openpilotLongitudinalControl: if not self.CP.openpilotLongitudinalControl:
speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS 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 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.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor
ret.cruiseState.standstill = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STANDSTILL"] == 1 ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1
ret.cruiseState.enabled = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STATUS"] != 0 ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2)
self.cruise_info = copy.copy(cp_cruise_info.vl["CRUISE_INFO"]) 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" 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] 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: if CP.flags & HyundaiFlags.CANFD_HDA2 and not CP.openpilotLongitudinalControl:
signals += [ signals += [
("CRUISE_STATUS", "CRUISE_INFO"), ("ACCMode", "SCC_CONTROL"),
("SET_SPEED", "CRUISE_INFO"), ("VSetDis", "SCC_CONTROL"),
("CRUISE_STANDSTILL", "CRUISE_INFO"), ("CRUISE_STANDSTILL", "SCC_CONTROL"),
] ]
checks += [ checks += [
("CRUISE_INFO", 50), ("SCC_CONTROL", 50),
] ]
if CP.carFingerprint in EV_CAR: if CP.carFingerprint in EV_CAR:
@ -497,21 +497,20 @@ class CarState(CarStateBase):
checks = [("CAM_0x2a4", 20)] checks = [("CAM_0x2a4", 20)]
else: else:
signals = [ signals = [
("COUNTER", "CRUISE_INFO"), ("COUNTER", "SCC_CONTROL"),
("NEW_SIGNAL_1", "CRUISE_INFO"), ("NEW_SIGNAL_1", "SCC_CONTROL"),
("CRUISE_MAIN", "CRUISE_INFO"), ("MainMode_ACC", "SCC_CONTROL"),
("CRUISE_STATUS", "CRUISE_INFO"), ("ACCMode", "SCC_CONTROL"),
("CRUISE_INACTIVE", "CRUISE_INFO"), ("CRUISE_INACTIVE", "SCC_CONTROL"),
("ZEROS_9", "CRUISE_INFO"), ("ZEROS_9", "SCC_CONTROL"),
("CRUISE_STANDSTILL", "CRUISE_INFO"), ("CRUISE_STANDSTILL", "SCC_CONTROL"),
("ZEROS_5", "CRUISE_INFO"), ("ZEROS_5", "SCC_CONTROL"),
("DISTANCE_SETTING", "CRUISE_INFO"), ("DISTANCE_SETTING", "SCC_CONTROL"),
("SET_SPEED", "CRUISE_INFO"), ("VSetDis", "SCC_CONTROL"),
("NEW_SIGNAL_4", "CRUISE_INFO"),
] ]
checks = [ checks = [
("CRUISE_INFO", 50), ("SCC_CONTROL", 50),
] ]
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 6) 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 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): def create_acc_cancel(packer, CP, cruise_info_copy):
values = cruise_info_copy values = cruise_info_copy
values.update({ values.update({
"CRUISE_STATUS": 0, "ACCMode": 4,
"CRUISE_INACTIVE": 1,
}) })
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): def create_lfahda_cluster(packer, CP, enabled):
values = { 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) 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): def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_override, set_speed):
cruise_status = 0 if not enabled else (4 if gas_override else 2) jerk = 5
jn = jerk / 50
if not enabled or gas_override: 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 = { values = {
"CRUISE_STATUS": cruise_status, "ACCMode": 0 if not enabled else (2 if gas_override else 1),
"CRUISE_INACTIVE": 0 if enabled else 1, "MainMode_ACC": 1,
"CRUISE_MAIN": 1, "StopReq": 1 if stopping else 0,
"CRUISE_STANDSTILL": 0, "aReqValue": a_val,
"STOP_REQ": 1 if stopping else 0, "aReqRaw": a_raw,
"ACCEL_REQ": accel, "VSetDis": set_speed,
"ACCEL_REQ2": accel, "JerkLowerLimit": jerk if enabled else 1,
"SET_SPEED": set_speed,
"DISTANCE_SETTING": 4,
"ACC_ObjDist": 1, "ACC_ObjDist": 1,
"ObjValid": 1, "ObjValid": 0,
"OBJ_STATUS": 2, "OBJ_STATUS": 2,
"SET_ME_2": 0x2, "SET_ME_2": 0x4,
"SET_ME_3": 0x3, "SET_ME_3": 0x3,
"SET_ME_TMP_64": 0x64, "SET_ME_TMP_64": 0x64,
"NEW_SIGNAL_9": 2,
"NEW_SIGNAL_10": 4, "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: if candidate in CANFD_CAR:
ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kpV = [0.1]
ret.longitudinalTuning.kiV = [0.0] ret.longitudinalTuning.kiV = [0.0]
ret.longitudinalActuatorDelayLowerBound = 0.15
ret.longitudinalActuatorDelayUpperBound = 0.5
ret.experimentalLongitudinalAvailable = bool(ret.flags & HyundaiFlags.CANFD_HDA2) ret.experimentalLongitudinalAvailable = bool(ret.flags & HyundaiFlags.CANFD_HDA2)
else: else:
ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kpV = [0.5]
ret.longitudinalTuning.kiV = [0.0] 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.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR)
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
ret.pcmCruise = not ret.openpilotLongitudinalControl ret.pcmCruise = not ret.openpilotLongitudinalControl
@ -218,6 +214,8 @@ class CarInterface(CarInterfaceBase):
ret.startingState = True ret.startingState = True
ret.vEgoStarting = 0.1 ret.vEgoStarting = 0.1
ret.startAccel = 2.0 ret.startAccel = 2.0
ret.longitudinalActuatorDelayLowerBound = 0.5
ret.longitudinalActuatorDelayUpperBound = 0.5
# *** feature detection *** # *** feature detection ***
if candidate in CANFD_CAR: 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\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\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',
b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06',
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', 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-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-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-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): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',

@ -25,6 +25,7 @@ non_tested_cars = [
GM.BOLT_EV, GM.BOLT_EV,
HYUNDAI.GENESIS_G90, HYUNDAI.GENESIS_G90,
HYUNDAI.KIA_OPTIMA_H, HYUNDAI.KIA_OPTIMA_H,
HONDA.ODYSSEY_CHN,
] ]
CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,)) 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("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED),
CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV), CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV),
CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX), 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("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T
CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T 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 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
assert cls.CP.carFingerprint == cls.car_model assert cls.CP.carFingerprint == cls.car_model
@classmethod
def tearDownClass(cls):
del cls.can_msgs
def setUp(self): def setUp(self):
self.CI = self.CarInterface(self.CP, self.CarController, self.CarState) self.CI = self.CarInterface(self.CP, self.CarController, self.CarState)
assert self.CI assert self.CI
@ -233,7 +237,6 @@ class TestCarModelBase(unittest.TestCase):
# TODO: check rest of panda's carstate (steering, ACC main on, etc.) # TODO: check rest of panda's carstate (steering, ACC main on, etc.)
checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() 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"): if self.CP.carName not in ("hyundai", "volkswagen", "body"):
# TODO: fix standstill mismatches for other makes # TODO: fix standstill mismatches for other makes
checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving() 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] COMMA BODY: [.nan, 1000, .nan]
# Totally new cars # Totally new cars
KIA EV6 2022: [3.5, 3.0, 0.0]
RAM 1500 5TH GEN: [2.0, 2.0, 0.0] RAM 1500 5TH GEN: [2.0, 2.0, 0.0]
RAM HD 5TH GEN: [1.4, 1.4, 0.0] RAM HD 5TH GEN: [1.4, 1.4, 0.0]
SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] 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] HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382]
JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185] JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185]
JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003] 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 K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716]
KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267]
KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558] KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558]
KIA STINGER GT2 2018: [2.7499043387418967, 1.849652021986449, 0.12048334239559202] KIA STINGER GT2 2018: [2.7499043387418967, 1.849652021986449, 0.12048334239559202]
LEXUS ES 2019: [2.0203086922726112, 2.134803912579666, 0.12757526789308554] LEXUS ES 2019: [1.935835, 2.134803912579666, 0.093439]
LEXUS ES HYBRID 2019: [2.392442298703042, 1.863360677810788, 0.17690002108856212] LEXUS ES HYBRID 2019: [2.135678, 1.863360677810788, 0.109627]
LEXUS NX 2018: [2.302625600642627, 2.1382378491466625, 0.14986840878892838] LEXUS NX 2018: [2.302625600642627, 2.1382378491466625, 0.14986840878892838]
LEXUS NX 2020: [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] LEXUS NX 2020: [2.4331999786982936, 2.1045680431705414, 0.14099899317761067]
LEXUS NX HYBRID 2018: [2.4025593501080955, 1.8080446063815507, 0.15349361249519017] LEXUS NX HYBRID 2018: [2.4025593501080955, 1.8080446063815507, 0.15349361249519017]
LEXUS RX 2016: [1.5876816543130423, 1.0427699298523752, 0.21334066732397142] 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 2017: [1.6984261557042386, 1.3211501880159107, 0.1820354534928893]
LEXUS RX HYBRID 2020: [1.5522309889823778, 1.255230465866663, 0.2220954003055114] LEXUS RX HYBRID 2020: [1.5522309889823778, 1.255230465866663, 0.2220954003055114]
MAZDA CX-9 2021: [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] 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 AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605]
TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193] TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193]
TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509]
TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.13519250664782062] TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.105192506]
TOYOTA CAMRY 2021: [2.6922769557433055, 2.3476510120007434, 0.1450430192989234] TOYOTA CAMRY 2021: [2.446083, 2.3476510120007434, 0.121615]
TOYOTA CAMRY HYBRID 2018: [2.0974120828287774, 1.7996193116697359, 0.13823613467632756] TOYOTA CAMRY HYBRID 2018: [1.996333, 1.7996193116697359, 0.112565]
TOYOTA CAMRY HYBRID 2021: [2.6426668350384457, 2.3901492458927986, 0.16103875108816076] TOYOTA CAMRY HYBRID 2021: [2.263582, 2.3901492458927986, 0.115257]
TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652] TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652]
TOYOTA COROLLA HYBRID TSS2 2019: [1.9079729107361805, 1.8118712531729109, 0.22251440891543514] TOYOTA COROLLA HYBRID TSS2 2019: [1.9079729107361805, 1.8118712531729109, 0.22251440891543514]
TOYOTA COROLLA TSS2 2019: [2.0742917676766712, 1.9258612322678952, 0.16888685704519352] TOYOTA COROLLA TSS2 2019: [2.0742917676766712, 1.9258612322678952, 0.16888685704519352]
TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796] TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796]
TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457] TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457]
TOYOTA HIGHLANDER HYBRID 2018: [1.9421825202382728, 1.6433903296845025, 0.16928956792275918] TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600]
TOYOTA HIGHLANDER HYBRID 2020: [2.103373061114133, 2.104015182965606, 0.14447040132184993] TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993]
TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565]
TOYOTA PRIUS 2017: [2.0183401513314294, 1.5023147650693636, 0.20856908464957724] TOYOTA PRIUS 2017: [1.746445, 1.5023147650693636, 0.151515]
TOYOTA PRIUS TSS2 2021: [2.327639738920072, 1.9104337425537743, 0.2030762265549664] TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968]
TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975] 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 8965: [2.5084506298290377, 2.4216520504763475, 0.11992835265067918]
TOYOTA RAV4 2019 x02: [2.7209621987605024, 2.2148637653781593, 0.10862567142268198] TOYOTA RAV4 2019 x02: [2.7209621987605024, 2.2148637653781593, 0.10862567142268198]
TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406]
TOYOTA RAV4 HYBRID 2019: [2.2271858492309153, 2.074844961405639, 0.14382216826893632] 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 8965: [2.1077397198131336, 1.8162215166877735, 0.13891369391200137]
TOYOTA RAV4 HYBRID 2019 x02: [2.803624333289342, 2.272367966572498, 0.11364569214387774] 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 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 ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032]
VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367]
VOLKSWAGEN GOLF 7TH GEN: [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] VOLKSWAGEN GOLF 7TH GEN: [1.3750394140491293, 1.5814743077200641, 0.2018321939386586]

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

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

@ -151,7 +151,8 @@ class Cluster():
def potential_low_speed_lead(self, v_ego): def potential_low_speed_lead(self, v_ego):
# stop for stuff in front of you and low speed, even without model confirmation # 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): def is_potential_fcw(self, model_prob):
return model_prob > .9 return model_prob > .9

@ -66,7 +66,6 @@ class Calibrator:
# Read saved calibration # Read saved calibration
params = Params() params = Params()
calibration_params = params.get("CalibrationParams") calibration_params = params.get("CalibrationParams")
self.wide_camera = params.get_bool('WideCameraOnly')
rpy_init = RPY_INIT rpy_init = RPY_INIT
wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
valid_blocks = 0 valid_blocks = 0
@ -166,10 +165,7 @@ class Calibrator:
self.old_rpy_weight = min(0.0, self.old_rpy_weight - 1/SMOOTH_CYCLES) 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)) 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 = MAX_VEL_ANGLE_STD
angle_std_threshold = 4*MAX_VEL_ANGLE_STD
else:
angle_std_threshold = MAX_VEL_ANGLE_STD
certain_if_calib = ((np.arctan2(trans_std[1], trans[0]) < angle_std_threshold) or certain_if_calib = ((np.arctan2(trans_std[1], trans[0]) < angle_std_threshold) or
(self.valid_blocks < INPUTS_NEEDED)) (self.valid_blocks < INPUTS_NEEDED))
if not (straight_and_fast and certain_if_calib): 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) new_wide_from_device_euler = np.array(wide_from_device_euler)
else: else:
new_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT new_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
self.rpys[self.block_idx] = (self.idx*self.rpys[self.block_idx] + self.rpys[self.block_idx] = (self.idx*self.rpys[self.block_idx] +
(BLOCK_SIZE - self.idx) * new_rpy) / float(BLOCK_SIZE) (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] + 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 HISTORY = 5 # secs
POINTS_PER_BUCKET = 1500 POINTS_PER_BUCKET = 1500
MIN_POINTS_TOTAL = 4000 MIN_POINTS_TOTAL = 4000
MIN_POINTS_TOTAL_QLOG = 800
FIT_POINTS_TOTAL = 2000 FIT_POINTS_TOTAL = 2000
FIT_POINTS_TOTAL_QLOG = 800
MIN_VEL = 15 # m/s MIN_VEL = 15 # m/s
FRICTION_FACTOR = 1.5 # ~85% of data coverage FRICTION_FACTOR = 1.5 # ~85% of data coverage
FACTOR_SANITY = 0.3 FACTOR_SANITY = 0.3
@ -26,7 +28,7 @@ MIN_FILTER_DECAY = 50
MAX_FILTER_DECAY = 250 MAX_FILTER_DECAY = 250
LAT_ACC_THRESHOLD = 1 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)] 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_RESETS = 5.0
MAX_INVALID_THRESHOLD = 10 MAX_INVALID_THRESHOLD = 10
MIN_ENGAGE_BUFFER = 2 # secs MIN_ENGAGE_BUFFER = 2 # secs
@ -58,10 +60,11 @@ class NPQueue:
class PointBuckets: 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.x_bounds = x_bounds
self.buckets = {bounds: NPQueue(maxlen=POINTS_PER_BUCKET, rowsize=3) for bounds in 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.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): def bucket_lengths(self):
return [len(v) for v in self.buckets.values()] return [len(v) for v in self.buckets.values()]
@ -70,7 +73,7 @@ class PointBuckets:
return sum(self.bucket_lengths()) return sum(self.bucket_lengths())
def is_valid(self): 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): def add_point(self, x, y):
for bound_min, bound_max in self.x_bounds: for bound_min, bound_max in self.x_bounds:
@ -90,9 +93,17 @@ class PointBuckets:
class TorqueEstimator: class TorqueEstimator:
def __init__(self, CP): def __init__(self, CP, decimated=False):
self.hist_len = int(HISTORY / DT_MDL) self.hist_len = int(HISTORY / DT_MDL)
self.lag = CP.steerActuatorDelay + .2 # from controlsd 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_friction = 0.0
self.offline_latAccelFactor = 0.0 self.offline_latAccelFactor = 0.0
@ -157,10 +168,10 @@ class TorqueEstimator:
self.invalid_values_tracker = 0.0 self.invalid_values_tracker = 0.0
self.decay = MIN_FILTER_DECAY self.decay = MIN_FILTER_DECAY
self.raw_points = defaultdict(lambda: deque(maxlen=self.hist_len)) 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): 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 # 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 # this is empirically the slope of the hysteresis parallelogram as opposed to the line through the diagonals
try: try:

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

@ -124,6 +124,7 @@ def setup_quectel(diag: ModemDiag):
# don't automatically turn on GNSS on powerup # don't automatically turn on GNSS on powerup
at_cmd("AT+QGPSCFG=\"autogps\",0") at_cmd("AT+QGPSCFG=\"autogps\",0")
at_cmd("AT+QGPSSUPLURL=\"supl.google.com:7275\"")
at_cmd("AT+QGPSCFG=\"outport\",\"usbnmea\"") at_cmd("AT+QGPSCFG=\"outport\",\"usbnmea\"")
at_cmd("AT+QGPS=1") 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['SKIP_FW_QUERY'] = "1"
os.environ['FINGERPRINT'] = CP.carFingerprint os.environ['FINGERPRINT'] = CP.carFingerprint
if CP.openpilotLongitudinalControl:
params.put_bool("ExperimentalLongitudinalEnabled", True)
def python_replay_process(cfg, lr, fingerprint=None): def python_replay_process(cfg, lr, fingerprint=None):
sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] 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 ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2
("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC) ("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC)
("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH) ("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 ("RAM", "2f4452b03ccb98f0|2022-09-07--13-55-08--10"), # CHRYSLER.RAM_1500
("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.OUTBACK ("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.OUTBACK
("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT ("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 ("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL
("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.GOLF ("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.GOLF
("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.CX9_2021 ("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"), ("RAM", "regen20490083AE7|2022-09-27--15-53-15--0"),
("SUBARU", "regen1E72BBDCED5|2022-09-27--15-55-31--0"), ("SUBARU", "regen1E72BBDCED5|2022-09-27--15-55-31--0"),
("GM", "regen45B05A80EF6|2022-09-27--15-57-22--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"), ("NISSAN", "regenC19D899B46D|2022-09-27--15-59-13--0"),
("VOLKSWAGEN", "regenD8F7AC4BD0D|2022-09-27--16-41-45--0"), ("VOLKSWAGEN", "regenD8F7AC4BD0D|2022-09-27--16-41-45--0"),
("MAZDA", "regenFC3F9ECBB64|2022-09-27--16-03-09--0"), ("MAZDA", "regenFC3F9ECBB64|2022-09-27--16-03-09--0"),

@ -100,10 +100,6 @@ void OnroadWindow::offroadTransition(bool offroad) {
#endif #endif
alerts->updateAlert({}, bg); 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) { void OnroadWindow::paintEvent(QPaintEvent *event) {
@ -232,8 +228,10 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD()); setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD());
} }
setStreamType(s.scene.wide_cam ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD);
if (s.scene.calibration_valid) { 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 { } else {
CameraWidget::updateCalibration(DEFAULT_CALIBRATION); 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) : CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) :
stream_name(stream_name), stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { stream_name(stream_name), stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); QObject::connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection);
connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived); QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection);
} }
CameraWidget::~CameraWidget() { CameraWidget::~CameraWidget() {
@ -162,13 +162,13 @@ void CameraWidget::initializeGL() {
} }
void CameraWidget::showEvent(QShowEvent *event) { void CameraWidget::showEvent(QShowEvent *event) {
frames.clear();
if (!vipc_thread) { if (!vipc_thread) {
vipc_thread = new QThread(); vipc_thread = new QThread();
connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); connect(vipc_thread, &QThread::started, [=]() { vipcThread(); });
connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater); connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater);
vipc_thread->start(); vipc_thread->start();
} }
clearFrames();
} }
void CameraWidget::hideEvent(QHideEvent *event) { void CameraWidget::hideEvent(QHideEvent *event) {
@ -178,6 +178,7 @@ void CameraWidget::hideEvent(QHideEvent *event) {
vipc_thread->wait(); vipc_thread->wait();
vipc_thread = nullptr; vipc_thread = nullptr;
} }
clearFrames();
} }
void CameraWidget::updateFrameMat() { void CameraWidget::updateFrameMat() {
@ -187,12 +188,17 @@ void CameraWidget::updateFrameMat() {
if (stream_type == VISION_STREAM_DRIVER) { if (stream_type == VISION_STREAM_DRIVER) {
frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); frame_mat = get_driver_view_transform(w, h, stream_width, stream_height);
} else { } 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 // Project point at "infinity" to compute x and y offsets
// to ensure this ends up in the middle of the screen // 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? // 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 inf = {{1000., 0., 0.}};
const vec3 Ep = matvecmul3(calibration, inf); const vec3 Ep = matvecmul3(calibration, inf);
const vec3 Kep = matvecmul3(intrinsic_matrix, Ep); const vec3 Kep = matvecmul3(intrinsic_matrix, Ep);
@ -233,45 +239,37 @@ void CameraWidget::paintGL() {
glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF());
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
if (frames.empty()) return; std::lock_guard lk(frame_lock);
int frame_idx = frames.size() - 1;
// Always draw latest frame until sync logic is more stable // use previous texture if update() is called without new frame.
// for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) { VisionBuf *frame = nullptr;
// if (frames[frame_idx].first == draw_frame_id) break; if (!frames.empty()) {
// } frame = frames.front().second;
frames.pop_front();
// 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;
} }
prev_frame_id = frames[frame_idx].first;
glViewport(0, 0, width(), height()); glViewport(0, 0, width(), height());
glBindVertexArray(frame_vao); glBindVertexArray(frame_vao);
glUseProgram(program->programId()); glUseProgram(program->programId());
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
VisionBuf *frame = frames[frame_idx].second;
#ifdef QCOM2 #ifdef QCOM2
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]); if (frame) {
assert(glGetError() == GL_NO_ERROR); glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]);
assert(glGetError() == GL_NO_ERROR);
}
#else #else
glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride); glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures[0]); 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); assert(glGetError() == GL_NO_ERROR);
glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride/2); glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride/2);
glActiveTexture(GL_TEXTURE0 + 1); glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_2D, textures[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); assert(glGetError() == GL_NO_ERROR);
#endif #endif
@ -288,7 +286,6 @@ void CameraWidget::paintGL() {
void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) {
makeCurrent(); makeCurrent();
frames.clear();
stream_width = vipc_client->buffers[0].width; stream_width = vipc_client->buffers[0].width;
stream_height = vipc_client->buffers[0].height; stream_height = vipc_client->buffers[0].height;
stream_stride = vipc_client->buffers[0].stride; stream_stride = vipc_client->buffers[0].stride;
@ -339,11 +336,7 @@ void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) {
updateFrameMat(); updateFrameMat();
} }
void CameraWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) { void CameraWidget::vipcFrameReceived() {
frames.push_back(std::make_pair(frame_id, buf));
while (frames.size() > FRAME_BUFFER_SIZE) {
frames.pop_front();
}
update(); update();
} }
@ -354,11 +347,13 @@ void CameraWidget::vipcThread() {
while (!QThread::currentThread()->isInterruptionRequested()) { while (!QThread::currentThread()->isInterruptionRequested()) {
if (!vipc_client || cur_stream_type != stream_type) { if (!vipc_client || cur_stream_type != stream_type) {
clearFrames();
cur_stream_type = stream_type; cur_stream_type = stream_type;
vipc_client.reset(new VisionIpcClient(stream_name, cur_stream_type, false)); vipc_client.reset(new VisionIpcClient(stream_name, cur_stream_type, false));
} }
if (!vipc_client->connected) { if (!vipc_client->connected) {
clearFrames();
if (!vipc_client->connect(false)) { if (!vipc_client->connect(false)) {
QThread::msleep(100); QThread::msleep(100);
continue; continue;
@ -367,7 +362,15 @@ void CameraWidget::vipcThread() {
} }
if (VisionBuf *buf = vipc_client->recv(&meta_main, 1000)) { 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(); egl_images.clear();
#endif #endif
} }
void CameraWidget::clearFrames() {
std::lock_guard lk(frame_lock);
frames.clear();
}

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

@ -56,7 +56,7 @@
</message> </message>
<message> <message>
<source>leave blank for automatic configuration</source> <source>leave blank for automatic configuration</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Cellular Metered</source> <source>Cellular Metered</source>
@ -136,7 +136,7 @@
</message> </message>
<message> <message>
<source>PREVIEW</source> <source>PREVIEW</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)</source> <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>
<message> <message>
<source>Review Training Guide</source> <source>Review Training Guide</source>
<translation></translation> <translation>使</translation>
</message> </message>
<message> <message>
<source>REVIEW</source> <source>REVIEW</source>
@ -168,7 +168,7 @@
</message> </message>
<message> <message>
<source>Are you sure you want to review the training guide?</source> <source>Are you sure you want to review the training guide?</source>
<translation></translation> <translation>使</translation>
</message> </message>
<message> <message>
<source>Regulatory</source> <source>Regulatory</source>
@ -200,11 +200,11 @@
</message> </message>
<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> <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>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <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>
<message> <message>
<source>down</source> <source>down</source>
@ -309,7 +309,7 @@
<name>MapETA</name> <name>MapETA</name>
<message> <message>
<source>eta</source> <source>eta</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>min</source> <source>min</source>
@ -452,15 +452,15 @@ location set</source>
</message> </message>
<message> <message>
<source>Go to https://connect.comma.ai on your phone</source> <source>Go to https://connect.comma.ai on your phone</source>
<translation>connect.comma.ai</translation> <translation>https://connect.comma.ai」にアクセスしてください。</translation>
</message> </message>
<message> <message>
<source>Click &quot;add new device&quot; and scan the QR code on the right</source> <source>Click &quot;add new device&quot; and scan the QR code on the right</source>
<translation>QRコードをスキャンしてください</translation> <translation>QRコードをスキャンしてください</translation>
</message> </message>
<message> <message>
<source>Bookmark connect.comma.ai to your home screen to use it like an app</source> <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> </message>
</context> </context>
<context> <context>
@ -608,7 +608,7 @@ location set</source>
</message> </message>
<message> <message>
<source>Toggles</source> <source>Toggles</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Software</source> <source>Software</source>
@ -627,7 +627,7 @@ location set</source>
</message> </message>
<message> <message>
<source>Power your device in a car with a harness or proceed at your own risk.</source> <source>Power your device in a car with a harness or proceed at your own risk.</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Power off</source> <source>Power off</source>
@ -643,7 +643,7 @@ location set</source>
</message> </message>
<message> <message>
<source>Before we get on the road, lets finish installation and cover some details.</source> <source>Before we get on the road, lets finish installation and cover some details.</source>
<translation></translation> <translation>使</translation>
</message> </message>
<message> <message>
<source>Connect to Wi-Fi</source> <source>Connect to Wi-Fi</source>
@ -655,7 +655,7 @@ location set</source>
</message> </message>
<message> <message>
<source>Continue without Wi-Fi</source> <source>Continue without Wi-Fi</source>
<translation>Wi-Fi </translation> <translation>Wi-Fi </translation>
</message> </message>
<message> <message>
<source>Waiting for internet</source> <source>Waiting for internet</source>
@ -663,7 +663,7 @@ location set</source>
</message> </message>
<message> <message>
<source>Choose Software to Install</source> <source>Choose Software to Install</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Dashcam</source> <source>Dashcam</source>
@ -710,7 +710,7 @@ location set</source>
</message> </message>
<message> <message>
<source>Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.</source> <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>
<message> <message>
<source>Pair device</source> <source>Pair device</source>
@ -836,7 +836,7 @@ location set</source>
</message> </message>
<message> <message>
<source>UNINSTALL</source> <source>UNINSTALL</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Uninstall %1</source> <source>Uninstall %1</source>
@ -859,7 +859,7 @@ location set</source>
</message> </message>
<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> <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>
<message> <message>
<source>ADD</source> <source>ADD</source>
@ -871,7 +871,7 @@ location set</source>
</message> </message>
<message> <message>
<source>LOADING</source> <source>LOADING</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>REMOVE</source> <source>REMOVE</source>
@ -924,7 +924,7 @@ location set</source>
</message> </message>
<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> <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>
<message> <message>
<source>Enable Lane Departure Warnings</source> <source>Enable Lane Departure Warnings</source>
@ -932,11 +932,11 @@ location set</source>
</message> </message>
<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> <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>
<message> <message>
<source>Use Metric System</source> <source>Use Metric System</source>
<translation></translation> <translation>使</translation>
</message> </message>
<message> <message>
<source>Display speed in km/h instead of mph.</source> <source>Display speed in km/h instead of mph.</source>
@ -952,7 +952,7 @@ location set</source>
</message> </message>
<message> <message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source> <source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source>
<translation>🌮 () 🌮</translation> <translation>🌮 (α) 🌮</translation>
</message> </message>
<message> <message>
<source>Experimental openpilot longitudinal control</source> <source>Experimental openpilot longitudinal control</source>
@ -976,11 +976,11 @@ location set</source>
</message> </message>
<message> <message>
<source>Disengage On Accelerator Pedal</source> <source>Disengage On Accelerator Pedal</source>
<translation> openpilot </translation> <translation> openpilot </translation>
</message> </message>
<message> <message>
<source>When enabled, pressing the accelerator pedal will disengage openpilot.</source> <source>When enabled, pressing the accelerator pedal will disengage openpilot.</source>
<translation> openpilot </translation> <translation>openpilotを利用中にアクセルを踏むとopenpilotによる運転サポートを中断しま</translation>
</message> </message>
<message> <message>
<source>Show ETA in 24h Format</source> <source>Show ETA in 24h Format</source>
@ -1003,11 +1003,11 @@ location set</source>
<name>Updater</name> <name>Updater</name>
<message> <message>
<source>Update Required</source> <source>Update Required</source>
<translation></translation> <translation></translation>
</message> </message>
<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> <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>
<message> <message>
<source>Connect to Wi-Fi</source> <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 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 pt = (vec3){{in_x, in_y, in_z}};
const vec3 Ep = matvecmul3(s->scene.view_from_calib, pt); 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->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep); const vec3 KEp = matvecmul3(s->scene.wide_cam ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep);
// Project. // Project.
QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]}); 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")) { if (sm.updated("liveCalibration")) {
auto rpy_list = sm["liveCalibration"].getLiveCalibration().getRpyCalib(); auto rpy_list = sm["liveCalibration"].getLiveCalibration().getRpyCalib();
auto wfde_list = sm["liveCalibration"].getLiveCalibration().getWideFromDeviceEuler();
Eigen::Vector3d rpy; 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 device_from_calib = euler2rot(rpy);
Eigen::Matrix3d wide_from_device = euler2rot(wfde);
Eigen::Matrix3d view_from_device; Eigen::Matrix3d view_from_device;
view_from_device << 0,1,0, view_from_device << 0,1,0,
0,0,1, 0,0,1,
1,0,0; 1,0,0;
Eigen::Matrix3d view_from_calib = view_from_device * device_from_calib; 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 i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j); scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j);
scene.view_from_wide_calib.v[i*3 + j] = view_from_wide_calib(i,j);
} }
} }
scene.calibration_valid = sm["liveCalibration"].getLiveCalibration().getCalStatus() == 1; 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.light_sensor = std::max(100.0f - scale * sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(), 0.0f);
} }
scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; 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) { void ui_update_params(UIState *s) {
@ -197,7 +214,7 @@ void UIState::updateStatus() {
if (scene.started) { if (scene.started) {
status = STATUS_DISENGAGED; status = STATUS_DISENGAGED;
scene.started_frame = sm->frame; scene.started_frame = sm->frame;
wide_camera = Params().getBool("WideCameraOnly"); wide_cam_only = Params().getBool("WideCameraOnly");
} }
started_prev = scene.started; started_prev = scene.started;
emit offroadTransition(!scene.started); emit offroadTransition(!scene.started);
@ -219,7 +236,7 @@ UIState::UIState(QObject *parent) : QObject(parent) {
}); });
Params params; Params params;
wide_camera = params.getBool("WideCameraOnly"); wide_cam_only = params.getBool("WideCameraOnly");
prime_type = std::atoi(params.get("PrimeType").c_str()); prime_type = std::atoi(params.get("PrimeType").c_str());
language = QString::fromStdString(params.get("LanguageSetting")); language = QString::fromStdString(params.get("LanguageSetting"));

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

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

@ -18,5 +18,9 @@ cabana_env = qt_env.Clone()
prev_moc_path = cabana_env['QT_MOCHPREFIX'] prev_moc_path = cabana_env['QT_MOCHPREFIX']
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' 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.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) '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); setItemDelegate(delegate);
horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
horizontalHeader()->hide(); horizontalHeader()->hide();
verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setMouseTracking(true); setMouseTracking(true);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
}
QObject::connect(model, &QAbstractItemModel::modelReset, [this]() { QSize BinaryView::sizeHint() const {
setFixedHeight((CELL_HEIGHT + 1) * std::min(model->rowCount(), 8) + 2); QSize sz = QTableView::sizeHint();
}); return {sz.width(), model->rowCount() <= 8 ? ((CELL_HEIGHT + 1) * model->rowCount() + 2) : sz.height()};
} }
void BinaryView::highlight(const Signal *sig) { void BinaryView::highlight(const Signal *sig) {
@ -108,9 +110,9 @@ void BinaryView::leaveEvent(QEvent *event) {
void BinaryView::setMessage(const QString &message_id) { void BinaryView::setMessage(const QString &message_id) {
msg_id = message_id; msg_id = message_id;
model->setMessage(message_id); model->setMessage(message_id);
resizeRowsToContents();
clearSelection(); clearSelection();
updateState(); updateState();
updateGeometry();
} }
void BinaryView::updateState() { void BinaryView::updateState() {

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

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

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

@ -1,5 +1,6 @@
#include "tools/cabana/dbcmanager.h" #include "tools/cabana/dbcmanager.h"
#include <limits>
#include <sstream> #include <sstream>
#include <QVector> #include <QVector>
@ -28,8 +29,25 @@ void DBCManager::open(const QString &name, const QString &content) {
emit DBCFileChanged(); emit DBCFileChanged();
} }
void save(const QString &dbc_file_name) { QString DBCManager::generateDBC() {
// TODO: save DBC to file 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) { 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 &dbc_file_name);
void open(const QString &name, const QString &content); 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 addSignal(const QString &id, const Signal &sig);
void updateSignal(const QString &id, const QString &sig_name, const Signal &sig); void updateSignal(const QString &id, const QString &sig_name, const Signal &sig);
void removeSignal(const QString &id, const QString &sig_name); void removeSignal(const QString &id, const QString &sig_name);
@ -24,6 +23,7 @@ public:
inline QString name() const { return dbc_name; } inline QString name() const { return dbc_name; }
void updateMsg(const QString &id, const QString &name, uint32_t size); 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(const QString &id) const { return msg(addressFromId(id)); }
inline const Msg *msg(uint32_t address) const { inline const Msg *msg(uint32_t address) const {
auto it = msg_map.find(address); auto it = msg_map.find(address);

@ -2,6 +2,7 @@
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QFormLayout> #include <QFormLayout>
#include <QMenu>
#include <QMessageBox> #include <QMessageBox>
#include <QTimer> #include <QTimer>
@ -11,25 +12,39 @@
// DetailWidget // DetailWidget
DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0); 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
tabbar = new QTabBar(this); tabbar = new QTabBar(this);
tabbar->setTabsClosable(true); tabbar->setTabsClosable(true);
tabbar->setDrawBase(false); tabbar->setDrawBase(false);
tabbar->setUsesScrollButtons(true); tabbar->setUsesScrollButtons(true);
tabbar->setAutoHide(true); tabbar->setAutoHide(true);
main_layout->addWidget(tabbar); tabbar->setContextMenuPolicy(Qt::CustomContextMenu);
bin_layout->addWidget(tabbar);
// message title TitleFrame *title_frame = new TitleFrame(this);
QFrame *title_frame = new QFrame();
main_layout->addWidget(title_frame);
QVBoxLayout *frame_layout = new QVBoxLayout(title_frame);
title_frame->setFrameShape(QFrame::StyledPanel); 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(); 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:")); title_layout->addWidget(new QLabel("time:"));
time_label = new QLabel(this); time_label = new QLabel(this);
time_label->setStyleSheet("font-weight:bold"); 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_hlayout->addWidget(warning_label, 1, Qt::AlignLeft);
warning_widget->hide(); warning_widget->hide();
frame_layout->addWidget(warning_widget); frame_layout->addWidget(warning_widget);
bin_layout->addWidget(title_frame);
// binary view // binary view
binary_view = new BinaryView(this); 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
signals_container = new QWidget(this); signals_container = new QWidget(this);
@ -68,33 +85,61 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) {
scroll->setWidget(signals_container); scroll->setWidget(signals_container);
scroll->setWidgetResizable(true); scroll->setWidgetResizable(true);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
main_layout->addWidget(scroll); right_column->addWidget(scroll);
// history log // history log
history_log = new HistoryLog(this); 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(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg);
QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal);
QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal);
QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); }); QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); });
QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) { setMessage(messages[index]); }); QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu);
QObject::connect(tabbar, &QTabBar::tabCloseRequested, [=](int index) { QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) {
messages.removeAt(index); if (index != -1 && tabbar->tabText(index) != msg_id) {
tabbar->removeTab(index); setMessage(tabbar->tabText(index));
setMessage(messages.isEmpty() ? "" : messages[0]); }
}); });
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) { void DetailWidget::setMessage(const QString &message_id) {
if (message_id.isEmpty()) return; 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) { if (index == -1) {
messages.push_back(message_id); index = tabbar->addTab(message_id);
tabbar->addTab(message_id);
index = tabbar->count() - 1;
auto msg = dbc()->msg(message_id); auto msg = dbc()->msg(message_id);
tabbar->setTabToolTip(index, msg ? msg->name.c_str() : "untitled"); 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)) { if (auto msg = dbc()->msg(msg_id)) {
for (int i = 0; i < msg->sigs.size(); ++i) { for (int i = 0; i < msg->sigs.size(); ++i) {
auto form = new SignalEdit(i, msg_id, &(msg->sigs[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); 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::showFormClicked, this, &DetailWidget::showForm);
QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal);
QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal);
QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight); QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight);
QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered); 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) { if (i == show_form_idx) {
QTimer::singleShot(0, [=]() { emit form->showFormClicked(); }); QTimer::singleShot(0, [=]() { emit form->showFormClicked(); });
} }
@ -155,6 +201,20 @@ void DetailWidget::updateState() {
history_log->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() { void DetailWidget::showForm() {
SignalEdit *sender = qobject_cast<SignalEdit *>(QObject::sender()); SignalEdit *sender = qobject_cast<SignalEdit *>(QObject::sender());
for (auto f : signals_container->findChildren<SignalEdit *>()) { 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() { void DetailWidget::editMsg() {
auto msg = dbc()->msg(msg_id); auto msg = dbc()->msg(msg_id);
QString name = msg ? msg->name.c_str() : "untitled"; QString name = msg ? msg->name.c_str() : "untitled";

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

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

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

@ -3,6 +3,8 @@
#include <QCompleter> #include <QCompleter>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QFile> #include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QFontDatabase> #include <QFontDatabase>
#include <QHeaderView> #include <QHeaderView>
#include <QLineEdit> #include <QLineEdit>
@ -69,9 +71,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
QObject::connect(can, &CANMessages::updated, [this]() { model->updateState(); }); QObject::connect(can, &CANMessages::updated, [this]() { model->updateState(); });
QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &))); QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &)));
QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste); QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste);
QObject::connect(save_btn, &QPushButton::clicked, [=]() { QObject::connect(save_btn, &QPushButton::clicked, this, &MessagesWidget::saveDBC);
// TODO: save DBC to file
});
QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) { QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) {
if (current.isValid()) { if (current.isValid()) {
emit msgSelectionChanged(current.data(Qt::UserRole).toString()); 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"); 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()); fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
} }
} }
void MessagesWidget::loadDBCFromName(const QString &name) { void MessagesWidget::loadDBCFromName(const QString &name) {
dbc()->open(name); if (name != dbc()->name()) {
dbc_combo->setCurrentText(name); dbc()->open(name);
// refresh model dbc_combo->setCurrentText(name);
model->updateState(); // re-sort model to refresh column 'Name'
model->updateState(true);
}
} }
void MessagesWidget::loadDBCFromPaste() { void MessagesWidget::loadDBCFromPaste() {
@ -96,19 +98,26 @@ void MessagesWidget::loadDBCFromPaste() {
if (dlg.exec()) { if (dlg.exec()) {
dbc()->open("from paste", dlg.dbc_edit->toPlainText()); dbc()->open("from paste", dlg.dbc_edit->toPlainText());
dbc_combo->setCurrentText("loaded from paste"); dbc_combo->setCurrentText("loaded from paste");
model->updateState(true);
} }
} }
void MessagesWidget::loadDBCFromFingerprint() { void MessagesWidget::loadDBCFromFingerprint() {
auto fingerprint = can->carFingerprint(); auto fingerprint = can->carFingerprint();
if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) { if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) {
auto dbc_name = fingerprint_to_dbc[fingerprint]; auto dbc_name = fingerprint_to_dbc[fingerprint];
if (dbc_name != QJsonValue::Undefined) { if (dbc_name != QJsonValue::Undefined) {
loadDBCFromName(dbc_name.toString()); loadDBCFromName(dbc_name.toString());
} }
} }
} }
void MessagesWidget::saveDBC() {
SaveDBCDialog dlg(this);
dlg.dbc_edit->setText(dbc()->generateDBC());
dlg.exec();
}
// MessageListModel // MessageListModel
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { 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) { LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
dbc_edit = new QTextEdit(this); dbc_edit = new QTextEdit(this);
@ -231,7 +242,48 @@ LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) {
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox); main_layout->addWidget(buttonBox);
setFixedWidth(640); setMinimumSize({640, 480});
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 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; QTextEdit *dbc_edit;
}; };
class SaveDBCDialog : public QDialog {
Q_OBJECT
public:
SaveDBCDialog(QWidget *parent);
void copytoClipboard();
void saveAs();
QTextEdit *dbc_edit;
};
class MessageListModel : public QAbstractTableModel { class MessageListModel : public QAbstractTableModel {
Q_OBJECT Q_OBJECT
@ -52,6 +62,7 @@ public slots:
void loadDBCFromName(const QString &name); void loadDBCFromName(const QString &name);
void loadDBCFromFingerprint(); void loadDBCFromFingerprint();
void loadDBCFromPaste(); void loadDBCFromPaste();
void saveDBC();
signals: signals:
void msgSelectionChanged(const QString &message_id); void msgSelectionChanged(const QString &message_id);

@ -17,6 +17,7 @@ void Settings::save() {
s.setValue("log_size", can_msg_log_size); s.setValue("log_size", can_msg_log_size);
s.setValue("cached_segment", cached_segment_limit); s.setValue("cached_segment", cached_segment_limit);
s.setValue("chart_height", chart_height); s.setValue("chart_height", chart_height);
s.setValue("chart_theme", chart_theme);
s.setValue("max_chart_x_range", max_chart_x_range); s.setValue("max_chart_x_range", max_chart_x_range);
emit changed(); emit changed();
} }
@ -27,6 +28,7 @@ void Settings::load() {
can_msg_log_size = s.value("log_size", 100).toInt(); can_msg_log_size = s.value("log_size", 100).toInt();
cached_segment_limit = s.value("cached_segment", 3).toInt(); cached_segment_limit = s.value("cached_segment", 3).toInt();
chart_height = s.value("chart_height", 200).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(); 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->setRange(3, 60);
cached_segment->setSingleStep(1); cached_segment->setSingleStep(1);
cached_segment->setValue(settings.cached_segment_limit); 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 = new QSpinBox(this);
max_chart_x_range->setRange(1, 60); max_chart_x_range->setRange(1, 60);
max_chart_x_range->setSingleStep(1); max_chart_x_range->setSingleStep(1);
max_chart_x_range->setValue(settings.max_chart_x_range / 60); 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 = new QSpinBox(this);
chart_height->setRange(100, 500); chart_height->setRange(100, 500);
chart_height->setSingleStep(10); chart_height->setSingleStep(10);
chart_height->setValue(settings.chart_height); 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); main_layout->addLayout(form_layout);
@ -82,6 +89,7 @@ void SettingsDlg::save() {
settings.can_msg_log_size = log_size->value(); settings.can_msg_log_size = log_size->value();
settings.cached_segment_limit = cached_segment->value(); settings.cached_segment_limit = cached_segment->value();
settings.chart_height = chart_height->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.max_chart_x_range = max_chart_x_range->value() * 60;
settings.save(); settings.save();
accept(); accept();

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <QComboBox>
#include <QDialog> #include <QDialog>
#include <QSpinBox> #include <QSpinBox>
@ -15,6 +16,7 @@ public:
int can_msg_log_size = 100; int can_msg_log_size = 100;
int cached_segment_limit = 3; int cached_segment_limit = 3;
int chart_height = 200; int chart_height = 200;
int chart_theme = 0;
int max_chart_x_range = 3 * 60; // 3 minutes int max_chart_x_range = 3 * 60; // 3 minutes
signals: signals:
@ -31,6 +33,7 @@ public:
QSpinBox *log_size ; QSpinBox *log_size ;
QSpinBox *cached_segment; QSpinBox *cached_segment;
QSpinBox *chart_height; QSpinBox *chart_height;
QComboBox *chart_theme;
QSpinBox *max_chart_x_range; 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); title_layout->addWidget(title, 1);
QPushButton *seek_btn = new QPushButton(""); 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->setToolTip(tr("Find signal values"));
seek_btn->setFixedSize(20, 20); seek_btn->setFixedSize(25, 25);
title_layout->addWidget(seek_btn); title_layout->addWidget(seek_btn);
QPushButton *plot_btn = new QPushButton("📈"); plot_btn = new QPushButton(this);
plot_btn->setToolTip(tr("Show Plot")); plot_btn->setStyleSheet("QPushButton {font-size:18px}");
plot_btn->setFixedSize(20, 20); plot_btn->setFixedSize(25, 25);
QObject::connect(plot_btn, &QPushButton::clicked, this, &SignalEdit::showChart);
title_layout->addWidget(plot_btn); title_layout->addWidget(plot_btn);
main_layout->addLayout(title_layout); 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(remove_btn, &QPushButton::clicked, [this]() { emit remove(this->sig); });
QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked);
QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::saveSignal); 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]() { QObject::connect(seek_btn, &QPushButton::clicked, [this, msg_id]() {
SignalFindDlg dlg(msg_id, this->sig, this); SignalFindDlg dlg(msg_id, this->sig, this);
dlg.exec(); dlg.exec();
@ -145,6 +145,12 @@ void SignalEdit::saveSignal() {
emit save(this->sig, s); 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) { void SignalEdit::setFormVisible(bool visible) {
form_container->setVisible(visible); form_container->setVisible(visible);
icon->setText(visible ? "" : ">"); icon->setText(visible ? "" : ">");

@ -26,13 +26,15 @@ class SignalEdit : public QWidget {
public: public:
SignalEdit(int index, const QString &msg_id, const Signal *sig, QWidget *parent = nullptr); SignalEdit(int index, const QString &msg_id, const Signal *sig, QWidget *parent = nullptr);
void setChartOpened(bool opened);
void setFormVisible(bool show); void setFormVisible(bool show);
void signalHovered(const Signal *sig); void signalHovered(const Signal *sig);
inline bool isFormVisible() const { return form_container->isVisible(); } inline bool isFormVisible() const { return form_container->isVisible(); }
const Signal *sig = nullptr;
signals: signals:
void highlight(const Signal *sig); void highlight(const Signal *sig);
void showChart(); void showChart(bool show);
void showFormClicked(); void showFormClicked();
void remove(const Signal *sig); void remove(const Signal *sig);
void save(const Signal *sig, const Signal &new_sig); void save(const Signal *sig, const Signal &new_sig);
@ -48,7 +50,8 @@ protected:
QLabel *icon; QLabel *icon;
int form_idx = 0; int form_idx = 0;
QString msg_id; QString msg_id;
const Signal *sig = nullptr; bool chart_opened = false;
QPushButton *plot_btn;
}; };
class SignalFindDlg : public QDialog { 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); QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0); 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 = new CameraWidget("camerad", VISION_STREAM_ROAD, false, this);
cam_widget->setFixedSize(parent->width(), parent->width() / 1.596); cam_widget->setFixedSize(parent->width(), parent->width() / 1.596);
main_layout->addWidget(cam_widget); main_layout->addWidget(cam_widget);

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

@ -65,7 +65,7 @@ def verify_ubloxgnss_data(socket: messaging.SubSocket, max_time: int):
sat_count.append(event.ubloxGnss.measurementReport.numMeas) sat_count.append(event.ubloxGnss.measurementReport.numMeas)
num_sat = int(sum(sat_count)/len(sat_count)) 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): def verify_gps_location(socket: messaging.SubSocket, max_time: int):
@ -188,4 +188,4 @@ class TestGPS(unittest.TestCase):
if __name__ == "__main__": 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) query = parse_qs(urlparse(route_or_segment_name).query)
route_or_segment_name = query["route"][0] 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] logs = [route_or_segment_name]
elif ci: elif ci:
route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) 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) 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 " 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, "tools/sim/rgb_to_nv12.cl")
kernel_fn = os.path.join(BASEDIR, "system", "camerad", "transforms", "rgb_to_yuv.cl")
with open(kernel_fn) as f: with open(kernel_fn) as f:
prg = cl.Program(self.ctx, f.read()).build(cl_arg) 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.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 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); 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) { const uchar8 rgbs1, const uchar8 rgbs2) {
// U & V: average of 2x2 pixels square // U & V: average of 2x2 pixels square
const short ab = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); 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 ag = AVERAGE(rgbs1.s1, rgbs1.s4, rgbs2.s1, rgbs2.s4);
const short ar = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5); const short ar = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5);
#ifdef CL_DEBUG #ifdef CL_DEBUG
if(ui >= RGB_SIZE + RGB_SIZE / 4) if(uvi >= RGB_SIZE + RGB_SIZE / 2)
printf("U overflow, %d >= %d\n", ui, RGB_SIZE + RGB_SIZE / 4); printf("UV overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2);
if(vi >= RGB_SIZE + RGB_SIZE / 2)
printf("V overflow, %d >= %d\n", vi, RGB_SIZE + RGB_SIZE / 2);
#endif #endif
out_yuv[ui] = RGB_TO_U(ar, ag, ab); out_yuv[uvi] = RGB_TO_U(ar, ag, ab);
out_yuv[vi] = RGB_TO_V(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) { const uchar8 rgbs1, const uchar8 rgbs2, const uchar8 rgbs3, const uchar8 rgbs4) {
// U & V: average of 2x2 pixels square // U & V: average of 2x2 pixels square
const short ab1 = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); 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 ab2 = AVERAGE(rgbs1.s6, rgbs3.s1, rgbs2.s6, rgbs4.s1);
const short ag2 = AVERAGE(rgbs1.s7, rgbs3.s2, rgbs2.s7, rgbs4.s2); const short ag2 = AVERAGE(rgbs1.s7, rgbs3.s2, rgbs2.s7, rgbs4.s2);
const short ar2 = AVERAGE(rgbs3.s0, rgbs3.s3, rgbs4.s0, rgbs4.s3); 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(ar1, ag1, ab1),
RGB_TO_U(ar2, ag2, ab2)
);
uchar2 v2 = (uchar2)(
RGB_TO_V(ar1, ag1, ab1), RGB_TO_V(ar1, ag1, ab1),
RGB_TO_U(ar2, ag2, ab2),
RGB_TO_V(ar2, ag2, ab2) RGB_TO_V(ar2, ag2, ab2)
); );
#ifdef CL_DEBUG1 #ifdef CL_DEBUG1
if(ui > RGB_SIZE + RGB_SIZE / 4 - 2) if(uvi > RGB_SIZE + RGB_SIZE / 2 - 4)
printf("U 2 overflow, %d >= %d\n", ui, RGB_SIZE + RGB_SIZE / 4 - 2); printf("UV2 overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2 - 2);
if(vi > RGB_SIZE + RGB_SIZE / 2 - 2)
printf("V 2 overflow, %d >= %d\n", vi, RGB_SIZE + RGB_SIZE / 2 - 2);
#endif #endif
vstore2(u2, 0, out_yuv + ui); vstore4(uv, 0, out_yuv + uvi);
vstore2(v2, 0, out_yuv + vi);
} }
__kernel void rgb_to_yuv(__global uchar const * const rgb, __kernel void rgb_to_nv12(__global uchar const * const rgb,
__global uchar * out_yuv) __global uchar * out_yuv)
{ {
const int dx = get_global_id(0); 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 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 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 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 uvi = mad24(row / 2, WIDTH, RGB_SIZE + col);
int vi = mad24(row / 2 , UV_WIDTH, RGB_SIZE + UV_WIDTH * UV_HEIGHT + col / 2);
int num_col = min(WIDTH - col, 4); int num_col = min(WIDTH - col, 4);
int num_row = min(HEIGHT - row, 4); int num_row = min(HEIGHT - row, 4);
if(num_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, 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 * 2, rgbs2_0, rgbs2_1);
convert_4_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0, rgbs3_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, uvi, 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 + WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1);
} else if(num_col == 2) { } else if(num_col == 2) {
convert_2_ys(out_yuv, yi_start, rgbs0_0); 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, rgbs1_0);
convert_2_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0); convert_2_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0);
convert_2_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_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, uvi, rgbs0_0, rgbs1_0);
convert_uv(out_yuv, ui + UV_WIDTH, vi + UV_WIDTH, rgbs2_0, rgbs3_0); convert_uv(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0);
} }
} else { } else {
const uchar8 rgbs0_0 = vload8(0, rgb + bgri_start); 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) { if(num_col == 4) {
convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1); convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1);
convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_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) { } else if(num_col == 2) {
convert_2_ys(out_yuv, yi_start, rgbs0_0); 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, rgbs1_0);
convert_uv(out_yuv, ui, vi, rgbs0_0, rgbs1_0); convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0);
} }
} }
} }
Loading…
Cancel
Save