mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-29 19:23:41 +08:00
Merge branch 'master' into dev-priv/master
# Conflicts: # selfdrive/car/car_helpers.py
This commit is contained in:
@@ -7,6 +7,7 @@ Version 0.9.1 (2023-02-23)
|
||||
* Adjust alert volume using ambient noise level
|
||||
* Driver monitoring icon shows driver's head pose
|
||||
* German translation thanks to Vrabetz and CzokNorris!
|
||||
* Cadillac Escalade 2017 support thanks to rickygilleland!
|
||||
* Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler!
|
||||
* Genesis GV60 2023 support thanks to sunnyhaibin!
|
||||
* Hyundai Tucson 2022-23 support
|
||||
|
||||
@@ -155,6 +155,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"NavSettingTime24h", PERSISTENT},
|
||||
{"NavSettingLeftSide", PERSISTENT},
|
||||
{"NavdRender", PERSISTENT},
|
||||
{"ObdMultiplexingDisabled", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
|
||||
{"OpenpilotEnabledToggle", PERSISTENT},
|
||||
{"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF},
|
||||
{"PandaSignatures", CLEAR_ON_MANAGER_START},
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system.
|
||||
|
||||
# 236 Supported Cars
|
||||
# 237 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -17,6 +17,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Audi&model=Q3 2019-23">J533</a>||
|
||||
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Audi&model=RS3 2018">J533</a>||
|
||||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Audi&model=S3 2015-17">J533</a>||
|
||||
|Cadillac|Escalade 2017[<sup>3</sup>](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Cadillac&model=Escalade 2017">OBD-II</a>||
|
||||
|Cadillac|Escalade ESV 2016[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Cadillac&model=Escalade ESV 2016">OBD-II</a>||
|
||||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Chevrolet&model=Bolt EUV 2022-23">GM</a>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Chevrolet&model=Bolt EV 2022-23">GM</a>||
|
||||
|
||||
@@ -137,6 +137,8 @@ bool safety_setter_thread(std::vector<Panda *> pandas) {
|
||||
panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U);
|
||||
}
|
||||
|
||||
p.putBool("ObdMultiplexingDisabled", true);
|
||||
|
||||
std::string params;
|
||||
LOGW("waiting for params to set safety model");
|
||||
while (true) {
|
||||
|
||||
@@ -11,7 +11,7 @@ from system.version import is_comma_remote, is_tested_branch
|
||||
from selfdrive.car.interfaces import get_interface_attr
|
||||
from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
|
||||
from selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN
|
||||
from selfdrive.car.fw_versions import get_fw_versions_ordered, match_fw_to_car, get_present_ecus
|
||||
from selfdrive.car.fw_versions import disable_obd_multiplexing, get_fw_versions_ordered, match_fw_to_car, get_present_ecus
|
||||
import selfdrive.sentry as sentry
|
||||
from system.swaglog import cloudlog
|
||||
import cereal.messaging as messaging
|
||||
@@ -120,7 +120,7 @@ def fingerprint(logcan, sendcan, num_pandas):
|
||||
|
||||
params = Params()
|
||||
params.put("CarVin", vin)
|
||||
params.put_bool("FirmwareObdQueryDone", True)
|
||||
disable_obd_multiplexing(params)
|
||||
|
||||
finger = gen_empty_fingerprint()
|
||||
candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1
|
||||
|
||||
@@ -59,6 +59,8 @@ class Request:
|
||||
bus: int = 1
|
||||
# FW responses from these queries will not be used for fingerprinting
|
||||
logging: bool = False
|
||||
# These requests are done once OBD multiplexing is disabled, after all others
|
||||
non_obd: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -5,6 +5,7 @@ from tqdm import tqdm
|
||||
|
||||
import panda.python.uds as uds
|
||||
from cereal import car
|
||||
from common.params import Params
|
||||
from selfdrive.car.ecu_addrs import get_ecu_addrs
|
||||
from selfdrive.car.interfaces import get_interface_attr
|
||||
from selfdrive.car.fingerprints import FW_VERSIONS
|
||||
@@ -89,7 +90,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
|
||||
return set()
|
||||
|
||||
|
||||
def match_fw_to_car_exact(fw_versions_dict):
|
||||
def match_fw_to_car_exact(fw_versions_dict) -> Set[str]:
|
||||
"""Do an exact FW match. Returns all cars that match the given
|
||||
FW versions for a list of "essential" ECUs. If an ECU is not considered
|
||||
essential the FW version can be missing to get a fingerprint, but if it's present it
|
||||
@@ -197,11 +198,21 @@ def get_brand_ecu_matches(ecu_rx_addrs):
|
||||
return brand_matches
|
||||
|
||||
|
||||
def disable_obd_multiplexing(params):
|
||||
if not params.get_bool("ObdMultiplexingDisabled"):
|
||||
params.put_bool("FirmwareObdQueryDone", True)
|
||||
|
||||
cloudlog.warning("Waiting for OBD multiplexing to be disabled")
|
||||
params.get_bool("ObdMultiplexingDisabled", block=True)
|
||||
cloudlog.warning("OBD multiplexing disabled")
|
||||
|
||||
|
||||
def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False):
|
||||
"""Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""
|
||||
|
||||
all_car_fw = []
|
||||
brand_matches = get_brand_ecu_matches(ecu_rx_addrs)
|
||||
matched_brand: Optional[str] = None
|
||||
|
||||
for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True):
|
||||
car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, debug=debug, progress=progress)
|
||||
@@ -209,12 +220,20 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pand
|
||||
# Try to match using FW returned from this brand only
|
||||
matches = match_fw_to_car_exact(build_fw_dict(car_fw))
|
||||
if len(matches) == 1:
|
||||
matched_brand = brand
|
||||
break
|
||||
|
||||
disable_obd_multiplexing(Params())
|
||||
|
||||
# Do non-OBD queries for matched brand, or all if no match is found
|
||||
for brand in FW_QUERY_CONFIGS.keys():
|
||||
if brand == matched_brand or matched_brand is None:
|
||||
all_car_fw.extend(get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, obd_multiplexed=False, debug=debug, progress=progress))
|
||||
|
||||
return all_car_fw
|
||||
|
||||
|
||||
def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False):
|
||||
def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, obd_multiplexed=True, debug=False, progress=False):
|
||||
versions = VERSIONS.copy()
|
||||
|
||||
# Each brand can define extra ECUs to query for data collection
|
||||
@@ -262,6 +281,9 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1,
|
||||
# Skip query if no panda available
|
||||
if r.bus > num_pandas * 4 - 1:
|
||||
continue
|
||||
# Or if request is not designated for current multiplexing mode
|
||||
elif r.non_obd == obd_multiplexed:
|
||||
continue
|
||||
|
||||
try:
|
||||
addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import numpy as np
|
||||
from cereal import car
|
||||
from math import fabs
|
||||
from panda import Panda
|
||||
@@ -59,23 +58,23 @@ class CarInterface(CarInterfaceBase):
|
||||
# TODO:
|
||||
# 1. Learn the correction factors from data
|
||||
# 2. Generalize the logic to other GM torque control platforms
|
||||
steer_break_pts = np.array([-1.0, -0.9, -0.75, -0.5, 0.0, 0.5, 0.75, 0.9, 1.0])
|
||||
steer_lataccel_factors = np.array([1.5, 1.15, 1.02, 1.0, 1.0, 1.0, 1.02, 1.15, 1.5])
|
||||
steer_correction_factor = np.interp(
|
||||
steer_break_pts = [-1.0, -0.9, -0.75, -0.5, 0.0, 0.5, 0.75, 0.9, 1.0]
|
||||
steer_lataccel_factors = [1.5, 1.15, 1.02, 1.0, 1.0, 1.0, 1.02, 1.15, 1.5]
|
||||
steer_correction_factor = interp(
|
||||
steer_torque,
|
||||
steer_break_pts,
|
||||
steer_lataccel_factors
|
||||
)
|
||||
|
||||
vego_break_pts = np.array([0.0, 10.0, 15.0, 20.0, 100.0])
|
||||
vego_lataccel_factors = np.array([1.5, 1.5, 1.25, 1.0, 1.0])
|
||||
vego_correction_factor = np.interp(
|
||||
vego_break_pts = [0.0, 10.0, 15.0, 20.0, 100.0]
|
||||
vego_lataccel_factors = [1.5, 1.5, 1.25, 1.0, 1.0]
|
||||
vego_correction_factor = interp(
|
||||
vego,
|
||||
vego_break_pts,
|
||||
vego_lataccel_factors,
|
||||
)
|
||||
|
||||
return float((steer_torque + friction) / (steer_correction_factor * vego_correction_factor))
|
||||
return (steer_torque + friction) / (steer_correction_factor * vego_correction_factor)
|
||||
|
||||
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
|
||||
if self.CP.carFingerprint == CAR.BOLT_EUV:
|
||||
@@ -199,6 +198,14 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerRatio = 15.3
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
|
||||
elif candidate == CAR.ESCALADE:
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
ret.mass = 5653. * CV.LB_TO_KG + STD_CARGO_KG # (5552+5815)/2
|
||||
ret.wheelbase = 2.95 # 116 inches in meters
|
||||
ret.steerRatio = 17.3
|
||||
ret.centerToFront = ret.wheelbase * 0.5
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
elif candidate == CAR.ESCALADE_ESV:
|
||||
ret.minEnableSpeed = -1. # engage speed is decided by pcm
|
||||
ret.mass = 2739. + STD_CARGO_KG
|
||||
@@ -214,7 +221,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.mass = 1669. + STD_CARGO_KG
|
||||
ret.wheelbase = 2.63779
|
||||
ret.steerRatio = 16.8
|
||||
ret.centerToFront = 2.15 # measured
|
||||
ret.centerToFront = ret.wheelbase * 0.4
|
||||
tire_stiffness_factor = 1.0
|
||||
ret.steerActuatorDelay = 0.12
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
|
||||
@@ -67,6 +67,7 @@ class CAR:
|
||||
MALIBU = "CHEVROLET MALIBU PREMIER 2017"
|
||||
ACADIA = "GMC ACADIA DENALI 2018"
|
||||
BUICK_REGAL = "BUICK REGAL ESSENCE 2018"
|
||||
ESCALADE = "CADILLAC ESCALADE 2017"
|
||||
ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016"
|
||||
BOLT_EUV = "CHEVROLET BOLT EUV 2022"
|
||||
SILVERADO = "CHEVROLET SILVERADO 1500 2020"
|
||||
@@ -99,6 +100,7 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = {
|
||||
CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"),
|
||||
CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"),
|
||||
CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"),
|
||||
CAR.ESCALADE: GMCarInfo("Cadillac Escalade 2017", "Driver Assist Package"),
|
||||
CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"),
|
||||
CAR.BOLT_EUV: [
|
||||
GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"),
|
||||
@@ -174,6 +176,10 @@ FINGERPRINTS = {
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 338: 6, 340: 6, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8
|
||||
}],
|
||||
CAR.ESCALADE: [
|
||||
{
|
||||
170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 4, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8
|
||||
}],
|
||||
CAR.ESCALADE_ESV: [
|
||||
{
|
||||
309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8
|
||||
|
||||
@@ -22,6 +22,8 @@ class CarInterface(CarInterfaceBase):
|
||||
def get_pid_accel_limits(CP, current_speed, cruise_speed):
|
||||
if CP.carFingerprint in HONDA_BOSCH:
|
||||
return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX
|
||||
elif CP.enableGasInterceptor:
|
||||
return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX
|
||||
else:
|
||||
# NIDECs don't allow acceleration near cruise_speed,
|
||||
# so limit limits of pid to prevent windup
|
||||
|
||||
@@ -171,14 +171,22 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
[HONDA_VERSION_REQUEST],
|
||||
[HONDA_VERSION_RESPONSE],
|
||||
bus=1,
|
||||
logging=True
|
||||
logging=True,
|
||||
),
|
||||
# Query Nidec PT bus from camera for data collection
|
||||
# Nidec PT bus
|
||||
Request(
|
||||
[StdQueries.UDS_VERSION_REQUEST],
|
||||
[StdQueries.UDS_VERSION_RESPONSE],
|
||||
bus=0,
|
||||
logging=True
|
||||
logging=True,
|
||||
),
|
||||
# Bosch PT bus
|
||||
Request(
|
||||
[StdQueries.UDS_VERSION_REQUEST],
|
||||
[StdQueries.UDS_VERSION_RESPONSE],
|
||||
bus=1,
|
||||
logging=True,
|
||||
non_obd=True,
|
||||
),
|
||||
],
|
||||
extra_ecus=[
|
||||
|
||||
@@ -190,7 +190,8 @@ class CarController:
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
|
||||
# send 25 messages at a time to increases the likelihood of resume being accepted
|
||||
can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25)
|
||||
self.last_button_frame = self.frame
|
||||
if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15:
|
||||
self.last_button_frame = self.frame
|
||||
|
||||
if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl:
|
||||
# TODO: unclear if this is needed
|
||||
|
||||
@@ -140,8 +140,8 @@ class CarState(CarStateBase):
|
||||
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear))
|
||||
|
||||
if not self.CP.openpilotLongitudinalControl:
|
||||
aeb_src = "FCA11" if self.CP.carFingerprint in FEATURES["use_fca"] else "SCC12"
|
||||
aeb_sig = "FCA_CmdAct" if self.CP.carFingerprint in FEATURES["use_fca"] else "AEB_CmdAct"
|
||||
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
|
||||
aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0
|
||||
ret.stockFcw = aeb_warning and not aeb_braking
|
||||
@@ -337,7 +337,7 @@ class CarState(CarStateBase):
|
||||
("SCC12", 50),
|
||||
]
|
||||
|
||||
if CP.carFingerprint in FEATURES["use_fca"]:
|
||||
if CP.flags & HyundaiFlags.USE_FCA.value:
|
||||
signals += [
|
||||
("FCA_CmdAct", "FCA11"),
|
||||
("CF_VSM_Warn", "FCA11"),
|
||||
@@ -432,7 +432,7 @@ class CarState(CarStateBase):
|
||||
("SCC12", 50),
|
||||
]
|
||||
|
||||
if CP.carFingerprint in FEATURES["use_fca"]:
|
||||
if CP.flags & HyundaiFlags.USE_FCA.value:
|
||||
signals += [
|
||||
("FCA_CmdAct", "FCA11"),
|
||||
("CF_VSM_Warn", "FCA11"),
|
||||
|
||||
@@ -49,6 +49,10 @@ class CarInterface(CarInterfaceBase):
|
||||
if 0x485 in fingerprint[2]:
|
||||
ret.flags |= HyundaiFlags.SEND_LFA.value
|
||||
|
||||
# These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
|
||||
if 0x38d in fingerprint[0] or 0x38d in fingerprint[2]:
|
||||
ret.flags |= HyundaiFlags.USE_FCA.value
|
||||
|
||||
ret.steerActuatorDelay = 0.1 # Default delay
|
||||
ret.steerLimitTimer = 0.4
|
||||
tire_stiffness_factor = 1.
|
||||
|
||||
@@ -61,6 +61,7 @@ class HyundaiFlags(IntFlag):
|
||||
ENABLE_BLINKERS = 32
|
||||
CANFD_ALT_GEARS_2 = 64
|
||||
SEND_LFA = 128
|
||||
USE_FCA = 256
|
||||
|
||||
SP_CAN_LFA_BTN = 128
|
||||
|
||||
@@ -439,13 +440,14 @@ FW_VERSIONS = {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x816U3J9051\000\000\xf1\0006U3H1_C2\000\0006U3J9051\000\000PAE0G16NL0\x82zT\xd2',
|
||||
b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00',
|
||||
b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt',
|
||||
b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00',
|
||||
b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00',
|
||||
b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt',
|
||||
],
|
||||
},
|
||||
CAR.IONIQ_EV_2020: {
|
||||
@@ -485,6 +487,7 @@ FW_VERSIONS = {
|
||||
CAR.IONIQ_HEV_2022: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ',
|
||||
b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101',
|
||||
@@ -497,6 +500,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00',
|
||||
b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x96\xda\xd4\xee',
|
||||
],
|
||||
},
|
||||
CAR.SONATA: {
|
||||
@@ -684,12 +688,14 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.SANTA_FE: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1210 ',
|
||||
b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S2000 ',
|
||||
b'\xf1\x00TM__ SCC F-CUP 1.00 1.02 99110-S2000 ',
|
||||
b'\xf1\x00TM__ SCC F-CUP 1.00 1.03 99110-S2000 ',
|
||||
],
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650',
|
||||
b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500',
|
||||
b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650',
|
||||
b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650',
|
||||
b'\xf1\x00TM ESC \x02 100\x18\x030 58910-S2600',
|
||||
@@ -708,11 +714,14 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409',
|
||||
b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8A12',
|
||||
b'\xf1\x00TM MDPS C 1.00 1.01 56340-S2000 9129',
|
||||
b'\xf1\x00TM MDPS R 1.00 1.02 57700-S1100 4TMDP102'
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00TM MFC AT EUR LHD 1.00 1.01 99211-S1010 181207',
|
||||
b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1',
|
||||
b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00',
|
||||
b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91',
|
||||
b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00',
|
||||
@@ -1652,9 +1661,6 @@ FEATURES = {
|
||||
"use_cluster_gears": {CAR.ELANTRA, CAR.KONA},
|
||||
"use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON},
|
||||
"use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020},
|
||||
|
||||
# these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
|
||||
"use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020, CAR.KIA_OPTIMA_G4_FL},
|
||||
}
|
||||
|
||||
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN}
|
||||
|
||||
@@ -50,6 +50,7 @@ routes = [
|
||||
|
||||
CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA),
|
||||
CarTestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL),
|
||||
CarTestRoute("ef8f2185104d862e|2023-02-09--18-37-13", GM.ESCALADE),
|
||||
CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV),
|
||||
CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT),
|
||||
CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1),
|
||||
|
||||
@@ -32,7 +32,8 @@ class TestCarInterfaces(unittest.TestCase):
|
||||
|
||||
self.assertGreater(car_params.mass, 1)
|
||||
self.assertGreater(car_params.wheelbase, 0)
|
||||
self.assertGreater(car_params.centerToFront, 0)
|
||||
# centerToFront is center of gravity to front wheels, assert a reasonable range
|
||||
self.assertTrue(car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7)
|
||||
self.assertGreater(car_params.maxLateralAccel, 0)
|
||||
|
||||
# Longitudinal sanity checks
|
||||
|
||||
@@ -26,6 +26,7 @@ COMMA BODY: [.nan, 1000, .nan]
|
||||
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]
|
||||
CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694]
|
||||
CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05]
|
||||
CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112]
|
||||
CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05]
|
||||
|
||||
@@ -72,6 +72,7 @@ class TestStartup(unittest.TestCase):
|
||||
params.clear_all()
|
||||
params.put_bool("Passive", False)
|
||||
params.put_bool("OpenpilotEnabledToggle", True)
|
||||
params.put_bool("ObdMultiplexingDisabled", True)
|
||||
|
||||
# Build capnn version of FW array
|
||||
if fw_versions is not None:
|
||||
|
||||
@@ -17,7 +17,7 @@ def get_fingerprint(lr):
|
||||
for c in msg.can:
|
||||
# read also msgs sent by EON on CAN bus 0x80 and filter out the
|
||||
# addr with more than 11 bits
|
||||
if c.src % 0x80 == 0 and c.address < 0x800:
|
||||
if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8):
|
||||
msgs[c.address] = len(c.dat)
|
||||
|
||||
# show CAN fingerprint
|
||||
|
||||
@@ -22,7 +22,7 @@ while True:
|
||||
for c in lc.can:
|
||||
# read also msgs sent by EON on CAN bus 0x80 and filter out the
|
||||
# addr with more than 11 bits
|
||||
if c.src in [0, 2] and c.address < 0x800:
|
||||
if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8):
|
||||
msgs[c.address] = len(c.dat)
|
||||
|
||||
fingerprint = ', '.join("%d: %d" % v for v in sorted(msgs.items()))
|
||||
|
||||
@@ -619,7 +619,7 @@ kj::ArrayPtr<capnp::byte> Localizer::get_message_bytes(MessageBuilder& msg_build
|
||||
}
|
||||
|
||||
bool Localizer::is_gps_ok() {
|
||||
return (this->kf->get_filter_time() - this->last_gps_msg) < 1.0;
|
||||
return (this->kf->get_filter_time() - this->last_gps_msg) < 2.0;
|
||||
}
|
||||
|
||||
bool Localizer::critical_services_valid(std::map<std::string, double> critical_services) {
|
||||
|
||||
@@ -407,6 +407,7 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None):
|
||||
params.put_bool("WideCameraOnly", False)
|
||||
params.put_bool("DisableLogging", False)
|
||||
params.put_bool("UbloxAvailable", True)
|
||||
params.put_bool("ObdMultiplexingDisabled", True)
|
||||
|
||||
os.environ["NO_RADAR_SLEEP"] = "1"
|
||||
os.environ["REPLAY"] = "1"
|
||||
|
||||
@@ -1 +1 @@
|
||||
3e53ce81f1ce26409fdc4479e650ef5626130876
|
||||
8883c476d5abc12b4b2949e04c6d7c0cd7c8b9fa
|
||||
|
||||
@@ -42,6 +42,21 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &BinaryView::refresh);
|
||||
|
||||
addShortcuts();
|
||||
setWhatsThis(R"(
|
||||
<b>Binary View</b><br/>
|
||||
<!-- TODO: add descprition here -->
|
||||
Shortcuts:<br />
|
||||
Delete Signal:
|
||||
<span style="background-color:lightGray;color:gray"> x </span>,
|
||||
<span style="background-color:lightGray;color:gray"> Backspace </span>,
|
||||
<span style="background-color:lightGray;color:gray"> Delete</span><br />
|
||||
Change endianness: <span style="background-color:lightGray;color:gray"> e </span><br />
|
||||
Change singedness: <span style="background-color:lightGray;color:gray"> s </span><br />
|
||||
Open chart:
|
||||
<span style="background-color:lightGray;color:gray"> c </span>,
|
||||
<span style="background-color:lightGray;color:gray"> p </span>,
|
||||
<span style="background-color:lightGray;color:gray"> g </span><br />
|
||||
)");
|
||||
}
|
||||
|
||||
void BinaryView::addShortcuts() {
|
||||
@@ -94,7 +109,7 @@ void BinaryView::addShortcuts() {
|
||||
QObject::connect(shortcut_plot_c, &QShortcut::activated, shortcut_plot, &QShortcut::activated);
|
||||
QObject::connect(shortcut_plot, &QShortcut::activated, [=]{
|
||||
if (hovered_sig != nullptr) {
|
||||
emit showChart(model->msg_id, hovered_sig, true, false);
|
||||
emit showChart(*model->msg_id, hovered_sig, true, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -189,14 +204,14 @@ void BinaryView::leaveEvent(QEvent *event) {
|
||||
QTableView::leaveEvent(event);
|
||||
}
|
||||
|
||||
void BinaryView::setMessage(const QString &message_id) {
|
||||
void BinaryView::setMessage(const MessageId &message_id) {
|
||||
model->msg_id = message_id;
|
||||
verticalScrollBar()->setValue(0);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void BinaryView::refresh() {
|
||||
if (model->msg_id.isEmpty()) return;
|
||||
if (!model->msg_id) return;
|
||||
|
||||
clearSelection();
|
||||
anchor_index = QModelIndex();
|
||||
@@ -231,7 +246,7 @@ std::tuple<int, int, bool> BinaryView::getSelection(QModelIndex index) {
|
||||
void BinaryViewModel::refresh() {
|
||||
beginResetModel();
|
||||
items.clear();
|
||||
if ((dbc_msg = dbc()->msg(msg_id))) {
|
||||
if ((dbc_msg = dbc()->msg(*msg_id))) {
|
||||
row_count = dbc_msg->size;
|
||||
items.resize(row_count * column_count);
|
||||
for (auto sig : dbc_msg->getSignals()) {
|
||||
@@ -250,7 +265,7 @@ void BinaryViewModel::refresh() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
row_count = can->lastMessage(msg_id).dat.size();
|
||||
row_count = can->lastMessage(*msg_id).dat.size();
|
||||
items.resize(row_count * column_count);
|
||||
}
|
||||
endResetModel();
|
||||
@@ -259,7 +274,7 @@ void BinaryViewModel::refresh() {
|
||||
|
||||
void BinaryViewModel::updateState() {
|
||||
auto prev_items = items;
|
||||
const auto &last_msg = can->lastMessage(msg_id);
|
||||
const auto &last_msg = can->lastMessage(*msg_id);
|
||||
const auto &binary = last_msg.dat;
|
||||
|
||||
// data size may changed.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
@@ -46,7 +48,7 @@ public:
|
||||
};
|
||||
std::vector<Item> items;
|
||||
|
||||
QString msg_id;
|
||||
std::optional<MessageId> msg_id;
|
||||
const DBCMsg *dbc_msg = nullptr;
|
||||
int row_count = 0;
|
||||
const int column_count = 9;
|
||||
@@ -57,7 +59,7 @@ class BinaryView : public QTableView {
|
||||
|
||||
public:
|
||||
BinaryView(QWidget *parent = nullptr);
|
||||
void setMessage(const QString &message_id);
|
||||
void setMessage(const MessageId &message_id);
|
||||
void highlight(const Signal *sig);
|
||||
QSet<const Signal*> getOverlappingSignals() const;
|
||||
inline void updateState() { model->updateState(); }
|
||||
@@ -70,7 +72,7 @@ signals:
|
||||
void resizeSignal(const Signal *sig, int from, int size);
|
||||
void removeSignal(const Signal *sig);
|
||||
void editSignal(const Signal *origin_s, Signal &s);
|
||||
void showChart(const QString &name, const Signal *sig, bool show, bool merge);
|
||||
void showChart(const MessageId &id, const Signal *sig, bool show, bool merge);
|
||||
|
||||
private:
|
||||
void addShortcuts();
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#include <QRubberBand>
|
||||
#include <QPushButton>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
#include <QToolTip>
|
||||
#include <QtConcurrent>
|
||||
|
||||
const int MAX_COLUMN_COUNT = 4;
|
||||
// ChartsWidget
|
||||
|
||||
ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
@@ -23,14 +25,17 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QToolBar *toolbar = new QToolBar(tr("Charts"), this);
|
||||
toolbar->setIconSize({16, 16});
|
||||
|
||||
QAction *new_plot_btn = toolbar->addAction(utils::icon("file-plus"), "");
|
||||
new_plot_btn->setToolTip(tr("New Plot"));
|
||||
QAction *new_plot_btn = toolbar->addAction(utils::icon("file-plus"), tr("New Plot"));
|
||||
toolbar->addWidget(title_label = new QLabel());
|
||||
title_label->setContentsMargins(0, 0, 12, 0);
|
||||
columns_cb = new QComboBox(this);
|
||||
columns_cb->addItems({"1", "2", "3", "4"});
|
||||
columns_lb_action = toolbar->addWidget(new QLabel(tr("Columns:")));
|
||||
columns_cb_action = toolbar->addWidget(columns_cb);
|
||||
|
||||
QMenu *menu = new QMenu(this);
|
||||
for (int i = 0; i < MAX_COLUMN_COUNT; ++i) {
|
||||
menu->addAction(tr("%1").arg(i + 1), [=]() { setColumnCount(i + 1); });
|
||||
}
|
||||
columns_action = toolbar->addAction("");
|
||||
columns_action->setMenu(menu);
|
||||
qobject_cast<QToolButton*>(toolbar->widgetForAction(columns_action))->setPopupMode(QToolButton::InstantPopup);
|
||||
|
||||
QLabel *stretch_label = new QLabel(this);
|
||||
stretch_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
@@ -44,13 +49,10 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
range_slider->setPageStep(60); // 1 min
|
||||
range_slider_action = toolbar->addWidget(range_slider);
|
||||
|
||||
reset_zoom_action = toolbar->addWidget(reset_zoom_btn = new QToolButton());
|
||||
reset_zoom_btn->setIcon(utils::icon("zoom-out"));
|
||||
reset_zoom_btn->setToolTip(tr("Reset zoom"));
|
||||
reset_zoom_btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
reset_zoom_action = toolbar->addAction(utils::icon("zoom-out"), tr("Reset Zoom"));
|
||||
qobject_cast<QToolButton*>(toolbar->widgetForAction(reset_zoom_action))->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
|
||||
remove_all_btn = toolbar->addAction(utils::icon("x"), "");
|
||||
remove_all_btn->setToolTip(tr("Remove all charts"));
|
||||
remove_all_btn = toolbar->addAction(utils::icon("x"), tr("Remove all charts"));
|
||||
dock_btn = toolbar->addAction("");
|
||||
main_layout->addWidget(toolbar);
|
||||
|
||||
@@ -73,10 +75,9 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
// init settings
|
||||
use_dark_theme = QApplication::style()->standardPalette().color(QPalette::WindowText).value() >
|
||||
QApplication::style()->standardPalette().color(QPalette::Background).value();
|
||||
column_count = std::clamp(settings.chart_column_count, 1, columns_cb->count());
|
||||
column_count = std::clamp(settings.chart_column_count, 1, MAX_COLUMN_COUNT);
|
||||
max_chart_range = std::clamp(settings.chart_range, 1, settings.max_cached_minutes * 60);
|
||||
display_range = {0, max_chart_range};
|
||||
columns_cb->setCurrentIndex(column_count - 1);
|
||||
range_slider->setValue(max_chart_range);
|
||||
updateToolBar();
|
||||
|
||||
@@ -86,14 +87,18 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange);
|
||||
QObject::connect(new_plot_btn, &QAction::triggered, this, &ChartsWidget::newChart);
|
||||
QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll);
|
||||
QObject::connect(reset_zoom_btn, &QToolButton::clicked, this, &ChartsWidget::zoomReset);
|
||||
QObject::connect(columns_cb, SIGNAL(activated(int)), SLOT(setColumnCount(int)));
|
||||
QObject::connect(reset_zoom_action, &QAction::triggered, this, &ChartsWidget::zoomReset);
|
||||
QObject::connect(&settings, &Settings::changed, this, &ChartsWidget::settingChanged);
|
||||
QObject::connect(dock_btn, &QAction::triggered, [this]() {
|
||||
emit dock(!docking);
|
||||
docking = !docking;
|
||||
updateToolBar();
|
||||
});
|
||||
|
||||
setWhatsThis(tr(R"(
|
||||
<b>Chart view</b><br />
|
||||
<!-- TODO: add descprition here -->
|
||||
)"));
|
||||
}
|
||||
|
||||
void ChartsWidget::eventsMerged() {
|
||||
@@ -162,11 +167,12 @@ void ChartsWidget::setMaxChartRange(int value) {
|
||||
|
||||
void ChartsWidget::updateToolBar() {
|
||||
title_label->setText(tr("Charts: %1").arg(charts.size()));
|
||||
columns_action->setText(tr("Column: %1").arg(column_count));
|
||||
range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0')));
|
||||
range_lb_action->setVisible(!is_zoomed);
|
||||
range_slider_action->setVisible(!is_zoomed);
|
||||
reset_zoom_action->setVisible(is_zoomed);
|
||||
reset_zoom_btn->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 1).arg(zoomed_range.second, 0, 'f', 1) : "");
|
||||
reset_zoom_action->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 1).arg(zoomed_range.second, 0, 'f', 1) : "");
|
||||
remove_all_btn->setEnabled(!charts.isEmpty());
|
||||
dock_btn->setIcon(utils::icon(docking ? "arrow-up-right-square" : "arrow-down-left-square"));
|
||||
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
|
||||
@@ -180,7 +186,7 @@ void ChartsWidget::settingChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) {
|
||||
ChartView *ChartsWidget::findChart(const MessageId &id, const Signal *sig) {
|
||||
for (auto c : charts)
|
||||
if (c->hasSeries(id, sig)) return c;
|
||||
return nullptr;
|
||||
@@ -203,7 +209,7 @@ ChartView *ChartsWidget::createChart() {
|
||||
return chart;
|
||||
}
|
||||
|
||||
void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) {
|
||||
void ChartsWidget::showChart(const MessageId &id, const Signal *sig, bool show, bool merge) {
|
||||
setUpdatesEnabled(false);
|
||||
ChartView *chart = findChart(id, sig);
|
||||
if (show && !chart) {
|
||||
@@ -211,29 +217,29 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bo
|
||||
chart->addSeries(id, sig);
|
||||
updateState();
|
||||
} else if (!show && chart) {
|
||||
chart->removeSeries(id, sig);
|
||||
chart->removeIf([&](auto &s) { return s.msg_id == id && s.sig == sig; });
|
||||
}
|
||||
updateToolBar();
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void ChartsWidget::setColumnCount(int n) {
|
||||
n = std::clamp(n + 1, 1, columns_cb->count());
|
||||
n = std::clamp(n, 1, MAX_COLUMN_COUNT);
|
||||
if (column_count != n) {
|
||||
column_count = settings.chart_column_count = n;
|
||||
updateToolBar();
|
||||
updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartsWidget::updateLayout() {
|
||||
int n = columns_cb->count();
|
||||
int n = MAX_COLUMN_COUNT;
|
||||
for (; n > 1; --n) {
|
||||
if ((n * CHART_MIN_WIDTH + (n - 1) * charts_layout->spacing()) < charts_layout->geometry().width()) break;
|
||||
}
|
||||
|
||||
bool show_column_cb = n > 1;
|
||||
columns_lb_action->setVisible(show_column_cb);
|
||||
columns_cb_action->setVisible(show_column_cb);
|
||||
columns_action->setVisible(show_column_cb);
|
||||
|
||||
n = std::min(column_count, n);
|
||||
if (charts.size() != charts_layout->count() || n != current_column_count) {
|
||||
@@ -305,7 +311,6 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
|
||||
|
||||
ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
series_type = settings.chart_series_type == 0 ? QAbstractSeries::SeriesTypeLine : QAbstractSeries::SeriesTypeScatter;
|
||||
|
||||
QChart *chart = new QChart();
|
||||
chart->setBackgroundVisible(false);
|
||||
axis_x = new QValueAxis(this);
|
||||
@@ -363,45 +368,40 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove);
|
||||
}
|
||||
|
||||
void ChartView::addSeries(const QString &msg_id, const Signal *sig) {
|
||||
void ChartView::addSeries(const MessageId &msg_id, const Signal *sig) {
|
||||
if (hasSeries(msg_id, sig)) return;
|
||||
|
||||
QXYSeries *series = createSeries(series_type, getColor(sig));
|
||||
chart()->addSeries(series);
|
||||
series->attachAxis(axis_x);
|
||||
series->attachAxis(axis_y);
|
||||
auto [source, address] = DBCManager::parseId(msg_id);
|
||||
sigs.push_back({.msg_id = msg_id, .address = address, .source = source, .sig = sig, .series = series});
|
||||
sigs.push_back({.msg_id = msg_id, .sig = sig, .series = series});
|
||||
updateTitle();
|
||||
updateSeries(sig);
|
||||
updateSeriesPoints();
|
||||
emit seriesAdded(msg_id, sig);
|
||||
}
|
||||
|
||||
void ChartView::removeSeries(const QString &msg_id, const Signal *sig) {
|
||||
auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; });
|
||||
if (it != sigs.end()) {
|
||||
it = removeItem(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool ChartView::hasSeries(const QString &msg_id, const Signal *sig) const {
|
||||
bool ChartView::hasSeries(const MessageId &msg_id, const Signal *sig) const {
|
||||
return std::any_of(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; });
|
||||
}
|
||||
|
||||
QList<ChartView::SigItem>::iterator ChartView::removeItem(const QList<ChartView::SigItem>::iterator &it) {
|
||||
chart()->removeSeries(it->series);
|
||||
it->series->deleteLater();
|
||||
QString msg_id = it->msg_id;
|
||||
const Signal *sig = it->sig;
|
||||
auto ret = sigs.erase(it);
|
||||
emit seriesRemoved(msg_id, sig);
|
||||
if (!sigs.isEmpty()) {
|
||||
void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) {
|
||||
int prev_size = sigs.size();
|
||||
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
|
||||
if (predicate(*it)) {
|
||||
chart()->removeSeries(it->series);
|
||||
it->series->deleteLater();
|
||||
auto msg_id = it->msg_id;
|
||||
auto sig = it->sig;
|
||||
it = sigs.erase(it);
|
||||
emit seriesRemoved(msg_id, sig);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (sigs.empty()) {
|
||||
emit remove();
|
||||
} else if (sigs.size() != prev_size) {
|
||||
updateAxisY();
|
||||
} else {
|
||||
emit remove();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ChartView::signalUpdated(const Signal *sig) {
|
||||
@@ -412,23 +412,11 @@ void ChartView::signalUpdated(const Signal *sig) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::signalRemoved(const Signal *sig) {
|
||||
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
|
||||
it = (it->sig == sig) ? removeItem(it) : ++it;
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::msgUpdated(uint32_t address) {
|
||||
if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.address == address; }))
|
||||
if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.msg_id.address == address; }))
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void ChartView::msgRemoved(uint32_t address) {
|
||||
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
|
||||
it = (it->address == address) ? removeItem(it) : ++it;
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::manageSeries() {
|
||||
SeriesSelector dlg(tr("Mange Chart"), this);
|
||||
for (auto &s : sigs) {
|
||||
@@ -436,19 +424,12 @@ void ChartView::manageSeries() {
|
||||
}
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
auto items = dlg.seletedItems();
|
||||
if (items.isEmpty()) {
|
||||
emit remove();
|
||||
} else {
|
||||
for (auto s : items) {
|
||||
addSeries(s->msg_id, s->sig);
|
||||
}
|
||||
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
|
||||
bool exists = std::any_of(items.cbegin(), items.cend(), [&](auto &s) {
|
||||
return s->msg_id == it->msg_id && s->sig == it->sig;
|
||||
});
|
||||
it = exists ? ++it : removeItem(it);
|
||||
}
|
||||
for (auto s : items) {
|
||||
addSeries(s->msg_id, s->sig);
|
||||
}
|
||||
removeIf([&](auto &s) {
|
||||
return std::none_of(items.cbegin(), items.cend(), [&](auto &it) { return s.msg_id == it->msg_id && s.sig == it->sig; });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +459,7 @@ void ChartView::updateTitle() {
|
||||
}
|
||||
for (auto &s : sigs) {
|
||||
auto decoration = s.series->isVisible() ? "none" : "line-through";
|
||||
s.series->setName(QString("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").arg(decoration, s.sig->name.c_str(), msgName(s.msg_id), s.msg_id));
|
||||
s.series->setName(QString("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").arg(decoration, s.sig->name.c_str(), msgName(s.msg_id), s.msg_id.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +484,7 @@ void ChartView::updateSeriesPoints() {
|
||||
int pixels_per_point = width() / num_points;
|
||||
|
||||
if (series_type == QAbstractSeries::SeriesTypeScatter) {
|
||||
((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 1, 8));
|
||||
((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 2, 8));
|
||||
} else {
|
||||
s.series->setPointsVisible(pixels_per_point > 20);
|
||||
}
|
||||
@@ -540,7 +521,7 @@ void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *even
|
||||
for (auto it = chunk.first; it != chunk.second; ++it) {
|
||||
if ((*it)->which == cereal::Event::Which::CAN) {
|
||||
for (const auto &c : (*it)->event.getCan()) {
|
||||
if (s.address == c.getAddress() && s.source == c.getSrc()) {
|
||||
if (s.msg_id.address == c.getAddress() && s.msg_id.source == c.getSrc()) {
|
||||
auto dat = c.getDat();
|
||||
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig);
|
||||
double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds
|
||||
@@ -698,7 +679,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
|
||||
text_list.push_front(QString::number(chart()->mapToValue(pt).x(), 'f', 3));
|
||||
QPointF tooltip_pt(pt.x() + 12, plot_area.top() - 20);
|
||||
QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), pt.isNull() ? "" : text_list.join("<br />"), this, plot_area.toRect());
|
||||
scene()->update();
|
||||
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
|
||||
} else {
|
||||
QToolTip::hideText();
|
||||
}
|
||||
@@ -795,6 +776,9 @@ QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type, QColor colo
|
||||
pen.setWidth(2.0 * qApp->devicePixelRatio());
|
||||
series->setPen(pen);
|
||||
#endif
|
||||
chart()->addSeries(series);
|
||||
series->attachAxis(axis_x);
|
||||
series->attachAxis(axis_y);
|
||||
return series;
|
||||
}
|
||||
|
||||
@@ -809,9 +793,6 @@ void ChartView::setSeriesType(QAbstractSeries::SeriesType type) {
|
||||
}
|
||||
for (auto &s : sigs) {
|
||||
auto series = createSeries(series_type, getColor(s.sig));
|
||||
chart()->addSeries(series);
|
||||
series->attachAxis(axis_x);
|
||||
series->attachAxis(axis_y);
|
||||
series->replace(s.vals);
|
||||
s.series = series;
|
||||
}
|
||||
@@ -870,7 +851,7 @@ SeriesSelector::SeriesSelector(QString title, QWidget *parent) : QDialog(parent)
|
||||
|
||||
for (auto it = can->can_msgs.cbegin(); it != can->can_msgs.cend(); ++it) {
|
||||
if (auto m = dbc()->msg(it.key())) {
|
||||
msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key()), it.key());
|
||||
msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key().toString()), QVariant::fromValue(it.key()));
|
||||
}
|
||||
}
|
||||
msgs_combo->model()->sort(0);
|
||||
@@ -895,7 +876,7 @@ void SeriesSelector::add(QListWidgetItem *item) {
|
||||
|
||||
void SeriesSelector::remove(QListWidgetItem *item) {
|
||||
auto it = (ListItem *)item;
|
||||
if (it->msg_id == msgs_combo->currentData().toString()) {
|
||||
if (it->msg_id == msgs_combo->currentData().value<MessageId>()) {
|
||||
addItemToList(available_list, it->msg_id, it->sig);
|
||||
}
|
||||
delete item;
|
||||
@@ -904,7 +885,7 @@ void SeriesSelector::remove(QListWidgetItem *item) {
|
||||
void SeriesSelector::updateAvailableList(int index) {
|
||||
if (index == -1) return;
|
||||
available_list->clear();
|
||||
QString msg_id = msgs_combo->itemData(index).toString();
|
||||
MessageId msg_id = msgs_combo->itemData(index).value<MessageId>();
|
||||
auto selected_items = seletedItems();
|
||||
for (auto &[name, s] : dbc()->msg(msg_id)->sigs) {
|
||||
bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig=&s](auto it) { return it->msg_id == msg_id && it->sig == sig; });
|
||||
@@ -914,9 +895,9 @@ void SeriesSelector::updateAvailableList(int index) {
|
||||
}
|
||||
}
|
||||
|
||||
void SeriesSelector::addItemToList(QListWidget *parent, const QString id, const Signal *sig, bool show_msg_name) {
|
||||
void SeriesSelector::addItemToList(QListWidget *parent, const MessageId id, const Signal *sig, bool show_msg_name) {
|
||||
QString text = QString("<span style=\"color:%0;\">■ </span> %1").arg(getColor(sig).name(), sig->name.c_str());
|
||||
if (show_msg_name) text += QString(" <font color=\"gray\">%0 %1</font>").arg(msgName(id), id);
|
||||
if (show_msg_name) text += QString(" <font color=\"gray\">%0 %1</font>").arg(msgName(id), id.toString());
|
||||
|
||||
QLabel *label = new QLabel(text);
|
||||
label->setContentsMargins(5, 0, 5, 0);
|
||||
|
||||
+16
-24
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
@@ -8,7 +7,6 @@
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QSlider>
|
||||
#include <QToolButton>
|
||||
#include <QtCharts/QChartView>
|
||||
#include <QtCharts/QLegendMarker>
|
||||
#include <QtCharts/QLineSeries>
|
||||
@@ -27,18 +25,15 @@ class ChartView : public QChartView {
|
||||
|
||||
public:
|
||||
ChartView(QWidget *parent = nullptr);
|
||||
void addSeries(const QString &msg_id, const Signal *sig);
|
||||
void removeSeries(const QString &msg_id, const Signal *sig);
|
||||
bool hasSeries(const QString &msg_id, const Signal *sig) const;
|
||||
void addSeries(const MessageId &msg_id, const Signal *sig);
|
||||
bool hasSeries(const MessageId &msg_id, const Signal *sig) const;
|
||||
void updateSeries(const Signal *sig = nullptr, const std::vector<Event*> *events = nullptr, bool clear = true);
|
||||
void updatePlot(double cur, double min, double max);
|
||||
void setSeriesType(QAbstractSeries::SeriesType type);
|
||||
void updatePlotArea(int left);
|
||||
|
||||
struct SigItem {
|
||||
QString msg_id;
|
||||
uint8_t source = 0;
|
||||
uint32_t address = 0;
|
||||
MessageId msg_id;
|
||||
const Signal *sig = nullptr;
|
||||
QXYSeries *series = nullptr;
|
||||
QVector<QPointF> vals;
|
||||
@@ -46,23 +41,22 @@ public:
|
||||
};
|
||||
|
||||
signals:
|
||||
void seriesRemoved(const QString &id, const Signal *sig);
|
||||
void seriesAdded(const QString &id, const Signal *sig);
|
||||
void seriesRemoved(const MessageId &id, const Signal *sig);
|
||||
void seriesAdded(const MessageId &id, const Signal *sig);
|
||||
void zoomIn(double min, double max);
|
||||
void zoomReset();
|
||||
void remove();
|
||||
void axisYLabelWidthChanged(int w);
|
||||
|
||||
private slots:
|
||||
void msgRemoved(uint32_t address);
|
||||
void msgUpdated(uint32_t address);
|
||||
void signalUpdated(const Signal *sig);
|
||||
void signalRemoved(const Signal *sig);
|
||||
void manageSeries();
|
||||
void handleMarkerClicked();
|
||||
void msgRemoved(uint32_t address) { removeIf([=](auto &s) { return s.msg_id.address == address; }); }
|
||||
void signalRemoved(const Signal *sig) { removeIf([=](auto &s) { return s.sig == sig; }); }
|
||||
|
||||
private:
|
||||
QList<ChartView::SigItem>::iterator removeItem(const QList<ChartView::SigItem>::iterator &it);
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *ev) override;
|
||||
@@ -78,6 +72,7 @@ private:
|
||||
qreal niceNumber(qreal x, bool ceiling);
|
||||
QXYSeries *createSeries(QAbstractSeries::SeriesType type, QColor color);
|
||||
void updateSeriesPoints();
|
||||
void removeIf(std::function<bool(const SigItem &)> predicate);
|
||||
|
||||
int y_label_width = 0;
|
||||
int align_to = 0;
|
||||
@@ -102,8 +97,8 @@ class ChartsWidget : public QWidget {
|
||||
|
||||
public:
|
||||
ChartsWidget(QWidget *parent = nullptr);
|
||||
void showChart(const QString &id, const Signal *sig, bool show, bool merge);
|
||||
inline bool hasSignal(const QString &id, const Signal *sig) { return findChart(id, sig) != nullptr; }
|
||||
void showChart(const MessageId &id, const Signal *sig, bool show, bool merge);
|
||||
inline bool hasSignal(const MessageId &id, const Signal *sig) { return findChart(id, sig) != nullptr; }
|
||||
|
||||
public slots:
|
||||
void setColumnCount(int n);
|
||||
@@ -129,7 +124,7 @@ private:
|
||||
void updateLayout();
|
||||
void settingChanged();
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
ChartView *findChart(const QString &id, const Signal *sig);
|
||||
ChartView *findChart(const MessageId &id, const Signal *sig);
|
||||
|
||||
QLabel *title_label;
|
||||
QLabel *range_lb;
|
||||
@@ -139,7 +134,6 @@ private:
|
||||
bool docking = true;
|
||||
QAction *dock_btn;
|
||||
QAction *reset_zoom_action;
|
||||
QToolButton *reset_zoom_btn;
|
||||
QAction *remove_all_btn;
|
||||
QGridLayout *charts_layout;
|
||||
QList<ChartView *> charts;
|
||||
@@ -148,9 +142,7 @@ private:
|
||||
std::pair<double, double> display_range;
|
||||
std::pair<double, double> zoomed_range;
|
||||
bool use_dark_theme = false;
|
||||
QAction *columns_lb_action;
|
||||
QAction *columns_cb_action;
|
||||
QComboBox *columns_cb;
|
||||
QAction *columns_action;
|
||||
int column_count = 1;
|
||||
int current_column_count = 0;
|
||||
};
|
||||
@@ -158,18 +150,18 @@ private:
|
||||
class SeriesSelector : public QDialog {
|
||||
public:
|
||||
struct ListItem : public QListWidgetItem {
|
||||
ListItem(const QString &msg_id, const Signal *sig, QListWidget *parent) : msg_id(msg_id), sig(sig), QListWidgetItem(parent) {}
|
||||
QString msg_id;
|
||||
ListItem(const MessageId &msg_id, const Signal *sig, QListWidget *parent) : msg_id(msg_id), sig(sig), QListWidgetItem(parent) {}
|
||||
MessageId msg_id;
|
||||
const Signal *sig;
|
||||
};
|
||||
|
||||
SeriesSelector(QString title, QWidget *parent);
|
||||
QList<ListItem *> seletedItems();
|
||||
inline void addSelected(const QString &id, const Signal *sig) { addItemToList(selected_list, id, sig, true); }
|
||||
inline void addSelected(const MessageId &id, const Signal *sig) { addItemToList(selected_list, id, sig, true); }
|
||||
|
||||
private:
|
||||
void updateAvailableList(int index);
|
||||
void addItemToList(QListWidget *parent, const QString id, const Signal *sig, bool show_msg_name = false);
|
||||
void addItemToList(QListWidget *parent, const MessageId id, const Signal *sig, bool show_msg_name = false);
|
||||
void add(QListWidgetItem *item);
|
||||
void remove(QListWidgetItem *item);
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
// EditMsgCommand
|
||||
|
||||
EditMsgCommand::EditMsgCommand(const QString &id, const QString &title, int size, QUndoCommand *parent)
|
||||
EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &title, int size, QUndoCommand *parent)
|
||||
: id(id), new_title(title), new_size(size), QUndoCommand(parent) {
|
||||
if (auto msg = dbc()->msg(id)) {
|
||||
old_title = msg->name;
|
||||
old_size = msg->size;
|
||||
}
|
||||
setText(QObject::tr("Edit message %1:%2").arg(DBCManager::parseId(id).second).arg(title));
|
||||
setText(QObject::tr("Edit message %1:%2").arg(id.address).arg(title));
|
||||
}
|
||||
|
||||
void EditMsgCommand::undo() {
|
||||
@@ -26,10 +26,10 @@ void EditMsgCommand::redo() {
|
||||
|
||||
// RemoveMsgCommand
|
||||
|
||||
RemoveMsgCommand::RemoveMsgCommand(const QString &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) {
|
||||
RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) {
|
||||
if (auto msg = dbc()->msg(id)) {
|
||||
message = *msg;
|
||||
setText(QObject::tr("Remove message %1:%2").arg(DBCManager::parseId(id).second).arg(message.name));
|
||||
setText(QObject::tr("Remove message %1:%2").arg(id.address).arg(message.name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,9 +48,9 @@ void RemoveMsgCommand::redo() {
|
||||
|
||||
// AddSigCommand
|
||||
|
||||
AddSigCommand::AddSigCommand(const QString &id, const Signal &sig, QUndoCommand *parent)
|
||||
AddSigCommand::AddSigCommand(const MessageId &id, const Signal &sig, QUndoCommand *parent)
|
||||
: id(id), signal(sig), QUndoCommand(parent) {
|
||||
setText(QObject::tr("Add signal %1 to %2").arg(sig.name.c_str()).arg(DBCManager::parseId(id).second));
|
||||
setText(QObject::tr("Add signal %1 to %2").arg(sig.name.c_str()).arg(id.address));
|
||||
}
|
||||
|
||||
void AddSigCommand::undo() { dbc()->removeSignal(id, signal.name.c_str()); }
|
||||
@@ -58,9 +58,9 @@ void AddSigCommand::redo() { dbc()->addSignal(id, signal); }
|
||||
|
||||
// RemoveSigCommand
|
||||
|
||||
RemoveSigCommand::RemoveSigCommand(const QString &id, const Signal *sig, QUndoCommand *parent)
|
||||
RemoveSigCommand::RemoveSigCommand(const MessageId &id, const Signal *sig, QUndoCommand *parent)
|
||||
: id(id), signal(*sig), QUndoCommand(parent) {
|
||||
setText(QObject::tr("Remove signal %1 from %2").arg(signal.name.c_str()).arg(DBCManager::parseId(id).second));
|
||||
setText(QObject::tr("Remove signal %1 from %2").arg(signal.name.c_str()).arg(id.address));
|
||||
}
|
||||
|
||||
void RemoveSigCommand::undo() { dbc()->addSignal(id, signal); }
|
||||
@@ -68,7 +68,7 @@ void RemoveSigCommand::redo() { dbc()->removeSignal(id, signal.name.c_str()); }
|
||||
|
||||
// EditSignalCommand
|
||||
|
||||
EditSignalCommand::EditSignalCommand(const QString &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent)
|
||||
EditSignalCommand::EditSignalCommand(const MessageId &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent)
|
||||
: id(id), old_signal(*sig), new_signal(new_sig), QUndoCommand(parent) {
|
||||
setText(QObject::tr("Edit signal %1").arg(old_signal.name.c_str()));
|
||||
}
|
||||
|
||||
+10
-10
@@ -7,57 +7,57 @@
|
||||
|
||||
class EditMsgCommand : public QUndoCommand {
|
||||
public:
|
||||
EditMsgCommand(const QString &id, const QString &title, int size, QUndoCommand *parent = nullptr);
|
||||
EditMsgCommand(const MessageId &id, const QString &title, int size, QUndoCommand *parent = nullptr);
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
const QString id;
|
||||
const MessageId id;
|
||||
QString old_title, new_title;
|
||||
int old_size = 0, new_size = 0;
|
||||
};
|
||||
|
||||
class RemoveMsgCommand : public QUndoCommand {
|
||||
public:
|
||||
RemoveMsgCommand(const QString &id, QUndoCommand *parent = nullptr);
|
||||
RemoveMsgCommand(const MessageId &id, QUndoCommand *parent = nullptr);
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
const QString id;
|
||||
const MessageId id;
|
||||
DBCMsg message;
|
||||
};
|
||||
|
||||
class AddSigCommand : public QUndoCommand {
|
||||
public:
|
||||
AddSigCommand(const QString &id, const Signal &sig, QUndoCommand *parent = nullptr);
|
||||
AddSigCommand(const MessageId &id, const Signal &sig, QUndoCommand *parent = nullptr);
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
const QString id;
|
||||
const MessageId id;
|
||||
Signal signal = {};
|
||||
};
|
||||
|
||||
class RemoveSigCommand : public QUndoCommand {
|
||||
public:
|
||||
RemoveSigCommand(const QString &id, const Signal *sig, QUndoCommand *parent = nullptr);
|
||||
RemoveSigCommand(const MessageId &id, const Signal *sig, QUndoCommand *parent = nullptr);
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
const QString id;
|
||||
const MessageId id;
|
||||
Signal signal = {};
|
||||
};
|
||||
|
||||
class EditSignalCommand : public QUndoCommand {
|
||||
public:
|
||||
EditSignalCommand(const QString &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent = nullptr);
|
||||
EditSignalCommand(const MessageId &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent = nullptr);
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
const QString id;
|
||||
const MessageId id;
|
||||
Signal old_signal = {};
|
||||
Signal new_signal = {};
|
||||
};
|
||||
|
||||
+15
-19
@@ -4,6 +4,10 @@
|
||||
#include <sstream>
|
||||
#include <QVector>
|
||||
|
||||
uint qHash(const MessageId &item) {
|
||||
return qHash(item.source) ^ qHash(item.address);
|
||||
}
|
||||
|
||||
DBCManager::DBCManager(QObject *parent) : QObject(parent) {}
|
||||
|
||||
DBCManager::~DBCManager() {}
|
||||
@@ -56,29 +60,27 @@ QString DBCManager::generateDBC() {
|
||||
return dbc_string;
|
||||
}
|
||||
|
||||
void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) {
|
||||
auto [_, address] = parseId(id);
|
||||
auto &m = msgs[address];
|
||||
void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) {
|
||||
auto &m = msgs[id.address];
|
||||
m.name = name;
|
||||
m.size = size;
|
||||
emit msgUpdated(address);
|
||||
emit msgUpdated(id.address);
|
||||
}
|
||||
|
||||
void DBCManager::removeMsg(const QString &id) {
|
||||
uint32_t address = parseId(id).second;
|
||||
msgs.erase(address);
|
||||
emit msgRemoved(address);
|
||||
void DBCManager::removeMsg(const MessageId &id) {
|
||||
msgs.erase(id.address);
|
||||
emit msgRemoved(id.address);
|
||||
}
|
||||
|
||||
void DBCManager::addSignal(const QString &id, const Signal &sig) {
|
||||
if (auto m = const_cast<DBCMsg *>(msg(id))) {
|
||||
void DBCManager::addSignal(const MessageId &id, const Signal &sig) {
|
||||
if (auto m = const_cast<DBCMsg *>(msg(id.address))) {
|
||||
auto &s = m->sigs[sig.name.c_str()];
|
||||
s = sig;
|
||||
emit signalAdded(parseId(id).second, &s);
|
||||
emit signalAdded(id.address, &s);
|
||||
}
|
||||
}
|
||||
|
||||
void DBCManager::updateSignal(const QString &id, const QString &sig_name, const Signal &sig) {
|
||||
void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const Signal &sig) {
|
||||
if (auto m = const_cast<DBCMsg *>(msg(id))) {
|
||||
// change key name
|
||||
QString new_name = QString::fromStdString(sig.name);
|
||||
@@ -91,7 +93,7 @@ void DBCManager::updateSignal(const QString &id, const QString &sig_name, const
|
||||
}
|
||||
}
|
||||
|
||||
void DBCManager::removeSignal(const QString &id, const QString &sig_name) {
|
||||
void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) {
|
||||
if (auto m = const_cast<DBCMsg *>(msg(id))) {
|
||||
auto it = m->sigs.find(sig_name);
|
||||
if (it != m->sigs.end()) {
|
||||
@@ -101,12 +103,6 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) {
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<uint8_t, uint32_t> DBCManager::parseId(const QString &id) {
|
||||
const auto list = id.split(':');
|
||||
if (list.size() != 2) return {0, 0};
|
||||
return {list[0].toInt(), list[1].toUInt(nullptr, 16)};
|
||||
}
|
||||
|
||||
DBCManager *dbc() {
|
||||
static DBCManager dbc_manager(nullptr);
|
||||
return &dbc_manager;
|
||||
|
||||
@@ -5,6 +5,35 @@
|
||||
#include <QString>
|
||||
#include "opendbc/can/common_dbc.h"
|
||||
|
||||
struct MessageId {
|
||||
uint8_t source;
|
||||
uint32_t address;
|
||||
|
||||
QString toString() const {
|
||||
return QString("%1:%2").arg(source).arg(address, 1, 16);
|
||||
}
|
||||
|
||||
bool operator==(const MessageId &other) const {
|
||||
return source == other.source && address == other.address;
|
||||
}
|
||||
|
||||
bool operator!=(const MessageId &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const MessageId &other) const {
|
||||
return std::pair{source, address} < std::pair{other.source, other.address};
|
||||
}
|
||||
|
||||
bool operator>(const MessageId &other) const {
|
||||
return std::pair{source, address} > std::pair{other.source, other.address};
|
||||
}
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(MessageId);
|
||||
|
||||
uint qHash(const MessageId &item);
|
||||
|
||||
struct DBCMsg {
|
||||
QString name;
|
||||
uint32_t size;
|
||||
@@ -24,17 +53,16 @@ public:
|
||||
void open(const QString &dbc_file_name);
|
||||
bool open(const QString &name, const QString &content, QString *error = nullptr);
|
||||
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);
|
||||
void addSignal(const MessageId &id, const Signal &sig);
|
||||
void updateSignal(const MessageId &id, const QString &sig_name, const Signal &sig);
|
||||
void removeSignal(const MessageId &id, const QString &sig_name);
|
||||
|
||||
static std::pair<uint8_t, uint32_t> parseId(const QString &id);
|
||||
inline static std::vector<std::string> allDBCNames() { return get_dbc_names(); }
|
||||
inline QString name() const { return dbc ? dbc->name.c_str() : ""; }
|
||||
void updateMsg(const QString &id, const QString &name, uint32_t size);
|
||||
void removeMsg(const QString &id);
|
||||
void updateMsg(const MessageId &id, const QString &name, uint32_t size);
|
||||
void removeMsg(const MessageId &id);
|
||||
inline const std::map<uint32_t, DBCMsg> &messages() const { return msgs; }
|
||||
inline const DBCMsg *msg(const QString &id) const { return msg(parseId(id).second); }
|
||||
inline const DBCMsg *msg(const MessageId &id) const { return msg(id.address); }
|
||||
inline const DBCMsg *msg(uint32_t address) const {
|
||||
auto it = msgs.find(address);
|
||||
return it != msgs.end() ? &it->second : nullptr;
|
||||
@@ -65,7 +93,7 @@ int bigEndianBitIndex(int index);
|
||||
void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size);
|
||||
std::pair<int, int> getSignalRange(const Signal *s);
|
||||
DBCManager *dbc();
|
||||
inline QString msgName(const QString &id) {
|
||||
inline QString msgName(const MessageId &id) {
|
||||
auto msg = dbc()->msg(id);
|
||||
return msg ? msg->name : UNTITLED;
|
||||
}
|
||||
|
||||
@@ -85,11 +85,15 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &DetailWidget::refresh);
|
||||
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));
|
||||
if (index != -1) {
|
||||
setMessage(tabbar_ids[index]);
|
||||
}
|
||||
});
|
||||
QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab);
|
||||
QObject::connect(tabbar, &QTabBar::tabCloseRequested, [this](int index) {
|
||||
tabbar_ids.removeAt(index);
|
||||
tabbar->removeTab(index);
|
||||
assert(tabbar_ids.size() == tabbar->count());
|
||||
});
|
||||
QObject::connect(charts, &ChartsWidget::seriesChanged, signal_view, &SignalView::updateChartState);
|
||||
}
|
||||
|
||||
@@ -99,38 +103,45 @@ void DetailWidget::showTabBarContextMenu(const QPoint &pt) {
|
||||
QMenu menu(this);
|
||||
menu.addAction(tr("Close Other Tabs"));
|
||||
if (menu.exec(tabbar->mapToGlobal(pt))) {
|
||||
tabbar_ids.move(index, 0);
|
||||
tabbar->moveTab(index, 0);
|
||||
tabbar->setCurrentIndex(0);
|
||||
while (tabbar->count() > 1)
|
||||
while (tabbar->count() > 1) {
|
||||
tabbar_ids.removeAt(1);
|
||||
tabbar->removeTab(1);
|
||||
}
|
||||
assert(tabbar_ids.size() == tabbar->count());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DetailWidget::removeAll() {
|
||||
msg_id = "";
|
||||
msg_id = std::nullopt;
|
||||
tabbar->blockSignals(true);
|
||||
while (tabbar->count() > 0) {
|
||||
tabbar->removeTab(0);
|
||||
}
|
||||
tabbar_ids.clear();
|
||||
tabbar->blockSignals(false);
|
||||
stacked_layout->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void DetailWidget::setMessage(const QString &message_id) {
|
||||
void DetailWidget::setMessage(const MessageId &message_id) {
|
||||
msg_id = message_id;
|
||||
int index = tabbar->count() - 1;
|
||||
for (/**/; index >= 0 && tabbar->tabText(index) != msg_id; --index) { /**/ }
|
||||
int index = tabbar_ids.indexOf(*msg_id);
|
||||
|
||||
if (index == -1) {
|
||||
index = tabbar->addTab(message_id);
|
||||
tabbar_ids.append(*msg_id);
|
||||
index = tabbar->addTab(message_id.toString());
|
||||
tabbar->setTabToolTip(index, msgName(message_id));
|
||||
}
|
||||
assert(tabbar->count() == tabbar_ids.size());
|
||||
|
||||
setUpdatesEnabled(false);
|
||||
|
||||
signal_view->setMessage(msg_id);
|
||||
binary_view->setMessage(msg_id);
|
||||
history_log->setMessage(msg_id);
|
||||
signal_view->setMessage(*msg_id);
|
||||
binary_view->setMessage(*msg_id);
|
||||
history_log->setMessage(*msg_id);
|
||||
|
||||
stacked_layout->setCurrentIndex(1);
|
||||
tabbar->setCurrentIndex(index);
|
||||
@@ -141,12 +152,12 @@ void DetailWidget::setMessage(const QString &message_id) {
|
||||
}
|
||||
|
||||
void DetailWidget::refresh() {
|
||||
if (msg_id.isEmpty()) return;
|
||||
if (!msg_id) return;
|
||||
|
||||
QStringList warnings;
|
||||
const DBCMsg *msg = dbc()->msg(msg_id);
|
||||
const DBCMsg *msg = dbc()->msg(*msg_id);
|
||||
if (msg) {
|
||||
if (msg->size != can->lastMessage(msg_id).dat.size()) {
|
||||
if (msg->size != can->lastMessage(*msg_id).dat.size()) {
|
||||
warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size));
|
||||
}
|
||||
for (auto s : binary_view->getOverlappingSignals()) {
|
||||
@@ -156,7 +167,7 @@ void DetailWidget::refresh() {
|
||||
warnings.push_back(tr("Drag-Select in binary view to create new signal."));
|
||||
}
|
||||
remove_msg_act->setEnabled(msg != nullptr);
|
||||
name_label->setText(msgName(msg_id));
|
||||
name_label->setText(msgName(*msg_id));
|
||||
|
||||
if (!warnings.isEmpty()) {
|
||||
warning_label->setText(warnings.join('\n'));
|
||||
@@ -165,9 +176,9 @@ void DetailWidget::refresh() {
|
||||
warning_widget->setVisible(!warnings.isEmpty());
|
||||
}
|
||||
|
||||
void DetailWidget::updateState(const QHash<QString, CanData> *msgs) {
|
||||
void DetailWidget::updateState(const QHash<MessageId, CanData> *msgs) {
|
||||
time_label->setText(QString::number(can->currentSec(), 'f', 3));
|
||||
if (msg_id.isEmpty() || (msgs && !msgs->contains(msg_id)))
|
||||
if (!msg_id || (msgs && !msgs->contains(*msg_id)))
|
||||
return;
|
||||
|
||||
if (tab_widget->currentIndex() == 0)
|
||||
@@ -177,24 +188,24 @@ void DetailWidget::updateState(const QHash<QString, CanData> *msgs) {
|
||||
}
|
||||
|
||||
void DetailWidget::editMsg() {
|
||||
QString id = msg_id;
|
||||
MessageId id = *msg_id;
|
||||
auto msg = dbc()->msg(id);
|
||||
int size = msg ? msg->size : can->lastMessage(id).dat.size();
|
||||
EditMessageDialog dlg(id, msgName(id), size, this);
|
||||
if (dlg.exec()) {
|
||||
UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text(), dlg.size_spin->value()));
|
||||
UndoStack::push(new EditMsgCommand(*msg_id, dlg.name_edit->text(), dlg.size_spin->value()));
|
||||
}
|
||||
}
|
||||
|
||||
void DetailWidget::removeMsg() {
|
||||
UndoStack::push(new RemoveMsgCommand(msg_id));
|
||||
UndoStack::push(new RemoveMsgCommand(*msg_id));
|
||||
}
|
||||
|
||||
// EditMessageDialog
|
||||
|
||||
EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent)
|
||||
EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent)
|
||||
: original_name(title), QDialog(parent) {
|
||||
setWindowTitle(tr("Edit message: %1").arg(msg_id));
|
||||
setWindowTitle(tr("Edit message: %1").arg(msg_id.toString()));
|
||||
QFormLayout *form_layout = new QFormLayout(this);
|
||||
|
||||
form_layout->addRow("", error_label = new QLabel);
|
||||
@@ -253,11 +264,12 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
|
||||
return hlayout;
|
||||
};
|
||||
|
||||
auto lb = new QLabel(tr("<-Select a message to to view details"));
|
||||
auto lb = new QLabel(tr("<-Select a message to view details"));
|
||||
lb->setAlignment(Qt::AlignHCenter);
|
||||
main_layout->addWidget(lb);
|
||||
main_layout->addLayout(newShortcutRow("Pause", "Space"));
|
||||
main_layout->addLayout(newShortcutRow("Help", "Alt + H"));
|
||||
main_layout->addLayout(newShortcutRow("Help", "F1"));
|
||||
main_layout->addLayout(newShortcutRow("WhatsThis", "Shift+F1"));
|
||||
main_layout->addStretch(0);
|
||||
|
||||
setStyleSheet("QLabel{color:darkGray;}");
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
class EditMessageDialog : public QDialog {
|
||||
public:
|
||||
EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent);
|
||||
EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent);
|
||||
void validateName(const QString &text);
|
||||
|
||||
QString original_name;
|
||||
@@ -34,7 +34,7 @@ class DetailWidget : public QWidget {
|
||||
|
||||
public:
|
||||
DetailWidget(ChartsWidget *charts, QWidget *parent);
|
||||
void setMessage(const QString &message_id);
|
||||
void setMessage(const MessageId &message_id);
|
||||
void refresh();
|
||||
void removeAll();
|
||||
QSize minimumSizeHint() const override { return binary_view->minimumSizeHint(); }
|
||||
@@ -43,13 +43,14 @@ private:
|
||||
void showTabBarContextMenu(const QPoint &pt);
|
||||
void editMsg();
|
||||
void removeMsg();
|
||||
void updateState(const QHash<QString, CanData> * msgs = nullptr);
|
||||
void updateState(const QHash<MessageId, CanData> * msgs = nullptr);
|
||||
|
||||
QString msg_id;
|
||||
std::optional<MessageId> msg_id;
|
||||
QLabel *time_label, *warning_icon, *warning_label;
|
||||
ElidedLabel *name_label;
|
||||
QWidget *warning_widget;
|
||||
QTabBar *tabbar;
|
||||
QList<MessageId> tabbar_ids;
|
||||
QTabWidget *tab_widget;
|
||||
QAction *remove_msg_act;
|
||||
LogsWidget *history_log;
|
||||
|
||||
@@ -17,19 +17,19 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
|
||||
}
|
||||
return show_signals ? QString::number(m.sig_values[index.column() - 1]) : toHex(m.data);
|
||||
} else if (role == Qt::UserRole && index.column() == 1 && !show_signals) {
|
||||
return ChangeTracker::toVariantList(m.colors);
|
||||
return QVariant::fromValue(m.colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void HistoryLogModel::setMessage(const QString &message_id) {
|
||||
void HistoryLogModel::setMessage(const MessageId &message_id) {
|
||||
msg_id = message_id;
|
||||
}
|
||||
|
||||
void HistoryLogModel::refresh() {
|
||||
beginResetModel();
|
||||
sigs.clear();
|
||||
if (auto dbc_msg = dbc()->msg(msg_id)) {
|
||||
if (auto dbc_msg = dbc()->msg(*msg_id)) {
|
||||
sigs = dbc_msg->getSignals();
|
||||
}
|
||||
last_fetch_time = 0;
|
||||
@@ -78,8 +78,8 @@ void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function
|
||||
}
|
||||
|
||||
void HistoryLogModel::updateState() {
|
||||
if (!msg_id.isEmpty()) {
|
||||
uint64_t current_time = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9 + 1;
|
||||
if (msg_id) {
|
||||
uint64_t current_time = (can->lastMessage(*msg_id).ts + can->routeStartTime()) * 1e9 + 1;
|
||||
auto new_msgs = dynamic_mode ? fetchData(current_time, last_fetch_time) : fetchData(0);
|
||||
if (!new_msgs.empty()) {
|
||||
beginInsertRows({}, 0, new_msgs.size() - 1);
|
||||
@@ -106,12 +106,11 @@ void HistoryLogModel::fetchMore(const QModelIndex &parent) {
|
||||
template <class InputIt>
|
||||
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) {
|
||||
std::deque<HistoryLogModel::Message> msgs;
|
||||
const auto [src, address] = DBCManager::parseId(msg_id);
|
||||
QVector<double> values(sigs.size());
|
||||
for (auto it = first; it != last && (*it)->mono_time > min_time; ++it) {
|
||||
if ((*it)->which == cereal::Event::Which::CAN) {
|
||||
for (const auto &c : (*it)->event.getCan()) {
|
||||
if (address == c.getAddress() && src == c.getSrc()) {
|
||||
if (msg_id->address == c.getAddress() && msg_id->source == c.getSrc()) {
|
||||
const auto dat = c.getDat();
|
||||
for (int i = 0; i < sigs.size(); ++i) {
|
||||
values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), *(sigs[i]));
|
||||
@@ -136,7 +135,7 @@ template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::
|
||||
|
||||
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) {
|
||||
auto events = can->events();
|
||||
const auto freq = can->lastMessage(msg_id).freq;
|
||||
const auto freq = can->lastMessage(*msg_id).freq;
|
||||
const bool update_colors = !display_signals_mode || sigs.empty();
|
||||
|
||||
if (dynamic_mode) {
|
||||
@@ -241,13 +240,13 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, model, &HistoryLogModel::segmentsMerged);
|
||||
}
|
||||
|
||||
void LogsWidget::setMessage(const QString &message_id) {
|
||||
void LogsWidget::setMessage(const MessageId &message_id) {
|
||||
model->setMessage(message_id);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void LogsWidget::refresh() {
|
||||
if (model->msg_id.isEmpty()) return;
|
||||
if (!model->msg_id) return;
|
||||
|
||||
model->setFilter(0, "", nullptr);
|
||||
model->refresh();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QHeaderView>
|
||||
@@ -22,7 +24,7 @@ class HistoryLogModel : public QAbstractTableModel {
|
||||
|
||||
public:
|
||||
HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {}
|
||||
void setMessage(const QString &message_id);
|
||||
void setMessage(const MessageId &message_id);
|
||||
void updateState();
|
||||
void setFilter(int sig_idx, const QString &value, std::function<bool(double, double)> cmp);
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
@@ -52,7 +54,7 @@ public:
|
||||
std::deque<HistoryLogModel::Message> fetchData(InputIt first, InputIt last, uint64_t min_time);
|
||||
std::deque<Message> fetchData(uint64_t from_time, uint64_t min_time = 0);
|
||||
|
||||
QString msg_id;
|
||||
std::optional<MessageId> msg_id;
|
||||
ChangeTracker hex_colors;
|
||||
bool has_more_data = true;
|
||||
const int batch_size = 50;
|
||||
@@ -71,7 +73,7 @@ class LogsWidget : public QWidget {
|
||||
|
||||
public:
|
||||
LogsWidget(QWidget *parent);
|
||||
void setMessage(const QString &message_id);
|
||||
void setMessage(const MessageId &message_id);
|
||||
void updateState() {if (dynamic_mode->isChecked()) model->updateState(); }
|
||||
void showEvent(QShowEvent *event) override { if (dynamic_mode->isChecked()) model->refresh(); }
|
||||
|
||||
|
||||
+64
-2
@@ -9,7 +9,9 @@
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QResizeEvent>
|
||||
#include <QShortcut>
|
||||
#include <QTextDocument>
|
||||
#include <QUndoView>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidgetAction>
|
||||
@@ -20,7 +22,7 @@
|
||||
static MainWindow *main_win = nullptr;
|
||||
void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
|
||||
if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl;
|
||||
if (main_win) emit main_win->showMessage(msg, 0);
|
||||
if (main_win) emit main_win->showMessage(msg, 2000);
|
||||
}
|
||||
|
||||
MainWindow::MainWindow() : QMainWindow() {
|
||||
@@ -43,7 +45,7 @@ MainWindow::MainWindow() : QMainWindow() {
|
||||
qRegisterMetaType<ReplyMsgType>("ReplyMsgType");
|
||||
installMessageHandler([this](ReplyMsgType type, const std::string msg) {
|
||||
// use queued connection to recv the log messages from replay.
|
||||
emit showMessage(QString::fromStdString(msg), 3000);
|
||||
emit showMessage(QString::fromStdString(msg), 2000);
|
||||
});
|
||||
installDownloadProgressHandler([this](uint64_t cur, uint64_t total, bool success) {
|
||||
emit updateProgressBar(cur, total, success);
|
||||
@@ -125,6 +127,7 @@ void MainWindow::createActions() {
|
||||
tools_menu->addAction(tr("Find &Similar Bits"), this, &MainWindow::findSimilarBits);
|
||||
|
||||
QMenu *help_menu = menuBar()->addMenu(tr("&Help"));
|
||||
help_menu->addAction(tr("Help"), this, &MainWindow::onlineHelp)->setShortcuts(QKeySequence::HelpContents);
|
||||
help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
|
||||
}
|
||||
|
||||
@@ -173,6 +176,7 @@ void MainWindow::createStatusBar() {
|
||||
progress_bar->setTextVisible(true);
|
||||
progress_bar->setFixedSize({230, 16});
|
||||
progress_bar->setVisible(false);
|
||||
statusBar()->addWidget(new QLabel(tr("For Help,Press F1")));
|
||||
statusBar()->addPermanentWidget(progress_bar);
|
||||
}
|
||||
|
||||
@@ -422,3 +426,61 @@ void MainWindow::findSimilarBits() {
|
||||
QObject::connect(dlg, &FindSimilarBitsDlg::openMessage, messages_widget, &MessagesWidget::selectMessage);
|
||||
dlg->show();
|
||||
}
|
||||
|
||||
void MainWindow::onlineHelp() {
|
||||
if (auto help = findChild<HelpOverlay*>()) {
|
||||
help->close();
|
||||
} else {
|
||||
help = new HelpOverlay(this);
|
||||
help->setGeometry(rect());
|
||||
help->show();
|
||||
help->raise();
|
||||
}
|
||||
}
|
||||
|
||||
// HelpOverlay
|
||||
HelpOverlay::HelpOverlay(MainWindow *parent) : QWidget(parent) {
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
parent->installEventFilter(this);
|
||||
}
|
||||
|
||||
void HelpOverlay::paintEvent(QPaintEvent *event) {
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), QColor(0, 0, 0, 50));
|
||||
MainWindow *parent = (MainWindow *)parentWidget();
|
||||
drawHelpForWidget(painter, parent->findChild<MessagesWidget *>());
|
||||
drawHelpForWidget(painter, parent->findChild<BinaryView *>());
|
||||
drawHelpForWidget(painter, parent->findChild<SignalView *>());
|
||||
drawHelpForWidget(painter, parent->findChild<ChartsWidget *>());
|
||||
drawHelpForWidget(painter, parent->findChild<VideoWidget *>());
|
||||
}
|
||||
|
||||
void HelpOverlay::drawHelpForWidget(QPainter &painter, QWidget *w) {
|
||||
if (w && w->isVisible() && !w->whatsThis().isEmpty()) {
|
||||
QPoint pt = mapFromGlobal(w->mapToGlobal(w->rect().center()));
|
||||
if (rect().contains(pt)) {
|
||||
QTextDocument document;
|
||||
document.setHtml(w->whatsThis());
|
||||
QSize doc_size = document.size().toSize();
|
||||
QPoint topleft = {pt.x() - doc_size.width() / 2, pt.y() - doc_size.height() / 2};
|
||||
painter.translate(topleft);
|
||||
painter.fillRect(QRect{{0, 0}, doc_size}, palette().toolTipBase());
|
||||
document.drawContents(&painter);
|
||||
painter.translate(-topleft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HelpOverlay::eventFilter(QObject *obj, QEvent *event) {
|
||||
if (obj == parentWidget() && event->type() == QEvent::Resize) {
|
||||
QResizeEvent *resize_event = (QResizeEvent *)(event);
|
||||
setGeometry(QRect{QPoint(0, 0), resize_event->size()});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HelpOverlay::mouseReleaseEvent(QMouseEvent *event) {
|
||||
close();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ protected:
|
||||
void setOption();
|
||||
void findSimilarBits();
|
||||
void undoStackCleanChanged(bool clean);
|
||||
void onlineHelp();
|
||||
|
||||
VideoWidget *video_widget = nullptr;
|
||||
QDockWidget *video_dock;
|
||||
@@ -69,4 +70,17 @@ protected:
|
||||
enum { MAX_RECENT_FILES = 15 };
|
||||
QAction *recent_files_acts[MAX_RECENT_FILES] = {};
|
||||
QMenu *open_recent_menu = nullptr;
|
||||
friend class OnlineHelp;
|
||||
};
|
||||
|
||||
class HelpOverlay : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
HelpOverlay(MainWindow *parent);
|
||||
|
||||
protected:
|
||||
void drawHelpForWidget(QPainter &painter, QWidget *w);
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
};
|
||||
|
||||
@@ -46,12 +46,12 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages);
|
||||
QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages);
|
||||
QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages);
|
||||
QObject::connect(model, &MessageListModel::modelReset, [this]() { selectMessage(current_msg_id); });
|
||||
QObject::connect(model, &MessageListModel::modelReset, [this]() { selectMessage(*current_msg_id); });
|
||||
QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) {
|
||||
if (current.isValid() && current.row() < model->msgs.size()) {
|
||||
if (model->msgs[current.row()] != current_msg_id) {
|
||||
if (model->msgs[current.row()] != *current_msg_id) {
|
||||
current_msg_id = model->msgs[current.row()];
|
||||
emit msgSelectionChanged(current_msg_id);
|
||||
emit msgSelectionChanged(*current_msg_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -65,9 +65,18 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
});
|
||||
|
||||
updateSuppressedButtons();
|
||||
|
||||
setWhatsThis(tr(R"(
|
||||
<b>Message View</b><br/>
|
||||
<!-- TODO: add descprition here -->
|
||||
Byte color: <br />
|
||||
<span style="color:gray;">■ </span> constant changing<br />
|
||||
<span style="color:blue;">■ </span> increasing<br />
|
||||
<span style="color:red;">■ </span> decreasing <br />
|
||||
)"));
|
||||
}
|
||||
|
||||
void MessagesWidget::selectMessage(const QString &msg_id) {
|
||||
void MessagesWidget::selectMessage(const MessageId &msg_id) {
|
||||
if (int row = model->msgs.indexOf(msg_id); row != -1) {
|
||||
table_widget->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
@@ -86,7 +95,7 @@ void MessagesWidget::updateSuppressedButtons() {
|
||||
void MessagesWidget::reset() {
|
||||
model->reset();
|
||||
filter->clear();
|
||||
current_msg_id = "";
|
||||
current_msg_id = std::nullopt;
|
||||
updateSuppressedButtons();
|
||||
}
|
||||
|
||||
@@ -106,31 +115,29 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (index.column()) {
|
||||
case 0: return msgName(id);
|
||||
case 1: return id;
|
||||
case 1: return id.toString(); // TODO: put source and address in separate columns
|
||||
case 2: return can_data.freq;
|
||||
case 3: return can_data.count;
|
||||
case 4: return toHex(can_data.dat);
|
||||
}
|
||||
} else if (role == Qt::UserRole && index.column() == 4) {
|
||||
QList<QVariant> colors;
|
||||
colors.reserve(can_data.dat.size());
|
||||
for (int i = 0; i < can_data.dat.size(); i++){
|
||||
if (suppressed_bytes.contains({id, i})) {
|
||||
colors.append(QColor(255, 255, 255, 0));
|
||||
} else {
|
||||
colors.append(i < can_data.colors.size() ? can_data.colors[i] : QColor(255, 255, 255, 0));
|
||||
QVector<QColor> colors = can_data.colors;
|
||||
if (!suppressed_bytes.empty()) {
|
||||
for (int i = 0; i < colors.size(); i++) {
|
||||
if (suppressed_bytes.contains({id, i})) {
|
||||
colors[i] = QColor(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return colors;
|
||||
|
||||
return QVariant::fromValue(colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void MessageListModel::setFilterString(const QString &string) {
|
||||
auto contains = [](const QString &id, const QString &txt) {
|
||||
auto contains = [](const MessageId &id, const QString &txt) {
|
||||
auto cs = Qt::CaseInsensitive;
|
||||
if (id.contains(txt, cs) || msgName(id).contains(txt, cs)) return true;
|
||||
if (id.toString().contains(txt, cs) || msgName(id).contains(txt, cs)) return true;
|
||||
// Search by signal name
|
||||
if (const auto msg = dbc()->msg(id)) {
|
||||
for (auto &signal : msg->getSignals()) {
|
||||
@@ -160,9 +167,7 @@ void MessageListModel::sortMessages() {
|
||||
});
|
||||
} else if (sort_column == 1) {
|
||||
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
|
||||
auto ll = DBCManager::parseId(l);
|
||||
auto rr = DBCManager::parseId(r);
|
||||
return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
|
||||
return sort_order == Qt::AscendingOrder ? l < r : l > r;
|
||||
});
|
||||
} else if (sort_column == 2) {
|
||||
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
|
||||
@@ -180,7 +185,7 @@ void MessageListModel::sortMessages() {
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void MessageListModel::msgsReceived(const QHash<QString, CanData> *new_msgs) {
|
||||
void MessageListModel::msgsReceived(const QHash<MessageId, CanData> *new_msgs) {
|
||||
int prev_row_count = msgs.size();
|
||||
if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) {
|
||||
msgs = can->can_msgs.keys();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QHeaderView>
|
||||
#include <QLineEdit>
|
||||
@@ -20,13 +22,13 @@ public:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return msgs.size(); }
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
void setFilterString(const QString &string);
|
||||
void msgsReceived(const QHash<QString, CanData> *new_msgs = nullptr);
|
||||
void msgsReceived(const QHash<MessageId, CanData> *new_msgs = nullptr);
|
||||
void sortMessages();
|
||||
void suppress();
|
||||
void clearSuppress();
|
||||
void reset();
|
||||
QStringList msgs;
|
||||
QSet<std::pair<QString, int>> suppressed_bytes;
|
||||
QList<MessageId> msgs;
|
||||
QSet<std::pair<MessageId, int>> suppressed_bytes;
|
||||
|
||||
private:
|
||||
QString filter_str;
|
||||
@@ -39,18 +41,18 @@ class MessagesWidget : public QWidget {
|
||||
|
||||
public:
|
||||
MessagesWidget(QWidget *parent);
|
||||
void selectMessage(const QString &message_id);
|
||||
void selectMessage(const MessageId &message_id);
|
||||
QByteArray saveHeaderState() const { return table_widget->horizontalHeader()->saveState(); }
|
||||
bool restoreHeaderState(const QByteArray &state) const { return table_widget->horizontalHeader()->restoreState(state); }
|
||||
void updateSuppressedButtons();
|
||||
void reset();
|
||||
|
||||
signals:
|
||||
void msgSelectionChanged(const QString &message_id);
|
||||
void msgSelectionChanged(const MessageId &message_id);
|
||||
|
||||
protected:
|
||||
QTableView *table_widget;
|
||||
QString current_msg_id;
|
||||
std::optional<MessageId> current_msg_id;
|
||||
QLineEdit *filter;
|
||||
MessageListModel *model;
|
||||
QPushButton *suppress_add;
|
||||
|
||||
@@ -31,7 +31,7 @@ void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const Sign
|
||||
}
|
||||
}
|
||||
|
||||
void SignalModel::setMessage(const QString &id) {
|
||||
void SignalModel::setMessage(const MessageId &id) {
|
||||
msg_id = id;
|
||||
filter_str = "";
|
||||
refresh();
|
||||
@@ -56,7 +56,7 @@ void SignalModel::refresh() {
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void SignalModel::updateState(const QHash<QString, CanData> *msgs) {
|
||||
void SignalModel::updateState(const QHash<MessageId, CanData> *msgs) {
|
||||
if (!msgs || (msgs->contains(msg_id))) {
|
||||
auto &dat = can->lastMessage(msg_id).dat;
|
||||
int row = 0;
|
||||
@@ -230,13 +230,13 @@ void SignalModel::removeSignal(const Signal *sig) {
|
||||
}
|
||||
|
||||
void SignalModel::handleMsgChanged(uint32_t address) {
|
||||
if (address == DBCManager::parseId(msg_id).second) {
|
||||
if (address == msg_id.address) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void SignalModel::handleSignalAdded(uint32_t address, const Signal *sig) {
|
||||
if (address == DBCManager::parseId(msg_id).second) {
|
||||
if (address == msg_id.address) {
|
||||
int i = 0;
|
||||
for (; i < root->children.size(); ++i) {
|
||||
if (sig->start_bit < root->children[i]->sig->start_bit) break;
|
||||
@@ -365,9 +365,14 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
|
||||
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &SignalView::rowsChanged);
|
||||
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged);
|
||||
QObject::connect(dbc(), &DBCManager::signalAdded, [this](uint32_t address, const Signal *sig) { expandSignal(sig); });
|
||||
|
||||
setWhatsThis(tr(R"(
|
||||
<b>Signal view</b><br />
|
||||
<!-- TODO: add descprition here -->
|
||||
)"));
|
||||
}
|
||||
|
||||
void SignalView::setMessage(const QString &id) {
|
||||
void SignalView::setMessage(const MessageId &id) {
|
||||
msg_id = id;
|
||||
filter_edit->clear();
|
||||
model->setMessage(id);
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
void setMessage(const QString &id);
|
||||
void setMessage(const MessageId &id);
|
||||
void setFilter(const QString &txt);
|
||||
void addSignal(int start_bit, int size, bool little_endian);
|
||||
bool saveSignal(const Signal *origin_s, Signal &s);
|
||||
@@ -54,9 +54,9 @@ private:
|
||||
void handleSignalRemoved(const Signal *sig);
|
||||
void handleMsgChanged(uint32_t address);
|
||||
void refresh();
|
||||
void updateState(const QHash<QString, CanData> *msgs);
|
||||
void updateState(const QHash<MessageId, CanData> *msgs);
|
||||
|
||||
QString msg_id;
|
||||
MessageId msg_id;
|
||||
QString filter_str;
|
||||
std::unique_ptr<Item> root;
|
||||
friend class SignalView;
|
||||
@@ -76,7 +76,7 @@ class SignalView : public QWidget {
|
||||
|
||||
public:
|
||||
SignalView(ChartsWidget *charts, QWidget *parent);
|
||||
void setMessage(const QString &id);
|
||||
void setMessage(const MessageId &id);
|
||||
void signalHovered(const Signal *sig);
|
||||
void updateChartState();
|
||||
void expandSignal(const Signal *sig);
|
||||
@@ -85,13 +85,13 @@ public:
|
||||
|
||||
signals:
|
||||
void highlight(const Signal *sig);
|
||||
void showChart(const QString &name, const Signal *sig, bool show, bool merge);
|
||||
void showChart(const MessageId &id, const Signal *sig, bool show, bool merge);
|
||||
|
||||
private:
|
||||
void rowsChanged();
|
||||
void leaveEvent(QEvent *event);
|
||||
|
||||
QString msg_id;
|
||||
MessageId msg_id;
|
||||
QTreeView *tree;
|
||||
QLineEdit *filter_edit;
|
||||
ChartsWidget *charts;
|
||||
|
||||
@@ -4,12 +4,12 @@ AbstractStream *can = nullptr;
|
||||
|
||||
AbstractStream::AbstractStream(QObject *parent, bool is_live_streaming) : is_live_streaming(is_live_streaming), QObject(parent) {
|
||||
can = this;
|
||||
new_msgs = std::make_unique<QHash<QString, CanData>>();
|
||||
new_msgs = std::make_unique<QHash<MessageId, CanData>>();
|
||||
QObject::connect(this, &AbstractStream::received, this, &AbstractStream::process, Qt::QueuedConnection);
|
||||
QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo);
|
||||
}
|
||||
|
||||
void AbstractStream::process(QHash<QString, CanData> *messages) {
|
||||
void AbstractStream::process(QHash<MessageId, CanData> *messages) {
|
||||
for (auto it = messages->begin(); it != messages->end(); ++it) {
|
||||
can_msgs[it.key()] = it.value();
|
||||
}
|
||||
@@ -25,7 +25,7 @@ bool AbstractStream::updateEvent(const Event *event) {
|
||||
if (event->which == cereal::Event::Which::CAN) {
|
||||
double current_sec = event->mono_time / 1e9 - routeStartTime();
|
||||
for (const auto &c : event->event.getCan()) {
|
||||
QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16);
|
||||
MessageId id = {.source = c.getSrc(), .address = c.getAddress()};
|
||||
CanData &data = (*new_msgs)[id];
|
||||
data.ts = current_sec;
|
||||
data.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size());
|
||||
@@ -44,29 +44,30 @@ bool AbstractStream::updateEvent(const Event *event) {
|
||||
prev_update_ts = ts;
|
||||
// use pointer to avoid data copy in queued connection.
|
||||
emit received(new_msgs.release());
|
||||
new_msgs.reset(new QHash<QString, CanData>);
|
||||
new_msgs.reset(new QHash<MessageId, CanData>);
|
||||
new_msgs->reserve(100);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const CanData &AbstractStream::lastMessage(const QString &id) {
|
||||
const CanData &AbstractStream::lastMessage(const MessageId &id) {
|
||||
static CanData empty_data;
|
||||
auto it = can_msgs.find(id);
|
||||
return it != can_msgs.end() ? it.value() : empty_data;
|
||||
}
|
||||
|
||||
void AbstractStream::updateLastMsgsTo(double sec) {
|
||||
QHash<std::pair<uint8_t, uint32_t>, CanData> last_msgs; // Much faster than QHash<String, CanData>
|
||||
QHash<MessageId, CanData> last_msgs;
|
||||
last_msgs.reserve(can_msgs.size());
|
||||
double route_start_time = routeStartTime();
|
||||
uint64_t last_ts = (sec + route_start_time) * 1e9;
|
||||
auto last = std::upper_bound(events()->rbegin(), events()->rend(), last_ts, [](uint64_t ts, auto &e) { return e->mono_time < ts; });
|
||||
for (auto it = last; it != events()->rend(); ++it) {
|
||||
auto evs = events();
|
||||
auto last = std::upper_bound(evs->rbegin(), evs->rend(), last_ts, [](uint64_t ts, auto &e) { return e->mono_time < ts; });
|
||||
for (auto it = last; it != evs->rend(); ++it) {
|
||||
if ((*it)->which == cereal::Event::Which::CAN) {
|
||||
for (const auto &c : (*it)->event.getCan()) {
|
||||
auto &m = last_msgs[{c.getSrc(), c.getAddress()}];
|
||||
auto &m = last_msgs[{.source = c.getSrc(), .address = c.getAddress()}];
|
||||
if (++m.count == 1) {
|
||||
m.ts = ((*it)->mono_time / 1e9) - route_start_time;
|
||||
m.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size());
|
||||
@@ -87,9 +88,8 @@ void AbstractStream::updateLastMsgsTo(double sec) {
|
||||
counters.clear();
|
||||
can_msgs.clear();
|
||||
for (auto it = last_msgs.cbegin(); it != last_msgs.cend(); ++it) {
|
||||
QString msg_id = QString("%1:%2").arg(it.key().first).arg(it.key().second, 1, 16);
|
||||
can_msgs[msg_id] = it.value();
|
||||
counters[msg_id] = it.value().count;
|
||||
can_msgs[it.key()] = it.value();
|
||||
counters[it.key()] = it.value().count;
|
||||
}
|
||||
emit updated();
|
||||
emit msgsReceived(&can_msgs);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QColor>
|
||||
#include <QHash>
|
||||
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
#include "tools/cabana/settings.h"
|
||||
#include "tools/cabana/util.h"
|
||||
#include "tools/replay/replay.h"
|
||||
@@ -33,7 +34,7 @@ public:
|
||||
virtual double routeStartTime() const { return 0; }
|
||||
virtual double currentSec() const = 0;
|
||||
virtual QDateTime currentDateTime() const { return {}; }
|
||||
virtual const CanData &lastMessage(const QString &id);
|
||||
virtual const CanData &lastMessage(const MessageId &id);
|
||||
virtual VisionStreamType visionStreamType() const { return VISION_STREAM_ROAD; }
|
||||
virtual const Route *route() const { return nullptr; }
|
||||
virtual const std::vector<Event *> *events() const = 0;
|
||||
@@ -49,22 +50,22 @@ signals:
|
||||
void streamStarted();
|
||||
void eventsMerged();
|
||||
void updated();
|
||||
void msgsReceived(const QHash<QString, CanData> *);
|
||||
void received(QHash<QString, CanData> *);
|
||||
void msgsReceived(const QHash<MessageId, CanData> *);
|
||||
void received(QHash<MessageId, CanData> *);
|
||||
|
||||
public:
|
||||
QHash<QString, CanData> can_msgs;
|
||||
QHash<MessageId, CanData> can_msgs;
|
||||
|
||||
protected:
|
||||
void process(QHash<QString, CanData> *);
|
||||
void process(QHash<MessageId, CanData> *);
|
||||
bool updateEvent(const Event *event);
|
||||
void updateLastMsgsTo(double sec);
|
||||
|
||||
bool is_live_streaming = false;
|
||||
std::atomic<bool> processing = false;
|
||||
QHash<QString, uint32_t> counters;
|
||||
std::unique_ptr<QHash<QString, CanData>> new_msgs;
|
||||
QHash<QString, ChangeTracker> change_trackers;
|
||||
QHash<MessageId, uint32_t> counters;
|
||||
std::unique_ptr<QHash<MessageId, CanData>> new_msgs;
|
||||
QHash<MessageId, ChangeTracker> change_trackers;
|
||||
};
|
||||
|
||||
// A global pointer referring to the unique AbstractStream object
|
||||
|
||||
@@ -20,10 +20,10 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi
|
||||
bus_combo = new QComboBox(this);
|
||||
QSet<uint8_t> bus_set;
|
||||
for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) {
|
||||
bus_set << DBCManager::parseId(it.key()).first;
|
||||
bus_set << it.key().source;
|
||||
}
|
||||
for (uint8_t bus : bus_set) {
|
||||
bus_combo->addItem(QString::number(bus));
|
||||
bus_combo->addItem(QString::number(bus), bus);
|
||||
}
|
||||
bus_combo->model()->sort(0);
|
||||
bus_combo->setCurrentIndex(0);
|
||||
@@ -69,9 +69,11 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi
|
||||
|
||||
setMinimumSize({700, 500});
|
||||
QObject::connect(search_btn, &QPushButton::clicked, this, &FindSimilarBitsDlg::find);
|
||||
|
||||
QObject::connect(table, &QTableWidget::doubleClicked, [this](const QModelIndex &index) {
|
||||
if (index.isValid()) {
|
||||
emit openMessage(bus_combo->currentText() + ":" + table->item(index.row(), 0)->text());
|
||||
MessageId msg_id = {.source = (uint8_t)bus_combo->currentData().toUInt(), .address = table->item(index.row(), 0)->text().toUInt(0, 16)};
|
||||
emit openMessage(msg_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <QSpinBox>
|
||||
#include <QTableWidget>
|
||||
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
|
||||
class FindSimilarBitsDlg : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -13,7 +15,7 @@ public:
|
||||
FindSimilarBitsDlg(QWidget *parent);
|
||||
|
||||
signals:
|
||||
void openMessage(const QString &msg_id);
|
||||
void openMessage(const MessageId &msg_id);
|
||||
|
||||
private:
|
||||
struct mismatched_struct {
|
||||
|
||||
+6
-19
@@ -66,13 +66,6 @@ void ChangeTracker::clear() {
|
||||
colors.clear();
|
||||
}
|
||||
|
||||
QList<QVariant> ChangeTracker::toVariantList(const QVector<QColor> &colors) {
|
||||
QList<QVariant> ret;
|
||||
ret.reserve(colors.size());
|
||||
for (auto &c : colors) ret.append(c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// MessageBytesDelegate
|
||||
|
||||
MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) {
|
||||
@@ -89,12 +82,8 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
return;
|
||||
}
|
||||
|
||||
if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) {
|
||||
painter->setPen(option.palette.color(QPalette::HighlightedText));
|
||||
} else {
|
||||
painter->setPen(option.palette.color(QPalette::Text));
|
||||
}
|
||||
|
||||
auto color_role = option.state & QStyle::State_Selected ? QPalette::HighlightedText: QPalette::Text;
|
||||
painter->setPen(option.palette.color(color_role));
|
||||
painter->setFont(fixed_font);
|
||||
QRect space = painter->boundingRect(opt.rect, opt.displayAlignment, " ");
|
||||
QRect pos = painter->boundingRect(opt.rect, opt.displayAlignment, "00");
|
||||
@@ -103,15 +92,13 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
int m = space.width() / 2;
|
||||
const QMargins margins(m, m, m, m);
|
||||
|
||||
QList<QVariant> colors = index.data(Qt::UserRole).toList();
|
||||
int i = 0;
|
||||
for (auto &byte : byte_list) {
|
||||
auto colors = index.data(Qt::UserRole).value<QVector<QColor>>();
|
||||
for (int i = 0; i < byte_list.size(); ++i) {
|
||||
if (i < colors.size()) {
|
||||
painter->fillRect(pos.marginsAdded(margins), colors[i].value<QColor>());
|
||||
painter->fillRect(pos.marginsAdded(margins), colors[i]);
|
||||
}
|
||||
painter->drawText(pos, opt.displayAlignment, byte);
|
||||
painter->drawText(pos, opt.displayAlignment, byte_list[i]);
|
||||
pos.moveLeft(pos.right() + space.width());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
class ChangeTracker {
|
||||
public:
|
||||
void compute(const QByteArray &dat, double ts, uint32_t freq);
|
||||
static QList<QVariant> toVariantList(const QVector<QColor> &colors);
|
||||
void clear();
|
||||
|
||||
QVector<double> last_change_t;
|
||||
|
||||
@@ -53,6 +53,13 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
|
||||
QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState);
|
||||
QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState);
|
||||
updatePlayBtnState();
|
||||
|
||||
setWhatsThis(tr(R"(
|
||||
<b>Video</b><br />
|
||||
<!-- TODO: add descprition here -->
|
||||
Shortcuts:<br />
|
||||
Pause/Resume: <span style="background-color:lightGray;color:gray"> space </span></br>
|
||||
)"));
|
||||
}
|
||||
|
||||
QWidget *VideoWidget::createCameraWidget() {
|
||||
|
||||
@@ -211,7 +211,10 @@ def fake_driver_monitoring(exit_event: threading.Event):
|
||||
while not exit_event.is_set():
|
||||
# dmonitoringmodeld output
|
||||
dat = messaging.new_message('driverStateV2')
|
||||
dat.driverStateV2.leftDriverData.faceOrientation = [0., 0., 0.]
|
||||
dat.driverStateV2.leftDriverData.faceProb = 1.0
|
||||
dat.driverStateV2.rightDriverData.faceOrientation = [0., 0., 0.]
|
||||
dat.driverStateV2.rightDriverData.faceProb = 1.0
|
||||
pm.send('driverStateV2', dat)
|
||||
|
||||
# dmonitoringd output
|
||||
|
||||
Reference in New Issue
Block a user