giMerge branch 'master' into oxtmt

pull/27754/head
ZwX1616 2 years ago
commit b8a8006b4a
  1. 3
      SConstruct
  2. 2
      cereal
  3. 2
      opendbc
  4. 2
      panda
  5. 2
      selfdrive/boardd/boardd.cc
  6. 4
      selfdrive/car/ford/values.py
  7. 1
      selfdrive/car/gm/interface.py
  8. 4
      selfdrive/car/honda/carstate.py
  9. 6
      selfdrive/car/hyundai/values.py
  10. 6
      selfdrive/car/isotp_parallel_query.py
  11. 1
      selfdrive/car/tests/routes.py
  12. 12
      selfdrive/car/toyota/carstate.py
  13. 14
      selfdrive/car/toyota/interface.py
  14. 1
      selfdrive/car/toyota/values.py
  15. 2
      selfdrive/test/test_onroad.py
  16. 8
      selfdrive/ui/qt/offroad/settings.cc
  17. 24
      selfdrive/ui/translations/main_de.ts
  18. 24
      selfdrive/ui/translations/main_ja.ts
  19. 24
      selfdrive/ui/translations/main_ko.ts
  20. 24
      selfdrive/ui/translations/main_pt-BR.ts
  21. 24
      selfdrive/ui/translations/main_zh-CHS.ts
  22. 24
      selfdrive/ui/translations/main_zh-CHT.ts
  23. 26
      system/sensord/tests/test_sensord.py
  24. 4
      tools/cabana/.gitignore
  25. 2
      tools/cabana/README.md
  26. 12
      tools/cabana/SConscript
  27. 4
      tools/cabana/cabana
  28. 1
      tools/cabana/cabana.cc
  29. 226
      tools/cabana/chartswidget.cc
  30. 23
      tools/cabana/chartswidget.h
  31. 1
      tools/cabana/detailwidget.cc
  32. 1
      tools/cabana/detailwidget.h
  33. 1
      tools/cabana/mainwin.cc
  34. 9
      tools/cabana/settings.cc
  35. 2
      tools/cabana/settings.h
  36. 77
      tools/cabana/signalview.cc
  37. 4
      tools/cabana/signalview.h
  38. 4
      tools/cabana/tests/test_cabana
  39. 38
      tools/cabana/util.cc
  40. 1
      tools/cabana/util.h

@ -434,9 +434,6 @@ SConscript(['selfdrive/navd/SConscript'])
if arch in ['x86_64', 'Darwin'] or GetOption('extras'): if arch in ['x86_64', 'Darwin'] or GetOption('extras'):
SConscript(['tools/replay/SConscript']) SConscript(['tools/replay/SConscript'])
opendbc = abspath([File('opendbc/can/libdbc.so')])
Export('opendbc')
SConscript(['tools/cabana/SConscript']) SConscript(['tools/cabana/SConscript'])
external_sconscript = GetOption('external_sconscript') external_sconscript = GetOption('external_sconscript')

@ -1 +1 @@
Subproject commit d70d215de6c584f671272d2de2f46a4f778e9f14 Subproject commit 5827c4e17ef0c6bb3de24f987ca2f5e0fb3f464b

@ -1 +1 @@
Subproject commit b7d4a6e2718d9ec3cf436441696528bedb1d44cf Subproject commit 9a1de83e4b0625c38c6bc677a762528b7026aa5e

@ -1 +1 @@
Subproject commit 12b9b65985edf7207a1dfd48de0b714192e6e55d Subproject commit 8efbcf041c1b6da1764bb56f822609fd4bb0758f

@ -363,6 +363,8 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
} }
auto ps = pss[i]; auto ps = pss[i];
ps.setVoltage(health.voltage_pkt);
ps.setCurrent(health.current_pkt);
ps.setUptime(health.uptime_pkt); ps.setUptime(health.uptime_pkt);
ps.setSafetyTxBlocked(health.safety_tx_blocked_pkt); ps.setSafetyTxBlocked(health.safety_tx_blocked_pkt);
ps.setSafetyRxInvalid(health.safety_rx_invalid_pkt); ps.setSafetyRxInvalid(health.safety_rx_invalid_pkt);

@ -84,7 +84,7 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
FordCarInfo("Lincoln Aviator Plug-in Hybrid 2021", "Co-Pilot360 Plus"), FordCarInfo("Lincoln Aviator Plug-in Hybrid 2021", "Co-Pilot360 Plus"),
], ],
CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"), CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"),
CAR.MAVERICK_MK1: FordCarInfo("Ford Maverick 2022", "Co-Pilot360 Assist"), CAR.MAVERICK_MK1: FordCarInfo("Ford Maverick 2022-23", "Co-Pilot360 Assist"),
} }
FW_QUERY_CONFIG = FwQueryConfig( FW_QUERY_CONFIG = FwQueryConfig(
@ -217,6 +217,7 @@ FW_VERSIONS = {
], ],
(Ecu.abs, 0x760, None): [ (Ecu.abs, 0x760, None): [
b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PZ6C-2D053-ED\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -228,6 +229,7 @@ FW_VERSIONS = {
b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PZ6A-14C204-JC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.shiftByWire, 0x732, None): [ (Ecu.shiftByWire, 0x732, None): [
b'NZ6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',

@ -98,7 +98,6 @@ class CarInterface(CarInterfaceBase):
# Tuning for experimental long # Tuning for experimental long
ret.longitudinalTuning.kpV = [2.0, 1.5] ret.longitudinalTuning.kpV = [2.0, 1.5]
ret.longitudinalTuning.kiV = [0.72] ret.longitudinalTuning.kiV = [0.72]
ret.stopAccel = -2.0
ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling
ret.vEgoStopping = 0.25 ret.vEgoStopping = 0.25
ret.vEgoStarting = 0.25 ret.vEgoStarting = 0.25

@ -293,7 +293,7 @@ class CarState(CarStateBase):
if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS: if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS:
self.lkas_hud = cp_cam.vl["LKAS_HUD"] self.lkas_hud = cp_cam.vl["LKAS_HUD"]
if self.CP.enableBsm and self.CP.carFingerprint in (CAR.CRV_5G, ): if self.CP.enableBsm:
# BSM messages are on B-CAN, requires a panda forwarding B-CAN messages to CAN 0 # BSM messages are on B-CAN, requires a panda forwarding B-CAN messages to CAN 0
# more info here: https://github.com/commaai/openpilot/pull/1867 # more info here: https://github.com/commaai/openpilot/pull/1867
ret.leftBlindspot = cp_body.vl["BSM_STATUS_LEFT"]["BSM_ALERT"] == 1 ret.leftBlindspot = cp_body.vl["BSM_STATUS_LEFT"]["BSM_ALERT"] == 1
@ -340,7 +340,7 @@ class CarState(CarStateBase):
@staticmethod @staticmethod
def get_body_can_parser(CP): def get_body_can_parser(CP):
if CP.enableBsm and CP.carFingerprint == CAR.CRV_5G: if CP.enableBsm:
signals = [("BSM_ALERT", "BSM_STATUS_RIGHT"), signals = [("BSM_ALERT", "BSM_STATUS_RIGHT"),
("BSM_ALERT", "BSM_STATUS_LEFT")] ("BSM_ALERT", "BSM_STATUS_LEFT")]

@ -1485,25 +1485,29 @@ FW_VERSIONS = {
b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930',
b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819',
b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426',
b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.08 99210-AA000 220728',
], ],
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\000CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', b'\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ',
b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ',
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103', b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103',
b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103',
b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102',
b'\xf1\x00CN7 MDPS C 1.00 1.04 56310BY050\x00 4CNHC104',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa',
b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000', b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000',
b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa',
b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00',
b'\xf1\x006U3L0_C2\x00\x006U3K9051\x00\x00HCN0G16NS1\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x816H6G8051\x00\x00\x00\x00\x00\x00\x00\x00',
] ]
}, },
CAR.KONA_HEV: { CAR.KONA_HEV: {

@ -105,13 +105,14 @@ class IsoTpParallelQuery:
for tx_addr, msg in msgs.items(): for tx_addr, msg in msgs.items():
try: try:
dat, updated = msg.recv() dat, rx_in_progress = msg.recv()
except Exception: except Exception:
cloudlog.exception(f"Error processing UDS response: {tx_addr}") cloudlog.exception(f"Error processing UDS response: {tx_addr}")
request_done[tx_addr] = True request_done[tx_addr] = True
continue continue
if updated: # Extend timeout for each consecutive ISO-TP frame to avoid timing out on long responses
if rx_in_progress:
response_timeouts[tx_addr] = time.monotonic() + timeout response_timeouts[tx_addr] = time.monotonic() + timeout
if not dat: if not dat:
@ -123,6 +124,7 @@ class IsoTpParallelQuery:
if response_valid: if response_valid:
if counter + 1 < len(self.request): if counter + 1 < len(self.request):
response_timeouts[tx_addr] = time.monotonic() + timeout
msg.send(self.request[counter + 1]) msg.send(self.request[counter + 1])
request_counter[tx_addr] += 1 request_counter[tx_addr] += 1
else: else:

@ -185,6 +185,7 @@ routes = [
CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.MIRAI), CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.MIRAI),
CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.CHR), CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.CHR),
CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2), CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2),
CarTestRoute("ea8fbe72b96a185c|2023-02-22--09-20-34", TOYOTA.CHR_TSS2), # openpilot longitudinal, with smartDSU
CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHRH), CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHRH),
CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHRH_TSS2), CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHRH_TSS2),
CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V), CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V),

