mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-22 13:28:15 +08:00
Merge remote-tracking branch 'sunnyhaibin/openpilot-1/palisade-2023-port-1' into palisade-2023-hda2-long
# Conflicts: # selfdrive/car/hyundai/hyundaican.py # selfdrive/car/hyundai/interface.py # selfdrive/car/hyundai/values.py
This commit is contained in:
+3
-1
@@ -1,8 +1,10 @@
|
||||
Version 0.9.7 (2024-XX-XX)
|
||||
Version 0.9.7 (2024-05-XX)
|
||||
========================
|
||||
* New driving model
|
||||
* Adjust driving personality with the follow distance button
|
||||
* Support for hybrid variants of supported Ford models
|
||||
* Added toggle to enable driver monitoring even when openpilot is not engaged
|
||||
* Fingerprinting without the OBD-II port on all cars
|
||||
* Hyundai Palisade (with HDA II) 2023-24 support thanks to sunnyhaibin!
|
||||
* Kia Telluride (with HDA II) 2023-24 support thanks to sunnyhaibin!
|
||||
|
||||
|
||||
@@ -104,10 +104,10 @@ class CarController(CarControllerBase):
|
||||
can_sends.append([0x7b1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", self.CAN.ECAN])
|
||||
|
||||
hda2 = self.CP.flags & HyundaiFlags.CANFD_HDA2
|
||||
can_canfd = self.CP.flags & HyundaiFlags.CAN_CANFD
|
||||
can_canfd_hybrid = self.CP.flags & HyundaiFlags.CAN_CANFD_HYBRID
|
||||
|
||||
# CAN-FD platforms
|
||||
if self.CP.carFingerprint in CANFD_CAR or (hda2 and can_canfd):
|
||||
if self.CP.carFingerprint in CANFD_CAR or (hda2 and can_canfd_hybrid):
|
||||
hda2_long = hda2 and self.CP.openpilotLongitudinalControl
|
||||
|
||||
# steering control
|
||||
@@ -146,7 +146,7 @@ class CarController(CarControllerBase):
|
||||
self.accel_last = accel
|
||||
else:
|
||||
# button presses
|
||||
can_sends.extend(self.create_button_messages(CC, CS, use_clu11=(hda2 and can_canfd)))
|
||||
can_sends.extend(self.create_button_messages(CC, CS, use_clu11=(hda2 and can_canfd_hybrid)))
|
||||
else:
|
||||
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_steer, apply_steer_req,
|
||||
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
|
||||
|
||||
@@ -8,7 +8,7 @@ from opendbc.can.parser import CANParser
|
||||
from opendbc.can.can_define import CANDefine
|
||||
from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
|
||||
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CAN_GEARS, CAMERA_SCC_CAR, \
|
||||
CANFD_CAR, Buttons, CarControllerParams
|
||||
CANFD_CAR, Buttons, CarControllerParams, CAN_CANFD_HYBRID_CAR
|
||||
from openpilot.selfdrive.car.interfaces import CarStateBase
|
||||
|
||||
PREV_BUTTON_SAMPLES = 8
|
||||
@@ -27,7 +27,7 @@ class CarState(CarStateBase):
|
||||
self.gear_msg_canfd = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \
|
||||
"GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \
|
||||
"GEAR_SHIFTER"
|
||||
if CP.carFingerprint in CANFD_CAR:
|
||||
if CP.carFingerprint in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
self.shifter_values = can_define.dv[self.gear_msg_canfd]["GEAR"]
|
||||
elif self.CP.carFingerprint in CAN_GEARS["use_cluster_gears"]:
|
||||
self.shifter_values = can_define.dv["CLU15"]["CF_Clu_Gear"]
|
||||
@@ -53,7 +53,7 @@ class CarState(CarStateBase):
|
||||
self.params = CarControllerParams(CP)
|
||||
|
||||
def update(self, cp, cp_cam):
|
||||
if self.CP.carFingerprint in CANFD_CAR:
|
||||
if self.CP.carFingerprint in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
return self.update_canfd(cp, cp_cam)
|
||||
|
||||
ret = car.CarState.new_message()
|
||||
@@ -107,7 +107,7 @@ class CarState(CarStateBase):
|
||||
ret.cruiseState.standstill = False
|
||||
ret.cruiseState.nonAdaptive = False
|
||||
else:
|
||||
scc_bus = "SCC12" if self.CP.flags & HyundaiFlags.CAN_CANFD else "SCC11"
|
||||
scc_bus = "SCC12" if self.CP.flags & HyundaiFlags.CAN_CANFD_HYBRID else "SCC11"
|
||||
ret.cruiseState.available = cp_cruise.vl[scc_bus]["MainMode_ACC"] == 1
|
||||
ret.cruiseState.enabled = cp_cruise.vl["SCC12"]["ACCMode"] != 0
|
||||
ret.cruiseState.standstill = cp_cruise.vl[scc_bus]["SCCInfoDisplay"] == 4.
|
||||
@@ -145,7 +145,7 @@ class CarState(CarStateBase):
|
||||
|
||||
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear))
|
||||
|
||||
if not self.CP.openpilotLongitudinalControl and not (self.CP.flags & HyundaiFlags.CAN_CANFD.value):
|
||||
if not self.CP.openpilotLongitudinalControl and not (self.CP.flags & HyundaiFlags.CAN_CANFD_HYBRID):
|
||||
aeb_src = "FCA11" if self.CP.flags & HyundaiFlags.USE_FCA.value else "SCC12"
|
||||
aeb_sig = "FCA_CmdAct" if self.CP.flags & HyundaiFlags.USE_FCA.value else "AEB_CmdAct"
|
||||
aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0
|
||||
@@ -159,10 +159,10 @@ class CarState(CarStateBase):
|
||||
ret.rightBlindspot = cp.vl["LCA11"]["CF_Lca_IndRight"] != 0
|
||||
|
||||
# save the entire LKAS11 and CLU11
|
||||
if not self.CP.flags & HyundaiFlags.CAN_CANFD:
|
||||
if not self.CP.flags & HyundaiFlags.CAN_CANFD_HYBRID:
|
||||
self.lkas11 = copy.copy(cp_cam.vl["LKAS11"])
|
||||
self.clu11 = copy.copy(cp.vl["CLU11"])
|
||||
if self.CP.flags & HyundaiFlags.CAN_CANFD and self.CP.flags & HyundaiFlags.CANFD_HDA2:
|
||||
if self.CP.flags & HyundaiFlags.CAN_CANFD_HYBRID and self.CP.flags & HyundaiFlags.CANFD_HDA2:
|
||||
self.hda2_lfa_block_msg = copy.copy(cp_cam.vl["CAM_0x2a4"])
|
||||
self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE
|
||||
self.prev_cruise_buttons = self.cruise_buttons[-1]
|
||||
@@ -254,12 +254,12 @@ class CarState(CarStateBase):
|
||||
return ret
|
||||
|
||||
def get_can_parser(self, CP):
|
||||
if CP.carFingerprint in CANFD_CAR:
|
||||
if CP.carFingerprint in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
return self.get_can_parser_canfd(CP)
|
||||
|
||||
messages = [
|
||||
# address, frequency
|
||||
("MDPS12", 100 if CP.flags & HyundaiFlags.CAN_CANFD else 50),
|
||||
("MDPS12", 100 if CP.flags & HyundaiFlags.CAN_CANFD_HYBRID else 50),
|
||||
("TCS11", 100),
|
||||
("TCS13", 50),
|
||||
("TCS15", 10),
|
||||
@@ -274,7 +274,7 @@ class CarState(CarStateBase):
|
||||
]
|
||||
|
||||
if not CP.openpilotLongitudinalControl:
|
||||
if CP.flags & HyundaiFlags.CAN_CANFD:
|
||||
if CP.flags & HyundaiFlags.CAN_CANFD_HYBRID:
|
||||
messages.append(("SCC12", 50))
|
||||
elif CP.carFingerprint not in CAMERA_SCC_CAR:
|
||||
messages += [
|
||||
@@ -285,7 +285,7 @@ class CarState(CarStateBase):
|
||||
messages.append(("FCA11", 50))
|
||||
|
||||
if CP.enableBsm:
|
||||
messages.append(("LCA11", 20 if CP.flags & HyundaiFlags.CAN_CANFD else 50))
|
||||
messages.append(("LCA11", 20 if CP.flags & HyundaiFlags.CAN_CANFD_HYBRID else 50))
|
||||
|
||||
if CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV):
|
||||
messages.append(("E_EMS11", 50))
|
||||
@@ -304,17 +304,17 @@ class CarState(CarStateBase):
|
||||
else:
|
||||
messages.append(("LVR12", 100))
|
||||
|
||||
bus = CanBus(CP).ECAN if CP.flags & HyundaiFlags.CAN_CANFD.value else 0
|
||||
bus = CanBus(CP).ECAN if CP.flags & HyundaiFlags.CAN_CANFD_HYBRID else 0
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], messages, bus)
|
||||
|
||||
@staticmethod
|
||||
def get_cam_can_parser(CP):
|
||||
if CP.carFingerprint in CANFD_CAR:
|
||||
if CP.carFingerprint in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
return CarState.get_cam_can_parser_canfd(CP)
|
||||
|
||||
messages = []
|
||||
|
||||
if CP.flags & HyundaiFlags.CAN_CANFD:
|
||||
if CP.flags & HyundaiFlags.CAN_CANFD_HYBRID:
|
||||
messages.append(("CAM_0x2a4", 20))
|
||||
else:
|
||||
messages.append(("LKAS11", 100))
|
||||
@@ -328,7 +328,7 @@ class CarState(CarStateBase):
|
||||
if CP.flags & HyundaiFlags.USE_FCA.value:
|
||||
messages.append(("FCA11", 50))
|
||||
|
||||
bus = CanBus(CP).CAM if CP.flags & HyundaiFlags.CAN_CANFD else 2
|
||||
bus = CanBus(CP).CAM if CP.flags & HyundaiFlags.CAN_CANFD_HYBRID else 2
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], messages, bus)
|
||||
|
||||
def get_can_parser_canfd(self, CP):
|
||||
|
||||
@@ -113,7 +113,7 @@ def create_clu11(packer, frame, clu11, button, CP, CAN):
|
||||
values["CF_Clu_CruiseSwState"] = button
|
||||
values["CF_Clu_AliveCnt1"] = frame % 0x10
|
||||
# send buttons to camera on camera-scc based cars
|
||||
bus = 2 if CP.flags & HyundaiFlags.CAMERA_SCC else CAN.ECAN if CP.flags & HyundaiFlags.CAN_CANFD else 0
|
||||
bus = 2 if CP.flags & HyundaiFlags.CAMERA_SCC else CAN.ECAN if CP.flags & HyundaiFlags.CAN_CANFD_HYBRID else 0
|
||||
return packer.make_can_msg("CLU11", bus, values)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from panda import Panda
|
||||
from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
|
||||
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, \
|
||||
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, \
|
||||
UNSUPPORTED_LONGITUDINAL_CAR, Buttons, CAN_CANFD_CAR
|
||||
UNSUPPORTED_LONGITUDINAL_CAR, Buttons, CAN_CANFD_HYBRID_CAR
|
||||
from openpilot.selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR
|
||||
from openpilot.selfdrive.car import create_button_events, get_safety_config
|
||||
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
|
||||
@@ -36,14 +36,13 @@ class CarInterface(CarInterfaceBase):
|
||||
if hda2:
|
||||
ret.flags |= HyundaiFlags.CANFD_HDA2.value
|
||||
|
||||
if candidate in CANFD_CAR:
|
||||
if candidate in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
# detect if car is hybrid
|
||||
if 0x105 in fingerprint[CAN.ECAN]:
|
||||
ret.flags |= HyundaiFlags.HYBRID.value
|
||||
elif candidate in EV_CAR:
|
||||
ret.flags |= HyundaiFlags.EV.value
|
||||
|
||||
# detect HDA2 with ADAS Driving ECU
|
||||
if hda2:
|
||||
if 0x110 in fingerprint[CAN.CAM]:
|
||||
ret.flags |= HyundaiFlags.CANFD_HDA2_ALT_STEERING.value
|
||||
@@ -66,10 +65,6 @@ class CarInterface(CarInterfaceBase):
|
||||
elif candidate in EV_CAR:
|
||||
ret.flags |= HyundaiFlags.EV.value
|
||||
|
||||
# detect cars with hybrid definitions of CAN and CAN-FD
|
||||
if candidate in CAN_CANFD_CAR:
|
||||
ret.flags |= HyundaiFlags.CAN_CANFD.value
|
||||
|
||||
# Send LFA message on cars with HDA
|
||||
if 0x485 in fingerprint[2]:
|
||||
ret.flags |= HyundaiFlags.SEND_LFA.value
|
||||
@@ -92,7 +87,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarInterfaceBase.configure_torque_tune(platform_str, ret.lateralTuning)
|
||||
|
||||
# *** longitudinal control ***
|
||||
if candidate in CANFD_CAR:
|
||||
if candidate in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
ret.longitudinalTuning.kpV = [0.1]
|
||||
ret.longitudinalTuning.kiV = [0.0]
|
||||
ret.experimentalLongitudinalAvailable = candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR)
|
||||
@@ -114,45 +109,43 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.stoppingDecelRate = 0.4
|
||||
|
||||
# *** feature detection ***
|
||||
if candidate in CANFD_CAR:
|
||||
if candidate in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
ret.enableBsm = 0x1e5 in fingerprint[CAN.ECAN]
|
||||
else:
|
||||
bus = CAN.ECAN if ret.flags & HyundaiFlags.CAN_CANFD else 0
|
||||
bus = CAN.ECAN if ret.flags & HyundaiFlags.CAN_CANFD_HYBRID else 0
|
||||
ret.enableBsm = 0x58b in fingerprint[bus]
|
||||
|
||||
# *** panda safety config ***
|
||||
if candidate in CANFD_CAR:
|
||||
if candidate in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
cfgs = [get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd), ]
|
||||
if CAN.ECAN >= 4:
|
||||
cfgs.insert(0, get_safety_config(car.CarParams.SafetyModel.noOutput))
|
||||
ret.safetyConfigs = cfgs
|
||||
|
||||
if ret.flags & HyundaiFlags.CANFD_HDA2:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2
|
||||
if ret.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING
|
||||
if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS
|
||||
if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC
|
||||
else:
|
||||
if ret.flags & HyundaiFlags.CAN_CANFD:
|
||||
if candidate in LEGACY_SAFETY_MODE_CAR:
|
||||
# these cars require a special panda safety mode due to missing counters and checksums in the messages
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)]
|
||||
else:
|
||||
cfgs = [get_safety_config(car.CarParams.SafetyModel.hyundai), ]
|
||||
if CAN.ECAN >= 4:
|
||||
cfgs.insert(0, get_safety_config(car.CarParams.SafetyModel.noOutput))
|
||||
ret.safetyConfigs = cfgs
|
||||
if ret.flags & HyundaiFlags.CANFD_HDA2:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CAN_CANFD
|
||||
elif candidate in LEGACY_SAFETY_MODE_CAR:
|
||||
# these cars require a special panda safety mode due to missing counters and checksums in the messages
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)]
|
||||
else:
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)]
|
||||
if ret.flags & HyundaiFlags.CAN_CANFD_HYBRID:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CAN_CANFD_HYBRID
|
||||
|
||||
if candidate in CAMERA_SCC_CAR:
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC
|
||||
|
||||
# these flags apply to both CANFD_CAR and CAN_CANFD_HYBRID_CAR platforms
|
||||
if ret.flags & HyundaiFlags.CANFD_HDA2:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2
|
||||
if ret.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING
|
||||
|
||||
if ret.openpilotLongitudinalControl:
|
||||
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_LONG
|
||||
if ret.flags & HyundaiFlags.HYBRID:
|
||||
@@ -166,6 +159,10 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
|
||||
# FIXME: Non-HDA2 2023-24 Hyundai Palisade / Kia Telluride is disabled in this branch;
|
||||
# The non-HDA2 variant is supported in another PR: https://github.com/commaai/openpilot/pull/27478
|
||||
ret.dashcamOnly |= candidate in (CAR.HYUNDAI_PALISADE_2023, ) and not hda2
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -25,7 +25,7 @@ class CarControllerParams:
|
||||
self.STEER_THRESHOLD = 150
|
||||
self.STEER_STEP = 1 # 100 Hz
|
||||
|
||||
if CP.carFingerprint in CANFD_CAR:
|
||||
if CP.carFingerprint in (CANFD_CAR - CAN_CANFD_HYBRID_CAR):
|
||||
self.STEER_MAX = 270
|
||||
self.STEER_DRIVER_ALLOWANCE = 250
|
||||
self.STEER_DRIVER_MULTIPLIER = 2
|
||||
@@ -95,8 +95,8 @@ class HyundaiFlags(IntFlag):
|
||||
|
||||
MIN_STEER_32_MPH = 2 ** 23
|
||||
|
||||
# CAN CAN-FD
|
||||
CAN_CANFD = 2 ** 24
|
||||
# CAN CAN-FD hybrid architecture
|
||||
CAN_CANFD_HYBRID = 2 ** 24
|
||||
|
||||
|
||||
class Footnote(Enum):
|
||||
@@ -126,9 +126,6 @@ class HyundaiPlatformConfig(PlatformConfig):
|
||||
if self.flags & HyundaiFlags.MIN_STEER_32_MPH:
|
||||
self.specs = self.specs.override(minSteerSpeed=32 * CV.MPH_TO_MS)
|
||||
|
||||
if self.flags & HyundaiFlags.CAN_CANFD:
|
||||
self.dbc_dict = dbc_dict('hyundai_palisade_2023_generated', None)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HyundaiCanFDPlatformConfig(PlatformConfig):
|
||||
@@ -137,6 +134,9 @@ class HyundaiCanFDPlatformConfig(PlatformConfig):
|
||||
def init(self):
|
||||
self.flags |= HyundaiFlags.CANFD
|
||||
|
||||
if self.flags & HyundaiFlags.CAN_CANFD_HYBRID:
|
||||
self.dbc_dict = dbc_dict('hyundai_palisade_2023_generated', None)
|
||||
|
||||
|
||||
class CAR(Platforms):
|
||||
# Hyundai
|
||||
@@ -300,13 +300,13 @@ class CAR(Platforms):
|
||||
CarSpecs(mass=1999, wheelbase=2.9, steerRatio=15.6 * 1.15, tireStiffnessFactor=0.63),
|
||||
flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.CHECKSUM_CRC8,
|
||||
)
|
||||
HYUNDAI_PALISADE_2023 = HyundaiPlatformConfig(
|
||||
HYUNDAI_PALISADE_2023 = HyundaiCanFDPlatformConfig(
|
||||
[
|
||||
HyundaiCarDocs("Hyundai Palisade (with HDA II) 2023-24", "All", car_parts=CarParts.common([CarHarness.hyundai_r])),
|
||||
HyundaiCarDocs("Kia Telluride (with HDA II) 2023-24", "All", car_parts=CarParts.common([CarHarness.hyundai_p])),
|
||||
],
|
||||
HYUNDAI_PALISADE.specs,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.CAN_CANFD | HyundaiFlags.RADAR_SCC,
|
||||
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.CAN_CANFD_HYBRID | HyundaiFlags.RADAR_SCC,
|
||||
)
|
||||
HYUNDAI_VELOSTER = HyundaiPlatformConfig(
|
||||
[HyundaiCarDocs("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_e]))],
|
||||
@@ -586,7 +586,7 @@ def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str
|
||||
# Non-electric CAN FD platforms often do not have platform code specifiers needed
|
||||
# to distinguish between hybrid and ICE. All EVs so far are either exclusively
|
||||
# electric or specify electric in the platform code.
|
||||
fuzzy_platform_blacklist = {str(c) for c in (CANFD_CAR - EV_CAR - CANFD_FUZZY_WHITELIST)}
|
||||
fuzzy_platform_blacklist = {str(c) for c in (CANFD_CAR - CAN_CANFD_HYBRID_CAR - EV_CAR - CANFD_FUZZY_WHITELIST)}
|
||||
candidates: set[str] = set()
|
||||
|
||||
for candidate, fws in offline_fw_versions.items():
|
||||
@@ -743,7 +743,7 @@ CAN_GEARS = {
|
||||
|
||||
CANFD_CAR = CAR.with_flags(HyundaiFlags.CANFD)
|
||||
CANFD_RADAR_SCC_CAR = CAR.with_flags(HyundaiFlags.RADAR_SCC)
|
||||
CAN_CANFD_CAR = CAR.with_flags(HyundaiFlags.CAN_CANFD)
|
||||
CAN_CANFD_HYBRID_CAR = CAR.with_flags(HyundaiFlags.CAN_CANFD_HYBRID)
|
||||
|
||||
# These CAN FD cars do not accept communication control to disable the ADAS ECU,
|
||||
# responds with 0x7F2822 - 'conditions not correct'
|
||||
|
||||
@@ -4,12 +4,13 @@ import time
|
||||
from cereal import car, log, messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.manager.process_config import managed_processes
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
|
||||
if __name__ == "__main__":
|
||||
CP = car.CarParams(notCar=True)
|
||||
CP = car.CarParams(notCar=True, wheelbase=1, steerRatio=10)
|
||||
Params().put("CarParams", CP.to_bytes())
|
||||
|
||||
procs = ['camerad', 'ui', 'modeld', 'calibrationd']
|
||||
procs = ['camerad', 'ui', 'modeld', 'calibrationd', 'plannerd', 'dmonitoringmodeld', 'dmonitoringd']
|
||||
for p in procs:
|
||||
managed_processes[p].start()
|
||||
|
||||
@@ -17,6 +18,7 @@ if __name__ == "__main__":
|
||||
|
||||
msgs = {s: messaging.new_message(s) for s in ['controlsState', 'deviceState', 'carParams']}
|
||||
msgs['deviceState'].deviceState.started = True
|
||||
msgs['deviceState'].deviceState.deviceType = HARDWARE.get_device_type()
|
||||
msgs['carParams'].carParams.openpilotLongitudinalControl = True
|
||||
|
||||
msgs['pandaStates'] = messaging.new_message('pandaStates', 1)
|
||||
|
||||
@@ -123,12 +123,15 @@ void MapWindow::updateState(const UIState &s) {
|
||||
auto locationd_pos = locationd_location.getPositionGeodetic();
|
||||
auto locationd_orientation = locationd_location.getCalibratedOrientationNED();
|
||||
auto locationd_velocity = locationd_location.getVelocityCalibrated();
|
||||
auto locationd_ecef = locationd_location.getPositionECEF();
|
||||
|
||||
// Check std norm
|
||||
auto pos_ecef_std = locationd_location.getPositionECEF().getStd();
|
||||
bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100;
|
||||
|
||||
locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && pos_accurate_enough);
|
||||
locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && locationd_ecef.getValid());
|
||||
if (locationd_valid) {
|
||||
// Check std norm
|
||||
auto pos_ecef_std = locationd_ecef.getStd();
|
||||
bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100;
|
||||
locationd_valid = pos_accurate_enough;
|
||||
}
|
||||
|
||||
if (locationd_valid) {
|
||||
last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "selfdrive/ui/qt/network/wifi_manager.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/widgets/prime.h"
|
||||
|
||||
@@ -14,9 +16,15 @@ bool compare_by_strength(const Network &a, const Network &b) {
|
||||
|
||||
template <typename T = QDBusMessage, typename... Args>
|
||||
T call(const QString &path, const QString &interface, const QString &method, Args &&...args) {
|
||||
QDBusInterface nm = QDBusInterface(NM_DBUS_SERVICE, path, interface, QDBusConnection::systemBus());
|
||||
QDBusInterface nm(NM_DBUS_SERVICE, path, interface, QDBusConnection::systemBus());
|
||||
nm.setTimeout(DBUS_TIMEOUT);
|
||||
QDBusMessage response = nm.call(method, args...);
|
||||
|
||||
QDBusMessage response = nm.call(method, std::forward<Args>(args)...);
|
||||
if (response.type() == QDBusMessage::ErrorMessage) {
|
||||
qCritical() << "DBus call error:" << response.errorMessage();
|
||||
return T();
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, QDBusMessage>) {
|
||||
return response;
|
||||
} else if (response.arguments().count() >= 1) {
|
||||
@@ -99,10 +107,20 @@ void WifiManager::refreshFinished(QDBusPendingCallWatcher *watcher) {
|
||||
seenNetworks.clear();
|
||||
|
||||
const QDBusReply<QList<QDBusObjectPath>> watcher_reply = *watcher;
|
||||
if (!watcher_reply.isValid()) {
|
||||
qCritical() << "Failed to refresh";
|
||||
watcher->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QDBusObjectPath &path : watcher_reply.value()) {
|
||||
QDBusReply<QVariantMap> reply = call(path.path(), NM_DBUS_INTERFACE_PROPERTIES, "GetAll", NM_DBUS_INTERFACE_ACCESS_POINT);
|
||||
auto properties = reply.value();
|
||||
if (!reply.isValid()) {
|
||||
qCritical() << "Failed to retrieve properties for path:" << path.path();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto properties = reply.value();
|
||||
const QByteArray ssid = properties["Ssid"].toByteArray();
|
||||
if (ssid.isEmpty()) continue;
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@ int CameraState::clear_req_queue() {
|
||||
req_mgr_flush_request.session_hdl = session_handle;
|
||||
req_mgr_flush_request.link_hdl = link_handle;
|
||||
req_mgr_flush_request.flush_type = CAM_REQ_MGR_FLUSH_TYPE_ALL;
|
||||
int ret;
|
||||
ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request));
|
||||
int ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request));
|
||||
// LOGD("flushed all req: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
@@ -470,7 +469,6 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num
|
||||
enabled = enabled_;
|
||||
if (!enabled) return;
|
||||
|
||||
int ret;
|
||||
sensor_fd = open_v4l_by_name_and_index("cam-sensor-driver", camera_num);
|
||||
assert(sensor_fd >= 0);
|
||||
LOGD("opened sensor for %d", camera_num);
|
||||
@@ -501,7 +499,7 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num
|
||||
|
||||
// create session
|
||||
struct cam_req_mgr_session_info session_info = {};
|
||||
ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_CREATE_SESSION, &session_info, sizeof(session_info));
|
||||
int ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_CREATE_SESSION, &session_info, sizeof(session_info));
|
||||
LOGD("get session: %d 0x%X", ret, session_info.session_hdl);
|
||||
session_handle = session_info.session_hdl;
|
||||
|
||||
@@ -654,8 +652,6 @@ void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_i
|
||||
}
|
||||
|
||||
void cameras_open(MultiCameraState *s) {
|
||||
int ret;
|
||||
|
||||
LOG("-- Opening devices");
|
||||
// video0 is req_mgr, the target of many ioctls
|
||||
s->video0_fd = HANDLE_EINTR(open("/dev/v4l/by-path/platform-soc:qcom_cam-req-mgr-video-index0", O_RDWR | O_NONBLOCK));
|
||||
@@ -679,7 +675,7 @@ void cameras_open(MultiCameraState *s) {
|
||||
query_cap_cmd.handle_type = 1;
|
||||
query_cap_cmd.caps_handle = (uint64_t)&isp_query_cap_cmd;
|
||||
query_cap_cmd.size = sizeof(isp_query_cap_cmd);
|
||||
ret = do_cam_control(s->isp_fd, CAM_QUERY_CAP, &query_cap_cmd, sizeof(query_cap_cmd));
|
||||
int ret = do_cam_control(s->isp_fd, CAM_QUERY_CAP, &query_cap_cmd, sizeof(query_cap_cmd));
|
||||
assert(ret == 0);
|
||||
LOGD("using MMU handle: %x", isp_query_cap_cmd.device_iommu.non_secure);
|
||||
LOGD("using MMU handle: %x", isp_query_cap_cmd.cdm_iommu.non_secure);
|
||||
@@ -703,15 +699,13 @@ void cameras_open(MultiCameraState *s) {
|
||||
}
|
||||
|
||||
void CameraState::camera_close() {
|
||||
int ret;
|
||||
|
||||
// stop devices
|
||||
LOG("-- Stop devices %d", camera_num);
|
||||
|
||||
if (enabled) {
|
||||
// ret = device_control(sensor_fd, CAM_STOP_DEV, session_handle, sensor_dev_handle);
|
||||
// LOGD("stop sensor: %d", ret);
|
||||
ret = device_control(multi_cam_state->isp_fd, CAM_STOP_DEV, session_handle, isp_dev_handle);
|
||||
int ret = device_control(multi_cam_state->isp_fd, CAM_STOP_DEV, session_handle, isp_dev_handle);
|
||||
LOGD("stop isp: %d", ret);
|
||||
ret = device_control(csiphy_fd, CAM_STOP_DEV, session_handle, csiphy_dev_handle);
|
||||
LOGD("stop csiphy: %d", ret);
|
||||
@@ -746,7 +740,7 @@ void CameraState::camera_close() {
|
||||
LOGD("released buffers");
|
||||
}
|
||||
|
||||
ret = device_control(sensor_fd, CAM_RELEASE_DEV, session_handle, sensor_dev_handle);
|
||||
int ret = device_control(sensor_fd, CAM_RELEASE_DEV, session_handle, sensor_dev_handle);
|
||||
LOGD("release sensor: %d", ret);
|
||||
|
||||
// destroyed session
|
||||
|
||||
@@ -86,11 +86,10 @@ void *alloc_w_mmu_hdl(int video0_fd, int len, uint32_t *handle, int align, int f
|
||||
}
|
||||
|
||||
void release(int video0_fd, uint32_t handle) {
|
||||
int ret;
|
||||
struct cam_mem_mgr_release_cmd mem_mgr_release_cmd = {0};
|
||||
mem_mgr_release_cmd.buf_handle = handle;
|
||||
|
||||
ret = do_cam_control(video0_fd, CAM_REQ_MGR_RELEASE_BUF, &mem_mgr_release_cmd, sizeof(mem_mgr_release_cmd));
|
||||
int ret = do_cam_control(video0_fd, CAM_REQ_MGR_RELEASE_BUF, &mem_mgr_release_cmd, sizeof(mem_mgr_release_cmd));
|
||||
assert(ret == 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ int main(int argc, char *argv[]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ret;
|
||||
ret = util::set_realtime_priority(53);
|
||||
int ret = util::set_realtime_priority(53);
|
||||
assert(ret == 0);
|
||||
ret = util::set_core_affinity({6});
|
||||
assert(ret == 0 || Params().getBool("IsOffroad")); // failure ok while offroad due to offlining cores
|
||||
|
||||
@@ -111,6 +111,8 @@ void ChartView::setTheme(QChart::ChartTheme theme) {
|
||||
axis_y->setLabelsBrush(palette().text());
|
||||
chart()->legend()->setLabelColor(palette().color(QPalette::Text));
|
||||
}
|
||||
axis_x->setLineVisible(false);
|
||||
axis_y->setLineVisible(false);
|
||||
for (auto &s : sigs) {
|
||||
s.series->setColor(s.sig->color);
|
||||
}
|
||||
@@ -745,8 +747,8 @@ void ChartView::drawTimeline(QPainter *painter) {
|
||||
const auto plot_area = chart()->plotArea();
|
||||
// draw vertical time line
|
||||
qreal x = std::clamp(chart()->mapToPosition(QPointF{cur_sec, 0}).x(), plot_area.left(), plot_area.right());
|
||||
painter->setPen(QPen(chart()->titleBrush().color(), 2));
|
||||
painter->drawLine(QPointF{x, plot_area.top()}, QPointF{x, plot_area.bottom() + 1});
|
||||
painter->setPen(QPen(chart()->titleBrush().color(), 1));
|
||||
painter->drawLine(QPointF{x, plot_area.top() - 1}, QPointF{x, plot_area.bottom() + 1});
|
||||
|
||||
// draw current time under the axis-x
|
||||
QString time_str = QString::number(cur_sec, 'f', 2);
|
||||
|
||||
@@ -29,11 +29,11 @@ struct MessageId {
|
||||
}
|
||||
|
||||
bool operator<(const MessageId &other) const {
|
||||
return std::pair{source, address} < std::pair{other.source, other.address};
|
||||
return std::tie(source, address) < std::tie(other.source, other.address);
|
||||
}
|
||||
|
||||
bool operator>(const MessageId &other) const {
|
||||
return std::pair{source, address} > std::pair{other.source, other.address};
|
||||
return std::tie(source, address) > std::tie(other.source, other.address);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+145
-98
@@ -3,7 +3,6 @@
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QRegularExpression>
|
||||
#include <QTextStream>
|
||||
|
||||
DBCFile::DBCFile(const QString &dbc_file_name) {
|
||||
QFile file(dbc_file_name);
|
||||
@@ -76,117 +75,44 @@ cabana::Msg *DBCFile::msg(const QString &name) {
|
||||
return it != msgs.end() ? &(it->second) : nullptr;
|
||||
}
|
||||
|
||||
cabana::Signal *DBCFile::signal(uint32_t address, const QString name) {
|
||||
auto m = msg(address);
|
||||
return m ? (cabana::Signal *)m->sig(name) : nullptr;
|
||||
}
|
||||
|
||||
void DBCFile::parse(const QString &content) {
|
||||
static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))");
|
||||
static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
static QRegularExpression msg_comment_regexp(R"(^CM_ BO_ *(\w+) *\"((?:[^"\\]|\\.)*)\"\s*;)");
|
||||
static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"((?:[^"\\]|\\.)*)\"\s*;)");
|
||||
static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))");
|
||||
msgs.clear();
|
||||
|
||||
int line_num = 0;
|
||||
QString line;
|
||||
auto dbc_assert = [&line_num, &line, this](bool condition, const QString &msg = "") {
|
||||
if (!condition) throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(msg).arg(line).toStdString());
|
||||
};
|
||||
auto get_sig = [this](uint32_t address, const QString &name) -> cabana::Signal * {
|
||||
auto m = (cabana::Msg *)msg(address);
|
||||
return m ? (cabana::Signal *)m->sig(name) : nullptr;
|
||||
};
|
||||
|
||||
msgs.clear();
|
||||
QTextStream stream((QString *)&content);
|
||||
cabana::Msg *current_msg = nullptr;
|
||||
int multiplexor_cnt = 0;
|
||||
bool seen_first = false;
|
||||
QTextStream stream((QString *)&content);
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
++line_num;
|
||||
QString raw_line = stream.readLine();
|
||||
line = raw_line.trimmed();
|
||||
|
||||
bool seen = true;
|
||||
if (line.startsWith("BO_ ")) {
|
||||
multiplexor_cnt = 0;
|
||||
auto match = bo_regexp.match(line);
|
||||
dbc_assert(match.hasMatch());
|
||||
auto address = match.captured(1).toUInt();
|
||||
dbc_assert(msgs.count(address) == 0, QString("Duplicate message address: %1").arg(address));
|
||||
current_msg = &msgs[address];
|
||||
current_msg->address = address;
|
||||
current_msg->name = match.captured(2);
|
||||
current_msg->size = match.captured(3).toULong();
|
||||
current_msg->transmitter = match.captured(4).trimmed();
|
||||
} else if (line.startsWith("SG_ ")) {
|
||||
int offset = 0;
|
||||
auto match = sg_regexp.match(line);
|
||||
if (!match.hasMatch()) {
|
||||
match = sgm_regexp.match(line);
|
||||
offset = 1;
|
||||
try {
|
||||
if (line.startsWith("BO_ ")) {
|
||||
multiplexor_cnt = 0;
|
||||
current_msg = parseBO(line);
|
||||
} else if (line.startsWith("SG_ ")) {
|
||||
parseSG(line, current_msg, multiplexor_cnt);
|
||||
} else if (line.startsWith("VAL_ ")) {
|
||||
parseVAL(line);
|
||||
} else if (line.startsWith("CM_ BO_")) {
|
||||
parseCM_BO(line, content, raw_line, stream);
|
||||
} else if (line.startsWith("CM_ SG_ ")) {
|
||||
parseCM_SG(line, content, raw_line, stream);
|
||||
} else {
|
||||
seen = false;
|
||||
}
|
||||
dbc_assert(match.hasMatch());
|
||||
dbc_assert(current_msg, "No Message");
|
||||
auto name = match.captured(1);
|
||||
dbc_assert(current_msg->sig(name) == nullptr, "Duplicate signal name");
|
||||
cabana::Signal s{};
|
||||
if (offset == 1) {
|
||||
auto indicator = match.captured(2);
|
||||
if (indicator == "M") {
|
||||
// Only one signal within a single message can be the multiplexer switch.
|
||||
dbc_assert(++multiplexor_cnt < 2, "Multiple multiplexor");
|
||||
s.type = cabana::Signal::Type::Multiplexor;
|
||||
} else {
|
||||
s.type = cabana::Signal::Type::Multiplexed;
|
||||
s.multiplex_value = indicator.mid(1).toInt();
|
||||
}
|
||||
}
|
||||
s.name = name;
|
||||
s.start_bit = match.captured(offset + 2).toInt();
|
||||
s.size = match.captured(offset + 3).toInt();
|
||||
s.is_little_endian = match.captured(offset + 4).toInt() == 1;
|
||||
s.is_signed = match.captured(offset + 5) == "-";
|
||||
s.factor = match.captured(offset + 6).toDouble();
|
||||
s.offset = match.captured(offset + 7).toDouble();
|
||||
s.min = match.captured(8 + offset).toDouble();
|
||||
s.max = match.captured(9 + offset).toDouble();
|
||||
s.unit = match.captured(10 + offset);
|
||||
s.receiver_name = match.captured(11 + offset).trimmed();
|
||||
|
||||
current_msg->sigs.push_back(new cabana::Signal(s));
|
||||
} else if (line.startsWith("VAL_ ")) {
|
||||
auto match = val_regexp.match(line);
|
||||
dbc_assert(match.hasMatch());
|
||||
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
|
||||
QStringList desc_list = match.captured(3).trimmed().split('"');
|
||||
for (int i = 0; i < desc_list.size(); i += 2) {
|
||||
auto val = desc_list[i].trimmed();
|
||||
if (!val.isEmpty() && (i + 1) < desc_list.size()) {
|
||||
auto desc = desc_list[i + 1].trimmed();
|
||||
s->val_desc.push_back({val.toDouble(), desc});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("CM_ BO_")) {
|
||||
if (!line.endsWith("\";")) {
|
||||
int pos = stream.pos() - raw_line.length() - 1;
|
||||
line = content.mid(pos, content.indexOf("\";", pos));
|
||||
}
|
||||
auto match = msg_comment_regexp.match(line);
|
||||
dbc_assert(match.hasMatch());
|
||||
if (auto m = (cabana::Msg *)msg(match.captured(1).toUInt())) {
|
||||
m->comment = match.captured(2).trimmed().replace("\\\"", "\"");
|
||||
}
|
||||
} else if (line.startsWith("CM_ SG_ ")) {
|
||||
if (!line.endsWith("\";")) {
|
||||
int pos = stream.pos() - raw_line.length() - 1;
|
||||
line = content.mid(pos, content.indexOf("\";", pos));
|
||||
}
|
||||
auto match = sg_comment_regexp.match(line);
|
||||
dbc_assert(match.hasMatch());
|
||||
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
|
||||
s->comment = match.captured(3).trimmed().replace("\\\"", "\"");
|
||||
}
|
||||
} else {
|
||||
seen = false;
|
||||
} catch (std::exception &e) {
|
||||
throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(e.what()).arg(line).toStdString());
|
||||
}
|
||||
|
||||
if (seen) {
|
||||
@@ -201,6 +127,127 @@ void DBCFile::parse(const QString &content) {
|
||||
}
|
||||
}
|
||||
|
||||
cabana::Msg *DBCFile::parseBO(const QString &line) {
|
||||
static QRegularExpression bo_regexp(R"(^BO_ (?<address>\w+) (?<name>\w+) *: (?<size>\w+) (?<transmitter>\w+))");
|
||||
|
||||
QRegularExpressionMatch match = bo_regexp.match(line);
|
||||
if (!match.hasMatch())
|
||||
throw std::runtime_error("Invalid BO_ line format");
|
||||
|
||||
uint32_t address = match.captured("address").toUInt();
|
||||
if (msgs.count(address) > 0)
|
||||
throw std::runtime_error(QString("Duplicate message address: %1").arg(address).toStdString());
|
||||
|
||||
// Create a new message object
|
||||
cabana::Msg *msg = &msgs[address];
|
||||
msg->address = address;
|
||||
msg->name = match.captured("name");
|
||||
msg->size = match.captured("size").toULong();
|
||||
msg->transmitter = match.captured("transmitter").trimmed();
|
||||
return msg;
|
||||
}
|
||||
|
||||
void DBCFile::parseCM_BO(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream) {
|
||||
static QRegularExpression msg_comment_regexp(R"(^CM_ BO_ *(?<address>\w+) *\"(?<comment>(?:[^"\\]|\\.)*)\"\s*;)");
|
||||
|
||||
QString parse_line = line;
|
||||
if (!parse_line.endsWith("\";")) {
|
||||
int pos = stream.pos() - raw_line.length() - 1;
|
||||
parse_line = content.mid(pos, content.indexOf("\";", pos));
|
||||
}
|
||||
auto match = msg_comment_regexp.match(parse_line);
|
||||
if (!match.hasMatch())
|
||||
throw std::runtime_error("Invalid message comment format");
|
||||
|
||||
if (auto m = (cabana::Msg *)msg(match.captured("address").toUInt()))
|
||||
m->comment = match.captured("comment").trimmed().replace("\\\"", "\"");
|
||||
}
|
||||
|
||||
void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt) {
|
||||
static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
|
||||
if (!current_msg)
|
||||
throw std::runtime_error("No Message");
|
||||
|
||||
int offset = 0;
|
||||
auto match = sg_regexp.match(line);
|
||||
if (!match.hasMatch()) {
|
||||
match = sgm_regexp.match(line);
|
||||
offset = 1;
|
||||
}
|
||||
if (!match.hasMatch())
|
||||
throw std::runtime_error("Invalid SG_ line format");
|
||||
|
||||
QString name = match.captured(1);
|
||||
if (current_msg->sig(name) != nullptr)
|
||||
throw std::runtime_error("Duplicate signal name");
|
||||
|
||||
cabana::Signal s{};
|
||||
if (offset == 1) {
|
||||
auto indicator = match.captured(2);
|
||||
if (indicator == "M") {
|
||||
++multiplexor_cnt;
|
||||
// Only one signal within a single message can be the multiplexer switch.
|
||||
if (multiplexor_cnt >= 2)
|
||||
throw std::runtime_error("Multiple multiplexor");
|
||||
|
||||
s.type = cabana::Signal::Type::Multiplexor;
|
||||
} else {
|
||||
s.type = cabana::Signal::Type::Multiplexed;
|
||||
s.multiplex_value = indicator.mid(1).toInt();
|
||||
}
|
||||
}
|
||||
s.name = name;
|
||||
s.start_bit = match.captured(offset + 2).toInt();
|
||||
s.size = match.captured(offset + 3).toInt();
|
||||
s.is_little_endian = match.captured(offset + 4).toInt() == 1;
|
||||
s.is_signed = match.captured(offset + 5) == "-";
|
||||
s.factor = match.captured(offset + 6).toDouble();
|
||||
s.offset = match.captured(offset + 7).toDouble();
|
||||
s.min = match.captured(8 + offset).toDouble();
|
||||
s.max = match.captured(9 + offset).toDouble();
|
||||
s.unit = match.captured(10 + offset);
|
||||
s.receiver_name = match.captured(11 + offset).trimmed();
|
||||
current_msg->sigs.push_back(new cabana::Signal(s));
|
||||
}
|
||||
|
||||
void DBCFile::parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream) {
|
||||
static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"((?:[^"\\]|\\.)*)\"\s*;)");
|
||||
|
||||
QString parse_line = line;
|
||||
if (!parse_line.endsWith("\";")) {
|
||||
int pos = stream.pos() - raw_line.length() - 1;
|
||||
parse_line = content.mid(pos, content.indexOf("\";", pos));
|
||||
}
|
||||
auto match = sg_comment_regexp.match(parse_line);
|
||||
if (!match.hasMatch())
|
||||
throw std::runtime_error("Invalid CM_ SG_ line format");
|
||||
|
||||
if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) {
|
||||
s->comment = match.captured(3).trimmed().replace("\\\"", "\"");
|
||||
}
|
||||
}
|
||||
|
||||
void DBCFile::parseVAL(const QString &line) {
|
||||
static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))");
|
||||
|
||||
auto match = val_regexp.match(line);
|
||||
if (!match.hasMatch())
|
||||
throw std::runtime_error("invalid VAL_ line format");
|
||||
|
||||
if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) {
|
||||
QStringList desc_list = match.captured(3).trimmed().split('"');
|
||||
for (int i = 0; i < desc_list.size(); i += 2) {
|
||||
auto val = desc_list[i].trimmed();
|
||||
if (!val.isEmpty() && (i + 1) < desc_list.size()) {
|
||||
auto desc = desc_list[i + 1].trimmed();
|
||||
s->val_desc.push_back({val.toDouble(), desc});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString DBCFile::generateDBC() {
|
||||
QString dbc_string, comment, val_desc;
|
||||
for (const auto &[address, m] : msgs) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "tools/cabana/dbc/dbc.h"
|
||||
|
||||
@@ -26,6 +27,7 @@ public:
|
||||
cabana::Msg *msg(uint32_t address);
|
||||
cabana::Msg *msg(const QString &name);
|
||||
inline cabana::Msg *msg(const MessageId &id) { return msg(id.address); }
|
||||
cabana::Signal *signal(uint32_t address, const QString name);
|
||||
|
||||
inline QString name() const { return name_.isEmpty() ? "untitled" : name_; }
|
||||
inline bool isEmpty() const { return msgs.empty() && name_.isEmpty(); }
|
||||
@@ -34,6 +36,12 @@ public:
|
||||
|
||||
private:
|
||||
void parse(const QString &content);
|
||||
cabana::Msg *parseBO(const QString &line);
|
||||
void parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt);
|
||||
void parseCM_BO(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream);
|
||||
void parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream);
|
||||
void parseVAL(const QString &line);
|
||||
|
||||
QString header;
|
||||
std::map<uint32_t, cabana::Msg> msgs;
|
||||
QString name_;
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
std::function<bool(double, double)> filter_cmp = nullptr;
|
||||
std::deque<Message> messages;
|
||||
std::vector<cabana::Signal *> sigs;
|
||||
bool hex_mode = true;
|
||||
bool hex_mode = false;
|
||||
};
|
||||
|
||||
class LogsWidget : public QFrame {
|
||||
|
||||
@@ -43,7 +43,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget
|
||||
view->setItemsExpandable(false);
|
||||
view->setIndentation(0);
|
||||
view->setRootIsDecorated(false);
|
||||
view->setUniformRowHeights(!settings.multiple_lines_hex);
|
||||
|
||||
// Must be called before setting any header parameters to avoid overriding
|
||||
restoreHeaderState(settings.message_header_state);
|
||||
@@ -135,15 +134,9 @@ void MessagesWidget::selectMessage(const MessageId &msg_id) {
|
||||
}
|
||||
|
||||
void MessagesWidget::suppressHighlighted() {
|
||||
if (sender() == suppress_add) {
|
||||
size_t n = can->suppressHighlighted();
|
||||
suppress_clear->setText(tr("Clear (%1)").arg(n));
|
||||
suppress_clear->setEnabled(true);
|
||||
} else {
|
||||
can->clearSuppressed();
|
||||
suppress_clear->setText(tr("Clear"));
|
||||
suppress_clear->setEnabled(false);
|
||||
}
|
||||
int n = sender() == suppress_add ? can->suppressHighlighted() : (can->clearSuppressed(), 0);
|
||||
suppress_clear->setText(n > 0 ? tr("Clear (%1)").arg(n) : tr("Clear"));
|
||||
suppress_clear->setEnabled(n > 0);
|
||||
}
|
||||
|
||||
void MessagesWidget::headerContextMenuEvent(const QPoint &pos) {
|
||||
@@ -174,7 +167,6 @@ void MessagesWidget::menuAboutToShow() {
|
||||
void MessagesWidget::setMultiLineBytes(bool multi) {
|
||||
settings.multiple_lines_hex = multi;
|
||||
delegate->setMultipleLines(multi);
|
||||
view->setUniformRowHeights(!multi);
|
||||
view->updateBytesSectionSize();
|
||||
view->doItemsLayout();
|
||||
}
|
||||
@@ -207,22 +199,22 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
}
|
||||
};
|
||||
|
||||
const static QString NA = QStringLiteral("N/A");
|
||||
const auto &item = items_[index.row()];
|
||||
const auto &data = can->lastMessage(item.id);
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (index.column()) {
|
||||
case Column::NAME: return item.name;
|
||||
case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : "N/A";
|
||||
case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : NA;
|
||||
case Column::ADDRESS: return QString::number(item.id.address, 16);
|
||||
case Column::NODE: return item.node;
|
||||
case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(data.freq) : "N/A";
|
||||
case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(data.count) : "N/A";
|
||||
case Column::DATA: return item.id.source != INVALID_SOURCE ? "" : "N/A";
|
||||
case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(can->lastMessage(item.id).freq) : NA;
|
||||
case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(can->lastMessage(item.id).count) : NA;
|
||||
case Column::DATA: return item.id.source != INVALID_SOURCE ? "" : NA;
|
||||
}
|
||||
} else if (role == ColorsRole) {
|
||||
return QVariant::fromValue((void*)(&data.colors));
|
||||
return QVariant::fromValue((void*)(&can->lastMessage(item.id).colors));
|
||||
} else if (role == BytesRole && index.column() == Column::DATA && item.id.source != INVALID_SOURCE) {
|
||||
return QVariant::fromValue((void*)(&data.dat));
|
||||
return QVariant::fromValue((void*)(&can->lastMessage(item.id).dat));
|
||||
} else if (role == Qt::ForegroundRole && !item.active) {
|
||||
return settings.theme == DARK_THEME ? QApplication::palette().color(QPalette::Text).darker(150) : QColor(Qt::gray);
|
||||
} else if (role == Qt::ToolTipRole && index.column() == Column::NAME) {
|
||||
@@ -366,18 +358,17 @@ bool MessageListModel::filterAndSort() {
|
||||
}
|
||||
|
||||
void MessageListModel::msgsReceived(const std::set<MessageId> *new_msgs, bool has_new_ids) {
|
||||
if (has_new_ids || filters_.contains(Column::FREQ) || filters_.contains(Column::COUNT) || filters_.contains(Column::DATA)) {
|
||||
if (has_new_ids || ((filters_.count(Column::FREQ) || filters_.count(Column::COUNT) || filters_.count(Column::DATA)) &&
|
||||
++sort_threshold_ == settings.fps)) {
|
||||
sort_threshold_ = 0;
|
||||
if (filterAndSort()) return;
|
||||
}
|
||||
for (int i = 0; i < items_.size(); ++i) {
|
||||
auto &item = items_[i];
|
||||
bool prev_active = item.active;
|
||||
|
||||
for (auto &item : items_) {
|
||||
item.active = isMessageActive(item.id);
|
||||
if (item.active != prev_active || !new_msgs || new_msgs->count(item.id)) {
|
||||
for (int col = 0; col < columnCount(); ++col)
|
||||
emit dataChanged(index(i, col), index(i, col), {Qt::DisplayRole});
|
||||
}
|
||||
}
|
||||
// Update viewport
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void MessageListModel::sort(int column, Qt::SortOrder order) {
|
||||
@@ -420,6 +411,7 @@ void MessageView::updateBytesSectionSize() {
|
||||
max_bytes = std::max<int>(max_bytes, m.dat.size());
|
||||
}
|
||||
}
|
||||
setUniformRowHeights(!delegate->multipleLines() || max_bytes <= 8);
|
||||
header()->resizeSection(MessageListModel::Column::DATA, delegate->sizeForBytes(max_bytes).width());
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ private:
|
||||
std::set<MessageId> dbc_messages_;
|
||||
int sort_column = 0;
|
||||
Qt::SortOrder sort_order = Qt::AscendingOrder;
|
||||
int sort_threshold_ = 0;
|
||||
};
|
||||
|
||||
class MessageView : public QTreeView {
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
|
||||
#include "tools/cabana/utils/util.h"
|
||||
|
||||
const int MIN_CACHE_MINIUTES = 30;
|
||||
const int MAX_CACHE_MINIUTES = 120;
|
||||
|
||||
Settings settings;
|
||||
|
||||
template <class SettingOperation>
|
||||
@@ -72,7 +75,7 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
|
||||
fps->setValue(settings.fps);
|
||||
|
||||
form_layout->addRow(tr("Max Cached Minutes"), cached_minutes = new QSpinBox(this));
|
||||
cached_minutes->setRange(5, 60);
|
||||
cached_minutes->setRange(MIN_CACHE_MINIUTES, MAX_CACHE_MINIUTES);
|
||||
cached_minutes->setSingleStep(1);
|
||||
cached_minutes->setValue(settings.max_cached_minutes);
|
||||
main_layout->addWidget(groupbox);
|
||||
|
||||
@@ -130,6 +130,7 @@ void AbstractStream::updateLastMsgsTo(double sec) {
|
||||
current_sec_ = sec;
|
||||
uint64_t last_ts = (sec + routeStartTime()) * 1e9;
|
||||
std::unordered_map<MessageId, CanData> msgs;
|
||||
msgs.reserve(events_.size());
|
||||
|
||||
for (const auto &[id, ev] : events_) {
|
||||
auto it = std::upper_bound(ev.begin(), ev.end(), last_ts, CompareCanEvent());
|
||||
@@ -171,6 +172,8 @@ const CanEvent *AbstractStream::newEvent(uint64_t mono_time, const cereal::CanDa
|
||||
void AbstractStream::mergeEvents(const std::vector<const CanEvent *> &events) {
|
||||
static MessageEventsMap msg_events;
|
||||
std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); });
|
||||
|
||||
// Group events by message ID
|
||||
for (auto e : events) {
|
||||
msg_events[{.source = e->src, .address = e->address}].push_back(e);
|
||||
}
|
||||
@@ -207,7 +210,7 @@ inline QColor blend(const QColor &a, const QColor &b) {
|
||||
return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2);
|
||||
}
|
||||
|
||||
// Calculate the frequency of the past minute.
|
||||
// Calculate the frequency from the past one minute data
|
||||
double calc_freq(const MessageId &msg_id, double current_sec) {
|
||||
const auto &events = can->events(msg_id);
|
||||
uint64_t cur_mono_time = (can->routeStartTime() + current_sec) * 1e9;
|
||||
@@ -255,11 +258,8 @@ void CanData::compute(const MessageId &msg_id, const uint8_t *can_data, const in
|
||||
if (last != cur) {
|
||||
const int delta = cur - last;
|
||||
// Keep track if signal is changing randomly, or mostly moving in the same direction
|
||||
if (std::signbit(delta) == std::signbit(last_change.delta)) {
|
||||
last_change.same_delta_counter = std::min(16, last_change.same_delta_counter + 1);
|
||||
} else {
|
||||
last_change.same_delta_counter = std::max(0, last_change.same_delta_counter - 4);
|
||||
}
|
||||
last_change.same_delta_counter += std::signbit(delta) == std::signbit(last_change.delta) ? 1 : -4;
|
||||
last_change.same_delta_counter = std::clamp(last_change.same_delta_counter, 0, 16);
|
||||
|
||||
const double delta_t = ts - last_change.ts;
|
||||
// Mostly moves in the same direction, color based on delta up/down
|
||||
@@ -272,10 +272,10 @@ void CanData::compute(const MessageId &msg_id, const uint8_t *can_data, const in
|
||||
}
|
||||
|
||||
// Track bit level changes
|
||||
const uint8_t tmp = (cur ^ last);
|
||||
const uint8_t diff = (cur ^ last);
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
if (tmp & (1 << (7 - bit))) {
|
||||
last_change.bit_change_counts[bit] += 1;
|
||||
if (diff & (1u << bit)) {
|
||||
++last_change.bit_change_counts[7 - bit];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-16
@@ -51,7 +51,8 @@ std::pair<double, double> SegmentTree::get_minmax(int n, int left, int right, in
|
||||
|
||||
// MessageBytesDelegate
|
||||
|
||||
MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines) : multiple_lines(multiple_lines), QStyledItemDelegate(parent) {
|
||||
MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines)
|
||||
: font_metrics(QApplication::font()), multiple_lines(multiple_lines), QStyledItemDelegate(parent) {
|
||||
fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
byte_size = QFontMetrics(fixed_font).size(Qt::TextSingleLine, "00 ") + QSize(0, 2);
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
@@ -73,31 +74,38 @@ QSize MessageBytesDelegate::sizeHint(const QStyleOptionViewItem &option, const Q
|
||||
}
|
||||
|
||||
void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
auto data = index.data(BytesRole);
|
||||
if (!data.isValid()) {
|
||||
return QStyledItemDelegate::paint(painter, option, index);
|
||||
}
|
||||
|
||||
QFont old_font = painter->font();
|
||||
QPen old_pen = painter->pen();
|
||||
if (option.state & QStyle::State_Selected) {
|
||||
painter->fillRect(option.rect, option.palette.brush(QPalette::Normal, QPalette::Highlight));
|
||||
}
|
||||
const QPoint pt{option.rect.left() + h_margin, option.rect.top() + v_margin};
|
||||
painter->setFont(fixed_font);
|
||||
|
||||
const auto &bytes = *static_cast<std::vector<uint8_t>*>(data.value<void*>());
|
||||
const auto &colors = *static_cast<std::vector<QColor>*>(index.data(ColorsRole).value<void*>());
|
||||
QRect item_rect = option.rect.adjusted(h_margin, v_margin, -h_margin, -v_margin);
|
||||
QColor highlighted_color = option.palette.color(QPalette::HighlightedText);
|
||||
auto text_color = index.data(Qt::ForegroundRole).value<QColor>();
|
||||
bool inactive = text_color.isValid();
|
||||
if (!inactive) {
|
||||
text_color = option.palette.color(QPalette::Text);
|
||||
}
|
||||
auto data = index.data(BytesRole);
|
||||
if (!data.isValid()) {
|
||||
painter->setFont(option.font);
|
||||
painter->setPen(option.state & QStyle::State_Selected ? highlighted_color : text_color);
|
||||
QString text = font_metrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, item_rect.width());
|
||||
painter->drawText(item_rect, Qt::AlignLeft | Qt::AlignVCenter, text);
|
||||
return;
|
||||
}
|
||||
|
||||
// Paint hex column
|
||||
const auto &bytes = *static_cast<std::vector<uint8_t> *>(data.value<void *>());
|
||||
const auto &colors = *static_cast<std::vector<QColor> *>(index.data(ColorsRole).value<void *>());
|
||||
|
||||
painter->setFont(fixed_font);
|
||||
const QPen text_pen(option.state & QStyle::State_Selected ? highlighted_color : text_color);
|
||||
const QPoint pt = item_rect.topLeft();
|
||||
for (int i = 0; i < bytes.size(); ++i) {
|
||||
int row = !multiple_lines ? 0 : i / 8;
|
||||
int column = !multiple_lines ? i : i % 8;
|
||||
QRect r = QRect({pt.x() + column * byte_size.width(), pt.y() + row * byte_size.height()}, byte_size);
|
||||
QRect r({pt.x() + column * byte_size.width(), pt.y() + row * byte_size.height()}, byte_size);
|
||||
|
||||
if (!inactive && i < colors.size() && colors[i].alpha() > 0) {
|
||||
if (option.state & QStyle::State_Selected) {
|
||||
painter->setPen(option.palette.color(QPalette::Text));
|
||||
@@ -105,12 +113,10 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
}
|
||||
painter->fillRect(r, colors[i]);
|
||||
} else {
|
||||
painter->setPen(option.state & QStyle::State_Selected ? option.palette.color(QPalette::HighlightedText) : text_color);
|
||||
painter->setPen(text_pen);
|
||||
}
|
||||
utils::drawStaticText(painter, r, hex_text_table[bytes[i]]);
|
||||
}
|
||||
painter->setFont(old_font);
|
||||
painter->setPen(old_pen);
|
||||
}
|
||||
|
||||
// TabBar
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QByteArray>
|
||||
#include <QDoubleValidator>
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QPainter>
|
||||
#include <QRegExpValidator>
|
||||
#include <QSocketNotifier>
|
||||
@@ -76,6 +77,7 @@ public:
|
||||
|
||||
private:
|
||||
std::array<QStaticText, 256> hex_text_table;
|
||||
QFontMetrics font_metrics;
|
||||
QFont fixed_font;
|
||||
QSize byte_size = {};
|
||||
bool multiple_lines = false;
|
||||
|
||||
@@ -54,8 +54,8 @@ fi
|
||||
eval "$(pyenv init --path)"
|
||||
|
||||
echo "update pip"
|
||||
pip install pip==23.3
|
||||
pip install poetry==1.6.1
|
||||
pip install pip==24.0
|
||||
pip install poetry==1.7.0
|
||||
|
||||
poetry config virtualenvs.prefer-active-python true --local
|
||||
poetry config virtualenvs.in-project true --local
|
||||
|
||||
+99
-63
@@ -1,5 +1,10 @@
|
||||
#include "tools/replay/framereader.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "third_party/libyuv/include/libyuv.h"
|
||||
#include "tools/replay/util.h"
|
||||
@@ -12,7 +17,6 @@
|
||||
#define HW_PIX_FMT AV_PIX_FMT_CUDA
|
||||
#endif
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) {
|
||||
@@ -21,11 +25,32 @@ enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *
|
||||
if (*p == *hw_pix_fmt) return *p;
|
||||
}
|
||||
rWarning("Please run replay with the --no-hw-decoder flag!");
|
||||
// fallback to YUV420p
|
||||
*hw_pix_fmt = AV_PIX_FMT_NONE;
|
||||
return AV_PIX_FMT_YUV420P;
|
||||
}
|
||||
|
||||
struct DecoderManager {
|
||||
VideoDecoder *acquire(CameraType type, AVCodecParameters *codecpar, bool hw_decoder) {
|
||||
auto key = std::tuple(type, codecpar->width, codecpar->height);
|
||||
std::unique_lock lock(mutex_);
|
||||
if (auto it = decoders_.find(key); it != decoders_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
auto decoder = std::make_unique<VideoDecoder>();
|
||||
if (!decoder->open(codecpar, hw_decoder)) {
|
||||
decoder.reset(nullptr);
|
||||
}
|
||||
decoders_[key] = std::move(decoder);
|
||||
return decoders_[key].get();
|
||||
}
|
||||
|
||||
std::mutex mutex_;
|
||||
std::map<std::tuple<CameraType, int, int>, std::unique_ptr<VideoDecoder>> decoders_;
|
||||
};
|
||||
|
||||
DecoderManager decoder_manager;
|
||||
|
||||
} // namespace
|
||||
|
||||
FrameReader::FrameReader() {
|
||||
@@ -33,12 +58,10 @@ FrameReader::FrameReader() {
|
||||
}
|
||||
|
||||
FrameReader::~FrameReader() {
|
||||
if (decoder_ctx) avcodec_free_context(&decoder_ctx);
|
||||
if (input_ctx) avformat_close_input(&input_ctx);
|
||||
if (hw_device_ctx) av_buffer_unref(&hw_device_ctx);
|
||||
}
|
||||
|
||||
bool FrameReader::load(const std::string &url, bool no_hw_decoder, std::atomic<bool> *abort, bool local_cache, int chunk_size, int retries) {
|
||||
bool FrameReader::load(CameraType type, const std::string &url, bool no_hw_decoder, std::atomic<bool> *abort, bool local_cache, int chunk_size, int retries) {
|
||||
auto local_file_path = url.find("https://") == 0 ? cacheFilePath(url) : url;
|
||||
if (!util::file_exists(local_file_path)) {
|
||||
FileReader f(local_cache, chunk_size, retries);
|
||||
@@ -46,10 +69,10 @@ bool FrameReader::load(const std::string &url, bool no_hw_decoder, std::atomic<b
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return loadFromFile(local_file_path, no_hw_decoder, abort);
|
||||
return loadFromFile(type, local_file_path, no_hw_decoder, abort);
|
||||
}
|
||||
|
||||
bool FrameReader::loadFromFile(const std::string &file, bool no_hw_decoder, std::atomic<bool> *abort) {
|
||||
bool FrameReader::loadFromFile(CameraType type, const std::string &file, bool no_hw_decoder, std::atomic<bool> *abort) {
|
||||
if (avformat_open_input(&input_ctx, file.c_str(), nullptr, nullptr) != 0 ||
|
||||
avformat_find_stream_info(input_ctx, nullptr) < 0) {
|
||||
rError("Failed to open input file or find video stream");
|
||||
@@ -57,27 +80,12 @@ bool FrameReader::loadFromFile(const std::string &file, bool no_hw_decoder, std:
|
||||
}
|
||||
input_ctx->probesize = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
AVStream *video = input_ctx->streams[0];
|
||||
const AVCodec *decoder = avcodec_find_decoder(video->codecpar->codec_id);
|
||||
if (!decoder) return false;
|
||||
|
||||
decoder_ctx = avcodec_alloc_context3(decoder);
|
||||
if (!decoder_ctx || avcodec_parameters_to_context(decoder_ctx, video->codecpar) != 0) {
|
||||
rError("Failed to allocate or initialize codec context");
|
||||
return false;
|
||||
}
|
||||
|
||||
width = (decoder_ctx->width + 3) & ~3;
|
||||
height = decoder_ctx->height;
|
||||
|
||||
if (has_hw_decoder && !no_hw_decoder && !initHardwareDecoder(HW_DEVICE_TYPE)) {
|
||||
rWarning("No device with hardware decoder found. fallback to CPU decoding.");
|
||||
}
|
||||
|
||||
if (avcodec_open2(decoder_ctx, decoder, nullptr) < 0) {
|
||||
rError("Failed to open codec");
|
||||
decoder_ = decoder_manager.acquire(type, input_ctx->streams[0]->codecpar, !no_hw_decoder);
|
||||
if (!decoder_) {
|
||||
return false;
|
||||
}
|
||||
width = decoder_->width;
|
||||
height = decoder_->height;
|
||||
|
||||
AVPacket pkt;
|
||||
packets_info.reserve(60 * 20); // 20fps, one minute
|
||||
@@ -85,29 +93,70 @@ bool FrameReader::loadFromFile(const std::string &file, bool no_hw_decoder, std:
|
||||
packets_info.emplace_back(PacketInfo{.flags = pkt.flags, .pos = pkt.pos});
|
||||
av_packet_unref(&pkt);
|
||||
}
|
||||
|
||||
avio_seek(input_ctx->pb, 0, SEEK_SET);
|
||||
return !packets_info.empty();
|
||||
}
|
||||
|
||||
bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) {
|
||||
for (int i = 0;; i++) {
|
||||
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder_ctx->codec, i);
|
||||
if (!config) {
|
||||
rWarning("decoder %s does not support hw device type %s.", decoder_ctx->codec->name,
|
||||
av_hwdevice_get_type_name(hw_device_type));
|
||||
return false;
|
||||
}
|
||||
bool FrameReader::get(int idx, VisionBuf *buf) {
|
||||
if (!buf || idx < 0 || idx >= packets_info.size()) {
|
||||
return false;
|
||||
}
|
||||
return decoder_->decode(this, idx, buf);
|
||||
}
|
||||
|
||||
// class VideoDecoder
|
||||
|
||||
VideoDecoder::VideoDecoder() {
|
||||
av_frame_ = av_frame_alloc();
|
||||
hw_frame_ = av_frame_alloc();
|
||||
}
|
||||
|
||||
VideoDecoder::~VideoDecoder() {
|
||||
if (hw_device_ctx) av_buffer_unref(&hw_device_ctx);
|
||||
if (decoder_ctx) avcodec_free_context(&decoder_ctx);
|
||||
av_frame_free(&av_frame_);
|
||||
av_frame_free(&hw_frame_);
|
||||
}
|
||||
|
||||
bool VideoDecoder::open(AVCodecParameters *codecpar, bool hw_decoder) {
|
||||
const AVCodec *decoder = avcodec_find_decoder(codecpar->codec_id);
|
||||
if (!decoder) return false;
|
||||
|
||||
decoder_ctx = avcodec_alloc_context3(decoder);
|
||||
if (!decoder_ctx || avcodec_parameters_to_context(decoder_ctx, codecpar) != 0) {
|
||||
rError("Failed to allocate or initialize codec context");
|
||||
return false;
|
||||
}
|
||||
width = (decoder_ctx->width + 3) & ~3;
|
||||
height = decoder_ctx->height;
|
||||
|
||||
if (hw_decoder && !initHardwareDecoder(HW_DEVICE_TYPE)) {
|
||||
rWarning("No device with hardware decoder found. fallback to CPU decoding.");
|
||||
}
|
||||
|
||||
if (avcodec_open2(decoder_ctx, decoder, nullptr) < 0) {
|
||||
rError("Failed to open codec");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VideoDecoder::initHardwareDecoder(AVHWDeviceType hw_device_type) {
|
||||
const AVCodecHWConfig *config = nullptr;
|
||||
for (int i = 0; (config = avcodec_get_hw_config(decoder_ctx->codec, i)) != nullptr; i++) {
|
||||
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == hw_device_type) {
|
||||
hw_pix_fmt = config->pix_fmt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!config) {
|
||||
rWarning("Hardware configuration not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = av_hwdevice_ctx_create(&hw_device_ctx, hw_device_type, nullptr, nullptr, 0);
|
||||
if (ret < 0) {
|
||||
hw_pix_fmt = AV_PIX_FMT_NONE;
|
||||
has_hw_decoder = false;
|
||||
rWarning("Failed to create specified HW device %d.", ret);
|
||||
return false;
|
||||
}
|
||||
@@ -118,34 +167,27 @@ bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FrameReader::get(int idx, VisionBuf *buf) {
|
||||
if (!buf || idx < 0 || idx >= packets_info.size()) {
|
||||
return false;
|
||||
}
|
||||
return decode(idx, buf);
|
||||
}
|
||||
|
||||
bool FrameReader::decode(int idx, VisionBuf *buf) {
|
||||
bool VideoDecoder::decode(FrameReader *reader, int idx, VisionBuf *buf) {
|
||||
int from_idx = idx;
|
||||
if (idx != prev_idx + 1) {
|
||||
if (idx != reader->prev_idx + 1) {
|
||||
// seeking to the nearest key frame
|
||||
for (int i = idx; i >= 0; --i) {
|
||||
if (packets_info[i].flags & AV_PKT_FLAG_KEY) {
|
||||
if (reader->packets_info[i].flags & AV_PKT_FLAG_KEY) {
|
||||
from_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
avio_seek(input_ctx->pb, packets_info[from_idx].pos, SEEK_SET);
|
||||
avio_seek(reader->input_ctx->pb, reader->packets_info[from_idx].pos, SEEK_SET);
|
||||
}
|
||||
prev_idx = idx;
|
||||
reader->prev_idx = idx;
|
||||
|
||||
bool result = false;
|
||||
AVPacket pkt;
|
||||
for (int i = from_idx; i <= idx; ++i) {
|
||||
if (av_read_frame(input_ctx, &pkt) == 0) {
|
||||
if (av_read_frame(reader->input_ctx, &pkt) == 0) {
|
||||
AVFrame *f = decodeFrame(&pkt);
|
||||
if (f && i == idx) {
|
||||
result = copyBuffers(f, buf);
|
||||
result = copyBuffer(f, buf);
|
||||
}
|
||||
av_packet_unref(&pkt);
|
||||
}
|
||||
@@ -153,33 +195,27 @@ bool FrameReader::decode(int idx, VisionBuf *buf) {
|
||||
return result;
|
||||
}
|
||||
|
||||
AVFrame *FrameReader::decodeFrame(AVPacket *pkt) {
|
||||
AVFrame *VideoDecoder::decodeFrame(AVPacket *pkt) {
|
||||
int ret = avcodec_send_packet(decoder_ctx, pkt);
|
||||
if (ret < 0) {
|
||||
rError("Error sending a packet for decoding: %d", ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
av_frame_.reset(av_frame_alloc());
|
||||
ret = avcodec_receive_frame(decoder_ctx, av_frame_.get());
|
||||
ret = avcodec_receive_frame(decoder_ctx, av_frame_);
|
||||
if (ret != 0) {
|
||||
rError("avcodec_receive_frame error: %d", ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (av_frame_->format == hw_pix_fmt) {
|
||||
hw_frame.reset(av_frame_alloc());
|
||||
if ((ret = av_hwframe_transfer_data(hw_frame.get(), av_frame_.get(), 0)) < 0) {
|
||||
rError("error transferring the data from GPU to CPU");
|
||||
return nullptr;
|
||||
}
|
||||
return hw_frame.get();
|
||||
} else {
|
||||
return av_frame_.get();
|
||||
if (av_frame_->format == hw_pix_fmt && av_hwframe_transfer_data(hw_frame_, av_frame_, 0) < 0) {
|
||||
rError("error transferring frame data from GPU to CPU");
|
||||
return nullptr;
|
||||
}
|
||||
return (av_frame_->format == hw_pix_fmt) ? hw_frame_ : av_frame_;
|
||||
}
|
||||
|
||||
bool FrameReader::copyBuffers(AVFrame *f, VisionBuf *buf) {
|
||||
bool VideoDecoder::copyBuffer(AVFrame *f, VisionBuf *buf) {
|
||||
if (hw_pix_fmt == HW_PIX_FMT) {
|
||||
for (int i = 0; i < height/2; i++) {
|
||||
memcpy(buf->y + (i*2 + 0)*buf->stride, f->data[0] + (i*2 + 0)*f->linesize[0], width);
|
||||
|
||||
+25
-18
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cereal/visionipc/visionbuf.h"
|
||||
#include "system/camerad/cameras/camera_common.h"
|
||||
#include "tools/replay/filereader.h"
|
||||
|
||||
extern "C" {
|
||||
@@ -12,39 +12,46 @@ extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
struct AVFrameDeleter {
|
||||
void operator()(AVFrame* frame) const { av_frame_free(&frame); }
|
||||
};
|
||||
class VideoDecoder;
|
||||
|
||||
class FrameReader {
|
||||
public:
|
||||
FrameReader();
|
||||
~FrameReader();
|
||||
bool load(const std::string &url, bool no_hw_decoder = false, std::atomic<bool> *abort = nullptr, bool local_cache = false,
|
||||
bool load(CameraType type, const std::string &url, bool no_hw_decoder = false, std::atomic<bool> *abort = nullptr, bool local_cache = false,
|
||||
int chunk_size = -1, int retries = 0);
|
||||
bool loadFromFile(const std::string &file, bool no_hw_decoder = false, std::atomic<bool> *abort = nullptr);
|
||||
bool loadFromFile(CameraType type, const std::string &file, bool no_hw_decoder = false, std::atomic<bool> *abort = nullptr);
|
||||
bool get(int idx, VisionBuf *buf);
|
||||
size_t getFrameCount() const { return packets_info.size(); }
|
||||
|
||||
int width = 0, height = 0;
|
||||
|
||||
private:
|
||||
bool initHardwareDecoder(AVHWDeviceType hw_device_type);
|
||||
bool decode(int idx, VisionBuf *buf);
|
||||
AVFrame * decodeFrame(AVPacket *pkt);
|
||||
bool copyBuffers(AVFrame *f, VisionBuf *buf);
|
||||
|
||||
std::unique_ptr<AVFrame, AVFrameDeleter>av_frame_, hw_frame;
|
||||
VideoDecoder *decoder_ = nullptr;
|
||||
AVFormatContext *input_ctx = nullptr;
|
||||
AVCodecContext *decoder_ctx = nullptr;
|
||||
|
||||
AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;
|
||||
AVBufferRef *hw_device_ctx = nullptr;
|
||||
int prev_idx = -1;
|
||||
struct PacketInfo {
|
||||
int flags;
|
||||
int64_t pos;
|
||||
};
|
||||
std::vector<PacketInfo> packets_info;
|
||||
inline static std::atomic<bool> has_hw_decoder = true;
|
||||
};
|
||||
|
||||
|
||||
class VideoDecoder {
|
||||
public:
|
||||
VideoDecoder();
|
||||
~VideoDecoder();
|
||||
bool open(AVCodecParameters *codecpar, bool hw_decoder);
|
||||
bool decode(FrameReader *reader, int idx, VisionBuf *buf);
|
||||
int width = 0, height = 0;
|
||||
|
||||
private:
|
||||
bool initHardwareDecoder(AVHWDeviceType hw_device_type);
|
||||
AVFrame *decodeFrame(AVPacket *pkt);
|
||||
bool copyBuffer(AVFrame *f, VisionBuf *buf);
|
||||
|
||||
AVFrame *av_frame_, *hw_frame_;
|
||||
AVCodecContext *decoder_ctx = nullptr;
|
||||
AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;
|
||||
AVBufferRef *hw_device_ctx = nullptr;
|
||||
};
|
||||
|
||||
@@ -160,7 +160,7 @@ void Segment::loadFile(int id, const std::string file) {
|
||||
bool success = false;
|
||||
if (id < MAX_CAMERAS) {
|
||||
frames[id] = std::make_unique<FrameReader>();
|
||||
success = frames[id]->load(file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3);
|
||||
success = frames[id]->load((CameraType)id, file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3);
|
||||
} else {
|
||||
log = std::make_unique<LogReader>(filters_);
|
||||
success = log->load(file, &abort_, local_cache, 0, 3);
|
||||
|
||||
Reference in New Issue
Block a user