merge latest master

pull/26850/head
Kurt Nistelberger 3 years ago
commit f8024017c3
  1. 3
      README.md
  2. 4
      docs/CARS.md
  3. 2
      laika_repo
  4. 19
      lgtm.yml
  5. 2
      panda
  6. 4
      selfdrive/car/honda/values.py
  7. 1
      selfdrive/car/tesla/interface.py
  8. 12
      selfdrive/car/toyota/values.py
  9. 1
      selfdrive/locationd/laikad.py
  10. 4
      selfdrive/navd/map_renderer.cc
  11. 16
      selfdrive/test/process_replay/model_replay.py
  12. 2
      selfdrive/test/process_replay/model_replay_ref_commit
  13. 2
      tools/cabana/SConscript
  14. 4
      tools/cabana/cabana.cc
  15. 50
      tools/cabana/chartswidget.cc
  16. 5
      tools/cabana/chartswidget.h
  17. 3
      tools/cabana/detailwidget.cc
  18. 145
      tools/cabana/historylog.cc
  19. 27
      tools/cabana/historylog.h
  20. 108
      tools/cabana/mainwin.cc
  21. 8
      tools/cabana/mainwin.h
  22. 6
      tools/cabana/settings.cc
  23. 2
      tools/cabana/settings.h
  24. 115
      tools/cabana/tools/findsimilarbits.cc
  25. 23
      tools/cabana/tools/findsimilarbits.h
  26. 13
      tools/cabana/videowidget.cc
  27. 2
      tools/cabana/videowidget.h
  28. 2
      tools/sim/bridge.py
  29. 11
      tools/ubuntu_setup.sh
  30. 10
      update_requirements.sh