@ -115,7 +115,8 @@ class CarState(CarStateBase):
cp_acc = cp_cam if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) else cp cp_acc = cp_cam if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) else cp
if self.CP.carFingerprint in (TSS2_CAR | RADAR_ACC_CAR): if self.CP.carFingerprint in (TSS2_CAR | RADAR_ACC_CAR):
self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"] if not (self.CP.flags & ToyotaFlags.SMART_DSU.value):
self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"]
ret.stockFcw = bool(cp_acc.vl["ACC_HUD"]["FCW"]) ret.stockFcw = bool(cp_acc.vl["ACC_HUD"]["FCW"])
# some TSS2 cars have low speed lockout permanently set, so ignore on those cars # some TSS2 cars have low speed lockout permanently set, so ignore on those cars
@ -235,12 +236,17 @@ class CarState(CarStateBase):
checks.append(("BSM", 1)) checks.append(("BSM", 1))
if CP.carFingerprint in RADAR_ACC_CAR: if CP.carFingerprint in RADAR_ACC_CAR:
if not CP.flags & ToyotaFlags.SMART_DSU.value:
signals += [
("ACC_TYPE", "ACC_CONTROL"),
]
checks += [
("ACC_CONTROL", 33),
]
signals += [ signals += [
("ACC_TYPE", "ACC_CONTROL"),
("FCW", "ACC_HUD"), ("FCW", "ACC_HUD"),
] ]
checks += [ checks += [
("ACC_CONTROL", 33),
("ACC_HUD", 1), ("ACC_HUD", 1),
] ]

@ -201,14 +201,18 @@ class CarInterface(CarInterfaceBase):
tire_stiffness_factor=tire_stiffness_factor) tire_stiffness_factor=tire_stiffness_factor)
ret.enableBsm = 0x3F6 in fingerprint[0] and candidate in TSS2_CAR ret.enableBsm = 0x3F6 in fingerprint[0] and candidate in TSS2_CAR
# Detect smartDSU, which intercepts ACC_CMD from the DSU allowing openpilot to send it
smartDsu = 0x2FF in fingerprint[0] # Detect smartDSU, which intercepts ACC_CMD from the DSU (or radar) allowing openpilot to send it
# In TSS2 cars the camera does long control if 0x2FF in fingerprint[0]:
ret.flags |= ToyotaFlags.SMART_DSU.value
# In TSS2 cars, the camera does long control
found_ecus = [fw.ecu for fw in car_fw] found_ecus = [fw.ecu for fw in car_fw]
ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) and not smartDsu ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) and not (ret.flags & ToyotaFlags.SMART_DSU)
ret.enableGasInterceptor = 0x201 in fingerprint[0] ret.enableGasInterceptor = 0x201 in fingerprint[0]
# if the smartDSU is detected, openpilot can send ACC_CMD (and the smartDSU will block it from the DSU) or not (the DSU is "connected") # if the smartDSU is detected, openpilot can send ACC_CMD (and the smartDSU will block it from the DSU) or not (the DSU is "connected")
ret.openpilotLongitudinalControl = smartDsu or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR) ret.openpilotLongitudinalControl = bool(ret.flags & ToyotaFlags.SMART_DSU) or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR)
ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR
if not ret.openpilotLongitudinalControl: if not ret.openpilotLongitudinalControl:

@ -33,6 +33,7 @@ class CarControllerParams:
class ToyotaFlags(IntFlag): class ToyotaFlags(IntFlag):
HYBRID = 1 HYBRID = 1
SMART_DSU = 2
class CAR: class CAR:

