diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index e921cd3d00..598f2c592b 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -232,6 +232,7 @@ jobs: ./selfdrive/loggerd/tests/test_logger &&\ ./system/proclogd/tests/test_proclog && \ ./tools/replay/tests/test_replay && \ + ./tools/cabana/tests/test_cabana && \ ./system/camerad/test/ae_gray_test && \ coverage xml" - name: "Upload coverage to Codecov" diff --git a/opendbc b/opendbc index a95b0ae8a5..b3dc569994 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit a95b0ae8a5244a9d6311bf72aa2a7d63d41b4a9f +Subproject commit b3dc569994fd10e4de04afd650980c51ddfce5e1 diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index c703ef6cb8..16530ed989 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -241,6 +241,7 @@ FW_VERSIONS = { b'68334977AH', b'68504022AB', b'68530686AB', + b'68504022AC', ], (Ecu.fwdRadar, 0x753, None): [ b'04672895AB', diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 7b3140fbbf..5114f8d065 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -61,10 +61,10 @@ class FordCarInfo(CarInfo): CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { CAR.ESCAPE_MK4: [ - FordCarInfo("Ford Escape 2020"), - FordCarInfo("Ford Kuga EU", "Driver Assistance Pack"), + FordCarInfo("Ford Escape 2020-21"), + FordCarInfo("Ford Kuga 2020-21", "Driver Assistance Pack"), ], - CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-21"), + CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-22"), CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"), } @@ -87,31 +87,41 @@ FW_QUERY_CONFIG = FwQueryConfig( FW_VERSIONS = { CAR.ESCAPE_MK4: { (Ecu.eps, 0x730, None): [ + b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x760, None): [ b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-NY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-SA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x706, None): [ b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7E0, None): [ + b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.shiftByWire, 0x732, None): [ + b'LX6P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.EXPLORER_MK6: { (Ecu.eps, 0x730, None): [ b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x760, None): [ b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -123,10 +133,12 @@ FW_VERSIONS = { (Ecu.engine, 0x7E0, None): [ b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.shiftByWire, 0x732, None): [ b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MP-14G395-JB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.FOCUS_MK4: { diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 61df66764c..c11828f311 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -220,23 +220,17 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] tire_stiffness_factor = 0.677 - elif candidate == CAR.ODYSSEY: - ret.mass = 4471. * CV.LB_TO_KG + STD_CARGO_KG + elif candidate in (CAR.ODYSSEY, CAR.ODYSSEY_CHN): + ret.mass = 1900. + STD_CARGO_KG ret.wheelbase = 3.00 ret.centerToFront = ret.wheelbase * 0.41 ret.steerRatio = 14.35 # as spec - ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - tire_stiffness_factor = 0.82 - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] - - elif candidate == CAR.ODYSSEY_CHN: - ret.mass = 1849.2 + STD_CARGO_KG # mean of 4 models in kg - ret.wheelbase = 2.90 - ret.centerToFront = ret.wheelbase * 0.41 # from CAR.ODYSSEY - ret.steerRatio = 14.35 - ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end tire_stiffness_factor = 0.82 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] + if candidate == CAR.ODYSSEY_CHN: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end + else: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end elif candidate in (CAR.PILOT, CAR.PASSPORT): ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index e0810e0bbb..2510f2e1ff 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -1031,6 +1031,23 @@ FW_VERSIONS = { b'54008-THR-A020\x00\x00', ], }, + CAR.ODYSSEY_CHN: { + (Ecu.eps, 0x18da30f1, None): [ + b'39990-T6D-H220\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-T6A-J010\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-T6A-F310\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T6A-P040\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-T6A-P110\x00\x00', + ], + }, CAR.PILOT: { (Ecu.shiftByWire, 0x18da0bf1, None): [ b'54008-TG7-A520\x00\x00', diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 913f683e2c..2f944edc0f 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -49,6 +49,7 @@ class CarController: self.angle_limit_counter = 0 self.frame = 0 + self.accel_last = 0 self.apply_steer_last = 0 self.car_fingerprint = CP.carFingerprint self.last_button_frame = 0 @@ -123,8 +124,9 @@ class CarController: if self.CP.openpilotLongitudinalControl: can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) if self.frame % 2 == 0: - can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, accel, stopping, CC.cruiseControl.override, + can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, set_speed_in_units)) + self.accel_last = accel else: # button presses if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index ec2b3059a3..3cce581868 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -196,10 +196,10 @@ class CarState(CarStateBase): if not self.CP.openpilotLongitudinalControl: speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam - ret.cruiseState.speed = cp_cruise_info.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor - ret.cruiseState.standstill = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STANDSTILL"] == 1 - ret.cruiseState.enabled = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STATUS"] != 0 - self.cruise_info = copy.copy(cp_cruise_info.vl["CRUISE_INFO"]) + ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor + ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 + ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) + self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"]) cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" self.prev_cruise_buttons = self.cruise_buttons[-1] @@ -464,12 +464,12 @@ class CarState(CarStateBase): if CP.flags & HyundaiFlags.CANFD_HDA2 and not CP.openpilotLongitudinalControl: signals += [ - ("CRUISE_STATUS", "CRUISE_INFO"), - ("SET_SPEED", "CRUISE_INFO"), - ("CRUISE_STANDSTILL", "CRUISE_INFO"), + ("ACCMode", "SCC_CONTROL"), + ("VSetDis", "SCC_CONTROL"), + ("CRUISE_STANDSTILL", "SCC_CONTROL"), ] checks += [ - ("CRUISE_INFO", 50), + ("SCC_CONTROL", 50), ] if CP.carFingerprint in EV_CAR: @@ -497,21 +497,20 @@ class CarState(CarStateBase): checks = [("CAM_0x2a4", 20)] else: signals = [ - ("COUNTER", "CRUISE_INFO"), - ("NEW_SIGNAL_1", "CRUISE_INFO"), - ("CRUISE_MAIN", "CRUISE_INFO"), - ("CRUISE_STATUS", "CRUISE_INFO"), - ("CRUISE_INACTIVE", "CRUISE_INFO"), - ("ZEROS_9", "CRUISE_INFO"), - ("CRUISE_STANDSTILL", "CRUISE_INFO"), - ("ZEROS_5", "CRUISE_INFO"), - ("DISTANCE_SETTING", "CRUISE_INFO"), - ("SET_SPEED", "CRUISE_INFO"), - ("NEW_SIGNAL_4", "CRUISE_INFO"), + ("COUNTER", "SCC_CONTROL"), + ("NEW_SIGNAL_1", "SCC_CONTROL"), + ("MainMode_ACC", "SCC_CONTROL"), + ("ACCMode", "SCC_CONTROL"), + ("CRUISE_INACTIVE", "SCC_CONTROL"), + ("ZEROS_9", "SCC_CONTROL"), + ("CRUISE_STANDSTILL", "SCC_CONTROL"), + ("ZEROS_5", "SCC_CONTROL"), + ("DISTANCE_SETTING", "SCC_CONTROL"), + ("VSetDis", "SCC_CONTROL"), ] checks = [ - ("CRUISE_INFO", 50), + ("SCC_CONTROL", 50), ] return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 6) diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index e1478e6f18..8b53e7c378 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -1,3 +1,4 @@ +from common.numpy_fast import clip from selfdrive.car.hyundai.values import HyundaiFlags @@ -52,10 +53,9 @@ def create_buttons(packer, CP, cnt, btn): def create_acc_cancel(packer, CP, cruise_info_copy): values = cruise_info_copy values.update({ - "CRUISE_STATUS": 0, - "CRUISE_INACTIVE": 1, + "ACCMode": 4, }) - return packer.make_can_msg("CRUISE_INFO", get_e_can_bus(CP), values) + return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values) def create_lfahda_cluster(packer, CP, enabled): values = { @@ -65,33 +65,37 @@ def create_lfahda_cluster(packer, CP, enabled): return packer.make_can_msg("LFAHDA_CLUSTER", get_e_can_bus(CP), values) -def create_acc_control(packer, CP, enabled, accel, stopping, gas_override, set_speed): - cruise_status = 0 if not enabled else (4 if gas_override else 2) +def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_override, set_speed): + jerk = 5 + jn = jerk / 50 if not enabled or gas_override: - accel = 0 + a_val, a_raw = 0, 0 + else: + a_raw = accel + a_val = clip(accel, accel_last - jn, accel_last + jn) + if stopping: + a_raw = 0 + values = { - "CRUISE_STATUS": cruise_status, - "CRUISE_INACTIVE": 0 if enabled else 1, - "CRUISE_MAIN": 1, - "CRUISE_STANDSTILL": 0, - "STOP_REQ": 1 if stopping else 0, - "ACCEL_REQ": accel, - "ACCEL_REQ2": accel, - "SET_SPEED": set_speed, - "DISTANCE_SETTING": 4, + "ACCMode": 0 if not enabled else (2 if gas_override else 1), + "MainMode_ACC": 1, + "StopReq": 1 if stopping else 0, + "aReqValue": a_val, + "aReqRaw": a_raw, + "VSetDis": set_speed, + "JerkLowerLimit": jerk if enabled else 1, "ACC_ObjDist": 1, - "ObjValid": 1, + "ObjValid": 0, "OBJ_STATUS": 2, - "SET_ME_2": 0x2, + "SET_ME_2": 0x4, "SET_ME_3": 0x3, "SET_ME_TMP_64": 0x64, - - "NEW_SIGNAL_9": 2, "NEW_SIGNAL_10": 4, + "DISTANCE_SETTING": 4, } - return packer.make_can_msg("CRUISE_INFO", get_e_can_bus(CP), values) + return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 6a1d741dec..4b4d51f3f1 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -202,14 +202,10 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] - ret.longitudinalActuatorDelayLowerBound = 0.15 - ret.longitudinalActuatorDelayUpperBound = 0.5 ret.experimentalLongitudinalAvailable = bool(ret.flags & HyundaiFlags.CANFD_HDA2) else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] - ret.longitudinalActuatorDelayLowerBound = 0.5 - ret.longitudinalActuatorDelayUpperBound = 0.5 ret.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR) ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable ret.pcmCruise = not ret.openpilotLongitudinalControl @@ -218,6 +214,8 @@ class CarInterface(CarInterfaceBase): ret.startingState = True ret.vEgoStarting = 0.1 ret.startAccel = 2.0 + ret.longitudinalActuatorDelayLowerBound = 0.5 + ret.longitudinalActuatorDelayUpperBound = 0.5 # *** feature detection *** if candidate in CANFD_CAR: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 98cb72293f..c760c724a9 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1232,6 +1232,7 @@ FW_VERSIONS = { b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106', b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', + b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', @@ -1244,6 +1245,7 @@ FW_VERSIONS = { b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800', b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800', + b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 326d80b822..5c6d214bcc 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -25,6 +25,7 @@ non_tested_cars = [ GM.BOLT_EV, HYUNDAI.GENESIS_G90, HYUNDAI.KIA_OPTIMA_H, + HONDA.ODYSSEY_CHN, ] CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,)) @@ -61,7 +62,6 @@ routes = [ CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED), CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV), CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX), - CarTestRoute("81722949a62ea724|2019-04-06--15-19-25", HONDA.ODYSSEY_CHN), CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index e4e141153f..a9999a4371 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -109,6 +109,10 @@ class TestCarModelBase(unittest.TestCase): assert cls.CP assert cls.CP.carFingerprint == cls.car_model + @classmethod + def tearDownClass(cls): + del cls.can_msgs + def setUp(self): self.CI = self.CarInterface(self.CP, self.CarController, self.CarState) assert self.CI @@ -233,7 +237,6 @@ class TestCarModelBase(unittest.TestCase): # TODO: check rest of panda's carstate (steering, ACC main on, etc.) checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() - checks['cruiseState'] += CS.cruiseState.enabled and not CS.cruiseState.available if self.CP.carName not in ("hyundai", "volkswagen", "body"): # TODO: fix standstill mismatches for other makes checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving() diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 13a0dae7a7..460b9a9097 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -21,7 +21,6 @@ FORD FOCUS 4TH GEN: [.nan, 1.5, .nan] COMMA BODY: [.nan, 1000, .nan] # Totally new cars -KIA EV6 2022: [3.5, 3.0, 0.0] RAM 1500 5TH GEN: [2.0, 2.0, 0.0] RAM HD 5TH GEN: [1.4, 1.4, 0.0] SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 60b1662cee..5c828fa669 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -39,17 +39,18 @@ HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.0781366561692759 HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185] JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003] +KIA EV6 2022: [3.2, 3.0, 0.05] KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716] KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558] KIA STINGER GT2 2018: [2.7499043387418967, 1.849652021986449, 0.12048334239559202] -LEXUS ES 2019: [2.0203086922726112, 2.134803912579666, 0.12757526789308554] -LEXUS ES HYBRID 2019: [2.392442298703042, 1.863360677810788, 0.17690002108856212] +LEXUS ES 2019: [1.935835, 2.134803912579666, 0.093439] +LEXUS ES HYBRID 2019: [2.135678, 1.863360677810788, 0.109627] LEXUS NX 2018: [2.302625600642627, 2.1382378491466625, 0.14986840878892838] LEXUS NX 2020: [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] LEXUS NX HYBRID 2018: [2.4025593501080955, 1.8080446063815507, 0.15349361249519017] LEXUS RX 2016: [1.5876816543130423, 1.0427699298523752, 0.21334066732397142] -LEXUS RX 2020: [1.5228812994274734, 1.431102486563665, 0.2093316728710659] +LEXUS RX 2020: [1.5228812994274734, 1.431102486563665, 0.164117] LEXUS RX HYBRID 2017: [1.6984261557042386, 1.3211501880159107, 0.1820354534928893] LEXUS RX HYBRID 2020: [1.5522309889823778, 1.255230465866663, 0.2220954003055114] MAZDA CX-9 2021: [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] @@ -62,31 +63,31 @@ TOYOTA AVALON 2019: [1.7036141952825095, 1.239619084240008, 0.08459830394899492] TOYOTA AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605] TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193] TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] -TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.13519250664782062] -TOYOTA CAMRY 2021: [2.6922769557433055, 2.3476510120007434, 0.1450430192989234] -TOYOTA CAMRY HYBRID 2018: [2.0974120828287774, 1.7996193116697359, 0.13823613467632756] -TOYOTA CAMRY HYBRID 2021: [2.6426668350384457, 2.3901492458927986, 0.16103875108816076] +TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.105192506] +TOYOTA CAMRY 2021: [2.446083, 2.3476510120007434, 0.121615] +TOYOTA CAMRY HYBRID 2018: [1.996333, 1.7996193116697359, 0.112565] +TOYOTA CAMRY HYBRID 2021: [2.263582, 2.3901492458927986, 0.115257] TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652] TOYOTA COROLLA HYBRID TSS2 2019: [1.9079729107361805, 1.8118712531729109, 0.22251440891543514] TOYOTA COROLLA TSS2 2019: [2.0742917676766712, 1.9258612322678952, 0.16888685704519352] TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796] TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457] -TOYOTA HIGHLANDER HYBRID 2018: [1.9421825202382728, 1.6433903296845025, 0.16928956792275918] -TOYOTA HIGHLANDER HYBRID 2020: [2.103373061114133, 2.104015182965606, 0.14447040132184993] +TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600] +TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993] TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] -TOYOTA PRIUS 2017: [2.0183401513314294, 1.5023147650693636, 0.20856908464957724] -TOYOTA PRIUS TSS2 2021: [2.327639738920072, 1.9104337425537743, 0.2030762265549664] +TOYOTA PRIUS 2017: [1.746445, 1.5023147650693636, 0.151515] +TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968] TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975] -TOYOTA RAV4 2019: [2.5038362866776835, 2.0993589721530252, 0.1552425356342368] +TOYOTA RAV4 2019: [2.331293, 2.0993589721530252, 0.129822] TOYOTA RAV4 2019 8965: [2.5084506298290377, 2.4216520504763475, 0.11992835265067918] TOYOTA RAV4 2019 x02: [2.7209621987605024, 2.2148637653781593, 0.10862567142268198] TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] TOYOTA RAV4 HYBRID 2019: [2.2271858492309153, 2.074844961405639, 0.14382216826893632] TOYOTA RAV4 HYBRID 2019 8965: [2.1077397198131336, 1.8162215166877735, 0.13891369391200137] TOYOTA RAV4 HYBRID 2019 x02: [2.803624333289342, 2.272367966572498, 0.11364569214387774] -TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.1565442715453653] +TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.112174] TOYOTA RAV4 HYBRID 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736] -TOYOTA SIENNA 2018: [1.8660896232147548, 1.3208264576110418, 0.18799149615227198] +TOYOTA SIENNA 2018: [1.689726, 1.3208264576110418, 0.140456] VOLKSWAGEN ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032] VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] VOLKSWAGEN GOLF 7TH GEN: [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 819b85d759..07c33d0173 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1367,6 +1367,7 @@ FW_VERSIONS = { b'\x01896634AA1000\x00\x00\x00\x00', b'\x01896634A88000\x00\x00\x00\x00', b'\x01896634A89000\x00\x00\x00\x00', + b'\x01896634A89100\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R01100\x00\x00\x00\x00', diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 5172cc4044..1adbba4171 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -254,8 +254,7 @@ class Controls: self.events.add(EventName.pedalPressed) if CS.gasPressed: - self.events.add(EventName.pedalPressedPreEnable if self.disengage_on_accelerator else - EventName.gasPressedOverride) + self.events.add(EventName.gasPressedOverride) if not self.CP.notCar: self.events.add_from_msg(self.sm['driverMonitoringState'].events) diff --git a/selfdrive/controls/lib/radar_helpers.py b/selfdrive/controls/lib/radar_helpers.py index bf788190cd..4bb0179267 100644 --- a/selfdrive/controls/lib/radar_helpers.py +++ b/selfdrive/controls/lib/radar_helpers.py @@ -151,7 +151,8 @@ class Cluster(): def potential_low_speed_lead(self, v_ego): # stop for stuff in front of you and low speed, even without model confirmation - return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and self.dRel < 25 + # Radar points closer than 0.75, are almost always glitches on toyota radars + return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and (0.75 < self.dRel < 25) def is_potential_fcw(self, model_prob): return model_prob > .9 diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 4e996bc3b9..1c68eb67bd 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -66,7 +66,6 @@ class Calibrator: # Read saved calibration params = Params() calibration_params = params.get("CalibrationParams") - self.wide_camera = params.get_bool('WideCameraOnly') rpy_init = RPY_INIT wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT valid_blocks = 0 @@ -166,10 +165,7 @@ class Calibrator: self.old_rpy_weight = min(0.0, self.old_rpy_weight - 1/SMOOTH_CYCLES) straight_and_fast = ((self.v_ego > MIN_SPEED_FILTER) and (trans[0] > MIN_SPEED_FILTER) and (abs(rot[2]) < MAX_YAW_RATE_FILTER)) - if self.wide_camera: - angle_std_threshold = 4*MAX_VEL_ANGLE_STD - else: - angle_std_threshold = MAX_VEL_ANGLE_STD + angle_std_threshold = MAX_VEL_ANGLE_STD certain_if_calib = ((np.arctan2(trans_std[1], trans[0]) < angle_std_threshold) or (self.valid_blocks < INPUTS_NEEDED)) if not (straight_and_fast and certain_if_calib): @@ -185,7 +181,6 @@ class Calibrator: new_wide_from_device_euler = np.array(wide_from_device_euler) else: new_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT - self.rpys[self.block_idx] = (self.idx*self.rpys[self.block_idx] + (BLOCK_SIZE - self.idx) * new_rpy) / float(BLOCK_SIZE) self.wide_from_device_eulers[self.block_idx] = (self.idx*self.wide_from_device_eulers[self.block_idx] + diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 66af234590..42dff60087 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -16,7 +16,9 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 MIN_POINTS_TOTAL = 4000 +MIN_POINTS_TOTAL_QLOG = 800 FIT_POINTS_TOTAL = 2000 +FIT_POINTS_TOTAL_QLOG = 800 MIN_VEL = 15 # m/s FRICTION_FACTOR = 1.5 # ~85% of data coverage FACTOR_SANITY = 0.3 @@ -26,7 +28,7 @@ MIN_FILTER_DECAY = 50 MAX_FILTER_DECAY = 250 LAT_ACC_THRESHOLD = 1 STEER_BUCKET_BOUNDS = [(-0.5, -0.3), (-0.3, -0.2), (-0.2, -0.1), (-0.1, 0), (0, 0.1), (0.1, 0.2), (0.2, 0.3), (0.3, 0.5)] -MIN_BUCKET_POINTS = [100, 300, 500, 500, 500, 500, 300, 100] +MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100]) MAX_RESETS = 5.0 MAX_INVALID_THRESHOLD = 10 MIN_ENGAGE_BUFFER = 2 # secs @@ -58,10 +60,11 @@ class NPQueue: class PointBuckets: - def __init__(self, x_bounds, min_points): + def __init__(self, x_bounds, min_points, min_points_total): self.x_bounds = x_bounds self.buckets = {bounds: NPQueue(maxlen=POINTS_PER_BUCKET, rowsize=3) for bounds in x_bounds} self.buckets_min_points = {bounds: min_point for bounds, min_point in zip(x_bounds, min_points)} + self.min_points_total = min_points_total def bucket_lengths(self): return [len(v) for v in self.buckets.values()] @@ -70,7 +73,7 @@ class PointBuckets: return sum(self.bucket_lengths()) def is_valid(self): - return all(len(v) >= min_pts for v, min_pts in zip(self.buckets.values(), self.buckets_min_points.values())) and (self.__len__() >= MIN_POINTS_TOTAL) + return all(len(v) >= min_pts for v, min_pts in zip(self.buckets.values(), self.buckets_min_points.values())) and (self.__len__() >= self.min_points_total) def add_point(self, x, y): for bound_min, bound_max in self.x_bounds: @@ -90,9 +93,17 @@ class PointBuckets: class TorqueEstimator: - def __init__(self, CP): + def __init__(self, CP, decimated=False): self.hist_len = int(HISTORY / DT_MDL) self.lag = CP.steerActuatorDelay + .2 # from controlsd + if decimated: + self.min_bucket_points = MIN_BUCKET_POINTS / 10 + self.min_points_total = MIN_POINTS_TOTAL_QLOG + self.fit_points = FIT_POINTS_TOTAL_QLOG + else: + self.min_bucket_points = MIN_BUCKET_POINTS + self.min_points_total = MIN_POINTS_TOTAL + self.fit_points = FIT_POINTS_TOTAL self.offline_friction = 0.0 self.offline_latAccelFactor = 0.0 @@ -157,10 +168,10 @@ class TorqueEstimator: self.invalid_values_tracker = 0.0 self.decay = MIN_FILTER_DECAY self.raw_points = defaultdict(lambda: deque(maxlen=self.hist_len)) - self.filtered_points = PointBuckets(x_bounds=STEER_BUCKET_BOUNDS, min_points=MIN_BUCKET_POINTS) + self.filtered_points = PointBuckets(x_bounds=STEER_BUCKET_BOUNDS, min_points=self.min_bucket_points, min_points_total=self.min_points_total) def estimate_params(self): - points = self.filtered_points.get_points(FIT_POINTS_TOTAL) + points = self.filtered_points.get_points(self.fit_points) # total least square solution as both x and y are noisy observations # this is empirically the slope of the hysteresis parallelogram as opposed to the line through the diagonals try: diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 49a1c3a3b8..243223eabd 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a41d42f92913e6cc3909e505b1220b16d31f7cfca5f8a4e82109577b4151b645 +oid sha256:dcfad22cecf37275d01a339d96174800c109e9a70f853fdef3e4ef62ed3f4bbe size 45922983 diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index fa23a161c4..1c65051665 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -124,6 +124,7 @@ def setup_quectel(diag: ModemDiag): # don't automatically turn on GNSS on powerup at_cmd("AT+QGPSCFG=\"autogps\",0") + at_cmd("AT+QGPSSUPLURL=\"supl.google.com:7275\"") at_cmd("AT+QGPSCFG=\"outport\",\"usbnmea\"") at_cmd("AT+QGPS=1") diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 5843a1e174..d13ced3a53 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -d2f1711fd58d4f2c25b81bd332270da60ff9636d +49ea844254883ac61caa2ac425f453799aeb28a6 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 9d37be4a56..bc55e33cbf 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -456,6 +456,9 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = CP.carFingerprint + if CP.openpilotLongitudinalControl: + params.put_bool("ExperimentalLongitudinalEnabled", True) + def python_replay_process(cfg, lr, fingerprint=None): sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index ba0f30e5c3..0e4ac94784 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -8f2919ba4d8509432e93ff0a44f951254c1ff3fe \ No newline at end of file +fc3a044c567a8702ed1500d745170c365dd6b3d4 \ No newline at end of file diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index cecabd8a3a..5c754d9312 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -24,10 +24,11 @@ source_segments = [ ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 ("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC) ("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH) - ("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA + ("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA_2018_HYBRID ("RAM", "2f4452b03ccb98f0|2022-09-07--13-55-08--10"), # CHRYSLER.RAM_1500 ("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.OUTBACK ("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT + ("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.BOLT_EUV ("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL ("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.GOLF ("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.CX9_2021 @@ -50,6 +51,7 @@ segments = [ ("RAM", "regen20490083AE7|2022-09-27--15-53-15--0"), ("SUBARU", "regen1E72BBDCED5|2022-09-27--15-55-31--0"), ("GM", "regen45B05A80EF6|2022-09-27--15-57-22--0"), + ("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), ("NISSAN", "regenC19D899B46D|2022-09-27--15-59-13--0"), ("VOLKSWAGEN", "regenD8F7AC4BD0D|2022-09-27--16-41-45--0"), ("MAZDA", "regenFC3F9ECBB64|2022-09-27--16-03-09--0"), diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 1f677fc92d..346cb3ab96 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -100,10 +100,6 @@ void OnroadWindow::offroadTransition(bool offroad) { #endif alerts->updateAlert({}, bg); - - // update stream type - bool wide_cam = Params().getBool("WideCameraOnly"); - nvg->setStreamType(wide_cam ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); } void OnroadWindow::paintEvent(QPaintEvent *event) { @@ -232,8 +228,10 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD()); } + setStreamType(s.scene.wide_cam ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); if (s.scene.calibration_valid) { - CameraWidget::updateCalibration(s.scene.view_from_calib); + auto calib = s.scene.wide_cam ? s.scene.view_from_wide_calib : s.scene.view_from_calib; + CameraWidget::updateCalibration(calib); } else { CameraWidget::updateCalibration(DEFAULT_CALIBRATION); } diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 200257235d..d7591633c9 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -96,8 +96,8 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio) CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) : stream_name(stream_name), stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); - connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); - connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived); + QObject::connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); + QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection); } CameraWidget::~CameraWidget() { @@ -162,13 +162,13 @@ void CameraWidget::initializeGL() { } void CameraWidget::showEvent(QShowEvent *event) { - frames.clear(); if (!vipc_thread) { vipc_thread = new QThread(); connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater); vipc_thread->start(); } + clearFrames(); } void CameraWidget::hideEvent(QHideEvent *event) { @@ -178,6 +178,7 @@ void CameraWidget::hideEvent(QHideEvent *event) { vipc_thread->wait(); vipc_thread = nullptr; } + clearFrames(); } void CameraWidget::updateFrameMat() { @@ -187,12 +188,17 @@ void CameraWidget::updateFrameMat() { if (stream_type == VISION_STREAM_DRIVER) { frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); } else { - intrinsic_matrix = (stream_type == VISION_STREAM_WIDE_ROAD) ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; - zoom = (stream_type == VISION_STREAM_WIDE_ROAD) ? 2.5 : 1.1; - // Project point at "infinity" to compute x and y offsets // to ensure this ends up in the middle of the screen + // for narrow come and a little lower for wide cam. // TODO: use proper perspective transform? + if (stream_type == VISION_STREAM_WIDE_ROAD) { + intrinsic_matrix = ecam_intrinsic_matrix; + zoom = 2.0; + } else { + intrinsic_matrix = fcam_intrinsic_matrix; + zoom = 1.1; + } const vec3 inf = {{1000., 0., 0.}}; const vec3 Ep = matvecmul3(calibration, inf); const vec3 Kep = matvecmul3(intrinsic_matrix, Ep); @@ -233,45 +239,37 @@ void CameraWidget::paintGL() { glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - if (frames.empty()) return; - - int frame_idx = frames.size() - 1; + std::lock_guard lk(frame_lock); - // Always draw latest frame until sync logic is more stable - // for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) { - // if (frames[frame_idx].first == draw_frame_id) break; - // } - - // Log duplicate/dropped frames - if (frames[frame_idx].first == prev_frame_id) { - qDebug() << "Drawing same frame twice" << frames[frame_idx].first; - } else if (frames[frame_idx].first != prev_frame_id + 1) { - qDebug() << "Skipped frame" << frames[frame_idx].first; + // use previous texture if update() is called without new frame. + VisionBuf *frame = nullptr; + if (!frames.empty()) { + frame = frames.front().second; + frames.pop_front(); } - prev_frame_id = frames[frame_idx].first; glViewport(0, 0, width(), height()); glBindVertexArray(frame_vao); glUseProgram(program->programId()); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - VisionBuf *frame = frames[frame_idx].second; - #ifdef QCOM2 glActiveTexture(GL_TEXTURE0); - glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]); - assert(glGetError() == GL_NO_ERROR); + if (frame) { + glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]); + assert(glGetError() == GL_NO_ERROR); + } #else glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textures[0]); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width, stream_height, GL_RED, GL_UNSIGNED_BYTE, frame->y); + if (frame) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width, stream_height, GL_RED, GL_UNSIGNED_BYTE, frame->y); assert(glGetError() == GL_NO_ERROR); glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride/2); glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, textures[1]); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width/2, stream_height/2, GL_RG, GL_UNSIGNED_BYTE, frame->uv); + if (frame) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width/2, stream_height/2, GL_RG, GL_UNSIGNED_BYTE, frame->uv); assert(glGetError() == GL_NO_ERROR); #endif @@ -288,7 +286,6 @@ void CameraWidget::paintGL() { void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { makeCurrent(); - frames.clear(); stream_width = vipc_client->buffers[0].width; stream_height = vipc_client->buffers[0].height; stream_stride = vipc_client->buffers[0].stride; @@ -339,11 +336,7 @@ void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { updateFrameMat(); } -void CameraWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) { - frames.push_back(std::make_pair(frame_id, buf)); - while (frames.size() > FRAME_BUFFER_SIZE) { - frames.pop_front(); - } +void CameraWidget::vipcFrameReceived() { update(); } @@ -354,11 +347,13 @@ void CameraWidget::vipcThread() { while (!QThread::currentThread()->isInterruptionRequested()) { if (!vipc_client || cur_stream_type != stream_type) { + clearFrames(); cur_stream_type = stream_type; vipc_client.reset(new VisionIpcClient(stream_name, cur_stream_type, false)); } if (!vipc_client->connected) { + clearFrames(); if (!vipc_client->connect(false)) { QThread::msleep(100); continue; @@ -367,7 +362,15 @@ void CameraWidget::vipcThread() { } if (VisionBuf *buf = vipc_client->recv(&meta_main, 1000)) { - emit vipcThreadFrameReceived(buf, meta_main.frame_id); + { + std::lock_guard lk(frame_lock); + frames.push_back(std::make_pair(meta_main.frame_id, buf)); + while (frames.size() > FRAME_BUFFER_SIZE) { + qDebug() << "Skipped frame" << frames.front().first; + frames.pop_front(); + } + } + emit vipcThreadFrameReceived(); } } @@ -378,3 +381,8 @@ void CameraWidget::vipcThread() { egl_images.clear(); #endif } + +void CameraWidget::clearFrames() { + std::lock_guard lk(frame_lock); + frames.clear(); +} diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 9bcad935c0..aff3abc836 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -37,7 +38,7 @@ public: signals: void clicked(); void vipcThreadConnected(VisionIpcClient *); - void vipcThreadFrameReceived(VisionBuf *, quint32); + void vipcThreadFrameReceived(); protected: void paintGL() override; @@ -49,6 +50,7 @@ protected: virtual void updateFrameMat(); void updateCalibration(const mat3 &calib); void vipcThread(); + void clearFrames(); bool zoomed_view; GLuint frame_vao, frame_vbo, frame_ibo; @@ -76,11 +78,11 @@ protected: mat3 calibration = DEFAULT_CALIBRATION; mat3 intrinsic_matrix = fcam_intrinsic_matrix; + std::mutex frame_lock; std::deque> frames; uint32_t draw_frame_id = 0; - uint32_t prev_frame_id = 0; protected slots: void vipcConnected(VisionIpcClient *vipc_client); - void vipcFrameReceived(VisionBuf *vipc_client, uint32_t frame_id); + void vipcFrameReceived(); }; diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 314a6b8338..a11333a54d 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -56,7 +56,7 @@ leave blank for automatic configuration - 空白のままにして、自動設定にします + 自動で設定するには、空白のままにしてください。 Cellular Metered @@ -136,7 +136,7 @@ PREVIEW - 見る + プレビュー Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) @@ -156,7 +156,7 @@ Review Training Guide - 入門書を見る + 使い方の確認 REVIEW @@ -168,7 +168,7 @@ Are you sure you want to review the training guide? - 入門書を見てもよろしいですか? + 使い方の確認をしますか? Regulatory @@ -200,11 +200,11 @@ 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. - openpilot は、左または右の4°以内、上の5°または下の8°以内にデバイスを取付ける必要があります。キャリブレーションを引き続きます、リセットはほとんど必要ありません。 + openpilotの本体は、左右4°以内、上5°、下8°以内の角度で取付ける必要があります。継続してキャリブレーションを続けているので、手動でリセットを行う必要はほぼありません。 Your device is pointed %1° %2 and %3° %4. - このデバイスは%2の%1°、%4の%3°に向けます。 + このデバイスは%2 %1°、%4 %3°の向きに設置されています。 down @@ -309,7 +309,7 @@ MapETA eta - 予定到着時間 + 到着予定時間 min @@ -452,15 +452,15 @@ location set Go to https://connect.comma.ai on your phone - モバイルデバイスで「connect.comma.ai」にアクセスして + スマートフォンで「https://connect.comma.ai」にアクセスしてください。 Click "add new device" and scan the QR code on the right - 「新しいデバイスを追加」を押すと、右側のQRコードをスキャンしてください + 「新しいデバイスを追加」を押し、右側のQRコードをスキャンしてください。 Bookmark connect.comma.ai to your home screen to use it like an app - 「connect.comma.ai」をホーム画面に追加して、アプリのように使うことができます + 「connect.comma.ai」をホーム画面に追加して、アプリのように使うことができます。 @@ -608,7 +608,7 @@ location set Toggles - 切り替え + 機能設定 Software @@ -627,7 +627,7 @@ location set Power your device in a car with a harness or proceed at your own risk. - 自己責任でハーネスから電源を供給してください。 + 自己責任で実行を継続するか、ハーネスから電源を供給してください。 Power off @@ -643,7 +643,7 @@ location set Before we get on the road, let’s finish installation and cover some details. - その前に、インストールを完了し、いくつかの詳細を説明します。 + 道路に向かう前に、インストールを完了して使い方を確認しましょう。 Connect to Wi-Fi @@ -655,7 +655,7 @@ location set Continue without Wi-Fi - Wi-Fi に未接続で続行 + Wi-Fi に接続せずに続行 Waiting for internet @@ -663,7 +663,7 @@ location set Choose Software to Install - インストールするソフトウェアを選びます + インストールするソフトウェアを選択してください Dashcam @@ -710,7 +710,7 @@ location set Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - デバイスを comma connect (connect.comma.ai)でペアリングし comma prime 特典を申請してください。 + デバイスを comma connect (connect.comma.ai)でペアリングし、comma primeの特典を申請してください。 Pair device @@ -836,7 +836,7 @@ location set UNINSTALL - アンインストール + 実行 Uninstall %1 @@ -859,7 +859,7 @@ location set 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. - 警告: これは、GitHub の設定にあるすべての公開鍵への SSH アクセスを許可するものです。自分以外の GitHub のユーザー名を入力しないでください。コンマのスタッフが GitHub のユーザー名を追加するようお願いすることはありません。 + 警告: これは、GitHub の設定にあるすべての公開鍵への SSH アクセスを許可するものです。自分以外の GitHub のユーザー名を入力しないでください。commaのスタッフが GitHub のユーザー名を追加するようお願いすることはありません。 ADD @@ -871,7 +871,7 @@ location set LOADING - ローディング + 読み込み中 REMOVE @@ -924,7 +924,7 @@ location set 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. - アダプティブクルーズコントロールとレーンキーピングドライバーアシスト(openpilotシステム)。この機能を使用するには、常に注意が必要です。この設定を変更すると、車の電源が切れたときに有効になります。 + openpilotによるアダプティブクルーズコントロールとレーンキーピングドライバーアシストを利用します。この機能を利用する際は、常に前方への注意が必要です。この設定を変更すると、車の電源が切れた時に反映されます。 Enable Lane Departure Warnings @@ -932,11 +932,11 @@ location set 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). - 時速31マイル(50km)を超えるスピードで走行中、ウインカーを作動させずに検出された車線ライン上に車両が触れた場合、車線に戻るアラートを受信します。 + 時速31マイル(50km)を超えるスピードで走行中、ウインカーを作動させずに検出された車線ライン上に車両が触れた場合、手動で車線内に戻るように警告を行います。 Use Metric System - メートル法を有効化 + メートル法を使用 Display speed in km/h instead of mph. @@ -952,7 +952,7 @@ location set 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 エンドツーエンドのアクセル制御 (超アルファ版) 🌮 + 🌮 エンドツーエンドのアクセル制御 (超α版) 🌮 Experimental openpilot longitudinal control @@ -976,11 +976,11 @@ location set Disengage On Accelerator Pedal - アクセル踏むと openpilot をキャンセル + アクセルを踏むと openpilot を中断 When enabled, pressing the accelerator pedal will disengage openpilot. - 有効な場合は、アクセルを踏むと openpilot をキャンセルします。 + この機能を有効化すると、openpilotを利用中にアクセルを踏むとopenpilotによる運転サポートを中断します。 Show ETA in 24h Format @@ -1003,11 +1003,11 @@ location set Updater Update Required - 更新が必要です + アップデートが必要です An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. - オペレーティングシステムのアップデートが必要です。Wi-Fi に接続することで、最速のアップデートを体験できます。ダウンロードサイズは約 1GB です。 + オペレーティングシステムのアップデートが必要です。Wi-Fi に接続してアップデートする事をお勧めします。ダウンロードサイズは約 1GB です。 Connect to Wi-Fi diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 1a6027bf7f..d1d72b4dad 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -23,8 +23,8 @@ static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; const vec3 pt = (vec3){{in_x, in_y, in_z}}; - const vec3 Ep = matvecmul3(s->scene.view_from_calib, pt); - const vec3 KEp = matvecmul3(s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep); + const vec3 Ep = matvecmul3(s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib, pt); + const vec3 KEp = matvecmul3(s->scene.wide_cam ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep); // Project. QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]}); @@ -121,17 +121,23 @@ static void update_state(UIState *s) { if (sm.updated("liveCalibration")) { auto rpy_list = sm["liveCalibration"].getLiveCalibration().getRpyCalib(); + auto wfde_list = sm["liveCalibration"].getLiveCalibration().getWideFromDeviceEuler(); Eigen::Vector3d rpy; - rpy << rpy_list[0], rpy_list[1], rpy_list[2]; + Eigen::Vector3d wfde; + if (rpy_list.size() == 3) rpy << rpy_list[0], rpy_list[1], rpy_list[2]; + if (wfde_list.size() == 3) wfde << wfde_list[0], wfde_list[1], wfde_list[2]; Eigen::Matrix3d device_from_calib = euler2rot(rpy); + Eigen::Matrix3d wide_from_device = euler2rot(wfde); Eigen::Matrix3d view_from_device; view_from_device << 0,1,0, 0,0,1, 1,0,0; Eigen::Matrix3d view_from_calib = view_from_device * device_from_calib; + Eigen::Matrix3d view_from_wide_calib = view_from_device * wide_from_device * device_from_calib ; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j); + scene.view_from_wide_calib.v[i*3 + j] = view_from_wide_calib(i,j); } } scene.calibration_valid = sm["liveCalibration"].getLiveCalibration().getCalStatus() == 1; @@ -167,6 +173,17 @@ static void update_state(UIState *s) { scene.light_sensor = std::max(100.0f - scale * sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(), 0.0f); } scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; + + if (sm.updated("carState")) { + float v_ego = sm["carState"].getCarState().getVEgo(); + // TODO: support replays without ecam by using fcam + // Wide or narrow cam dependent on speed + if ((v_ego < 10) || s->wide_cam_only) { + scene.wide_cam = true; + } else if (v_ego > 15) { + scene.wide_cam = false; + } + } } void ui_update_params(UIState *s) { @@ -197,7 +214,7 @@ void UIState::updateStatus() { if (scene.started) { status = STATUS_DISENGAGED; scene.started_frame = sm->frame; - wide_camera = Params().getBool("WideCameraOnly"); + wide_cam_only = Params().getBool("WideCameraOnly"); } started_prev = scene.started; emit offroadTransition(!scene.started); @@ -219,7 +236,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { }); Params params; - wide_camera = params.getBool("WideCameraOnly"); + wide_cam_only = params.getBool("WideCameraOnly"); prime_type = std::atoi(params.get("PrimeType").c_str()); language = QString::fromStdString(params.get("LanguageSetting")); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index f60c26b59a..5a51dda8f8 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -87,7 +87,9 @@ const QColor bg_colors [] = { typedef struct UIScene { bool calibration_valid = false; + bool wide_cam = true; mat3 view_from_calib = DEFAULT_CALIBRATION; + mat3 view_from_wide_calib = DEFAULT_CALIBRATION; cereal::PandaState::PandaType pandaType; // modelV2 @@ -130,7 +132,7 @@ public: QString language; QTransform car_space_transform; - bool wide_camera; + bool wide_cam_only; signals: void uiUpdate(const UIState &s); diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index d7a552eabb..73879ab05d 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -4,4 +4,4 @@ moc_* _cabana settings car_fingerprint_to_dbc.json - +tests/_test_cabana diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 4e4e11dbd8..b7321e1f8d 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -18,5 +18,9 @@ cabana_env = qt_env.Clone() prev_moc_path = cabana_env['QT_MOCHPREFIX'] cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' cabana_env.Execute('./generate_dbc_json.py --out car_fingerprint_to_dbc.json') -cabana_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', +cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', 'canmessages.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + +if GetOption('test'): + cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 26fecd8c38..91353e9726 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -20,13 +20,15 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { setItemDelegate(delegate); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); horizontalHeader()->hide(); - verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); + verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setMouseTracking(true); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); +} - QObject::connect(model, &QAbstractItemModel::modelReset, [this]() { - setFixedHeight((CELL_HEIGHT + 1) * std::min(model->rowCount(), 8) + 2); - }); +QSize BinaryView::sizeHint() const { + QSize sz = QTableView::sizeHint(); + return {sz.width(), model->rowCount() <= 8 ? ((CELL_HEIGHT + 1) * model->rowCount() + 2) : sz.height()}; } void BinaryView::highlight(const Signal *sig) { @@ -108,9 +110,9 @@ void BinaryView::leaveEvent(QEvent *event) { void BinaryView::setMessage(const QString &message_id) { msg_id = message_id; model->setMessage(message_id); - resizeRowsToContents(); clearSelection(); updateState(); + updateGeometry(); } void BinaryView::updateState() { diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 0f58e9ed20..060a2eef7f 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -61,6 +61,7 @@ public: void highlight(const Signal *sig); const Signal *hoveredSignal() const { return hovered_sig; } QSet getOverlappingSignals() const; + QSize sizeHint() const override; signals: void signalHovered(const Signal *sig); diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index ea44a4033c..56346c6d6e 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -142,24 +142,33 @@ void ChartsWidget::updateTitleBar() { dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } -void ChartsWidget::addChart(const QString &id, const Signal *sig) { +void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show) { auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; }); - if (it == charts.end()) { + if (it != charts.end()) { + if (!show) removeChart((*it)); + } else if (show) { auto chart = new ChartWidget(id, sig, this); chart->chart_view->updateSeries(display_range); - QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(chart); });; + QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(chart); }); QObject::connect(chart->chart_view, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); QObject::connect(chart->chart_view, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); charts_layout->insertWidget(0, chart); charts.push_back(chart); + emit chartOpened(chart->id, chart->signal); } updateTitleBar(); } +bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) { + auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; }); + return it != charts.end(); +} + void ChartsWidget::removeChart(ChartWidget *chart) { charts.removeOne(chart); chart->deleteLater(); updateTitleBar(); + emit chartClosed(chart->id, chart->signal); } void ChartsWidget::removeAll(const Signal *sig) { @@ -168,6 +177,7 @@ void ChartsWidget::removeAll(const Signal *sig) { auto c = it.next(); if (sig == nullptr || c->signal == sig) { c->deleteLater(); + emit chartClosed(c->id, c->signal); it.remove(); } } @@ -184,7 +194,6 @@ void ChartsWidget::signalUpdated(const Signal *sig) { } } - bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { if (obj != this && event->type() == QEvent::Close) { emit dock_btn->clicked(); @@ -200,33 +209,31 @@ ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent) main_layout->setSpacing(0); main_layout->setContentsMargins(0, 0, 0, 0); - QWidget *header = new QWidget(this); - header->setStyleSheet("background-color:white"); + header = new QWidget(this); QGridLayout *header_layout = new QGridLayout(header); header_layout->setContentsMargins(11, 11, 11, 0); msg_name_label = new QLabel(this); msg_name_label->setTextFormat(Qt::RichText); header_layout->addWidget(msg_name_label, 0, 0, Qt::AlignLeft); sig_name_label = new QLabel(this); - sig_name_label->setStyleSheet("font-weight:bold"); header_layout->addWidget(sig_name_label, 0, 1, Qt::AlignCenter); //, 0, Qt::AlignCenter); - QPushButton *remove_btn = new QPushButton("✖", this); + remove_btn = new QPushButton("✖", this); remove_btn->setFixedSize(20, 20); remove_btn->setToolTip(tr("Remove chart")); header_layout->addWidget(remove_btn, 0, 2, Qt::AlignRight); main_layout->addWidget(header); chart_view = new ChartView(id, sig, this); - chart_view->setFixedHeight(settings.chart_height); main_layout->addWidget(chart_view); main_layout->addStretch(); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); updateTitle(); + updateFromSettings(); QObject::connect(remove_btn, &QPushButton::clicked, [=]() { emit remove(id, sig); }); - QObject::connect(&settings, &Settings::changed, [this]() { chart_view->setFixedHeight(settings.chart_height); }); + QObject::connect(&settings, &Settings::changed, this, &ChartWidget::updateFromSettings); } void ChartWidget::updateTitle() { @@ -234,12 +241,22 @@ void ChartWidget::updateTitle() { sig_name_label->setText(signal->name.c_str()); } +void ChartWidget::updateFromSettings() { + header->setStyleSheet(settings.chart_theme == 0 ? "background-color:white" : "background-color:#23242c"); + QString color_style = settings.chart_theme == 0 ? "color:black" : "color:white"; + sig_name_label->setStyleSheet("font-weight:bold;" + color_style); + msg_name_label->setStyleSheet(color_style); + remove_btn->setStyleSheet(color_style); + chart_view->updateFromSettings(); +} + // ChartView ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) : id(id), signal(sig), QChartView(nullptr, parent) { QLineSeries *series = new QLineSeries(); QChart *chart = new QChart(); + chart->setBackgroundRoundness(0); chart->addSeries(series); chart->createDefaultAxes(); chart->legend()->hide(); @@ -247,11 +264,15 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) chart->layout()->setContentsMargins(0, 0, 0, 0); track_line = new QGraphicsLineItem(chart); - track_line->setPen(QPen(Qt::gray, 1, Qt::DashLine)); - value_text = new QGraphicsSimpleTextItem(chart); - value_text->setBrush(Qt::gray); + track_line->setZValue(chart->zValue() + 10); + track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine)); + track_ellipse = new QGraphicsEllipseItem(chart); + track_ellipse->setZValue(chart->zValue() + 10); + track_ellipse->setBrush(Qt::darkGray); + value_text = new QGraphicsTextItem(chart); + value_text->setZValue(chart->zValue() + 10); line_marker = new QGraphicsLineItem(chart); - line_marker->setPen(QPen(Qt::black, 2)); + line_marker->setZValue(chart->zValue() + 10); setChart(chart); @@ -274,6 +295,12 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) }); } +void ChartView::updateFromSettings() { + setFixedHeight(settings.chart_height); + chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark); + line_marker->setPen(QPen(settings.chart_theme == 0 ? Qt::black : Qt::white, 2)); +} + void ChartView::setRange(double min, double max, bool force_update) { auto axis_x = dynamic_cast(chart()->axisX()); if (force_update || (min != axis_x->min() || max != axis_x->max())) { @@ -348,12 +375,14 @@ void ChartView::updateAxisY() { void ChartView::enterEvent(QEvent *event) { track_line->setVisible(true); value_text->setVisible(true); + track_ellipse->setVisible(true); QChartView::enterEvent(event); } void ChartView::leaveEvent(QEvent *event) { track_line->setVisible(false); value_text->setVisible(false); + track_ellipse->setVisible(false); QChartView::leaveEvent(event); } @@ -373,29 +402,39 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { // zoom in if selected range is greater than 0.5s emit zoomIn(min, max); } + event->accept(); } else if (event->button() == Qt::RightButton) { emit zoomReset(); + event->accept(); + } else { + QGraphicsView::mouseReleaseEvent(event); } - event->accept(); } void ChartView::mouseMoveEvent(QMouseEvent *ev) { auto rubber = findChild(); - bool dragging = rubber && rubber->isVisible(); - if (!dragging) { + bool is_zooming = rubber && rubber->isVisible(); + if (!is_zooming) { const auto plot_area = chart()->plotArea(); - float x = std::clamp((float)ev->pos().x(), (float)plot_area.left(), (float)plot_area.right()); - track_line->setLine(x, plot_area.top(), x, plot_area.bottom()); - auto axis_x = dynamic_cast(chart()->axisX()); - double sec = axis_x->min() + ((x - plot_area.x()) / plot_area.width()) * (axis_x->max() - axis_x->min()); - auto value = std::lower_bound(vals.begin(), vals.end(), sec, [](auto &p, double x) { return p.x() < x; }); - value_text->setPos(x + 6, plot_area.bottom() - 25); + double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right()-1); + double sec = axis_x->min() + (axis_x->max() - axis_x->min()) * (x - plot_area.left()) / plot_area.width(); + auto value = std::upper_bound(vals.begin(), vals.end(), sec, [](double x, auto &p) { return x < p.x(); }); if (value != vals.end()) { - value_text->setText(QString("(%1, %2)").arg(value->x(), 0, 'f', 3).arg(value->y())); - } else { - value_text->setText("(--, --)"); + QPointF pos = chart()->mapToPosition((*value)); + track_line->setLine(pos.x(), plot_area.top(), pos.x(), plot_area.bottom()); + track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10); + value_text->setHtml(tr("
%1, %2)
") + .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); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index a7c6f4bbd3..602d50ca0a 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -3,8 +3,9 @@ #include #include +#include #include -#include +#include #include #include #include @@ -23,6 +24,7 @@ public: void updateSeries(const std::pair &range); void setRange(double min, double max, bool force_update = false); void updateLineMarker(double current_sec); + void updateFromSettings(); signals: void zoomIn(double min, double max); @@ -34,11 +36,11 @@ private: void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; void adjustChartMargins(); - void updateAxisY(); QGraphicsLineItem *track_line; - QGraphicsSimpleTextItem *value_text; + QGraphicsEllipseItem *track_ellipse; + QGraphicsTextItem *value_text; QGraphicsLineItem *line_marker; QList vals; QString id; @@ -51,6 +53,7 @@ Q_OBJECT public: ChartWidget(const QString &id, const Signal *sig, QWidget *parent); void updateTitle(); + void updateFromSettings(); signals: void remove(const QString &msg_id, const Signal *sig); @@ -58,8 +61,10 @@ signals: public: QString id; const Signal *signal; + QWidget *header; QLabel *msg_name_label; QLabel *sig_name_label; + QPushButton *remove_btn; ChartView *chart_view = nullptr; }; @@ -68,12 +73,15 @@ class ChartsWidget : public QWidget { public: ChartsWidget(QWidget *parent = nullptr); - void addChart(const QString &id, const Signal *sig); + void showChart(const QString &id, const Signal *sig, bool show); void removeChart(ChartWidget *chart); + bool isChartOpened(const QString &id, const Signal *sig); signals: void dock(bool floating); void rangeChanged(double min, double max, bool is_zommed); + void chartOpened(const QString &id, const Signal *sig); + void chartClosed(const QString &id, const Signal *sig); private: void eventsMerged(); diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index c3fbab0349..0ab67dd305 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -1,5 +1,6 @@ #include "tools/cabana/dbcmanager.h" +#include #include #include @@ -28,8 +29,25 @@ void DBCManager::open(const QString &name, const QString &content) { emit DBCFileChanged(); } -void save(const QString &dbc_file_name) { - // TODO: save DBC to file +QString DBCManager::generateDBC() { + if (!dbc) return {}; + + QString dbc_string; + for (auto &m : dbc->msgs) { + dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(m.address).arg(m.name.c_str()).arg(m.size); + for (auto &sig : m.sigs) { + dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [0|0] \"\" XXX\n") + .arg(sig.name.c_str()) + .arg(sig.start_bit) + .arg(sig.size) + .arg(sig.is_little_endian ? '1' : '0') + .arg(sig.is_signed ? '-' : '+') + .arg(sig.factor, 0, 'g', std::numeric_limits::digits10) + .arg(sig.offset, 0, 'g', std::numeric_limits::digits10); + } + dbc_string += "\n"; + } + return dbc_string; } void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) { diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 9e64c4ed53..913445d44e 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -13,8 +13,7 @@ public: void open(const QString &dbc_file_name); void open(const QString &name, const QString &content); - void save(const QString &dbc_file_name); - + QString generateDBC(); void addSignal(const QString &id, const Signal &sig); void updateSignal(const QString &id, const QString &sig_name, const Signal &sig); void removeSignal(const QString &id, const QString &sig_name); @@ -24,6 +23,7 @@ public: inline QString name() const { return dbc_name; } void updateMsg(const QString &id, const QString &name, uint32_t size); + inline const DBC *getDBC() const { return dbc; } inline const Msg *msg(const QString &id) const { return msg(addressFromId(id)); } inline const Msg *msg(uint32_t address) const { auto it = msg_map.find(address); diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 7290e4e6ff..57a3910303 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -11,25 +12,39 @@ // DetailWidget -DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); +DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { + main_layout = new QHBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - main_layout->setSpacing(0); + right_column = new QVBoxLayout(); + main_layout->addLayout(right_column); + + binary_view_container = new QWidget(this); + binary_view_container->setMinimumWidth(500); + binary_view_container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + QVBoxLayout *bin_layout = new QVBoxLayout(binary_view_container); + bin_layout->setContentsMargins(0, 0, 0, 0); + bin_layout->setSpacing(0); // tabbar tabbar = new QTabBar(this); tabbar->setTabsClosable(true); tabbar->setDrawBase(false); tabbar->setUsesScrollButtons(true); tabbar->setAutoHide(true); - main_layout->addWidget(tabbar); + tabbar->setContextMenuPolicy(Qt::CustomContextMenu); + bin_layout->addWidget(tabbar); - // message title - QFrame *title_frame = new QFrame(); - main_layout->addWidget(title_frame); - QVBoxLayout *frame_layout = new QVBoxLayout(title_frame); + TitleFrame *title_frame = new TitleFrame(this); title_frame->setFrameShape(QFrame::StyledPanel); + title_frame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + QVBoxLayout *frame_layout = new QVBoxLayout(title_frame); + + // message title QHBoxLayout *title_layout = new QHBoxLayout(); + split_btn = new QPushButton("⬅", this); + split_btn->setFixedSize(20, 20); + split_btn->setToolTip(tr("Split to two columns")); + title_layout->addWidget(split_btn); title_layout->addWidget(new QLabel("time:")); time_label = new QLabel(this); time_label->setStyleSheet("font-weight:bold"); @@ -54,10 +69,12 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { warning_hlayout->addWidget(warning_label, 1, Qt::AlignLeft); warning_widget->hide(); frame_layout->addWidget(warning_widget); + bin_layout->addWidget(title_frame); // binary view binary_view = new BinaryView(this); - main_layout->addWidget(binary_view, 0, Qt::AlignTop); + bin_layout->addWidget(binary_view); + right_column->addWidget(binary_view_container); // signals signals_container = new QWidget(this); @@ -68,33 +85,61 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { scroll->setWidget(signals_container); scroll->setWidgetResizable(true); scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - main_layout->addWidget(scroll); + right_column->addWidget(scroll); // history log history_log = new HistoryLog(this); - main_layout->addWidget(history_log); + right_column->addWidget(history_log); + QObject::connect(split_btn, &QPushButton::clicked, this, &DetailWidget::moveBinaryView); + QObject::connect(title_frame, &TitleFrame::doubleClicked, this, &DetailWidget::moveBinaryView); QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); }); - QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) { setMessage(messages[index]); }); - QObject::connect(tabbar, &QTabBar::tabCloseRequested, [=](int index) { - messages.removeAt(index); - tabbar->removeTab(index); - setMessage(messages.isEmpty() ? "" : messages[0]); + QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu); + QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) { + if (index != -1 && tabbar->tabText(index) != msg_id) { + setMessage(tabbar->tabText(index)); + } }); + QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab); + QObject::connect(charts, &ChartsWidget::chartOpened, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, true); }); + QObject::connect(charts, &ChartsWidget::chartClosed, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, false); }); +} + +void DetailWidget::showTabBarContextMenu(const QPoint &pt) { + int index = tabbar->tabAt(pt); + if (index >= 0) { + QMenu menu(this); + menu.addAction(tr("Close Other Tabs")); + if (menu.exec(tabbar->mapToGlobal(pt))) { + tabbar->setCurrentIndex(index); + // remove all tabs before the one to keep + for (int i = 0; i < index; ++i) { + tabbar->removeTab(0); + } + // remove all tabs after the one to keep + while (tabbar->count() > 1) { + tabbar->removeTab(1); + } + } + } } void DetailWidget::setMessage(const QString &message_id) { if (message_id.isEmpty()) return; - int index = messages.indexOf(message_id); + int index = -1; + for (int i = 0; i < tabbar->count(); ++i) { + if (tabbar->tabText(i) == message_id) { + index = i; + break; + } + } if (index == -1) { - messages.push_back(message_id); - tabbar->addTab(message_id); - index = tabbar->count() - 1; + index = tabbar->addTab(message_id); auto msg = dbc()->msg(message_id); tabbar->setTabToolTip(index, msg ? msg->name.c_str() : "untitled"); } @@ -114,13 +159,14 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { if (auto msg = dbc()->msg(msg_id)) { for (int i = 0; i < msg->sigs.size(); ++i) { auto form = new SignalEdit(i, msg_id, &(msg->sigs[i])); + form->setChartOpened(charts->isChartOpened(msg_id, &(msg->sigs[i]))); signals_container->layout()->addWidget(form); - QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]]() { emit showChart(msg_id, sig); }); QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight); QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered); + QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]](bool show) { charts->showChart(msg_id, sig, show); }); if (i == show_form_idx) { QTimer::singleShot(0, [=]() { emit form->showFormClicked(); }); } @@ -155,6 +201,20 @@ void DetailWidget::updateState() { history_log->updateState(); } +void DetailWidget::moveBinaryView() { + if (binview_in_left_col) { + right_column->insertWidget(0, binary_view_container); + emit binaryViewMoved(true); + } else { + main_layout->insertWidget(0, binary_view_container); + emit binaryViewMoved(false); + } + split_btn->setText(binview_in_left_col ? "⬅" : "➡"); + split_btn->setToolTip(binview_in_left_col ? tr("Split to two columns") : tr("Move back")); + binary_view->updateGeometry(); + binview_in_left_col = !binview_in_left_col; +} + void DetailWidget::showForm() { SignalEdit *sender = qobject_cast(QObject::sender()); for (auto f : signals_container->findChildren()) { @@ -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()) + if (f->sig == sig) f->setChartOpened(opened); + } +} + void DetailWidget::editMsg() { auto msg = dbc()->msg(msg_id); QString name = msg ? msg->name.c_str() : "untitled"; diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 5924cdd43f..656aacb106 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -2,11 +2,22 @@ #include #include +#include #include "tools/cabana/binaryview.h" +#include "tools/cabana/chartswidget.h" #include "tools/cabana/historylog.h" #include "tools/cabana/signaledit.h" +class TitleFrame : public QFrame { + Q_OBJECT +public: + TitleFrame(QWidget *parent) : QFrame(parent) {} + void mouseDoubleClickEvent(QMouseEvent *e) { emit doubleClicked(); } +signals: + void doubleClicked(); +}; + class EditMessageDialog : public QDialog { Q_OBJECT @@ -30,15 +41,16 @@ class DetailWidget : public QWidget { Q_OBJECT public: - DetailWidget(QWidget *parent); + DetailWidget(ChartsWidget *charts, QWidget *parent); void setMessage(const QString &message_id); void dbcMsgChanged(int show_form_idx = -1); signals: - void showChart(const QString &msg_id, const Signal *sig); - void removeChart(const Signal *sig); + void binaryViewMoved(bool in); private: + void updateChartState(const QString &id, const Signal *sig, bool opened); + void showTabBarContextMenu(const QPoint &pt); void addSignal(int start_bit, int to); void resizeSignal(const Signal *sig, int from, int to); void saveSignal(const Signal *sig, const Signal &new_sig); @@ -46,6 +58,7 @@ private: void editMsg(); void showForm(); void updateState(); + void moveBinaryView(); QString msg_id; QLabel *name_label, *time_label, *warning_label; @@ -53,8 +66,13 @@ private: QPushButton *edit_btn; QWidget *signals_container; QTabBar *tabbar; - QStringList messages; + QHBoxLayout *main_layout; + QVBoxLayout *right_column; + bool binview_in_left_col = false; + QWidget *binary_view_container; + QPushButton *split_btn; HistoryLog *history_log; BinaryView *binary_view; ScrollArea *scroll; + ChartsWidget *charts; }; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 30c7e5e0a2..c2baca4d22 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -3,14 +3,13 @@ #include #include #include -#include #include #include "tools/replay/util.h" static MainWindow *main_win = nullptr; void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - if (main_win) main_win->showStatusMessage(msg); + if (main_win) emit main_win->showMessage(msg, 0); } MainWindow::MainWindow() : QWidget() { @@ -22,14 +21,15 @@ MainWindow::MainWindow() : QWidget() { h_layout->setContentsMargins(0, 0, 0, 0); main_layout->addLayout(h_layout); - QSplitter *splitter = new QSplitter(Qt::Horizontal, this); + splitter = new QSplitter(Qt::Horizontal, this); + splitter->setHandleWidth(11); messages_widget = new MessagesWidget(this); splitter->addWidget(messages_widget); - detail_widget = new DetailWidget(this); + charts_widget = new ChartsWidget(this); + detail_widget = new DetailWidget(charts_widget, this); splitter->addWidget(detail_widget); - splitter->setSizes({100, 500}); h_layout->addWidget(splitter); // right widgets @@ -51,13 +51,13 @@ MainWindow::MainWindow() : QWidget() { video_widget = new VideoWidget(this); r_layout->addWidget(video_widget, 0, Qt::AlignTop); - charts_widget = new ChartsWidget(this); r_layout->addWidget(charts_widget); h_layout->addWidget(right_container); // status bar status_bar = new QStatusBar(this); + status_bar->setFixedHeight(20); status_bar->setContentsMargins(0, 0, 0, 0); status_bar->setSizeGripEnabled(true); progress_bar = new QProgressBar(); @@ -72,16 +72,16 @@ MainWindow::MainWindow() : QWidget() { qRegisterMetaType("ReplyMsgType"); installMessageHandler([this](ReplyMsgType type, const std::string msg) { // use queued connection to recv the log messages from replay. - emit logMessageFromReplay(QString::fromStdString(msg), 3000); + emit showMessage(QString::fromStdString(msg), 3000); }); installDownloadProgressHandler([this](uint64_t cur, uint64_t total, bool success) { emit updateProgressBar(cur, total, success); }); - QObject::connect(this, &MainWindow::logMessageFromReplay, status_bar, &QStatusBar::showMessage); + QObject::connect(this, &MainWindow::showMessage, status_bar, &QStatusBar::showMessage); QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress); QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); - QObject::connect(detail_widget, &DetailWidget::showChart, charts_widget, &ChartsWidget::addChart); + QObject::connect(detail_widget, &DetailWidget::binaryViewMoved, [this](bool in) { splitter->setSizes({in ? 100 : 0, 500}); }); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged); QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption); @@ -101,7 +101,6 @@ void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool succe } } - void MainWindow::dockCharts(bool dock) { if (dock && floating_window) { floating_window->removeEventFilter(charts_widget); diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 63f704dcc8..b77744ba9c 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "tools/cabana/chartswidget.h" @@ -17,7 +18,7 @@ public: void showStatusMessage(const QString &msg, int timeout = 0) { status_bar->showMessage(msg, timeout); } signals: - void logMessageFromReplay(const QString &msg, int timeout); + void showMessage(const QString &msg, int timeout); void updateProgressBar(uint64_t cur, uint64_t total, bool success); protected: @@ -29,6 +30,7 @@ protected: MessagesWidget *messages_widget; DetailWidget *detail_widget; ChartsWidget *charts_widget; + QSplitter *splitter; QWidget *floating_window = nullptr; QVBoxLayout *r_layout; QProgressBar *progress_bar; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 9b831ad18c..205e5347ce 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include @@ -69,9 +71,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QObject::connect(can, &CANMessages::updated, [this]() { model->updateState(); }); QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &))); QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste); - QObject::connect(save_btn, &QPushButton::clicked, [=]() { - // TODO: save DBC to file - }); + QObject::connect(save_btn, &QPushButton::clicked, this, &MessagesWidget::saveDBC); QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { if (current.isValid()) { emit msgSelectionChanged(current.data(Qt::UserRole).toString()); @@ -79,16 +79,18 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { }); QFile json_file("./car_fingerprint_to_dbc.json"); - if(json_file.open(QIODevice::ReadOnly)) { + if (json_file.open(QIODevice::ReadOnly)) { fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); } } void MessagesWidget::loadDBCFromName(const QString &name) { - dbc()->open(name); - dbc_combo->setCurrentText(name); - // refresh model - model->updateState(); + if (name != dbc()->name()) { + dbc()->open(name); + dbc_combo->setCurrentText(name); + // re-sort model to refresh column 'Name' + model->updateState(true); + } } void MessagesWidget::loadDBCFromPaste() { @@ -96,19 +98,26 @@ void MessagesWidget::loadDBCFromPaste() { if (dlg.exec()) { dbc()->open("from paste", dlg.dbc_edit->toPlainText()); dbc_combo->setCurrentText("loaded from paste"); + model->updateState(true); } } void MessagesWidget::loadDBCFromFingerprint() { auto fingerprint = can->carFingerprint(); - if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) { + if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) { auto dbc_name = fingerprint_to_dbc[fingerprint]; - if (dbc_name != QJsonValue::Undefined) { + if (dbc_name != QJsonValue::Undefined) { loadDBCFromName(dbc_name.toString()); } } } +void MessagesWidget::saveDBC() { + SaveDBCDialog dlg(this); + dlg.dbc_edit->setText(dbc()->generateDBC()); + dlg.exec(); +} + // MessageListModel QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { @@ -222,6 +231,8 @@ void MessageListModel::sort(int column, Qt::SortOrder order) { } } +// LoadDBCDialog + LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); dbc_edit = new QTextEdit(this); @@ -231,7 +242,48 @@ LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) { auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox); - setFixedWidth(640); - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + setMinimumSize({640, 480}); + QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +// SaveDBCDialog + +SaveDBCDialog::SaveDBCDialog(QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Save DBC")); + QVBoxLayout *main_layout = new QVBoxLayout(this); + dbc_edit = new QTextEdit(this); + dbc_edit->setAcceptRichText(false); + main_layout->addWidget(dbc_edit); + + QPushButton *copy_to_clipboard = new QPushButton(tr("Copy To Clipboard"), this); + QPushButton *save_as = new QPushButton(tr("Save As"), this); + + QHBoxLayout *btn_layout = new QHBoxLayout(); + btn_layout->addStretch(); + btn_layout->addWidget(copy_to_clipboard); + btn_layout->addWidget(save_as); + main_layout->addLayout(btn_layout); + setMinimumSize({640, 480}); + + QObject::connect(copy_to_clipboard, &QPushButton::clicked, this, &SaveDBCDialog::copytoClipboard); + QObject::connect(save_as, &QPushButton::clicked, this, &SaveDBCDialog::saveAs); +} + +void SaveDBCDialog::copytoClipboard() { + dbc_edit->selectAll(); + dbc_edit->copy(); + QDialog::accept(); +} + +void SaveDBCDialog::saveAs() { + QString file_name = QFileDialog::getSaveFileName(this, tr("Save File"), + QDir::homePath() + "/untitled.dbc", tr("DBC (*.dbc)")); + if (!file_name.isEmpty()) { + QFile file(file_name); + if (file.open(QIODevice::WriteOnly)) { + file.write(dbc_edit->toPlainText().toUtf8()); + } + QDialog::accept(); + } } diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index fcd4939dac..a3d4d860b2 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -17,6 +17,16 @@ public: QTextEdit *dbc_edit; }; +class SaveDBCDialog : public QDialog { + Q_OBJECT + +public: + SaveDBCDialog(QWidget *parent); + void copytoClipboard(); + void saveAs(); + QTextEdit *dbc_edit; +}; + class MessageListModel : public QAbstractTableModel { Q_OBJECT @@ -52,6 +62,7 @@ public slots: void loadDBCFromName(const QString &name); void loadDBCFromFingerprint(); void loadDBCFromPaste(); + void saveDBC(); signals: void msgSelectionChanged(const QString &message_id); diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index 6e9d7f17cc..17299ebca4 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -17,6 +17,7 @@ void Settings::save() { s.setValue("log_size", can_msg_log_size); s.setValue("cached_segment", cached_segment_limit); s.setValue("chart_height", chart_height); + s.setValue("chart_theme", chart_theme); s.setValue("max_chart_x_range", max_chart_x_range); emit changed(); } @@ -27,6 +28,7 @@ void Settings::load() { can_msg_log_size = s.value("log_size", 100).toInt(); cached_segment_limit = s.value("cached_segment", 3).toInt(); chart_height = s.value("chart_height", 200).toInt(); + chart_theme = s.value("chart_theme", 0).toInt(); max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt(); } @@ -53,19 +55,24 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { cached_segment->setRange(3, 60); cached_segment->setSingleStep(1); cached_segment->setValue(settings.cached_segment_limit); - form_layout->addRow(tr("Cached segments limit(minute)"), cached_segment); + form_layout->addRow(tr("Cached segments limit"), cached_segment); max_chart_x_range = new QSpinBox(this); max_chart_x_range->setRange(1, 60); max_chart_x_range->setSingleStep(1); max_chart_x_range->setValue(settings.max_chart_x_range / 60); - form_layout->addRow(tr("Chart's max X-axis range(minute)"), max_chart_x_range); + form_layout->addRow(tr("Chart range (minutes)"), max_chart_x_range); chart_height = new QSpinBox(this); chart_height->setRange(100, 500); chart_height->setSingleStep(10); chart_height->setValue(settings.chart_height); - form_layout->addRow(tr("Chart's height"), chart_height); + form_layout->addRow(tr("Chart height"), chart_height); + + chart_theme = new QComboBox(); + chart_theme->addItems({"Light", "Dark"}); + chart_theme->setCurrentIndex(settings.chart_theme == 1 ? 1 : 0); + form_layout->addRow(tr("Chart theme"), chart_theme); main_layout->addLayout(form_layout); @@ -82,6 +89,7 @@ void SettingsDlg::save() { settings.can_msg_log_size = log_size->value(); settings.cached_segment_limit = cached_segment->value(); settings.chart_height = chart_height->value(); + settings.chart_theme = chart_theme->currentIndex(); settings.max_chart_x_range = max_chart_x_range->value() * 60; settings.save(); accept(); diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index fa97c49140..88eeebc722 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -15,6 +16,7 @@ public: int can_msg_log_size = 100; int cached_segment_limit = 3; int chart_height = 200; + int chart_theme = 0; int max_chart_x_range = 3 * 60; // 3 minutes signals: @@ -31,6 +33,7 @@ public: QSpinBox *log_size ; QSpinBox *cached_segment; QSpinBox *chart_height; + QComboBox *chart_theme; QSpinBox *max_chart_x_range; }; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 1bb5e9e533..ef0a85eba3 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -82,15 +82,14 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal *sig, QWid title_layout->addWidget(title, 1); QPushButton *seek_btn = new QPushButton("⌕"); - seek_btn->setStyleSheet("font-weight:bold;font-size:20px"); + seek_btn->setStyleSheet("QPushButton{font-weight:bold;font-size:18px}"); seek_btn->setToolTip(tr("Find signal values")); - seek_btn->setFixedSize(20, 20); + seek_btn->setFixedSize(25, 25); title_layout->addWidget(seek_btn); - QPushButton *plot_btn = new QPushButton("📈"); - plot_btn->setToolTip(tr("Show Plot")); - plot_btn->setFixedSize(20, 20); - QObject::connect(plot_btn, &QPushButton::clicked, this, &SignalEdit::showChart); + plot_btn = new QPushButton(this); + plot_btn->setStyleSheet("QPushButton {font-size:18px}"); + plot_btn->setFixedSize(25, 25); title_layout->addWidget(plot_btn); main_layout->addLayout(title_layout); @@ -120,6 +119,7 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal *sig, QWid QObject::connect(remove_btn, &QPushButton::clicked, [this]() { emit remove(this->sig); }); QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::saveSignal); + QObject::connect(plot_btn, &QPushButton::clicked, [this]() { emit showChart(!chart_opened); }); QObject::connect(seek_btn, &QPushButton::clicked, [this, msg_id]() { SignalFindDlg dlg(msg_id, this->sig, this); dlg.exec(); @@ -145,6 +145,12 @@ void SignalEdit::saveSignal() { emit save(this->sig, s); } +void SignalEdit::setChartOpened(bool opened) { + plot_btn->setText(opened ? "☒" : "📈"); + plot_btn->setToolTip(opened ? tr("Close Plot") :tr("Show Plot")); + chart_opened = opened; +} + void SignalEdit::setFormVisible(bool visible) { form_container->setVisible(visible); icon->setText(visible ? "▼" : ">"); diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index 71719852f1..e3d38d5b25 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -26,13 +26,15 @@ class SignalEdit : public QWidget { public: SignalEdit(int index, const QString &msg_id, const Signal *sig, QWidget *parent = nullptr); + void setChartOpened(bool opened); void setFormVisible(bool show); void signalHovered(const Signal *sig); inline bool isFormVisible() const { return form_container->isVisible(); } + const Signal *sig = nullptr; signals: void highlight(const Signal *sig); - void showChart(); + void showChart(bool show); void showFormClicked(); void remove(const Signal *sig); void save(const Signal *sig, const Signal &new_sig); @@ -48,7 +50,8 @@ protected: QLabel *icon; int form_idx = 0; QString msg_id; - const Signal *sig = nullptr; + bool chart_opened = false; + QPushButton *plot_btn; }; class SignalFindDlg : public QDialog { diff --git a/tools/cabana/tests/test_cabana b/tools/cabana/tests/test_cabana new file mode 100755 index 0000000000..bac242fbdd --- /dev/null +++ b/tools/cabana/tests/test_cabana @@ -0,0 +1,4 @@ +#!/bin/sh +cd "$(dirname "$0")" +export LD_LIBRARY_PATH="../../../opendbc/can:$LD_LIBRARY_PATH" +exec ./_test_cabana "$1" diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc new file mode 100644 index 0000000000..d0aa2cbb4f --- /dev/null +++ b/tools/cabana/tests/test_cabana.cc @@ -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); + } + } +} diff --git a/tools/cabana/tests/test_runner.cc b/tools/cabana/tests/test_runner.cc new file mode 100644 index 0000000000..b20ac86c64 --- /dev/null +++ b/tools/cabana/tests/test_runner.cc @@ -0,0 +1,10 @@ +#define CATCH_CONFIG_RUNNER +#include "catch2/catch.hpp" +#include + +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); +} diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 7180d5ac0f..9a28f71bb9 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -17,7 +17,6 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - // TODO: figure out why the CameraWidget crashed occasionally. cam_widget = new CameraWidget("camerad", VISION_STREAM_ROAD, false, this); cam_widget->setFixedSize(parent->width(), parent->width() / 1.596); main_layout->addWidget(cam_widget); diff --git a/tools/gpstest/run_unittest.sh b/tools/gpstest/run_unittest.sh index d284fa74e5..9f93fdfc9a 100755 --- a/tools/gpstest/run_unittest.sh +++ b/tools/gpstest/run_unittest.sh @@ -3,8 +3,8 @@ # NOTE: can only run inside limeGPS test box! # run limeGPS with random static location -timeout 300 ./simulate_gps_signal.py & -gps_PID=$? +timeout 300 ./simulate_gps_signal.py 32.7518 -117.1962 & +gps_PID=$(ps -aux | grep -m 1 "timeout 300" | cut -d ' ' -f 7) echo "starting limeGPS..." sleep 10 diff --git a/tools/gpstest/test_gps.py b/tools/gpstest/test_gps.py index b5d0cdd254..8bc5dc89a8 100644 --- a/tools/gpstest/test_gps.py +++ b/tools/gpstest/test_gps.py @@ -65,7 +65,7 @@ def verify_ubloxgnss_data(socket: messaging.SubSocket, max_time: int): sat_count.append(event.ubloxGnss.measurementReport.numMeas) num_sat = int(sum(sat_count)/len(sat_count)) - assert num_sat > 5, f"Not enough satellites {num_sat} (TestBox setup!)" + assert num_sat >= 5, f"Not enough satellites {num_sat} (TestBox setup!)" def verify_gps_location(socket: messaging.SubSocket, max_time: int): @@ -188,4 +188,4 @@ class TestGPS(unittest.TestCase): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index b7c32d9bf1..1e592da3b1 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -89,7 +89,7 @@ def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=No query = parse_qs(urlparse(route_or_segment_name).query) route_or_segment_name = query["route"][0] - if route_or_segment_name.startswith(("http://", "https://")) or os.path.isfile(route_or_segment_name): + if route_or_segment_name.startswith(("http://", "https://", "cd:/")) or os.path.isfile(route_or_segment_name): logs = [route_or_segment_name] elif ci: route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index a105ed751e..f72927ba9a 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -82,11 +82,10 @@ class Camerad: self.queue = cl.CommandQueue(self.ctx) cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W * 3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG " - # TODO: move rgb_to_yuv.cl to local dir once the frame stream camera is removed - kernel_fn = os.path.join(BASEDIR, "system", "camerad", "transforms", "rgb_to_yuv.cl") + kernel_fn = os.path.join(BASEDIR, "tools/sim/rgb_to_nv12.cl") with open(kernel_fn) as f: prg = cl.Program(self.ctx, f.read()).build(cl_arg) - self.krnl = prg.rgb_to_yuv + self.krnl = prg.rgb_to_nv12 self.Wdiv4 = W // 4 if (W % 4 == 0) else (W + (4 - W % 4)) // 4 self.Hdiv4 = H // 4 if (H % 4 == 0) else (H + (4 - H % 4)) // 4 diff --git a/system/camerad/transforms/rgb_to_yuv.cl b/tools/sim/rgb_to_nv12.cl similarity index 75% rename from system/camerad/transforms/rgb_to_yuv.cl rename to tools/sim/rgb_to_nv12.cl index 60dbdb4d5e..54816d5d7d 100644 --- a/system/camerad/transforms/rgb_to_yuv.cl +++ b/tools/sim/rgb_to_nv12.cl @@ -29,23 +29,21 @@ inline void convert_4_ys(__global uchar * out_yuv, int yi, const uchar8 rgbs1, c vstore4(yy, 0, out_yuv + yi); } -inline void convert_uv(__global uchar * out_yuv, int ui, int vi, +inline void convert_uv(__global uchar * out_yuv, int uvi, const uchar8 rgbs1, const uchar8 rgbs2) { // U & V: average of 2x2 pixels square const short ab = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); const short ag = AVERAGE(rgbs1.s1, rgbs1.s4, rgbs2.s1, rgbs2.s4); const short ar = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5); #ifdef CL_DEBUG - if(ui >= RGB_SIZE + RGB_SIZE / 4) - printf("U overflow, %d >= %d\n", ui, RGB_SIZE + RGB_SIZE / 4); - if(vi >= RGB_SIZE + RGB_SIZE / 2) - printf("V overflow, %d >= %d\n", vi, RGB_SIZE + RGB_SIZE / 2); + if(uvi >= RGB_SIZE + RGB_SIZE / 2) + printf("UV overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2); #endif - out_yuv[ui] = RGB_TO_U(ar, ag, ab); - out_yuv[vi] = RGB_TO_V(ar, ag, ab); + out_yuv[uvi] = RGB_TO_U(ar, ag, ab); + out_yuv[uvi+1] = RGB_TO_V(ar, ag, ab); } -inline void convert_2_uvs(__global uchar * out_yuv, int ui, int vi, +inline void convert_2_uvs(__global uchar * out_yuv, int uvi, const uchar8 rgbs1, const uchar8 rgbs2, const uchar8 rgbs3, const uchar8 rgbs4) { // U & V: average of 2x2 pixels square const short ab1 = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); @@ -54,25 +52,20 @@ inline void convert_2_uvs(__global uchar * out_yuv, int ui, int vi, const short ab2 = AVERAGE(rgbs1.s6, rgbs3.s1, rgbs2.s6, rgbs4.s1); const short ag2 = AVERAGE(rgbs1.s7, rgbs3.s2, rgbs2.s7, rgbs4.s2); const short ar2 = AVERAGE(rgbs3.s0, rgbs3.s3, rgbs4.s0, rgbs4.s3); - uchar2 u2 = (uchar2)( + uchar4 uv = (uchar4)( RGB_TO_U(ar1, ag1, ab1), - RGB_TO_U(ar2, ag2, ab2) - ); - uchar2 v2 = (uchar2)( RGB_TO_V(ar1, ag1, ab1), + RGB_TO_U(ar2, ag2, ab2), RGB_TO_V(ar2, ag2, ab2) ); #ifdef CL_DEBUG1 - if(ui > RGB_SIZE + RGB_SIZE / 4 - 2) - printf("U 2 overflow, %d >= %d\n", ui, RGB_SIZE + RGB_SIZE / 4 - 2); - if(vi > RGB_SIZE + RGB_SIZE / 2 - 2) - printf("V 2 overflow, %d >= %d\n", vi, RGB_SIZE + RGB_SIZE / 2 - 2); + if(uvi > RGB_SIZE + RGB_SIZE / 2 - 4) + printf("UV2 overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2 - 2); #endif - vstore2(u2, 0, out_yuv + ui); - vstore2(v2, 0, out_yuv + vi); + vstore4(uv, 0, out_yuv + uvi); } -__kernel void rgb_to_yuv(__global uchar const * const rgb, +__kernel void rgb_to_nv12(__global uchar const * const rgb, __global uchar * out_yuv) { const int dx = get_global_id(0); @@ -81,8 +74,7 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb, const int row = mul24(dy, 4); // Current row in rgb image const int bgri_start = mad24(row, RGB_STRIDE, mul24(col, 3)); // Start offset of rgb data being converted const int yi_start = mad24(row, WIDTH, col); // Start offset in the target yuv buffer - int ui = mad24(row / 2, UV_WIDTH, RGB_SIZE + col / 2); - int vi = mad24(row / 2 , UV_WIDTH, RGB_SIZE + UV_WIDTH * UV_HEIGHT + col / 2); + int uvi = mad24(row / 2, WIDTH, RGB_SIZE + col); int num_col = min(WIDTH - col, 4); int num_row = min(HEIGHT - row, 4); if(num_row == 4) { @@ -99,15 +91,15 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb, convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1); convert_4_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0, rgbs2_1); convert_4_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0, rgbs3_1); - convert_2_uvs(out_yuv, ui, vi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); - convert_2_uvs(out_yuv, ui + UV_WIDTH, vi + UV_WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1); + convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); + convert_2_uvs(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1); } else if(num_col == 2) { convert_2_ys(out_yuv, yi_start, rgbs0_0); convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0); convert_2_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0); convert_2_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0); - convert_uv(out_yuv, ui, vi, rgbs0_0, rgbs1_0); - convert_uv(out_yuv, ui + UV_WIDTH, vi + UV_WIDTH, rgbs2_0, rgbs3_0); + convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0); + convert_uv(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0); } } else { const uchar8 rgbs0_0 = vload8(0, rgb + bgri_start); @@ -117,11 +109,11 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb, if(num_col == 4) { convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1); convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1); - convert_2_uvs(out_yuv, ui, vi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); + convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); } else if(num_col == 2) { convert_2_ys(out_yuv, yi_start, rgbs0_0); convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0); - convert_uv(out_yuv, ui, vi, rgbs0_0, rgbs1_0); + convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0); } } }