@ -143,7 +143,4 @@ NO WARRANTY EXPRESSED OR IMPLIED.**
<img src="https://d1qb2nb5cznatu.cloudfront.net/startups/i/1061157-bc7e9bf3b246ece7322e6ffe653f6af8-medium_jpg.jpg?buster=1458363130" width="75"></img> <img src="https://cdn-images-1.medium.com/max/1600/1*C87EjxGeMPrkTuVRVWVg4w.png" width="225"></img> <img src="https://d1qb2nb5cznatu.cloudfront.net/startups/i/1061157-bc7e9bf3b246ece7322e6ffe653f6af8-medium_jpg.jpg?buster=1458363130" width="75"></img> <img src="https://cdn-images-1.medium.com/max/1600/1*C87EjxGeMPrkTuVRVWVg4w.png" width="225"></img>
[![openpilot tests](https://github.com/commaai/openpilot/workflows/openpilot%20tests/badge.svg?event=push)](https://github.com/commaai/openpilot/actions) [![openpilot tests](https://github.com/commaai/openpilot/workflows/openpilot%20tests/badge.svg?event=push)](https://github.com/commaai/openpilot/actions)
[![Total alerts](https://img.shields.io/lgtm/alerts/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/alerts/)
[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/context:python)
[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/context:cpp)
[![codecov](https://codecov.io/gh/commaai/openpilot/branch/master/graph/badge.svg)](https://codecov.io/gh/commaai/openpilot) [![codecov](https://codecov.io/gh/commaai/openpilot/branch/master/graph/badge.svg)](https://codecov.io/gh/commaai/openpilot)

@ -180,9 +180,9 @@ A supported vehicle is one that just works when you install a comma three. All s
|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Highlander 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Highlander 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Highlander Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Prius 2017-20|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius 2017-20|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|

@ -1 +1 @@
Subproject commit 25d5da56f9693e090dd8cf7b28479e83162d5d11 Subproject commit 5eb0c3c2596dd12a232b83bdb057a716810e89cf

@ -1,19 +0,0 @@
path_classifiers:
library:
- external
- third_party
- pyextra
- tools/lib/mkvparse
extraction:
cpp:
after_prepare:
- "pip3 install --upgrade --user pkgconfig cython setuptools wheel"
- "pip3 install --upgrade --user jinja2 pyyaml cython pycapnp numpy sympy tqdm\
\ cffi logentries zmq scons"
- "export PATH=/opt/work/.local/bin:$PATH"
index:
build_command: "scons"
javascript:
index:
filters:
- exclude: "*"

@ -1 +1 @@
Subproject commit 9e5c28e568a1efe4bac340b48b3c16aafc82e84c Subproject commit 345147fe2bc3a06c44709426f9fcd298588b9fe4

@ -1167,6 +1167,7 @@ FW_VERSIONS = {
(Ecu.programmedFuelInjection, 0x18da10f1, None): [ (Ecu.programmedFuelInjection, 0x18da10f1, None): [
b'37805-RLV-B220\x00\x00', b'37805-RLV-B220\x00\x00',
b'37805-RLV-B210\x00\x00', b'37805-RLV-B210\x00\x00',
b'37805-RLV-L160\x00\x00',
], ],
(Ecu.eps, 0x18da30f1, None): [ (Ecu.eps, 0x18da30f1, None): [
b'39990-TGS-A230\x00\x00', b'39990-TGS-A230\x00\x00',
@ -1177,6 +1178,7 @@ FW_VERSIONS = {
], ],
(Ecu.gateway, 0x18daeff1, None): [ (Ecu.gateway, 0x18daeff1, None): [
b'38897-TG7-A040\x00\x00', b'38897-TG7-A040\x00\x00',
b'38897-TG7-A030\x00\x00',
], ],
(Ecu.srs, 0x18da53f1, None): [ (Ecu.srs, 0x18da53f1, None): [
b'77959-TGS-A010\x00\x00', b'77959-TGS-A010\x00\x00',
@ -1186,10 +1188,12 @@ FW_VERSIONS = {
], ],
(Ecu.transmission, 0x18da1ef1, None): [ (Ecu.transmission, 0x18da1ef1, None): [
b'28101-5EZ-A600\x00\x00', b'28101-5EZ-A600\x00\x00',
b'28101-5EZ-A430\x00\x00',
], ],
(Ecu.combinationMeter, 0x18da60f1, None): [ (Ecu.combinationMeter, 0x18da60f1, None): [
b'78109-TGS-AT20\x00\x00', b'78109-TGS-AT20\x00\x00',
b'78109-TGS-AX20\x00\x00', b'78109-TGS-AX20\x00\x00',
b'78109-TGS-AJ20\x00\x00',
], ],
(Ecu.vsa, 0x18da28f1, None): [ (Ecu.vsa, 0x18da28f1, None): [
b'57114-TGS-A530\x00\x00', b'57114-TGS-A530\x00\x00',

@ -23,7 +23,6 @@ class CarInterface(CarInterfaceBase):
ret.longitudinalTuning.kpV = [0] ret.longitudinalTuning.kpV = [0]
ret.longitudinalTuning.kiBP = [0] ret.longitudinalTuning.kiBP = [0]
ret.longitudinalTuning.kiV = [0] ret.longitudinalTuning.kiV = [0]
ret.stopAccel = 0.0
ret.longitudinalActuatorDelayUpperBound = 0.5 # s ret.longitudinalActuatorDelayUpperBound = 0.5 # s
ret.radarTimeStep = (1.0 / 8) # 8Hz ret.radarTimeStep = (1.0 / 8) # 8Hz

@ -129,9 +129,9 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
ToyotaCarInfo("Lexus UX Hybrid 2019-22"), ToyotaCarInfo("Lexus UX Hybrid 2019-22"),
], ],
CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"),
CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"), CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-23"),
CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"), CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"),
CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-22"), CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"),
CAR.PRIUS: [ CAR.PRIUS: [
ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0"), ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0"),
ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
@ -980,12 +980,14 @@ FW_VERSIONS = {
b'8965B48241\x00\x00\x00\x00\x00\x00', b'8965B48241\x00\x00\x00\x00\x00\x00',
b'8965B48310\x00\x00\x00\x00\x00\x00', b'8965B48310\x00\x00\x00\x00\x00\x00',
b'8965B48320\x00\x00\x00\x00\x00\x00', b'8965B48320\x00\x00\x00\x00\x00\x00',
b'8965B48400\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x7b0, None): [ (Ecu.abs, 0x7b0, None): [
b'\x01F15260E051\x00\x00\x00\x00\x00\x00', b'\x01F15260E051\x00\x00\x00\x00\x00\x00',
b'\x01F15260E061\x00\x00\x00\x00\x00\x00', b'\x01F15260E061\x00\x00\x00\x00\x00\x00',
b'\x01F15260E110\x00\x00\x00\x00\x00\x00', b'\x01F15260E110\x00\x00\x00\x00\x00\x00',
b'\x01F15260E170\x00\x00\x00\x00\x00\x00', b'\x01F15260E170\x00\x00\x00\x00\x00\x00',
b'\x01F15260E05300\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x700, None): [ (Ecu.engine, 0x700, None): [
b'\x01896630E62100\x00\x00\x00\x00', b'\x01896630E62100\x00\x00\x00\x00',
@ -1003,6 +1005,7 @@ FW_VERSIONS = {
b'\x01896630ED9100\x00\x00\x00\x00', b'\x01896630ED9100\x00\x00\x00\x00',
b'\x01896630EE1000\x00\x00\x00\x00', b'\x01896630EE1000\x00\x00\x00\x00',
b'\x01896630EE1100\x00\x00\x00\x00', b'\x01896630EE1100\x00\x00\x00\x00',
b'\x01896630EG5000\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301400\x00\x00\x00\x00', b'\x018821F3301400\x00\x00\x00\x00',
@ -1013,17 +1016,20 @@ FW_VERSIONS = {
b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
], ],
}, },
CAR.HIGHLANDERH_TSS2: { CAR.HIGHLANDERH_TSS2: {
(Ecu.eps, 0x7a1, None): [ (Ecu.eps, 0x7a1, None): [
b'8965B48241\x00\x00\x00\x00\x00\x00', b'8965B48241\x00\x00\x00\x00\x00\x00',
b'8965B48310\x00\x00\x00\x00\x00\x00', b'8965B48310\x00\x00\x00\x00\x00\x00',
b'8965B48400\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x7b0, None): [ (Ecu.abs, 0x7b0, None): [
b'\x01F15264872300\x00\x00\x00\x00', b'\x01F15264872300\x00\x00\x00\x00',
b'\x01F15264872400\x00\x00\x00\x00', b'\x01F15264872400\x00\x00\x00\x00',
b'\x01F15264872500\x00\x00\x00\x00', b'\x01F15264872500\x00\x00\x00\x00',
b'\x01F15264872600\x00\x00\x00\x00',
b'\x01F15264873500\x00\x00\x00\x00', b'\x01F15264873500\x00\x00\x00\x00',
b'\x01F152648C6300\x00\x00\x00\x00', b'\x01F152648C6300\x00\x00\x00\x00',
b'\x01F152648J4000\x00\x00\x00\x00', b'\x01F152648J4000\x00\x00\x00\x00',
@ -1037,6 +1043,7 @@ FW_VERSIONS = {
b'\x01896630EE4100\x00\x00\x00\x00', b'\x01896630EE4100\x00\x00\x00\x00',
b'\x01896630EE5000\x00\x00\x00\x00', b'\x01896630EE5000\x00\x00\x00\x00',
b'\x01896630EE6000\x00\x00\x00\x00', b'\x01896630EE6000\x00\x00\x00\x00',
b'\x01896630EF8000\x00\x00\x00\x00',
b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
b'\x01896630EA1000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x01896630EA1000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
@ -1052,6 +1059,7 @@ FW_VERSIONS = {
b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
], ],
}, },
CAR.LEXUS_IS: { CAR.LEXUS_IS: {

@ -411,7 +411,6 @@ def main(sm=None, pm=None, qc=None):
if msg is None: if msg is None:
# TODO: beautify this, locationd needs a valid message # TODO: beautify this, locationd needs a valid message
msg = messaging.new_message("gnssMeasurements") msg = messaging.new_message("gnssMeasurements")
pm.send('gnssMeasurements', msg) pm.send('gnssMeasurements', msg)
if not laikad.got_first_gnss_msg and sm.updated['clocks']: if not laikad.got_first_gnss_msg and sm.updated['clocks']:

@ -169,7 +169,7 @@ void MapRenderer::publish(const double render_time) {
VisionBuf* buf = vipc_server->get_buffer(VisionStreamType::VISION_STREAM_MAP); VisionBuf* buf = vipc_server->get_buffer(VisionStreamType::VISION_STREAM_MAP);
VisionIpcBufExtra extra = { VisionIpcBufExtra extra = {
.frame_id = frame_id, .frame_id = frame_id,
.timestamp_sof = sm->rcv_time("liveLocationKalman"), .timestamp_sof = (*sm)["liveLocationKalman"].getLogMonoTime(),
.timestamp_eof = ts, .timestamp_eof = ts,
}; };
@ -206,7 +206,7 @@ void MapRenderer::publish(const double render_time) {
// Send state msg // Send state msg
MessageBuilder msg; MessageBuilder msg;
auto state = msg.initEvent().initMapRenderState(); auto state = msg.initEvent().initMapRenderState();
state.setLocationMonoTime(sm->rcv_time("liveLocationKalman")); state.setLocationMonoTime((*sm)["liveLocationKalman"].getLogMonoTime());
state.setRenderTime(render_time); state.setRenderTime(render_time);
state.setFrameId(frame_id); state.setFrameId(frame_id);
pm->send("mapRenderState", msg); pm->send("mapRenderState", msg);

@ -43,12 +43,12 @@ def replace_calib(msg, calib):
def nav_model_replay(lr): def nav_model_replay(lr):
sm = messaging.SubMaster(['navModel', 'navThumbnail']) sm = messaging.SubMaster(['navModel', 'navThumbnail', 'mapRenderState'])
pm = messaging.PubMaster(['liveLocationKalman', 'navRoute']) pm = messaging.PubMaster(['liveLocationKalman', 'navRoute'])
nav = [m for m in lr if m.which() == 'navRoute'] nav = [m for m in lr if m.which() == 'navRoute']
llk = [m for m in lr if m.which() == 'liveLocationKalman'] llk = [m for m in lr if m.which() == 'liveLocationKalman']
assert len(nav) > 0 and len(llk) >= NAV_FRAMES assert len(nav) > 0 and len(llk) >= NAV_FRAMES and nav[0].logMonoTime < llk[-NAV_FRAMES].logMonoTime
log_msgs = [] log_msgs = []
try: try:
@ -59,8 +59,8 @@ def nav_model_replay(lr):
# setup position and route # setup position and route
for _ in range(10): for _ in range(10):
for s in (llk, nav): for s in (llk[-NAV_FRAMES], nav[0]):
pm.send(s[0].which(), s[0].as_builder().to_bytes()) pm.send(s.which(), s.as_builder().to_bytes())
sm.update(1000) sm.update(1000)
if sm.updated['navModel']: if sm.updated['navModel']:
break break
@ -74,12 +74,16 @@ def nav_model_replay(lr):
sm.update(0) sm.update(0)
# run replay # run replay
for n in range(NAV_FRAMES): for n in range(len(llk) - NAV_FRAMES, len(llk)):
pm.send(llk[n].which(), llk[n].as_builder().to_bytes()) pm.send(llk[n].which(), llk[n].as_builder().to_bytes())
m = messaging.recv_one(sm.sock['navThumbnail']) m = messaging.recv_one(sm.sock['navThumbnail'])
assert m is not None, f"no navThumbnail, frame={n}" assert m is not None, f"no navThumbnail, frame={n}"
log_msgs.append(m) log_msgs.append(m)
m = messaging.recv_one(sm.sock['mapRenderState'])
assert m is not None, f"no mapRenderState, frame={n}"
log_msgs.append(m)
m = messaging.recv_one(sm.sock['navModel']) m = messaging.recv_one(sm.sock['navModel'])
assert m is not None, f"no navModel response, frame={n}" assert m is not None, f"no navModel response, frame={n}"
log_msgs.append(m) log_msgs.append(m)
@ -231,6 +235,8 @@ if __name__ == "__main__":
'navModel.dspExecutionTime', 'navModel.dspExecutionTime',
'navModel.modelExecutionTime', 'navModel.modelExecutionTime',
'navThumbnail.timestampEof', 'navThumbnail.timestampEof',
'mapRenderState.locationMonoTime',
'mapRenderState.renderTime',
] ]
if PC: if PC:
ignore += [ ignore += [

@ -1 +1 @@
4ff972367fdb9546be68ee0ba0d45cf4f839dae7 db587bfef2317c5a3471632ac47381457e1be853

@ -18,7 +18,7 @@ cabana_env = qt_env.Clone()
prev_moc_path = cabana_env['QT_MOCHPREFIX'] prev_moc_path = cabana_env['QT_MOCHPREFIX']
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
'canmessages.cc', 'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) 'canmessages.cc', 'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
if GetOption('test'): if GetOption('test'):

@ -6,6 +6,8 @@
#include "tools/cabana/mainwin.h" #include "tools/cabana/mainwin.h"
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
QCoreApplication::setApplicationName("Cabana");
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
initApp(argc, argv); initApp(argc, argv);
QApplication app(argc, argv); QApplication app(argc, argv);
@ -36,7 +38,7 @@ int main(int argc, char *argv[]) {
int ret = 0; int ret = 0;
if (p.loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) { if (p.loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
MainWindow w; MainWindow w;
w.showMaximized(); w.show();
ret = app.exec(); ret = app.exec();
} }
return ret; return ret;

@ -1,5 +1,6 @@
#include "tools/cabana/chartswidget.h" #include "tools/cabana/chartswidget.h"
#include <QApplication>
#include <QCompleter> #include <QCompleter>
#include <QLineEdit> #include <QLineEdit>
#include <QFutureSynchronizer> #include <QFutureSynchronizer>
@ -42,7 +43,8 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
main_layout->addWidget(charts_scroll); main_layout->addWidget(charts_scroll);
max_chart_range = settings.max_chart_x_range; max_chart_range = settings.max_chart_x_range;
use_dark_theme = palette().color(QPalette::WindowText).value() > palette().color(QPalette::Background).value(); use_dark_theme = QApplication::style()->standardPalette().color(QPalette::WindowText).value() >
QApplication::style()->standardPalette().color(QPalette::Background).value();
updateToolBar(); updateToolBar();
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll);
@ -143,8 +145,9 @@ ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) {
void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) { void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) {
setUpdatesEnabled(false); setUpdatesEnabled(false);
if (show) { ChartView *chart = findChart(id, sig);
ChartView *chart = merge && charts.size() > 0 ? charts.back() : nullptr; if (show && !chart) {
chart = merge && charts.size() > 0 ? charts.back() : nullptr;
if (!chart) { if (!chart) {
chart = new ChartView(this); chart = new ChartView(this);
chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight); chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight);
@ -156,11 +159,12 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bo
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
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::axisYUpdated, this, &ChartsWidget::alignCharts);
charts_layout->insertWidget(0, chart); charts_layout->insertWidget(0, chart);
charts.push_back(chart); charts.push_back(chart);
} }
chart->addSeries(id, sig); chart->addSeries(id, sig);
} else if (ChartView *chart = findChart(id, sig)) { } else if (!show && chart) {
chart->removeSeries(id, sig); chart->removeSeries(id, sig);
} }
updateToolBar(); updateToolBar();
@ -171,6 +175,7 @@ void ChartsWidget::removeChart(ChartView *chart) {
charts.removeOne(chart); charts.removeOne(chart);
chart->deleteLater(); chart->deleteLater();
updateToolBar(); updateToolBar();
alignCharts();
emit seriesChanged(); emit seriesChanged();
} }
@ -183,6 +188,16 @@ void ChartsWidget::removeAll() {
emit seriesChanged(); emit seriesChanged();
} }
void ChartsWidget::alignCharts() {
int plot_left = 0;
for (auto c : charts) {
plot_left = qMax((qreal)plot_left, c->getYAsixLabelWidth());
}
for (auto c : charts) {
c->setPlotAreaLeftPosition(plot_left);
}
}
bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
if (obj != this && event->type() == QEvent::Close) { if (obj != this && event->type() == QEvent::Close) {
emit dock_btn->triggered(); emit dock_btn->triggered();
@ -202,7 +217,6 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
chart->addAxis(axis_y, Qt::AlignLeft); chart->addAxis(axis_y, Qt::AlignLeft);
chart->legend()->setShowToolTips(true); chart->legend()->setShowToolTips(true);
chart->layout()->setContentsMargins(0, 0, 0, 0); chart->layout()->setContentsMargins(0, 0, 0, 0);
chart->setMargins(QMargins(20, 11, 11, 11));
QToolButton *remove_btn = new QToolButton(); QToolButton *remove_btn = new QToolButton();
remove_btn->setText("X"); remove_btn->setText("X");
@ -234,6 +248,19 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries); QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries);
} }
qreal ChartView::getYAsixLabelWidth() const {
QFontMetrics fm(axis_y->labelsFont());
int n = qMax(int(-qFloor(std::log10((axis_y->max() - axis_y->min()) / (axis_y->tickCount() - 1)))), 0) + 1;
return qMax(fm.width(QString::number(axis_y->min(), 'f', n)), fm.width(QString::number(axis_y->max(), 'f', n))) + 20;
}
void ChartView::setPlotAreaLeftPosition(int pos) {
if (std::ceil(chart()->plotArea().left()) != pos) {
const float left_margin = chart()->margins().left() + pos - chart()->plotArea().left();
chart()->setMargins(QMargins(left_margin, 11, 11, 11));
}
}
void ChartView::addSeries(const QString &msg_id, const Signal *sig) { void ChartView::addSeries(const QString &msg_id, const Signal *sig) {
QLineSeries *series = new QLineSeries(this); QLineSeries *series = new QLineSeries(this);
series->setUseOpenGL(true); series->setUseOpenGL(true);
@ -361,16 +388,6 @@ void ChartView::setDisplayRange(double min, double max) {
} }
} }
void ChartView::adjustChartMargins() {
// TODO: Remove hardcoded aligned_pos
const int aligned_pos = 60;
if ((int)chart()->plotArea().left() != aligned_pos) {
const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left();
chart()->setMargins(QMargins(left_margin, 11, 11, 11));
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
}
}
void ChartView::updateSeries(const Signal *sig) { void ChartView::updateSeries(const Signal *sig) {
auto events = can->events(); auto events = can->events();
if (!events || sigs.isEmpty()) return; if (!events || sigs.isEmpty()) return;
@ -436,8 +453,7 @@ void ChartView::updateAxisY() {
double range = max_y - min_y; double range = max_y - min_y;
applyNiceNumbers(min_y - range * 0.05, max_y + range * 0.05); applyNiceNumbers(min_y - range * 0.05, max_y + range * 0.05);
} }
QTimer::singleShot(0, this, &ChartView::axisYUpdated);
adjustChartMargins();
} }
void ChartView::applyNiceNumbers(qreal min, qreal max) { void ChartView::applyNiceNumbers(qreal min, qreal max) {

@ -26,6 +26,8 @@ public:
void updateSeries(const Signal *sig = nullptr); void updateSeries(const Signal *sig = nullptr);
void setEventsRange(const std::pair<double, double> &range); void setEventsRange(const std::pair<double, double> &range);
void setDisplayRange(double min, double max); void setDisplayRange(double min, double max);
void setPlotAreaLeftPosition(int pos);
qreal getYAsixLabelWidth() const;
struct SigItem { struct SigItem {
QString msg_id; QString msg_id;
@ -44,6 +46,7 @@ signals:
void zoomIn(double min, double max); void zoomIn(double min, double max);
void zoomReset(); void zoomReset();
void remove(); void remove();
void axisYUpdated();
private slots: private slots:
void msgRemoved(uint32_t address); void msgRemoved(uint32_t address);
@ -58,7 +61,6 @@ private:
void mouseMoveEvent(QMouseEvent *ev) override; void mouseMoveEvent(QMouseEvent *ev) override;
void leaveEvent(QEvent *event) override; void leaveEvent(QEvent *event) override;
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;
void adjustChartMargins();
void updateAxisY(); void updateAxisY();
void updateTitle(); void updateTitle();
void updateFromSettings(); void updateFromSettings();
@ -89,6 +91,7 @@ signals:
void seriesChanged(); void seriesChanged();
private: private:
void alignCharts();
void removeChart(ChartView *chart); void removeChart(ChartView *chart);
void eventsMerged(); void eventsMerged();
void updateState(); void updateState();

@ -108,6 +108,9 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
}); });
QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab); QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab);
QObject::connect(charts, &ChartsWidget::seriesChanged, this, &DetailWidget::updateChartState); QObject::connect(charts, &ChartsWidget::seriesChanged, this, &DetailWidget::updateChartState);
QObject::connect(history_log, &LogsWidget::openChart, [this](const QString &id, const Signal *sig) {
this->charts->showChart(id, sig, true, false);
});
QObject::connect(undo_stack, &QUndoStack::indexChanged, [this]() { QObject::connect(undo_stack, &QUndoStack::indexChanged, [this]() {
if (undo_stack->count() > 0) if (undo_stack->count() > 0)
dbcMsgChanged(); dbcMsgChanged();

@ -7,12 +7,6 @@
// HistoryLogModel // HistoryLogModel
HistoryLogModel::HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {
QObject::connect(can, &CANMessages::seekedTo, [this]() {
if (!msg_id.isEmpty()) setMessage(msg_id);
});
}
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
const auto &m = messages[index.row()]; const auto &m = messages[index.row()];
@ -22,22 +16,26 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
return !sigs.empty() ? QString::number(m.sig_values[index.column() - 1]) : m.data; return !sigs.empty() ? QString::number(m.sig_values[index.column() - 1]) : m.data;
} else if (role == Qt::FontRole && index.column() == 1 && sigs.empty()) { } else if (role == Qt::FontRole && index.column() == 1 && sigs.empty()) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont); return QFontDatabase::systemFont(QFontDatabase::FixedFont);
} else if (role == Qt::ToolTipRole && index.column() > 0 && !sigs.empty()) {
return tr("double click to open the chart");
} }
return {}; return {};
} }
void HistoryLogModel::setMessage(const QString &message_id) { void HistoryLogModel::setMessage(const QString &message_id) {
beginResetModel(); msg_id = message_id;
sigs.clear(); sigs.clear();
messages.clear(); if (auto dbc_msg = dbc()->msg(msg_id)) {
has_more_data = true;
if (auto dbc_msg = dbc()->msg(message_id)) {
sigs = dbc_msg->getSignals(); sigs = dbc_msg->getSignals();
} }
if (msg_id != message_id || sigs.empty()) {
filter_cmp = nullptr; filter_cmp = nullptr;
} refresh();
msg_id = message_id; }
void HistoryLogModel::refresh() {
beginResetModel();
last_fetch_time = 0;
messages.clear();
updateState(); updateState();
endResetModel(); endResetModel();
} }
@ -58,33 +56,40 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i
return {}; return {};
} }
void HistoryLogModel::setDynamicMode(int state) {
dynamic_mode = state != 0;
refresh();
}
void HistoryLogModel::segmentsMerged() {
if (!dynamic_mode) {
has_more_data = true;
}
}
void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function<bool(double, double)> cmp) { void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function<bool(double, double)> cmp) {
if (sig_idx < sigs.size()) {
filter_sig_idx = sig_idx; filter_sig_idx = sig_idx;
filter_value = value.toDouble(); filter_value = value.toDouble();
filter_cmp = value.isEmpty() ? nullptr : cmp; filter_cmp = value.isEmpty() ? nullptr : cmp;
beginResetModel(); refresh();
messages.clear();
updateState();
endResetModel();
}
} }
void HistoryLogModel::updateState() { void HistoryLogModel::updateState() {
if (!msg_id.isEmpty()) { if (!msg_id.isEmpty()) {
uint64_t last_mono_time = messages.empty() ? 0 : messages.front().mono_time; uint64_t current_time = (can->currentSec() + can->routeStartTime()) * 1e9;
auto new_msgs = fetchData(last_mono_time, (can->currentSec() + can->routeStartTime()) * 1e9); auto new_msgs = dynamic_mode ? fetchData(current_time, last_fetch_time) : fetchData(0);
if ((has_more_data = !new_msgs.empty())) { if ((has_more_data = !new_msgs.empty())) {
beginInsertRows({}, 0, new_msgs.size() - 1); beginInsertRows({}, 0, new_msgs.size() - 1);
messages.insert(messages.begin(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end())); messages.insert(messages.begin(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end()));
endInsertRows(); endInsertRows();
} }
last_fetch_time = current_time;
} }
} }
void HistoryLogModel::fetchMore(const QModelIndex &parent) { void HistoryLogModel::fetchMore(const QModelIndex &parent) {
if (!messages.empty()) { if (!messages.empty()) {
auto new_msgs = fetchData(0, messages.back().mono_time); auto new_msgs = fetchData(messages.back().mono_time);
if ((has_more_data = !new_msgs.empty())) { if ((has_more_data = !new_msgs.empty())) {
beginInsertRows({}, messages.size(), messages.size() + new_msgs.size() - 1); beginInsertRows({}, messages.size(), messages.size() + new_msgs.size() - 1);
messages.insert(messages.end(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end())); messages.insert(messages.end(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end()));
@ -93,19 +98,12 @@ void HistoryLogModel::fetchMore(const QModelIndex &parent) {
} }
} }
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t min_mono_time, uint64_t max_mono_time) { template <class InputIt>
auto events = can->events(); std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) {
auto it = std::lower_bound(events->begin(), events->end(), max_mono_time, [=](auto &e, uint64_t ts) {
return e->mono_time < ts;
});
if (it == events->end() || it == events->begin())
return {};
std::deque<HistoryLogModel::Message> msgs; std::deque<HistoryLogModel::Message> msgs;
const auto [src, address] = DBCManager::parseId(msg_id); const auto [src, address] = DBCManager::parseId(msg_id);
uint32_t cnt = 0;
QVector<double> values(sigs.size()); QVector<double> values(sigs.size());
for (--it; it != events->begin() && (*it)->mono_time > min_mono_time; --it) { for (auto it = first; it != last && (*it)->mono_time > min_time; ++it) {
if ((*it)->which == cereal::Event::Which::CAN) { if ((*it)->which == cereal::Event::Which::CAN) {
for (const auto &c : (*it)->event.getCan()) { for (const auto &c : (*it)->event.getCan()) {
if (src == c.getSrc() && address == c.getAddress()) { if (src == c.getSrc() && address == c.getAddress()) {
@ -118,7 +116,7 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t min_mon
m.mono_time = (*it)->mono_time; m.mono_time = (*it)->mono_time;
m.data = toHex(QByteArray((char *)dat.begin(), dat.size())); m.data = toHex(QByteArray((char *)dat.begin(), dat.size()));
m.sig_values = values; m.sig_values = values;
if (++cnt >= batch_size && min_mono_time == 0) if (msgs.size() >= batch_size && min_time == 0)
return msgs; return msgs;
} }
} }
@ -127,6 +125,25 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t min_mon
} }
return msgs; return msgs;
} }
template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::vector<const Event*>::iterator first, std::vector<const Event*>::iterator last, uint64_t min_time);
template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::vector<const Event*>::reverse_iterator first, std::vector<const Event*>::reverse_iterator last, uint64_t min_time);
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) {
auto events = can->events();
if (dynamic_mode) {
auto it = std::lower_bound(events->rbegin(), events->rend(), from_time, [=](auto &e, uint64_t ts) {
return e->mono_time > ts;
});
if (it != events->rend()) ++it;
return fetchData(it, events->rend(), min_time);
} else {
assert(min_time == 0);
auto it = std::upper_bound(events->begin(), events->end(), from_time, [=](uint64_t ts, auto &e) {
return ts < e->mono_time;
});
return fetchData(it, events->end(), 0);
}
}
// HeaderView // HeaderView
@ -164,9 +181,9 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) {
LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) { LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
filter_container = new QWidget(this); QHBoxLayout *h = new QHBoxLayout();
QHBoxLayout *h = new QHBoxLayout(filter_container);
signals_cb = new QComboBox(this); signals_cb = new QComboBox(this);
signals_cb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
h->addWidget(signals_cb); h->addWidget(signals_cb);
comp_box = new QComboBox(); comp_box = new QComboBox();
comp_box->addItems({">", "=", "!=", "<"}); comp_box->addItems({">", "=", "!=", "<"});
@ -175,40 +192,48 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) {
value_edit->setClearButtonEnabled(true); value_edit->setClearButtonEnabled(true);
value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this)); value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this));
h->addWidget(value_edit); h->addWidget(value_edit);
main_layout->addWidget(filter_container); dynamic_mode = new QCheckBox(tr("Dynamic"));
h->addWidget(dynamic_mode, 0, Qt::AlignRight);
main_layout->addLayout(h);
model = new HistoryLogModel(this); model = new HistoryLogModel(this);
logs = new HistoryLog(this); logs = new HistoryLog(this);
logs->setModel(model); logs->setModel(model);
main_layout->addWidget(logs); main_layout->addWidget(logs);
QObject::connect(signals_cb, SIGNAL(currentIndexChanged(int)), this, SLOT(setFilter())); QObject::connect(logs, &QTableView::doubleClicked, this, &LogsWidget::doubleClicked);
QObject::connect(comp_box, SIGNAL(currentIndexChanged(int)), this, SLOT(setFilter())); QObject::connect(signals_cb, SIGNAL(activated(int)), this, SLOT(setFilter()));
QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(setFilter()));
QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter); QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter);
QObject::connect(dynamic_mode, &QCheckBox::stateChanged, model, &HistoryLogModel::setDynamicMode);
QObject::connect(can, &CANMessages::seekedTo, model, &HistoryLogModel::refresh);
QObject::connect(can, &CANMessages::eventsMerged, model, &HistoryLogModel::segmentsMerged);
} }
void LogsWidget::setMessage(const QString &message_id) { void LogsWidget::setMessage(const QString &message_id) {
blockSignals(true); model->setMessage(message_id);
cur_filter_text = "";
value_edit->setText(""); value_edit->setText("");
signals_cb->clear(); signals_cb->clear();
comp_box->setCurrentIndex(0); comp_box->setCurrentIndex(0);
sigs.clear(); bool has_signals = model->sigs.size() > 0;
if (auto dbc_msg = dbc()->msg(message_id)) { if (has_signals) {
sigs = dbc_msg->getSignals(); for (auto s : model->sigs) {
for (auto s : sigs) {
signals_cb->addItem(s->name.c_str()); signals_cb->addItem(s->name.c_str());
} }
} }
filter_container->setVisible(!sigs.empty()); comp_box->setVisible(has_signals);
model->setMessage(message_id); value_edit->setVisible(has_signals);
blockSignals(false); signals_cb->setVisible(has_signals);
} }
static bool not_equal(double l, double r) { static bool not_equal(double l, double r) { return l != r; }
return l != r;
}
void LogsWidget::setFilter() { void LogsWidget::setFilter() {
if (cur_filter_text.isEmpty() && value_edit->text().isEmpty()) {
return;
}
std::function<bool(double, double)> cmp; std::function<bool(double, double)> cmp;
switch (comp_box->currentIndex()) { switch (comp_box->currentIndex()) {
case 0: cmp = std::greater<double>{}; break; case 0: cmp = std::greater<double>{}; break;
@ -217,4 +242,26 @@ void LogsWidget::setFilter() {
case 3: cmp = std::less<double>{}; break; case 3: cmp = std::less<double>{}; break;
} }
model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp); model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp);
cur_filter_text = value_edit->text();
}
void LogsWidget::showEvent(QShowEvent *event) {
if (dynamic_mode->isChecked()) {
model->refresh();
}
}
void LogsWidget::updateState() {
if (dynamic_mode->isChecked()) {
model->updateState();
}
}
void LogsWidget::doubleClicked(const QModelIndex &index) {
if (index.isValid()) {
if (model->sigs.size() > 0 && index.column() > 0) {
emit openChart(model->msg_id, model->sigs[index.column()-1]);
}
can->seekTo(model->messages[index.row()].mono_time / (double)1e9 - can->routeStartTime());
}
} }

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <deque> #include <deque>
#include <QCheckBox>
#include <QComboBox> #include <QComboBox>
#include <QHeaderView> #include <QHeaderView>
#include <QLineEdit> #include <QLineEdit>
@ -17,8 +18,10 @@ public:
}; };
class HistoryLogModel : public QAbstractTableModel { class HistoryLogModel : public QAbstractTableModel {
Q_OBJECT
public: public:
HistoryLogModel(QObject *parent); HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {}
void setMessage(const QString &message_id); void setMessage(const QString &message_id);
void updateState(); void updateState();
void setFilter(int sig_idx, const QString &value, std::function<bool(double, double)> cmp); void setFilter(int sig_idx, const QString &value, std::function<bool(double, double)> cmp);
@ -28,6 +31,9 @@ public:
inline bool canFetchMore(const QModelIndex &parent) const override { return has_more_data; } inline bool canFetchMore(const QModelIndex &parent) const override { return has_more_data; }
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return messages.size(); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return messages.size(); }
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return std::max(1ul, sigs.size()) + 1; } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return std::max(1ul, sigs.size()) + 1; }
void setDynamicMode(int state);
void segmentsMerged();
void refresh();
struct Message { struct Message {
uint64_t mono_time = 0; uint64_t mono_time = 0;
@ -35,15 +41,20 @@ public:
QString data; QString data;
}; };
std::deque<Message> fetchData(uint64_t min_mono_time, uint64_t max_mono_time); template <class InputIt>
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; QString msg_id;
bool has_more_data = true; bool has_more_data = true;
const int batch_size = 50; const int batch_size = 50;
int filter_sig_idx = -1; int filter_sig_idx = -1;
double filter_value = 0; double filter_value = 0;
uint64_t last_fetch_time = 0;
std::function<bool(double, double)> filter_cmp = nullptr; std::function<bool(double, double)> filter_cmp = nullptr;
std::deque<Message> messages; std::deque<Message> messages;
std::vector<const Signal*> sigs; std::vector<const Signal*> sigs;
bool dynamic_mode = false;
}; };
class HistoryLog : public QTableView { class HistoryLog : public QTableView {
@ -58,18 +69,22 @@ class LogsWidget : public QWidget {
public: public:
LogsWidget(QWidget *parent); LogsWidget(QWidget *parent);
void setMessage(const QString &message_id); void setMessage(const QString &message_id);
void updateState() { model->updateState(); } void updateState();
signals:
void openChart(const QString &msg_id, const Signal *sig);
private slots: private slots:
void setFilter(); void setFilter();
private: private:
void showEvent(QShowEvent *event) override { model->setMessage(model->msg_id); }; void doubleClicked(const QModelIndex &index);
void showEvent(QShowEvent *event) override;
HistoryLog *logs; HistoryLog *logs;
HistoryLogModel *model; HistoryLogModel *model;
QWidget *filter_container; QCheckBox *dynamic_mode;
QComboBox *signals_cb, *comp_box; QComboBox *signals_cb, *comp_box;
QLineEdit *value_edit; QLineEdit *value_edit;
std::vector<const Signal*> sigs; QString cur_filter_text;
}; };

@ -1,19 +1,16 @@
#include "tools/cabana/mainwin.h" #include "tools/cabana/mainwin.h"
#include <iostream> #include <iostream>
#include <QApplication>
#include <QClipboard> #include <QClipboard>
#include <QCompleter> #include <QCompleter>
#include <QDesktopWidget>
#include <QFile> #include <QFile>
#include <QFileDialog> #include <QFileDialog>
#include <QFileInfo> #include <QFileInfo>
#include <QHBoxLayout>
#include <QMenu> #include <QMenu>
#include <QMenuBar> #include <QMenuBar>
#include <QMessageBox> #include <QMessageBox>
#include <QShortcut> #include <QShortcut>
#include <QScreen>
#include <QToolBar>
#include <QUndoView> #include <QUndoView>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QWidgetAction> #include <QWidgetAction>
@ -25,58 +22,20 @@ void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const
} }
MainWindow::MainWindow() : QMainWindow() { MainWindow::MainWindow() : QMainWindow() {
setWindowTitle("Cabana"); createDockWindows();
QWidget *central_widget = new QWidget(this);
QHBoxLayout *main_layout = new QHBoxLayout(central_widget);
main_layout->setContentsMargins(11, 11, 11, 0);
main_layout->setSpacing(0);
splitter = new QSplitter(Qt::Horizontal, this);
splitter->setHandleWidth(11);
QWidget *messages_container = new QWidget(this);
QVBoxLayout *messages_layout = new QVBoxLayout(messages_container);
messages_layout->setContentsMargins(0, 0, 0, 0);
// left panel
dbc_combo = createDBCSelector();
messages_layout->addWidget(dbc_combo);
messages_widget = new MessagesWidget(this);
messages_layout->addWidget(messages_widget);
splitter->addWidget(messages_container);
charts_widget = new ChartsWidget(this);
detail_widget = new DetailWidget(charts_widget, this); detail_widget = new DetailWidget(charts_widget, this);
splitter->addWidget(detail_widget); detail_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
if (!settings.splitter_state.isEmpty()) { setCentralWidget(detail_widget);
splitter->restoreState(settings.splitter_state);
}
main_layout->addWidget(splitter);
// right widgets
QWidget *right_container = new QWidget(this);
right_container->setFixedWidth(640);
r_layout = new QVBoxLayout(right_container);
r_layout->setContentsMargins(11, 0, 0, 0);
QHBoxLayout *right_hlayout = new QHBoxLayout();
fingerprint_label = new QLabel(this);
right_hlayout->addWidget(fingerprint_label, 0, Qt::AlignLeft);
// TODO: click to select another route.
right_hlayout->addWidget(new QLabel(can->routeName()), 0, Qt::AlignRight);
r_layout->addLayout(right_hlayout);
video_widget = new VideoWidget(this);
r_layout->addWidget(video_widget, 0, Qt::AlignTop);
r_layout->addWidget(charts_widget, 1);
r_layout->addStretch(0);
main_layout->addWidget(right_container);
setCentralWidget(central_widget);
createActions(); createActions();
createStatusBar(); createStatusBar();
createShortcuts(); createShortcuts();
restoreGeometry(settings.geometry);
if (isMaximized()) {
setGeometry(QApplication::desktop()->availableGeometry(this));
}
restoreState(settings.window_state);
qRegisterMetaType<uint64_t>("uint64_t"); qRegisterMetaType<uint64_t>("uint64_t");
qRegisterMetaType<ReplyMsgType>("ReplyMsgType"); qRegisterMetaType<ReplyMsgType>("ReplyMsgType");
installMessageHandler([this](ReplyMsgType type, const std::string msg) { installMessageHandler([this](ReplyMsgType type, const std::string msg) {
@ -133,10 +92,46 @@ void MainWindow::createActions() {
commands_act->setDefaultWidget(undo_view); commands_act->setDefaultWidget(undo_view);
commands_menu->addAction(commands_act); commands_menu->addAction(commands_act);
QMenu *tools_menu = menuBar()->addMenu(tr("&Tools"));
tools_menu->addAction(tr("Find &Similar Bits"), this, &MainWindow::findSimilarBits);
QMenu *help_menu = menuBar()->addMenu(tr("&Help")); QMenu *help_menu = menuBar()->addMenu(tr("&Help"));
help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
} }
void MainWindow::createDockWindows() {
// left panel
QWidget *messages_container = new QWidget(this);
QVBoxLayout *messages_layout = new QVBoxLayout(messages_container);
dbc_combo = createDBCSelector();
messages_layout->addWidget(dbc_combo);
messages_widget = new MessagesWidget(this);
messages_layout->addWidget(messages_widget);
QDockWidget *dock = new QDockWidget(tr("MESSAGES"), this);
dock->setObjectName("MessagesPanel");
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
dock->setWidget(messages_container);
addDockWidget(Qt::LeftDockWidgetArea, dock);
// right panel
QWidget *right_container = new QWidget(this);
r_layout = new QVBoxLayout(right_container);
charts_widget = new ChartsWidget(this);
video_widget = new VideoWidget(this);
r_layout->addWidget(video_widget, 0, Qt::AlignTop);
r_layout->addWidget(charts_widget, 1);
r_layout->addStretch(0);
video_dock = new QDockWidget(can->routeName(), this);
video_dock->setObjectName(tr("VideoPanel"));
video_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
video_dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
video_dock->setWidget(right_container);
addDockWidget(Qt::RightDockWidgetArea, video_dock);
}
QComboBox *MainWindow::createDBCSelector() { QComboBox *MainWindow::createDBCSelector() {
QComboBox *c = new QComboBox(this); QComboBox *c = new QComboBox(this);
c->setEditable(true); c->setEditable(true);
@ -202,7 +197,7 @@ void MainWindow::loadDBCFromClipboard() {
void MainWindow::loadDBCFromFingerprint() { void MainWindow::loadDBCFromFingerprint() {
auto fingerprint = can->carFingerprint(); auto fingerprint = can->carFingerprint();
fingerprint_label->setText(fingerprint.isEmpty() ? tr("Unknown Car") : fingerprint); video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPINT: %2").arg(can->routeName()).arg(fingerprint.isEmpty() ? tr("Unknown Car") : fingerprint));
if (!fingerprint.isEmpty()) { if (!fingerprint.isEmpty()) {
auto dbc_name = fingerprint_to_dbc[fingerprint]; auto dbc_name = fingerprint_to_dbc[fingerprint];
if (dbc_name != QJsonValue::Undefined) { if (dbc_name != QJsonValue::Undefined) {
@ -254,7 +249,6 @@ void MainWindow::dockCharts(bool dock) {
floating_window->setLayout(new QVBoxLayout()); floating_window->setLayout(new QVBoxLayout());
floating_window->layout()->addWidget(charts_widget); floating_window->layout()->addWidget(charts_widget);
floating_window->installEventFilter(charts_widget); floating_window->installEventFilter(charts_widget);
floating_window->setMinimumSize(QGuiApplication::primaryScreen()->size() / 2);
floating_window->showMaximized(); floating_window->showMaximized();
} }
} }
@ -274,7 +268,8 @@ void MainWindow::closeEvent(QCloseEvent *event) {
if (floating_window) if (floating_window)
floating_window->deleteLater(); floating_window->deleteLater();
settings.splitter_state = splitter->saveState(); settings.geometry = saveGeometry();
settings.window_state = saveState();
settings.save(); settings.save();
QWidget::closeEvent(event); QWidget::closeEvent(event);
} }
@ -283,3 +278,8 @@ void MainWindow::setOption() {
SettingsDlg dlg(this); SettingsDlg dlg(this);
dlg.exec(); dlg.exec();
} }
void MainWindow::findSimilarBits() {
FindSimilarBitsDlg dlg(this);
dlg.exec();
}

@ -1,16 +1,17 @@
#pragma once #pragma once
#include <QComboBox> #include <QComboBox>
#include <QDockWidget>
#include <QJsonDocument> #include <QJsonDocument>
#include <QMainWindow> #include <QMainWindow>
#include <QProgressBar> #include <QProgressBar>
#include <QSplitter>
#include <QStatusBar> #include <QStatusBar>
#include "tools/cabana/chartswidget.h" #include "tools/cabana/chartswidget.h"
#include "tools/cabana/detailwidget.h" #include "tools/cabana/detailwidget.h"
#include "tools/cabana/messageswidget.h" #include "tools/cabana/messageswidget.h"
#include "tools/cabana/videowidget.h" #include "tools/cabana/videowidget.h"
#include "tools/cabana/tools/findsimilarbits.h"
class MainWindow : public QMainWindow { class MainWindow : public QMainWindow {
Q_OBJECT Q_OBJECT
@ -34,6 +35,7 @@ signals:
protected: protected:
void createActions(); void createActions();
void createDockWindows();
QComboBox *createDBCSelector(); QComboBox *createDBCSelector();
void createStatusBar(); void createStatusBar();
void createShortcuts(); void createShortcuts();
@ -41,16 +43,16 @@ protected:
void DBCFileChanged(); void DBCFileChanged();
void updateDownloadProgress(uint64_t cur, uint64_t total, bool success); void updateDownloadProgress(uint64_t cur, uint64_t total, bool success);
void setOption(); void setOption();
void findSimilarBits();
VideoWidget *video_widget; VideoWidget *video_widget;
QDockWidget *video_dock;
MessagesWidget *messages_widget; MessagesWidget *messages_widget;
DetailWidget *detail_widget; DetailWidget *detail_widget;
ChartsWidget *charts_widget; ChartsWidget *charts_widget;
QSplitter *splitter;
QWidget *floating_window = nullptr; QWidget *floating_window = nullptr;
QVBoxLayout *r_layout; QVBoxLayout *r_layout;
QProgressBar *progress_bar; QProgressBar *progress_bar;
QLabel *fingerprint_label;
QJsonDocument fingerprint_to_dbc; QJsonDocument fingerprint_to_dbc;
QComboBox *dbc_combo; QComboBox *dbc_combo;
}; };

@ -19,7 +19,8 @@ void Settings::save() {
s.setValue("chart_height", chart_height); s.setValue("chart_height", chart_height);
s.setValue("max_chart_x_range", max_chart_x_range); s.setValue("max_chart_x_range", max_chart_x_range);
s.setValue("last_dir", last_dir); s.setValue("last_dir", last_dir);
s.setValue("splitter_state", splitter_state); s.setValue("window_state", window_state);
s.setValue("geometry", geometry);
} }
void Settings::load() { void Settings::load() {
@ -29,7 +30,8 @@ void Settings::load() {
chart_height = s.value("chart_height", 200).toInt(); chart_height = s.value("chart_height", 200).toInt();
max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt(); max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt();
last_dir = s.value("last_dir", QDir::homePath()).toString(); last_dir = s.value("last_dir", QDir::homePath()).toString();
splitter_state = s.value("splitter_state").toByteArray(); window_state = s.value("window_state").toByteArray();
geometry = s.value("geometry").toByteArray();
} }
// SettingsDlg // SettingsDlg

@ -18,7 +18,7 @@ public:
int chart_height = 200; int chart_height = 200;
int max_chart_x_range = 3 * 60; // 3 minutes int max_chart_x_range = 3 * 60; // 3 minutes
QString last_dir; QString last_dir;
QByteArray splitter_state; QByteArray window_state, geometry;
signals: signals:
void changed(); void changed();

@ -0,0 +1,115 @@
#include "tools/cabana/tools/findsimilarbits.h"
#include <QHeaderView>
#include <QHBoxLayout>
#include <QIntValidator>
#include <QLabel>
#include <QPushButton>
#include <QRadioButton>
#include "tools/cabana/canmessages.h"
#include "tools/cabana/dbcmanager.h"
FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Find similar bits"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
QHBoxLayout *form_layout = new QHBoxLayout();
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;
}
for (uint8_t bus : bus_set) {
bus_combo->addItem(QString::number(bus));
}
bus_combo->model()->sort(0);
bus_combo->setCurrentIndex(0);
form_layout->addWidget(new QLabel("Bus"));
form_layout->addWidget(bus_combo);
bit_combo = new QComboBox(this);
bit_combo->addItems({"0", "1"});
bit_combo->setCurrentIndex(1);
form_layout->addWidget(new QLabel("Bit"));
form_layout->addWidget(bit_combo);
min_msgs = new QLineEdit(this);
min_msgs->setValidator(new QIntValidator(this));
min_msgs->setText("100");
form_layout->addWidget(new QLabel("Min msg count"));
form_layout->addWidget(min_msgs);
search_btn = new QPushButton(tr("&Find"), this);
form_layout->addWidget(search_btn);
form_layout->addStretch(1);
main_layout->addLayout(form_layout);
table = new QTableWidget(this);
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->horizontalHeader()->setStretchLastSection(true);
main_layout->addWidget(table);
setMinimumSize({700, 500});
QObject::connect(search_btn, &QPushButton::clicked, this, &FindSimilarBitsDlg::find);
}
void FindSimilarBitsDlg::find() {
search_btn->setEnabled(false);
table->clear();
auto msg_mismatched = calcBits(bus_combo->currentText().toUInt(), bit_combo->currentIndex(), min_msgs->text().toInt());
table->setRowCount(msg_mismatched.size());
table->setColumnCount(6);
table->setHorizontalHeaderLabels({"address", "byte idx", "bit idx", "mismatches", "total", "perc%"});
for (int i = 0; i < msg_mismatched.size(); ++i) {
auto &m = msg_mismatched[i];
table->setItem(i, 0, new QTableWidgetItem(QString("%1").arg(m.address, 1, 16)));
table->setItem(i, 1, new QTableWidgetItem(QString::number(m.byte_idx)));
table->setItem(i, 2, new QTableWidgetItem(QString::number(m.bit_idx)));
table->setItem(i, 3, new QTableWidgetItem(QString::number(m.mismatches)));
table->setItem(i, 4, new QTableWidgetItem(QString::number(m.total)));
table->setItem(i, 5, new QTableWidgetItem(QString::number(m.perc)));
}
search_btn->setEnabled(true);
}
QList<FindSimilarBitsDlg::mismatched_struct> FindSimilarBitsDlg::calcBits(uint8_t bus, int bit_to_find, int min_msgs_cnt) {
QHash<uint32_t, QVector<uint32_t>> mismatches;
QHash<uint32_t, uint32_t> msg_count;
auto events = can->events();
for (auto e : *events) {
if (e->which == cereal::Event::Which::CAN) {
for (const auto &c : e->event.getCan()) {
if (c.getSrc() == bus) {
uint32_t address = c.getAddress();
++msg_count[address];
auto &mismatched = mismatches[address];
const auto dat = c.getDat();
if (mismatched.size() < dat.size() * 8) {
mismatched.resize(dat.size() * 8);
}
for (int i = 0; i < dat.size(); ++i) {
for (int j = 0; j < 8; ++j) {
int bit = ((dat[i] >> (7 - j)) & 1) != 0;
mismatched[i * 8 + j] += (bit != bit_to_find);
}
}
}
}
}
}
QList<mismatched_struct> result;
result.reserve(mismatches.size());
for (auto it = mismatches.begin(); it != mismatches.end(); ++it) {
if (auto cnt = msg_count[it.key()]; cnt > min_msgs_cnt) {
auto &mismatched = it.value();
for (int i = 0; i < mismatched.size(); ++i) {
if (uint32_t perc = (mismatched[i] / (double)cnt) * 100; perc < 50) {
result.push_back({it.key(), (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc});
}
}
}
}
std::sort(result.begin(), result.end(), [](auto &l, auto &r) { return l.perc > r.perc; });
return result;
}

@ -0,0 +1,23 @@
#pragma once
#include <QComboBox>
#include <QDialog>
#include <QLineEdit>
#include <QTableWidget>
class FindSimilarBitsDlg : public QDialog {
public:
FindSimilarBitsDlg(QWidget *parent);
private:
struct mismatched_struct {
uint32_t address, byte_idx, bit_idx, mismatches, total, perc;
};
QList<mismatched_struct> calcBits(uint8_t bus, int bit_to_find, int min_msgs_cnt);
void find();
QTableWidget *table;
QComboBox *bus_combo, *bit_combo;
QPushButton *search_btn;
QLineEdit *min_msgs;
};

@ -17,10 +17,17 @@ inline QString formatTime(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");
} }
VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
QHBoxLayout *containter_layout = new QHBoxLayout(this);
QVBoxLayout *main_layout = new QVBoxLayout();
main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setContentsMargins(0, 0, 0, 0);
containter_layout->addStretch(1);
containter_layout->addLayout(main_layout);
containter_layout->addStretch(1);
cam_widget = new CameraWidget("camerad", can->visionStreamType(), false, this); cam_widget = new CameraWidget("camerad", can->visionStreamType(), false, this);
cam_widget->setFixedSize(parent->width(), parent->width() / 1.596); cam_widget->setFixedSize(parent->width(), parent->width() / 1.596);
main_layout->addWidget(cam_widget); main_layout->addWidget(cam_widget);
@ -56,8 +63,6 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
} }
main_layout->addLayout(control_layout); main_layout->addLayout(control_layout);
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QObject::connect(can, &CANMessages::updated, this, &VideoWidget::updateState); QObject::connect(can, &CANMessages::updated, this, &VideoWidget::updateState);
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); });
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); });