@ -25,7 +25,7 @@ PROCS = {
"./loggerd": 10.0, "./loggerd": 10.0,
"./encoderd": 17.0, "./encoderd": 17.0,
"./camerad": 14.5, "./camerad": 14.5,
"./locationd": 9.1, "./locationd": 11.0,
"selfdrive.controls.plannerd": 16.5, "selfdrive.controls.plannerd": 16.5,
"./_ui": 19.2, "./_ui": 19.2,
"selfdrive.locationd.paramsd": 9.0, "selfdrive.locationd.paramsd": 9.0,

@ -43,10 +43,10 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
}, },
{ {
"ExperimentalLongitudinalEnabled", "ExperimentalLongitudinalEnabled",
tr("Experimental openpilot Longitudinal Control"), tr("openpilot Longitudinal Control (Alpha)"),
QString("<b>%1</b><br>%2") QString("<b>%1</b><br><br>%2")
.arg(tr("WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).")) .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
.arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.")), .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")),
"../assets/offroad/icon_speed_limit.png", "../assets/offroad/icon_speed_limit.png",
}, },
{ {

@ -992,22 +992,10 @@ This may take up to a minute.</source>
<source>Experimental Mode</source> <source>Experimental Mode</source>
<translation>Experimenteller Modus</translation> <translation>Experimenteller Modus</translation>
</message> </message>
<message>
<source>Experimental openpilot Longitudinal Control</source>
<translation>Experimenteller Openpilot Tempomat</translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation>WARNUNG: Der Openpilot Tempomat ist für dieses Auto experimentell und deaktiviert den Notbremsassistenten.</translation>
</message>
<message> <message>
<source>Disengage on Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation>Bei Gasbetätigung ausschalten</translation> <translation>Bei Gasbetätigung ausschalten</translation>
</message> </message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
<translation>Bei diesem auto wird standardmäßig der im Auto eingebaute adaptive Tempomat anstelle des Openpilot Tempomats benutzt. Aktiviere diesen Schalter, um zum Openpilot Tempomaten zu wechseln. Es ist empfohlen den Experimentellen Modus bei Nutzung des Openpilot Tempomats zu aktivieren.</translation>
</message>
<message> <message>
<source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source> <source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source>
<translation>Openpilot fährt standardmäßig im &lt;b&gt;entspannten Modus&lt;/b&gt;. Der Experimentelle Modus aktiviert&lt;b&gt;Alpha-level Funktionen&lt;/b&gt;, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden:</translation> <translation>Openpilot fährt standardmäßig im &lt;b&gt;entspannten Modus&lt;/b&gt;. Der Experimentelle Modus aktiviert&lt;b&gt;Alpha-level Funktionen&lt;/b&gt;, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden:</translation>
@ -1044,6 +1032,18 @@ This may take up to a minute.</source>
<source>Enable experimental longitudinal control to allow Experimental mode.</source> <source>Enable experimental longitudinal control to allow Experimental mode.</source>
<translation>Aktiviere den experimentellen Openpilot Tempomaten für experimentelle Funktionen.</translation> <translation>Aktiviere den experimentellen Openpilot Tempomaten für experimentelle Funktionen.</translation>
</message> </message>
<message>
<source>openpilot Longitudinal Control (Alpha)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -962,10 +962,6 @@ This may take up to a minute.</source>
<source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source> <source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Experimental openpilot Longitudinal Control</source>
<translation>openpilotによるアクセル制御</translation>
</message>
<message> <message>
<source>Disengage on Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation> openpilot </translation> <translation> openpilot </translation>
@ -994,14 +990,6 @@ This may take up to a minute.</source>
<source>Experimental Mode</source> <source>Experimental Mode</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation>警告: この車種でのopenpilotによるアクセル制御は実験段階であり(AEB)</translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
<translation>openpilotはこの車の場合ACCを標準で利用しますopenpilotによるアクセル制御を利用できます</translation>
</message>
<message> <message>
<source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source> <source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source>
<translation>openpilotは標準ではゆっくりとくつろげる運転を提供します</translation> <translation>openpilotは標準ではゆっくりとくつろげる運転を提供します</translation>
@ -1038,6 +1026,18 @@ This may take up to a minute.</source>
<source>Enable experimental longitudinal control to allow Experimental mode.</source> <source>Enable experimental longitudinal control to allow Experimental mode.</source>
<translation>openpilotによるアクセル制御を有効にしてください</translation> <translation>openpilotによるアクセル制御を有効にしてください</translation>
</message> </message>
<message>
<source>openpilot Longitudinal Control (Alpha)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -963,10 +963,6 @@ This may take up to a minute.</source>
<source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source> <source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source>
<translation> .</translation> <translation> .</translation>
</message> </message>
<message>
<source>Experimental openpilot Longitudinal Control</source>
<translation>openpilot ()</translation>
</message>
<message> <message>
<source>Disengage on Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation> </translation> <translation> </translation>
@ -995,14 +991,6 @@ This may take up to a minute.</source>
<source>Experimental Mode</source> <source>Experimental Mode</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation>경고: openpilot (AEB) .</translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
<translation> openpilot ACC로 . openpilot . openpilot .</translation>
</message>
<message> <message>
<source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source> <source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source>
<translation>openpilot은 &lt;b&gt; &lt;/b&gt; . &lt;b&gt; &lt;/b&gt; . </translation> <translation>openpilot은 &lt;b&gt; &lt;/b&gt; . &lt;b&gt; &lt;/b&gt; . </translation>
@ -1039,6 +1027,18 @@ This may take up to a minute.</source>
<source>Enable experimental longitudinal control to allow Experimental mode.</source> <source>Enable experimental longitudinal control to allow Experimental mode.</source>
<translation> .</translation> <translation> .</translation>
</message> </message>
<message>
<source>openpilot Longitudinal Control (Alpha)</source>
<translation>openpilot ()</translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation>경고: openpilot (AEB) .</translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.</source>
<translation> openpilot ACC로 . openpilot . openpilot .</translation>
</message>
</context> </context>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -967,10 +967,6 @@ Isso pode levar até um minuto.</translation>
<source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source> <source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source>
<translation>Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor.</translation> <translation>Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor.</translation>
</message> </message>
<message>
<source>Experimental openpilot Longitudinal Control</source>
<translation>Controle longitudinal experimental openpilot</translation>
</message>
<message> <message>
<source>Disengage on Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation>Desacionar com Pedal do Acelerador</translation> <translation>Desacionar com Pedal do Acelerador</translation>
@ -999,14 +995,6 @@ Isso pode levar até um minuto.</translation>
<source>Experimental Mode</source> <source>Experimental Mode</source>
<translation>Modo Experimental</translation> <translation>Modo Experimental</translation>
</message> </message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation>ATENÇÃO: o controle longitudinal do openpilot é experimental para este carro e desativará a Frenagem Automática de Emergência (AEB).</translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
<translation>Neste carro o penpilot por padrão utiliza o ACC nativo do veículo ao invés de controlar longitudinalmente. Ative isto para mudar para o controle longitudinal do openpilot. Ativar o Modo Experimental é recomendado quando em uso do controle longitudinal experimental do openpilot.</translation>
</message>
<message> <message>
<source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source> <source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source>
<translation>openpilot por padrão funciona em &lt;b&gt;modo chill&lt;/b&gt;. modo Experimental ativa &lt;b&gt;recursos de nível-alfa&lt;/b&gt; que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo:</translation> <translation>openpilot por padrão funciona em &lt;b&gt;modo chill&lt;/b&gt;. modo Experimental ativa &lt;b&gt;recursos de nível-alfa&lt;/b&gt; que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo:</translation>
@ -1043,6 +1031,18 @@ Isso pode levar até um minuto.</translation>
<source>Enable experimental longitudinal control to allow Experimental mode.</source> <source>Enable experimental longitudinal control to allow Experimental mode.</source>
<translation>Ative o controle longitudinal experimental para permitir o modo Experimental.</translation> <translation>Ative o controle longitudinal experimental para permitir o modo Experimental.</translation>
</message> </message>
<message>
<source>openpilot Longitudinal Control (Alpha)</source>
<translation>Controle Longitudinal openpilot (Alpha)</translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation>AVISO: o controle longitudinal openpilot está em alfa para este carro e desativará a Frenagem Automática de Emergência (AEB).</translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.</source>
<translation>Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o alfa de controle longitudinal openpilot.</translation>
</message>
</context> </context>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -960,10 +960,6 @@ This may take up to a minute.</source>
<source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source> <source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Experimental openpilot Longitudinal Control</source>
<translation>openpilot纵向控制</translation>
</message>
<message> <message>
<source>Disengage on Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation></translation> <translation></translation>
@ -992,14 +988,6 @@ This may take up to a minute.</source>
<source>Experimental Mode</source> <source>Experimental Mode</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation> openpilot纵向控制是试验性功能AEB自动刹车功能</translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
<translation>openpilot默认使用车辆自带的ACCopenpilot的纵向控制openpilot纵向控制使openpilot纵向控制时</translation>
</message>
<message> <message>
<source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source> <source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source>
<translation>openpilot &lt;b&gt;&lt;/b&gt; &lt;b&gt;&lt;/b&gt;</translation> <translation>openpilot &lt;b&gt;&lt;/b&gt; &lt;b&gt;&lt;/b&gt;</translation>
@ -1036,6 +1024,18 @@ This may take up to a minute.</source>
<source>Enable experimental longitudinal control to allow Experimental mode.</source> <source>Enable experimental longitudinal control to allow Experimental mode.</source>
<translation>便使</translation> <translation>便使</translation>
</message> </message>
<message>
<source>openpilot Longitudinal Control (Alpha)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -962,10 +962,6 @@ This may take up to a minute.</source>
<source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source> <source>Upload data from the driver facing camera and help improve the driver monitoring algorithm.</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Experimental openpilot Longitudinal Control</source>
<translation>使 openpilot </translation>
</message>
<message> <message>
<source>Disengage on Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation></translation> <translation></translation>
@ -994,14 +990,6 @@ This may take up to a minute.</source>
<source>Experimental Mode</source> <source>Experimental Mode</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation>openpilot (AEB) </translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
<translation>openpilot預設將使用原車內建的ACC系統openpilot縱向控制openpilot縱向控制使</translation>
</message>
<message> <message>
<source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source> <source>openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below:</source>
<translation>openpilot &lt;b&gt;&lt;/b&gt; &lt;b&gt;alpha &lt;/b&gt;</translation> <translation>openpilot &lt;b&gt;&lt;/b&gt; &lt;b&gt;alpha &lt;/b&gt;</translation>
@ -1038,6 +1026,18 @@ This may take up to a minute.</source>
<source>Enable experimental longitudinal control to allow Experimental mode.</source> <source>Enable experimental longitudinal control to allow Experimental mode.</source>
<translation>使</translation> <translation>使</translation>
</message> </message>
<message>
<source>openpilot Longitudinal Control (Alpha)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import glob
import time import time
import unittest import unittest
import numpy as np import numpy as np
@ -7,7 +8,7 @@ from collections import namedtuple, defaultdict
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal import log from cereal import log
from system.hardware import TICI, HARDWARE from system.hardware import TICI
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
BMX = { BMX = {
@ -70,7 +71,6 @@ ALL_SENSORS = {
} }
} }
LSM_IRQ = 336
def get_irq_count(irq: int): def get_irq_count(irq: int):
with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f: with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f:
@ -101,9 +101,6 @@ class TestSensord(unittest.TestCase):
if not TICI: if not TICI:
raise unittest.SkipTest raise unittest.SkipTest
# make sure gpiochip0 is readable
HARDWARE.initialize_hardware()
# enable LSM self test # enable LSM self test
os.environ["LSM_SELF_TEST"] = "1" os.environ["LSM_SELF_TEST"] = "1"
@ -114,6 +111,15 @@ class TestSensord(unittest.TestCase):
time.sleep(3) time.sleep(3)
cls.sample_secs = 10 cls.sample_secs = 10
cls.events = read_sensor_events(cls.sample_secs) cls.events = read_sensor_events(cls.sample_secs)
# determine sensord's irq
cls.sensord_irq = None
for fn in glob.glob('/sys/kernel/irq/*/actions'):
with open(fn) as f:
if "sensord" in f.read():
cls.sensord_irq = int(fn.split('/')[-2])
break
assert cls.sensord_irq is not None
finally: finally:
# teardown won't run if this doesn't succeed # teardown won't run if this doesn't succeed
managed_processes["sensord"].stop() managed_processes["sensord"].stop()
@ -121,8 +127,6 @@ class TestSensord(unittest.TestCase):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
managed_processes["sensord"].stop() managed_processes["sensord"].stop()
if "LSM_SELF_TEST" in os.environ:
del os.environ['LSM_SELF_TEST']
def tearDown(self): def tearDown(self):
managed_processes["sensord"].stop() managed_processes["sensord"].stop()
@ -250,9 +254,9 @@ class TestSensord(unittest.TestCase):
time.sleep(3) time.sleep(3)
# read /proc/interrupts to verify interrupts are received # read /proc/interrupts to verify interrupts are received
state_one = get_irq_count(LSM_IRQ) state_one = get_irq_count(self.sensord_irq)
time.sleep(1) time.sleep(1)
state_two = get_irq_count(LSM_IRQ) state_two = get_irq_count(self.sensord_irq)
error_msg = f"no interrupts received after sensord start!\n{state_one} {state_two}" error_msg = f"no interrupts received after sensord start!\n{state_one} {state_two}"
assert state_one != state_two, error_msg assert state_one != state_two, error_msg
@ -261,9 +265,9 @@ class TestSensord(unittest.TestCase):
time.sleep(1) time.sleep(1)
# read /proc/interrupts to verify no more interrupts are received # read /proc/interrupts to verify no more interrupts are received
state_one = get_irq_count(LSM_IRQ) state_one = get_irq_count(self.sensord_irq)
time.sleep(1) time.sleep(1)
state_two = get_irq_count(LSM_IRQ) state_two = get_irq_count(self.sensord_irq)
assert state_one == state_two, "Interrupts received after sensord stop!" assert state_one == state_two, "Interrupts received after sensord stop!"

@ -1,7 +1,7 @@
moc_* moc_*
*.moc *.moc
_cabana cabana
settings settings
dbc/car_fingerprint_to_dbc.json dbc/car_fingerprint_to_dbc.json
tests/_test_cabana tests/test_cabana

@ -8,7 +8,7 @@ Cabana is a tool developed to view raw CAN data. One use for this is creating an
```bash ```bash
$ ./cabana -h $ ./cabana -h
Usage: ./_cabana [options] route Usage: ./cabana [options] route
Options: Options:
-h, --help Displays this help. -h, --help Displays this help.

@ -1,6 +1,6 @@
import os import os
Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib',
'cereal', 'transformations', 'widgets', 'opendbc') 'cereal', 'transformations', 'widgets')
base_frameworks = qt_env['FRAMEWORKS'] base_frameworks = qt_env['FRAMEWORKS']
base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq',
@ -15,8 +15,9 @@ else:
qt_libs = ['qt_util'] + base_libs qt_libs = ['qt_util'] + base_libs
cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs
cabana_env = qt_env.Clone() cabana_env = qt_env.Clone()
cabana_env["LIBPATH"] += ['../../opendbc/can']
cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, 'libdbc_static', 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs
opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc").abspath) opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc").abspath)
cabana_env['CXXFLAGS'] += [opendbc_path] cabana_env['CXXFLAGS'] += [opendbc_path]
@ -31,13 +32,10 @@ cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc',
'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) 'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
if arch == "Darwin":
cabana_env.Execute('install_name_tool -change opendbc/can/libdbc.dylib @loader_path/../../opendbc/can/libdbc.dylib ./_cabana')
if GetOption('test'): if GetOption('test'):
cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) cabana_env.Program('tests/test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs])
def generate_dbc_json(target, source, env): def generate_dbc_json(target, source, env):
env.Execute('tools/cabana/dbc/generate_dbc_json.py --out tools/cabana/dbc/car_fingerprint_to_dbc.json') env.Execute('tools/cabana/dbc/generate_dbc_json.py --out tools/cabana/dbc/car_fingerprint_to_dbc.json')

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

@ -15,6 +15,7 @@ int main(int argc, char *argv[]) {
QApplication app(argc, argv); QApplication app(argc, argv);
app.setApplicationDisplayName("Cabana"); app.setApplicationDisplayName("Cabana");
app.setWindowIcon(QIcon(":cabana-icon.png")); app.setWindowIcon(QIcon(":cabana-icon.png"));
utils::setTheme(settings.theme);
QCommandLineParser cmd_parser; QCommandLineParser cmd_parser;
cmd_parser.addHelpOption(); cmd_parser.addHelpOption();

@ -12,6 +12,7 @@
#include <QOpenGLWidget> #include <QOpenGLWidget>
#include <QPushButton> #include <QPushButton>
#include <QRubberBand> #include <QRubberBand>
#include <QStylePainter>
#include <QToolBar> #include <QToolBar>
#include <QToolTip> #include <QToolTip>
#include <QtConcurrent> #include <QtConcurrent>
@ -70,13 +71,13 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), QFrame(parent)
charts_layout = new QGridLayout(); charts_layout = new QGridLayout();
charts_layout->setSpacing(10); charts_layout->setSpacing(10);
QWidget *charts_container = new QWidget(this); charts_container = new QWidget(this);
QVBoxLayout *charts_main_layout = new QVBoxLayout(charts_container); QVBoxLayout *charts_main_layout = new QVBoxLayout(charts_container);
charts_main_layout->setContentsMargins(0, 0, 0, 0); charts_main_layout->setContentsMargins(0, 0, 0, 0);
charts_main_layout->addLayout(charts_layout); charts_main_layout->addLayout(charts_layout);
charts_main_layout->addStretch(0); charts_main_layout->addStretch(0);
QScrollArea *charts_scroll = new QScrollArea(this); charts_scroll = new QScrollArea(this);
charts_scroll->setFrameStyle(QFrame::NoFrame); charts_scroll->setFrameStyle(QFrame::NoFrame);
charts_scroll->setWidgetResizable(true); charts_scroll->setWidgetResizable(true);
charts_scroll->setWidget(charts_container); charts_scroll->setWidget(charts_container);
@ -84,8 +85,6 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), QFrame(parent)
main_layout->addWidget(charts_scroll); main_layout->addWidget(charts_scroll);
// init settings // init settings
use_dark_theme = QApplication::palette().color(QPalette::WindowText).value() >
QApplication::palette().color(QPalette::Background).value();
column_count = std::clamp(settings.chart_column_count, 1, MAX_COLUMN_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); max_chart_range = std::clamp(settings.chart_range, 1, settings.max_cached_minutes * 60);
display_range = {0, max_chart_range}; display_range = {0, max_chart_range};
@ -157,6 +156,17 @@ void ChartsWidget::zoomUndo() {
} }
} }
void ChartsWidget::showValueTip(double sec) {
const QRect visible_rect(-charts_container->pos(), charts_scroll->viewport()->size());
for (auto c : charts) {
if (sec >= 0 && visible_rect.contains(QRect(c->mapTo(charts_container, QPoint(0, 0)), c->size()))) {
c->showTip(sec);
} else {
c->hideTip();
}
}
}
void ChartsWidget::updateState() { void ChartsWidget::updateState() {
if (charts.isEmpty()) return; if (charts.isEmpty()) return;
@ -219,13 +229,14 @@ ChartView *ChartsWidget::createChart() {
chart->setFixedHeight(settings.chart_height); chart->setFixedHeight(settings.chart_height);
chart->setMinimumWidth(CHART_MIN_WIDTH); chart->setMinimumWidth(CHART_MIN_WIDTH);
chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight); chart->chart()->setTheme(settings.theme == 2 ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight);
QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
QObject::connect(chart, &ChartView::zoomUndo, this, &ChartsWidget::zoomUndo); QObject::connect(chart, &ChartView::zoomUndo, this, &ChartsWidget::zoomUndo);
QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged); QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged);
QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged); QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged);
QObject::connect(chart, &ChartView::axisYLabelWidthChanged, &align_timer, qOverload<>(&QTimer::start)); QObject::connect(chart, &ChartView::axisYLabelWidthChanged, &align_timer, qOverload<>(&QTimer::start));
QObject::connect(chart, &ChartView::hovered, this, &ChartsWidget::showValueTip);
charts.push_back(chart); charts.push_back(chart);
updateLayout(); updateLayout();
return chart; return chart;
@ -329,12 +340,26 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
bool ChartsWidget::event(QEvent *event) { bool ChartsWidget::event(QEvent *event) {
bool back_button = false; bool back_button = false;
if (event->type() == QEvent::MouseButtonPress) { switch (event->type()) {
QMouseEvent *ev = static_cast<QMouseEvent *>(event); case QEvent::MouseButtonPress: {
back_button = ev->button() == Qt::BackButton; QMouseEvent *ev = static_cast<QMouseEvent *>(event);
} else if (event->type() == QEvent::NativeGesture) { // MacOS emulates a back swipe on pressing the mouse back button back_button = ev->button() == Qt::BackButton;
QNativeGestureEvent *ev = static_cast<QNativeGestureEvent *>(event); break;
back_button = (ev->value() == 180); }
case QEvent::NativeGesture: {
QNativeGestureEvent *ev = static_cast<QNativeGestureEvent *>(event);
back_button = (ev->value() == 180);
break;
}
case QEvent::WindowActivate:
case QEvent::WindowDeactivate:
case QEvent::FocusIn:
case QEvent::FocusOut:
showValueTip(-1);
break;
default:
break;
} }
if (back_button) { if (back_button) {
@ -346,7 +371,7 @@ bool ChartsWidget::event(QEvent *event) {
// ChartView // ChartView
ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { ChartView::ChartView(QWidget *parent) : tip_label(this), QChartView(nullptr, parent) {
series_type = (SeriesType)settings.chart_series_type; series_type = (SeriesType)settings.chart_series_type;
QChart *chart = new QChart(); QChart *chart = new QChart();
chart->setBackgroundVisible(false); chart->setBackgroundVisible(false);
@ -358,19 +383,18 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
chart->legend()->setShowToolTips(true); chart->legend()->setShowToolTips(true);
chart->setMargins({0, 0, 0, 0}); chart->setMargins({0, 0, 0, 0});
background = new QGraphicsRectItem(chart);
background->setBrush(QApplication::palette().color(QPalette::Base));
background->setPen(Qt::NoPen);
background->setZValue(chart->zValue() - 1);
setChart(chart); setChart(chart);
createToolButtons(); createToolButtons();
setRenderHint(QPainter::Antialiasing);
// TODO: enable zoomIn/seekTo in live streaming mode. // TODO: enable zoomIn/seekTo in live streaming mode.
setRubberBand(can->liveStreaming() ? QChartView::NoRubberBand : QChartView::HorizontalRubberBand); setRubberBand(can->liveStreaming() ? QChartView::NoRubberBand : QChartView::HorizontalRubberBand);
setMouseTracking(true); setMouseTracking(true);
QObject::connect(axis_x, &QValueAxis::rangeChanged, [this]() { resetChartCache(); });
QObject::connect(axis_y, &QValueAxis::rangeChanged, [this]() { resetChartCache(); });
QObject::connect(axis_y, &QAbstractAxis::titleTextChanged, [this]() { resetChartCache(); });
QObject::connect(chart, &QChart::plotAreaChanged, [this]() { resetChartCache(); });
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved); QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated); QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated);
QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved); QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved);
@ -447,6 +471,7 @@ void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) {
emit remove(); emit remove();
} else if (sigs.size() != prev_size) { } else if (sigs.size() != prev_size) {
updateAxisY(); updateAxisY();
resetChartCache();
} }
} }
@ -488,15 +513,14 @@ void ChartView::resizeEvent(QResizeEvent *event) {
manage_btn_proxy->setPos(x, top); manage_btn_proxy->setPos(x, top);
chart()->legend()->setGeometry({move_icon->sceneBoundingRect().topRight(), manage_btn_proxy->sceneBoundingRect().bottomLeft()}); chart()->legend()->setGeometry({move_icon->sceneBoundingRect().topRight(), manage_btn_proxy->sceneBoundingRect().bottomLeft()});
if (align_to > 0) { if (align_to > 0) {
updatePlotArea(align_to); updatePlotArea(align_to, true);
} }
QChartView::resizeEvent(event); QChartView::resizeEvent(event);
} }
void ChartView::updatePlotArea(int left_pos) { void ChartView::updatePlotArea(int left_pos, bool force) {
if (align_to != left_pos || rect() != background->rect()) { if (align_to != left_pos || force) {
align_to = left_pos; align_to = left_pos;
background->setRect(rect());
qreal left, top, right, bottom; qreal left, top, right, bottom;
chart()->layout()->getContentsMargins(&left, &top, &right, &bottom); chart()->layout()->getContentsMargins(&left, &top, &right, &bottom);
@ -505,9 +529,6 @@ void ChartView::updatePlotArea(int left_pos) {
int adjust_top = chart()->legend()->geometry().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin); int adjust_top = chart()->legend()->geometry().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin);
chart()->setPlotArea(rect().adjusted(align_to + left, adjust_top + top, -x_label_size.width() / 2 - right, -x_label_size.height() - bottom)); chart()->setPlotArea(rect().adjusted(align_to + left, adjust_top + top, -x_label_size.width() / 2 - right, -x_label_size.height() - bottom));
chart()->layout()->invalidate(); chart()->layout()->invalidate();
if (can->isPaused()) {
update();
}
} }
} }
@ -519,6 +540,7 @@ void ChartView::updateTitle() {
auto decoration = s.series->isVisible() ? "none" : "line-through"; 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, msgName(s.msg_id), s.msg_id.toString())); s.series->setName(QString("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").arg(decoration, s.sig->name, msgName(s.msg_id), s.msg_id.toString()));
} }
resetChartCache();
} }
void ChartView::updatePlot(double cur, double min, double max) { void ChartView::updatePlot(double cur, double min, double max) {
@ -528,7 +550,7 @@ void ChartView::updatePlot(double cur, double min, double max) {
updateAxisY(); updateAxisY();
updateSeriesPoints(); updateSeriesPoints();
} }
scene()->invalidate({}, QGraphicsScene::ForegroundLayer); viewport()->update();
} }
void ChartView::updateSeriesPoints() { void ChartView::updateSeriesPoints() {
@ -542,7 +564,11 @@ void ChartView::updateSeriesPoints() {
double pixels_per_point = (chart()->mapToPosition(right_pt).x() - chart()->mapToPosition(*begin).x()) / num_points; double pixels_per_point = (chart()->mapToPosition(right_pt).x() - chart()->mapToPosition(*begin).x()) / num_points;
if (series_type == SeriesType::Scatter) { if (series_type == SeriesType::Scatter) {
((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 2.0, 2.0, 8.0) * devicePixelRatioF()); qreal size = std::clamp(pixels_per_point / 2.0, 2.0, 8.0);
if (s.series->useOpenGL()) {
size *= devicePixelRatioF();
}
((QScatterSeries *)s.series)->setMarkerSize(size);
} else { } else {
s.series->setPointsVisible(pixels_per_point > 20); s.series->setPointsVisible(pixels_per_point > 20);
} }
@ -586,6 +612,7 @@ void ChartView::updateSeries(const cabana::Signal *sig) {
} }
} }
updateAxisY(); updateAxisY();
resetChartCache();
} }
// auto zoom on yaxis // auto zoom on yaxis
@ -606,16 +633,20 @@ void ChartView::updateAxisY() {
auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan);
auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan); auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan);
s.min = std::numeric_limits<double>::max();
s.max = std::numeric_limits<double>::lowest();
if (can->liveStreaming()) { if (can->liveStreaming()) {
for (auto it = first; it != last; ++it) { for (auto it = first; it != last; ++it) {
if (it->y() < min) min = it->y(); if (it->y() < s.min) s.min = it->y();
if (it->y() > max) max = it->y(); if (it->y() > s.max) s.max = it->y();
} }
} else { } else {
auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.begin(), first), std::distance(s.vals.begin(), last)); auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.begin(), first), std::distance(s.vals.begin(), last));
min = std::min(min, min_y); s.min = min_y;
max = std::max(max, max_y); s.max = max_y;
} }
min = std::min(min, s.min);
max = std::max(max, s.max);
} }
if (min == std::numeric_limits<double>::max()) min = 0; if (min == std::numeric_limits<double>::max()) min = 0;
if (max == std::numeric_limits<double>::lowest()) max = 0; if (max == std::numeric_limits<double>::lowest()) max = 0;
@ -668,8 +699,9 @@ qreal ChartView::niceNumber(qreal x, bool ceiling) {
} }
void ChartView::leaveEvent(QEvent *event) { void ChartView::leaveEvent(QEvent *event) {
clearTrackPoints(); if (tip_label.isVisible()) {
scene()->update(); emit hovered(-1);
}
QChartView::leaveEvent(event); QChartView::leaveEvent(event);
} }
@ -716,7 +748,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
} else if (rubber->width() > 10) { } else if (rubber->width() > 10) {
emit zoomIn(min_rounded, max_rounded); emit zoomIn(min_rounded, max_rounded);
} else { } else {
scene()->invalidate({}, QGraphicsScene::ForegroundLayer); viewport()->update();
} }
event->accept(); event->accept();
} else if (!can->liveStreaming() && event->button() == Qt::RightButton) { } else if (!can->liveStreaming() && event->button() == Qt::RightButton) {
@ -741,42 +773,17 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
if (plot_area.contains(ev->pos())) { if (plot_area.contains(ev->pos())) {
can->seekTo(std::clamp(chart()->mapToValue(ev->pos()).x(), 0., can->totalSeconds())); can->seekTo(std::clamp(chart()->mapToValue(ev->pos()).x(), 0., can->totalSeconds()));
} }
return;
} }
auto rubber = findChild<QRubberBand *>(); auto rubber = findChild<QRubberBand *>();
bool is_zooming = rubber && rubber->isVisible(); bool is_zooming = rubber && rubber->isVisible();
is_scrubbing = false;
clearTrackPoints(); clearTrackPoints();
if (!is_zooming && plot_area.contains(ev->pos())) { if (!is_zooming && plot_area.contains(ev->pos())) {
QStringList text_list;
const double sec = chart()->mapToValue(ev->pos()).x(); const double sec = chart()->mapToValue(ev->pos()).x();
qreal x = -1; emit hovered(sec);
for (auto &s : sigs) { } else if (tip_label.isVisible()) {
if (!s.series->isVisible()) continue; emit hovered(-1);
// use reverse iterator to find last item <= sec.
double value = 0;
auto it = std::lower_bound(s.vals.rbegin(), s.vals.rend(), sec, [](auto &p, double x) { return p.x() > x; });
if (it != s.vals.rend() && it->x() >= axis_x->min()) {
value = it->y();
s.track_pt = chart()->mapToPosition(*it);
x = std::max(x, s.track_pt.x());
}
text_list.push_back(QString("<span style=\"color:%1;\">■ </span>%2: <b>%3</b>")
.arg(s.series->color().name(), s.sig->name,
s.track_pt.isNull() ? "--" : QString::number(value)));
}
if (x < 0) {
x = ev->pos().x();
}
text_list.push_front(QString::number(chart()->mapToValue({x, 0}).x(), 'f', 3));
QPointF tooltip_pt(x + 12, plot_area.top() - 20);
QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), text_list.join("<br />"), this, plot_area.toRect());
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
} else {
QToolTip::hideText();
} }
QChartView::mouseMoveEvent(ev); QChartView::mouseMoveEvent(ev);
@ -787,10 +794,39 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
if (rubber_rect != rubber->geometry()) { if (rubber_rect != rubber->geometry()) {
rubber->setGeometry(rubber_rect); rubber->setGeometry(rubber_rect);
} }
scene()->invalidate({}, QGraphicsScene::ForegroundLayer); viewport()->update();
} }
} }
void ChartView::showTip(double sec) {
qreal x = chart()->mapToPosition({sec, 0}).x();
QStringList text_list(QString::number(chart()->mapToValue({x, 0}).x(), 'f', 3));
for (auto &s : sigs) {
if (s.series->isVisible()) {
QString value = "--";
// use reverse iterator to find last item <= sec.
auto it = std::lower_bound(s.vals.rbegin(), s.vals.rend(), sec, [](auto &p, double x) { return p.x() > x; });
if (it != s.vals.rend() && it->x() >= axis_x->min()) {
value = QString::number(it->y());
s.track_pt = chart()->mapToPosition(*it);
x = std::max(x, s.track_pt.x());
}
text_list << QString("<span style=\"color:%1;\">■ </span>%2: <b>%3</b> (%4 - %5)")
.arg(s.series->color().name(), s.sig->name, value, QString::number(s.min), QString::number(s.max));
}
}
QPointF tooltip_pt(x, chart()->plotArea().top());
int plot_right = mapToGlobal(chart()->plotArea().topRight().toPoint()).x();
tip_label.showText(mapToGlobal(tooltip_pt.toPoint()), "<p style='white-space:pre'>" + text_list.join("<br />") + "</p>", plot_right);
viewport()->update();
}
void ChartView::hideTip() {
clearTrackPoints();
tip_label.hide();
viewport()->update();
}
void ChartView::dragMoveEvent(QDragMoveEvent *event) { void ChartView::dragMoveEvent(QDragMoveEvent *event) {
if (event->mimeData()->hasFormat(mime_type)) { if (event->mimeData()->hasFormat(mime_type)) {
event->setDropAction(event->source() == this ? Qt::MoveAction : Qt::CopyAction); event->setDropAction(event->source() == this ? Qt::MoveAction : Qt::CopyAction);
@ -818,6 +854,34 @@ void ChartView::dropEvent(QDropEvent *event) {
} }
} }
void ChartView::resetChartCache() {
chart_pixmap = QPixmap();
viewport()->update();
}
void ChartView::paintEvent(QPaintEvent *event) {
if (!can->liveStreaming()) {
if (chart_pixmap.isNull()) {
const qreal dpr = viewport()->devicePixelRatioF();
chart_pixmap = QPixmap(viewport()->size() * dpr);
chart_pixmap.setDevicePixelRatio(dpr);
chart_pixmap.fill(palette().color(QPalette::Base));
QPainter p(&chart_pixmap);
p.setRenderHints(QPainter::Antialiasing);
scene()->setSceneRect(viewport()->rect());
scene()->render(&p);
}
QPainter painter(viewport());
painter.setRenderHints(QPainter::Antialiasing);
painter.drawPixmap(QPoint(), chart_pixmap);
QRectF exposed_rect = mapToScene(event->region().boundingRect()).boundingRect();
drawForeground(&painter, exposed_rect);
} else {
QChartView::paintEvent(event);
}
}
void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
// draw time line // draw time line
qreal x = chart()->mapToPosition(QPointF{cur_sec, 0}).x(); qreal x = chart()->mapToPosition(QPointF{cur_sec, 0}).x();
@ -1033,3 +1097,39 @@ QList<SeriesSelector::ListItem *> SeriesSelector::seletedItems() {
for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i)); for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i));
return ret; return ret;
} }
// ValueTipLabel
ValueTipLabel::ValueTipLabel(QWidget *parent) : QLabel(parent, Qt::ToolTip | Qt::FramelessWindowHint) {
setForegroundRole(QPalette::ToolTipText);
setBackgroundRole(QPalette::ToolTipBase);
setPalette(QToolTip::palette());
ensurePolished();
setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr, this));
setAttribute(Qt::WA_ShowWithoutActivating);
setTextFormat(Qt::RichText);
setVisible(false);
}
void ValueTipLabel::showText(const QPoint &pt, const QString &text, int right_edge) {
setText(text);
if (!text.isEmpty()) {
QSize extra(1, 1);
resize(sizeHint() + extra);
QPoint tip_pos(pt.x() + 12, pt.y());
if (tip_pos.x() + size().width() >= right_edge) {
tip_pos.rx() = pt.x() - size().width() - 12;
}
move(tip_pos);
}
setVisible(!text.isEmpty());
}
void ValueTipLabel::paintEvent(QPaintEvent *ev) {
QStylePainter p(this);
QStyleOptionFrame opt;
opt.init(this);
p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
p.end();
QLabel::paintEvent(ev);
}

@ -25,6 +25,13 @@ enum class SeriesType {
Scatter Scatter
}; };
class ValueTipLabel : public QLabel {
public:
ValueTipLabel(QWidget *parent = nullptr);
void showText(const QPoint &pt, const QString &sec, int right_edge);
void paintEvent(QPaintEvent *ev) override;
};
class ChartView : public QChartView { class ChartView : public QChartView {
Q_OBJECT Q_OBJECT
@ -35,7 +42,9 @@ public:
void updateSeries(const cabana::Signal *sig = nullptr); void updateSeries(const cabana::Signal *sig = nullptr);
void updatePlot(double cur, double min, double max); void updatePlot(double cur, double min, double max);
void setSeriesType(SeriesType type); void setSeriesType(SeriesType type);
void updatePlotArea(int left); void updatePlotArea(int left, bool force = false);
void showTip(double sec);
void hideTip();
struct SigItem { struct SigItem {
MessageId msg_id; MessageId msg_id;
@ -46,6 +55,8 @@ public:
uint64_t last_value_mono_time = 0; uint64_t last_value_mono_time = 0;
QPointF track_pt{}; QPointF track_pt{};
SegmentTree segment_tree; SegmentTree segment_tree;
double min = 0;
double max = 0;
}; };
signals: signals:
@ -55,6 +66,7 @@ signals:
void zoomUndo(); void zoomUndo();
void remove(); void remove();
void axisYLabelWidthChanged(int w); void axisYLabelWidthChanged(int w);
void hovered(double sec);
private slots: private slots:
void signalUpdated(const cabana::Signal *sig); void signalUpdated(const cabana::Signal *sig);
@ -76,6 +88,8 @@ private:
QSize sizeHint() const override { return {CHART_MIN_WIDTH, settings.chart_height}; } QSize sizeHint() const override { return {CHART_MIN_WIDTH, settings.chart_height}; }
void updateAxisY(); void updateAxisY();
void updateTitle(); void updateTitle();
void resetChartCache();
void paintEvent(QPaintEvent *event) override;
void drawForeground(QPainter *painter, const QRectF &rect) override; void drawForeground(QPainter *painter, const QRectF &rect) override;
std::tuple<double, double, int> getNiceAxisNumbers(qreal min, qreal max, int tick_count); std::tuple<double, double, int> getNiceAxisNumbers(qreal min, qreal max, int tick_count);
qreal niceNumber(qreal x, bool ceiling); qreal niceNumber(qreal x, bool ceiling);
@ -91,13 +105,14 @@ private:
QGraphicsPixmapItem *move_icon; QGraphicsPixmapItem *move_icon;
QGraphicsProxyWidget *close_btn_proxy; QGraphicsProxyWidget *close_btn_proxy;
QGraphicsProxyWidget *manage_btn_proxy; QGraphicsProxyWidget *manage_btn_proxy;
QGraphicsRectItem *background; ValueTipLabel tip_label;
QList<SigItem> sigs; QList<SigItem> sigs;
double cur_sec = 0; double cur_sec = 0;
const QString mime_type = "application/x-cabanachartview"; const QString mime_type = "application/x-cabanachartview";
SeriesType series_type = SeriesType::Line; SeriesType series_type = SeriesType::Line;
bool is_scrubbing = false; bool is_scrubbing = false;
bool resume_after_scrub = false; bool resume_after_scrub = false;
QPixmap chart_pixmap;
friend class ChartsWidget; friend class ChartsWidget;
}; };
@ -135,6 +150,7 @@ private:
void setMaxChartRange(int value); void setMaxChartRange(int value);
void updateLayout(); void updateLayout();
void settingChanged(); void settingChanged();
void showValueTip(double sec);
bool eventFilter(QObject *obj, QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override;
ChartView *findChart(const MessageId &id, const cabana::Signal *sig); ChartView *findChart(const MessageId &id, const cabana::Signal *sig);
@ -150,12 +166,13 @@ private:
QAction *remove_all_btn; QAction *remove_all_btn;
QGridLayout *charts_layout; QGridLayout *charts_layout;
QList<ChartView *> charts; QList<ChartView *> charts;
QWidget *charts_container;
QScrollArea *charts_scroll;
uint32_t max_chart_range = 0; uint32_t max_chart_range = 0;
bool is_zoomed = false; bool is_zoomed = false;
std::pair<double, double> display_range; std::pair<double, double> display_range;
std::pair<double, double> zoomed_range; std::pair<double, double> zoomed_range;
QStack<QPair<double, double>> zoom_stack; QStack<QPair<double, double>> zoom_stack;
bool use_dark_theme = false;
QAction *columns_action; QAction *columns_action;
int column_count = 1; int column_count = 1;
int current_column_count = 0; int current_column_count = 0;

@ -48,7 +48,6 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
// msg widget // msg widget
splitter = new QSplitter(Qt::Vertical, this); splitter = new QSplitter(Qt::Vertical, this);
splitter->setAutoFillBackground(true);
splitter->addWidget(binary_view = new BinaryView(this)); splitter->addWidget(binary_view = new BinaryView(this));
splitter->addWidget(signal_view = new SignalView(charts, this)); splitter->addWidget(signal_view = new SignalView(charts, this));
binary_view->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); binary_view->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);