@ -35,7 +35,7 @@ private:
QSize thumbnail_size = {}; QSize thumbnail_size = {};
}; };
class VideoWidget : public QWidget { class VideoWidget : public QFrame {
Q_OBJECT Q_OBJECT
public: public:

@ -128,7 +128,7 @@ def imu_callback(imu, vehicle_state):
vehicle_state.bearing_deg = math.degrees(imu.compass) vehicle_state.bearing_deg = math.degrees(imu.compass)
dat = messaging.new_message('accelerometer') dat = messaging.new_message('accelerometer')
dat.accelerometer.sensor = 4 dat.accelerometer.sensor = 4
dat.accelerometer.type = 0x1 dat.accelerometer.type = 0x10
dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp
dat.accelerometer.init('acceleration') dat.accelerometer.init('acceleration')
dat.accelerometer.acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z] dat.accelerometer.acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z]

@ -82,7 +82,7 @@ function install_ubuntu_common_requirements() {
} }
# Install Ubuntu 22.04 LTS packages # Install Ubuntu 22.04 LTS packages
function install_ubuntu_jammy_requirements() { function install_ubuntu_lts_latest_requirements() {
install_ubuntu_common_requirements install_ubuntu_common_requirements
$SUDO apt-get install -y --no-install-recommends \ $SUDO apt-get install -y --no-install-recommends \
@ -108,7 +108,10 @@ if [ -f "/etc/os-release" ]; then
source /etc/os-release source /etc/os-release
case "$VERSION_CODENAME" in case "$VERSION_CODENAME" in
"jammy") "jammy")
install_ubuntu_jammy_requirements install_ubuntu_lts_latest_requirements
;;
"kinetic")
install_ubuntu_lts_latest_requirements
;; ;;
"focal") "focal")
install_ubuntu_focal_requirements install_ubuntu_focal_requirements
@ -120,8 +123,8 @@ if [ -f "/etc/os-release" ]; then
if [[ ! $REPLY =~ ^[Yy]$ ]]; then if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1 exit 1
fi fi
if [ "$UBUNTU_CODENAME" = "jammy" ]; then if [ "$UBUNTU_CODENAME" = "jammy" ] || [ "$UBUNTU_CODENAME" = "kinetic" ]; then
install_ubuntu_jammy_requirements install_ubuntu_lts_latest_requirements
else else
install_ubuntu_focal_requirements install_ubuntu_focal_requirements
fi fi

@ -69,11 +69,13 @@ else
RUN="poetry run" RUN="poetry run"
fi fi
echo "pre-commit hooks install..." if [ "$(uname)" != "Darwin" ]; then
shopt -s nullglob echo "pre-commit hooks install..."
for f in .pre-commit-config.yaml */.pre-commit-config.yaml; do shopt -s nullglob
for f in .pre-commit-config.yaml */.pre-commit-config.yaml; do
cd $DIR/$(dirname $f) cd $DIR/$(dirname $f)
if [ -e ".git" ]; then if [ -e ".git" ]; then
$RUN pre-commit install $RUN pre-commit install
fi fi
done done
fi

Loading…
Cancel
Save