@ -30,7 +30,6 @@ public:
DetailWidget(ChartsWidget *charts, QWidget *parent); DetailWidget(ChartsWidget *charts, QWidget *parent);
void setMessage(const MessageId &message_id); void setMessage(const MessageId &message_id);
void refresh(); void refresh();
QSize minimumSizeHint() const override { return binary_view->minimumSizeHint(); }
private: private:
void showTabBarContextMenu(const QPoint &pt); void showTabBarContextMenu(const QPoint &pt);

@ -541,6 +541,7 @@ void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool succe
void MainWindow::updateStatus() { void MainWindow::updateStatus() {
status_label->setText(tr("Cached Minutes:%1 FPS:%2").arg(settings.max_cached_minutes).arg(settings.fps)); status_label->setText(tr("Cached Minutes:%1 FPS:%2").arg(settings.max_cached_minutes).arg(settings.fps));
utils::setTheme(settings.theme);
} }
void MainWindow::dockCharts(bool dock) { void MainWindow::dockCharts(bool dock) {

@ -27,6 +27,7 @@ void Settings::save() {
s.setValue("recent_files", recent_files); s.setValue("recent_files", recent_files);
s.setValue("message_header_state", message_header_state); s.setValue("message_header_state", message_header_state);
s.setValue("chart_series_type", chart_series_type); s.setValue("chart_series_type", chart_series_type);
s.setValue("theme", theme);
s.setValue("sparkline_range", sparkline_range); s.setValue("sparkline_range", sparkline_range);
} }
@ -45,6 +46,7 @@ void Settings::load() {
recent_files = s.value("recent_files").toStringList(); recent_files = s.value("recent_files").toStringList();
message_header_state = s.value("message_header_state").toByteArray(); message_header_state = s.value("message_header_state").toByteArray();
chart_series_type = s.value("chart_series_type", 0).toInt(); chart_series_type = s.value("chart_series_type", 0).toInt();
theme = s.value("theme", 0).toInt();
sparkline_range = s.value("sparkline_range", 15).toInt(); sparkline_range = s.value("sparkline_range", 15).toInt();
} }
@ -54,6 +56,12 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Settings")); setWindowTitle(tr("Settings"));
QFormLayout *form_layout = new QFormLayout(this); QFormLayout *form_layout = new QFormLayout(this);
theme = new QComboBox(this);
theme->setToolTip(tr("You may need to restart cabana after changes theme"));
theme->addItems({tr("Automatic"), tr("Light"), tr("Dark")});
theme->setCurrentIndex(settings.theme);
form_layout->addRow(tr("Color Theme"), theme);
fps = new QSpinBox(this); fps = new QSpinBox(this);
fps->setRange(10, 100); fps->setRange(10, 100);
fps->setSingleStep(10); fps->setSingleStep(10);
@ -87,6 +95,7 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
void SettingsDlg::save() { void SettingsDlg::save() {
settings.fps = fps->value(); settings.fps = fps->value();
settings.theme = theme->currentIndex();
settings.max_cached_minutes = cached_minutes->value(); settings.max_cached_minutes = cached_minutes->value();
settings.chart_series_type = chart_series_type->currentIndex(); settings.chart_series_type = chart_series_type->currentIndex();
settings.chart_height = chart_height->value(); settings.chart_height = chart_height->value();

@ -19,6 +19,7 @@ public:
int chart_column_count = 1; int chart_column_count = 1;
int chart_range = 3 * 60; // 3 minutes int chart_range = 3 * 60; // 3 minutes
int chart_series_type = 0; int chart_series_type = 0;
int theme = 0;
int sparkline_range = 15; // 15 seconds int sparkline_range = 15; // 15 seconds
QString last_dir; QString last_dir;
QString last_route_dir; QString last_route_dir;
@ -42,6 +43,7 @@ public:
QSpinBox *cached_minutes; QSpinBox *cached_minutes;
QSpinBox *chart_height; QSpinBox *chart_height;
QComboBox *chart_series_type; QComboBox *chart_series_type;
QComboBox *theme;
}; };
extern Settings settings; extern Settings settings;

@ -168,6 +168,8 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const {
if (item->type == Item::Signed) return item->sig->is_signed ? Qt::Checked : Qt::Unchecked; if (item->type == Item::Signed) return item->sig->is_signed ? Qt::Checked : Qt::Unchecked;
} else if (role == Qt::DecorationRole && index.column() == 0 && item->type == Item::ExtraInfo) { } else if (role == Qt::DecorationRole && index.column() == 0 && item->type == Item::ExtraInfo) {
return utils::icon(item->parent->extra_expanded ? "chevron-compact-down" : "chevron-compact-up"); return utils::icon(item->parent->extra_expanded ? "chevron-compact-down" : "chevron-compact-up");
} else if (role == Qt::ToolTipRole && item->type == Item::Sig) {
return (index.column() == 0) ? item->sig->name : item->sig_val;
} }
} }
return {}; return {};
@ -323,11 +325,36 @@ QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo
int spacing = option.widget->style()->pixelMetric(QStyle::PM_TreeViewIndentation) + color_label_width + 8; int spacing = option.widget->style()->pixelMetric(QStyle::PM_TreeViewIndentation) + color_label_width + 8;
it = width_cache.insert(text, option.fontMetrics.width(text) + spacing); it = width_cache.insert(text, option.fontMetrics.width(text) + spacing);
} }
width = std::min(width, it.value()); width = std::min<int>(option.widget->size().width() / 3.0, it.value());
} }
return {width, QApplication::fontMetrics().height()}; return {width, QApplication::fontMetrics().height()};
} }
bool SignalItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) {
if (event && event->type() == QEvent::ToolTip && index.isValid()) {
auto item = (SignalModel::Item *)index.internalPointer();
if (item->type == SignalModel::Item::Sig && index.column() == 1) {
QRect rc = option.rect.adjusted(0, 0, -option.rect.width() * 0.4, 0);
if (rc.contains(event->pos())) {
event->setAccepted(false);
return false;
}
}
}
return QStyledItemDelegate::helpEvent(event, view, option, index);
}
void SignalItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto item = (SignalModel::Item *)index.internalPointer();
if (editor && item->type == SignalModel::Item::Sig && index.column() == 1) {
QRect geom = option.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &option);
geom.setLeft(geom.right() - editor->sizeHint().width());
editor->setGeometry(geom);
return;
}
QStyledItemDelegate::updateEditorGeometry(editor, option, index);
}
void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin); int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
@ -359,33 +386,41 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->drawText(text_rect, option.displayAlignment, text); painter->drawText(text_rect, option.displayAlignment, text);
painter->restore(); painter->restore();
} else if (index.column() == 1 && item && item->type == SignalModel::Item::Sig) { } else if (index.column() == 1 && item && item->type == SignalModel::Item::Sig) {
painter->save();
if (option.state & QStyle::State_Selected) { if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight()); painter->fillRect(option.rect, option.palette.highlight());
} }
drawSparkline(painter, option, index); int adjust_right = ((SignalView *)parent())->tree->indexWidget(index)->sizeHint().width() + 2 * h_margin;
QRect r = option.rect.adjusted(h_margin, v_margin, -adjust_right, -v_margin);
// draw signal value // draw signal value
int right_offset = ((SignalView *)parent())->tree->indexWidget(index)->sizeHint().width() + 2 * h_margin; QRect value_rect = r.adjusted(r.width() * 0.6 + h_margin, 0, 0, 0);
QRect rc = option.rect.adjusted(0, 0, -right_offset, 0); auto text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, value_rect.width());
auto text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, rc.width());
painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text));
painter->drawText(rc, Qt::AlignRight | Qt::AlignVCenter, text); painter->drawText(value_rect, Qt::AlignRight | Qt::AlignVCenter, text);
QRect sparkline_rect = r.adjusted(0, 0, -r.width() * 0.4 - h_margin, 0);
drawSparkline(painter, sparkline_rect, index);
painter->restore();
} else { } else {
QStyledItemDelegate::paint(painter, option, index); QStyledItemDelegate::paint(painter, option, index);
} }
} }
void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { void SignalItemDelegate::drawSparkline(QPainter *painter, const QRect &rect, const QModelIndex &index) const {
static std::vector<QPointF> points; static std::vector<QPointF> points;
const auto &msg_id = ((SignalView *)parent())->msg_id; const auto &msg_id = ((SignalView *)parent())->msg_id;
const auto &msgs = can->events().at(msg_id); const auto &msgs = can->events().at(msg_id);
uint64_t ts = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9; uint64_t ts = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9;
auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max<int64_t>(ts - settings.sparkline_range * 1e9, 0)}); auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max<int64_t>(ts - settings.sparkline_range * 1e9, 0)});
auto last = std::upper_bound(first, msgs.cend(), CanEvent{.mono_time = ts}); auto last = std::upper_bound(first, msgs.cend(), CanEvent{.mono_time = ts});
if (first != last) { if (first != last) {
double min = std::numeric_limits<double>::max(); double min = std::numeric_limits<double>::max();
double max = std::numeric_limits<double>::lowest(); double max = std::numeric_limits<double>::lowest();
const auto sig = ((SignalModel::Item *)index.internalPointer())->sig; const auto item = (const SignalModel::Item *)index.internalPointer();
const auto sig = item->sig;
points.clear(); points.clear();
for (auto it = first; it != last; ++it) { for (auto it = first; it != last; ++it) {
double value = get_raw_value(it->dat, it->size, *sig); double value = get_raw_value(it->dat, it->size, *sig);
@ -398,16 +433,13 @@ void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionView
max += 1; max += 1;
} }
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); const double xscale = rect.width() / (double)settings.sparkline_range;
int v_margin = std::max(option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 2, 4); const double yscale = rect.height() / (max - min);
const double xscale = (option.rect.width() - 175.0 - h_margin * 2) / settings.sparkline_range;
const double yscale = (option.rect.height() - v_margin * 2) / (max - min);
const int left = option.rect.left();
const int top = option.rect.top() + v_margin;
for (auto &pt : points) { for (auto &pt : points) {
pt.rx() = left + pt.x() * xscale; pt.rx() = rect.left() + pt.x() * xscale;
pt.ry() = top + std::abs(pt.y() - max) * yscale; pt.ry() = rect.top() + std::abs(pt.y() - max) * yscale;
} }
painter->setPen(getColor(sig)); painter->setPen(getColor(sig));
painter->drawPolyline(points.data(), points.size()); painter->drawPolyline(points.data(), points.size());
if ((points.back().x() - points.front().x()) / points.size() > 10) { if ((points.back().x() - points.front().x()) / points.size() > 10) {
@ -417,6 +449,17 @@ void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionView
painter->drawEllipse(pt, 2, 2); painter->drawEllipse(pt, 2, 2);
} }
} }
if (item->highlight) {
QFont font;
font.setPixelSize(10);
painter->setFont(font);
painter->setPen(Qt::darkGray);
painter->drawLine(rect.topRight(), rect.bottomRight());
QRect minmax_rect{rect.right() + 3, rect.top(), 1000, rect.height()};
painter->drawText(minmax_rect, Qt::AlignLeft | Qt::AlignTop, QString::number(max));
painter->drawText(minmax_rect, Qt::AlignLeft | Qt::AlignBottom, QString::number(min));
}
} }
} }
@ -534,7 +577,6 @@ void SignalView::rowsChanged() {
int h_margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin); int h_margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
h->setContentsMargins(0, v_margin, -h_margin, v_margin); h->setContentsMargins(0, v_margin, -h_margin, v_margin);
h->setSpacing(style()->pixelMetric(QStyle::PM_ToolBarItemSpacing)); h->setSpacing(style()->pixelMetric(QStyle::PM_ToolBarItemSpacing));
h->addStretch(0);
auto remove_btn = toolButton("x", tr("Remove signal")); auto remove_btn = toolButton("x", tr("Remove signal"));
auto plot_btn = toolButton("graph-up", ""); auto plot_btn = toolButton("graph-up", "");
@ -594,6 +636,7 @@ void SignalView::signalHovered(const cabana::Signal *sig) {
bool highlight = children[i]->sig == sig; bool highlight = children[i]->sig == sig;
if (std::exchange(children[i]->highlight, highlight) != highlight) { if (std::exchange(children[i]->highlight, highlight) != highlight) {
emit model->dataChanged(model->index(i, 0), model->index(i, 0), {Qt::DecorationRole}); emit model->dataChanged(model->index(i, 0), model->index(i, 0), {Qt::DecorationRole});
emit model->dataChanged(model->index(i, 1), model->index(i, 1), {Qt::DisplayRole});
} }
} }
} }

@ -83,7 +83,9 @@ public:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override;
void drawSparkline(QPainter *painter, const QRect &rect, const QModelIndex &index) const;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QValidator *name_validator, *double_validator; QValidator *name_validator, *double_validator;
QFont small_font; QFont small_font;
const int color_label_width = 18; const int color_label_width = 18;

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

@ -8,6 +8,7 @@
#include <limits> #include <limits>
#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/util.h"
#include "tools/cabana/settings.h"
static QColor blend(QColor a, QColor b) { static QColor blend(QColor a, QColor b) {
return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2);
@ -145,8 +146,7 @@ QValidator::State NameValidator::validate(QString &input, int &pos) const {
namespace utils { namespace utils {
QPixmap icon(const QString &id) { QPixmap icon(const QString &id) {
static bool dark_theme = QApplication::palette().color(QPalette::WindowText).value() > bool dark_theme = settings.theme == 2;
QApplication::palette().color(QPalette::Background).value();
QPixmap pm; QPixmap pm;
QString key = "bootstrap_" % id % (dark_theme ? "1" : "0"); QString key = "bootstrap_" % id % (dark_theme ? "1" : "0");
if (!QPixmapCache::find(key, &pm)) { if (!QPixmapCache::find(key, &pm)) {
@ -154,12 +154,44 @@ QPixmap icon(const QString &id) {
if (dark_theme) { if (dark_theme) {
QPainter p(&pm); QPainter p(&pm);
p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.setCompositionMode(QPainter::CompositionMode_SourceIn);
p.fillRect(pm.rect(), Qt::lightGray); p.fillRect(pm.rect(), Qt::white);
} }
QPixmapCache::insert(key, pm); QPixmapCache::insert(key, pm);
} }
return pm; return pm;
} }
void setTheme(int theme) {
auto style = QApplication::style();
if (!style) return;
static int prev_theme = 0;
if (theme != prev_theme) {
prev_theme = theme;
if (theme == 2) {
// modify palette to dark
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, QColor(53, 53, 53));
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Base, QColor(25, 25, 25));
darkPalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, QColor(41, 41, 41));
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Button, QColor(53, 53, 53));
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
darkPalette.setColor(QPalette::HighlightedText, Qt::black);
qApp->setPalette(darkPalette);
} else {
qApp->setPalette(style->standardPalette());
}
style->polish(qApp);
}
}
} // namespace utils } // namespace utils
QToolButton *toolButton(const QString &icon, const QString &tooltip) { QToolButton *toolButton(const QString &icon, const QString &tooltip) {

@ -98,6 +98,7 @@ public:
namespace utils { namespace utils {
QPixmap icon(const QString &id); QPixmap icon(const QString &id);
void setTheme(int theme);
inline QString formatSeconds(int seconds) { inline QString formatSeconds(int seconds) {
return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
} }

Loading…
Cancel
Save