Merge remote-tracking branch 'upstream/master' into sportage-2023-port

# Conflicts:
#	opendbc
#	panda
pull/26342/head^2
Jason Wen 3 years ago
commit cf873633cb
No known key found for this signature in database
GPG Key ID: 6AAEBEBBB33E2AD2
  1. 8
      .github/workflows/prebuilt.yaml
  2. 109
      .github/workflows/selfdrive_tests.yaml
  3. 8
      .github/workflows/setup/action.yaml
  4. 45
      .github/workflows/tools_tests.yaml
  5. 1
      .pre-commit-config.yaml
  6. 1
      SConstruct
  7. 2
      cereal
  8. 2
      opendbc
  9. 2
      panda
  10. 4
      release/build_devel.sh
  11. 68
      selfdrive/athena/tests/test_athenad.py
  12. 8
      selfdrive/car/car_helpers.py
  13. 14
      selfdrive/car/fw_versions.py
  14. 3
      selfdrive/car/gm/carstate.py
  15. 29
      selfdrive/car/hyundai/carcontroller.py
  16. 29
      selfdrive/car/hyundai/tests/test_hyundai.py
  17. 48
      selfdrive/car/hyundai/values.py
  18. 30
      selfdrive/controls/controlsd.py
  19. 3
      selfdrive/controls/tests/test_startup.py
  20. 2
      selfdrive/debug/check_timings.py
  21. 2
      selfdrive/loggerd/loggerd.cc
  22. 4
      selfdrive/modeld/models/dmonitoring_model.current
  23. 4
      selfdrive/modeld/models/dmonitoring_model.onnx
  24. 4
      selfdrive/modeld/models/dmonitoring_model_q.dlc
  25. 4
      selfdrive/modeld/models/supercombo.onnx
  26. 0
      selfdrive/modeld/tests/__init__.py
  27. 0
      selfdrive/modeld/tests/dmon_lag/repro.cc
  28. 0
      selfdrive/modeld/tests/snpe_benchmark/.gitignore
  29. 1
      selfdrive/modeld/tests/snpe_benchmark/benchmark.cc
  30. 0
      selfdrive/modeld/tests/snpe_benchmark/benchmark.sh
  31. 0
      selfdrive/modeld/tests/test_modeld.py
  32. 0
      selfdrive/modeld/tests/tf_test/build.sh
  33. 0
      selfdrive/modeld/tests/tf_test/main.cc
  34. 0
      selfdrive/modeld/tests/tf_test/pb_loader.py
  35. 0
      selfdrive/modeld/tests/timing/benchmark.py
  36. 21
      selfdrive/monitoring/driver_monitor.py
  37. 2
      selfdrive/test/process_replay/model_replay_ref_commit
  38. 2
      selfdrive/test/process_replay/ref_commit
  39. 23
      selfdrive/test/test_onroad.py
  40. 4
      selfdrive/ui/qt/offroad/driverview.cc
  41. 2
      selfdrive/ui/qt/offroad/driverview.h
  42. 2
      selfdrive/ui/qt/offroad/wifiManager.cc
  43. 55
      selfdrive/ui/qt/onroad.cc
  44. 7
      selfdrive/ui/qt/onroad.h
  45. 26
      selfdrive/ui/qt/widgets/cameraview.cc
  46. 6
      selfdrive/ui/qt/widgets/cameraview.h
  47. 2
      selfdrive/ui/translations/main_ar.ts
  48. 46
      selfdrive/ui/translations/main_ja.ts
  49. 50
      selfdrive/ui/translations/main_ko.ts
  50. 2
      selfdrive/ui/translations/main_nl.ts
  51. 2
      selfdrive/ui/translations/main_pl.ts
  52. 46
      selfdrive/ui/translations/main_pt-BR.ts
  53. 2
      selfdrive/ui/translations/main_th.ts
  54. 46
      selfdrive/ui/translations/main_zh-CHS.ts
  55. 46
      selfdrive/ui/translations/main_zh-CHT.ts
  56. 8
      selfdrive/ui/watch3.cc
  57. 1
      selfdrive/updated.py
  58. 2
      system/hardware/tici/test_power_draw.py
  59. 2
      tools/cabana/SConscript
  60. 184
      tools/cabana/binaryview.cc
  61. 72
      tools/cabana/binaryview.h
  62. 3
      tools/cabana/cabana.cc
  63. 5
      tools/cabana/canmessages.cc
  64. 2
      tools/cabana/canmessages.h
  65. 133
      tools/cabana/chartswidget.cc
  66. 27
      tools/cabana/chartswidget.h
  67. 34
      tools/cabana/dbcmanager.cc
  68. 8
      tools/cabana/dbcmanager.h
  69. 202
      tools/cabana/detailwidget.cc
  70. 46
      tools/cabana/detailwidget.h
  71. 61
      tools/cabana/historylog.cc
  72. 13
      tools/cabana/historylog.h
  73. 18
      tools/cabana/mainwin.cc
  74. 1
      tools/cabana/mainwin.h
  75. 73
      tools/cabana/messageswidget.cc
  76. 13
      tools/cabana/messageswidget.h
  77. 82
      tools/cabana/signaledit.cc
  78. 29
      tools/cabana/signaledit.h
  79. 25
      tools/cabana/videowidget.cc
  80. 5
      tools/cabana/videowidget.h

@ -2,7 +2,6 @@ name: prebuilt
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:
env:
@ -11,9 +10,7 @@ env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
jobs:
build_prebuilt:
@ -37,8 +34,7 @@ jobs:
- name: Build Docker image
run: |
eval "$BUILD"
docker pull $DOCKER_REGISTRY/$IMAGE_NAME:latest || true
docker build --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f Dockerfile.openpilot .
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f Dockerfile.openpilot .
- name: Push to container registry
run: |
$DOCKER_LOGIN

@ -18,15 +18,12 @@ env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c
BUILD_CL: |
docker pull $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c
UNIT_TEST: coverage run --append -m unittest discover
@ -41,13 +38,10 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: true
- name: Build devel
run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
- uses: ./.github/workflows/setup
with:
save-cache: true
- name: Check submodules
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
run: release/check-submodules.sh
@ -72,18 +66,20 @@ jobs:
build_all:
name: build all
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: ./.github/workflows/setup
with:
save-cache: true
- name: Build openpilot with all flags
run: ${{ env.RUN }} "scons -j$(nproc) --extras && release/check-dirty.sh"
- name: Cleanup scons cache
run: |
${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \
scons -j$(nproc) --extras --cache-populate"
scons -j$(nproc) --cache-populate"
#build_mac:
# name: build macos
@ -145,7 +141,7 @@ jobs:
docker_push:
name: docker push
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 22
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
needs: static_analysis # hack to ensure slow tests run first since this and static_analysis are fast
steps:
@ -154,12 +150,14 @@ jobs:
submodules: true
- name: Build Docker image
run: eval "$BUILD"
timeout-minutes: 13
- name: Push to container registry
run: |
$DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest
- name: Build CL Docker image
run: eval "$BUILD_CL"
timeout-minutes: 4
- name: Push to container registry
run: |
$DOCKER_LOGIN
@ -168,7 +166,7 @@ jobs:
static_analysis:
name: static analysis
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 20
steps:
- uses: actions/checkout@v3
with:
@ -176,21 +174,23 @@ jobs:
- name: Build Docker image
run: eval "$BUILD"
- name: pre-commit
timeout-minutes: 5
run: ${{ env.RUN }} "pre-commit run --all"
valgrind:
name: valgrind
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 20
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: ./.github/workflows/setup
- name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Run valgrind
run: |
${{ env.RUN }} "scons -j$(nproc) && \
python selfdrive/test/test_valgrind_replay.py"
${{ env.RUN }} "python selfdrive/test/test_valgrind_replay.py"
- name: Print logs
if: always()
run: cat selfdrive/test/valgrind_logs.txt
@ -198,16 +198,18 @@ jobs:
unit_tests:
name: unit tests
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: ./.github/workflows/setup
- name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Run unit tests
timeout-minutes: 15
run: |
${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \
scons -j$(nproc) && \
$UNIT_TEST common && \
$UNIT_TEST opendbc/can && \
$UNIT_TEST selfdrive/boardd && \
@ -220,7 +222,6 @@ jobs:
$UNIT_TEST selfdrive/athena && \
$UNIT_TEST selfdrive/thermald && \
$UNIT_TEST system/hardware/tici && \
$UNIT_TEST selfdrive/modeld && \
$UNIT_TEST tools/lib/tests && \
./selfdrive/ui/tests/create_test_translations.sh && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
@ -239,7 +240,7 @@ jobs:
process_replay:
name: process replay
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 25
steps:
- uses: actions/checkout@v3
with:
@ -251,10 +252,12 @@ jobs:
with:
path: /tmp/comma_download_cache
key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit') }}
- name: Build openpilot
run: |
${{ env.RUN }} "scons -j$(nproc)"
- name: Run replay
run: |
${{ env.RUN }} "scons -j$(nproc) && \
CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
coverage xml"
- name: Print diff
if: always()
@ -268,15 +271,14 @@ jobs:
- name: Upload reference logs
if: ${{ failure() && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }}
run: |
${{ env.RUN }} "scons -j$(nproc) && \
CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
${{ env.RUN }} "CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2
model_replay_onnx:
name: model replay onnx
test_modeld:
name: model tests
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 20
steps:
- uses: actions/checkout@v3
with:
@ -285,29 +287,41 @@ jobs:
- name: Build Docker image
# Sim docker is needed to get the OpenCL drivers
run: eval "$BUILD_CL"
- name: Run replay
- name: Build openpilot
run: |
${{ env.RUN }} "scons -j$(nproc)"
- name: Run model replay with ONNX
timeout-minutes: 2
run: |
${{ env.RUN_CL }} "ONNXCPU=1 CI=1 coverage run selfdrive/test/process_replay/model_replay.py && \
coverage xml"
- name: Run unit tests
timeout-minutes: 5
run: |
${{ env.RUN_CL }} "scons -j$(nproc) && \
ONNXCPU=1 CI=1 coverage run \
selfdrive/test/process_replay/model_replay.py -j$(nproc) && \
${{ env.RUN_CL }} "$UNIT_TEST selfdrive/modeld && \
coverage xml"
- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2
test_longitudinal:
name: longitudinal
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 20
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: ./.github/workflows/setup
- name: Build openpilot
run: |
${{ env.RUN }} "scons -j$(nproc)"
- name: Test longitudinal
run: |
${{ env.RUN }} "mkdir -p selfdrive/test/out && \
scons -j$(nproc) && \
cd selfdrive/test/longitudinal_maneuvers && \
coverage run ./test_longitudinal.py && \
coverage xml"
timeout-minutes: 2
- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2
- uses: actions/upload-artifact@v2
@ -320,7 +334,7 @@ jobs:
test_cars:
name: cars
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
@ -336,10 +350,12 @@ jobs:
with:
path: /tmp/comma_download_cache
key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }}
- name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Test car models
timeout-minutes: 12
run: |
${{ env.RUN }} "scons -j$(nproc) && \
coverage run -m pytest selfdrive/car/tests/test_models.py && \
${{ env.RUN }} "coverage run -m pytest selfdrive/car/tests/test_models.py && \
coverage xml && \
chmod -R 777 /tmp/comma_download_cache"
env:
@ -348,29 +364,10 @@ jobs:
- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2
docs:
name: build docs
runs-on: ubuntu-20.04
timeout-minutes: 50
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Build docker container
run: |
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker pull $DOCKER_REGISTRY/openpilot-docs:latest || true
DOCKER_BUILDKIT=1 docker build --cache-from $DOCKER_REGISTRY/openpilot-docs:latest -t $DOCKER_REGISTRY/openpilot-docs:latest -f docs/docker/Dockerfile .
- name: Push docker container
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
run: |
$DOCKER_LOGIN
docker push $DOCKER_REGISTRY/openpilot-docs:latest
car_docs_diff:
name: comment on PR with car docs diff
name: PR comments
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 20
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3

@ -15,9 +15,9 @@ runs:
# build cache
- id: date
shell: bash
run: echo "::set-output name=date::$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d')"
run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
- shell: bash
run: echo "${{ steps.date.outputs.date }}"
run: echo "$CACHE_COMMIT_DATE"
- shell: bash
run: echo "CACHE_SKIP_SAVE=true" >> $GITHUB_ENV
if: github.ref != 'refs/heads/master' || inputs.save-cache == 'false'
@ -27,9 +27,9 @@ runs:
uses: actions/cache@03e00da99d75a2204924908e1cca7902cafce66b
with:
path: /tmp/scons_cache
key: scons-${{ steps.date.outputs.date }}-${{ github.sha }}
key: scons-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
restore-keys: |
scons-${{ steps.date.outputs.date }}-
scons-${{ env.CACHE_COMMIT_DATE }}-
scons-
# build our docker image

@ -2,6 +2,8 @@ name: tools
on:
push:
branches-ignore:
- 'testing-closet*'
pull_request:
concurrency:
@ -15,21 +17,20 @@ env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c
BUILD_CL: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base_cl) || true
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e \
GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/comma_download_cache:/tmp/comma_download_cache $BASE_IMAGE /bin/sh -c
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c
jobs:
plotjuggler:
name: plotjuggler
runs-on: ubuntu-20.04
timeout-minutes: 30
timeout-minutes: 20
steps:
- uses: actions/checkout@v3
with:
@ -37,6 +38,7 @@ jobs:
- name: Build Docker image
run: eval "$BUILD"
- name: Unit test
timeout-minutes: 2
run: |
${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \
apt-get update && \
@ -47,7 +49,7 @@ jobs:
simulator:
name: simulator
runs-on: ubuntu-20.04
timeout-minutes: 50
timeout-minutes: 30
env:
IMAGE_NAME: openpilot-sim
if: github.repository == 'commaai/openpilot'
@ -61,12 +63,29 @@ jobs:
run: eval "$BUILD"
- name: Build base cl image
run: eval "$BUILD_CL"
- name: Pull latest simulator image
run: docker pull $DOCKER_REGISTRY/$IMAGE_NAME:latest || true
- name: Build simulator image
run: docker build --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim .
run: DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim .
- name: Push to container registry
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
run: |
$DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest
docs:
name: build docs
runs-on: ubuntu-20.04
timeout-minutes: 25
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Build docker container
run: |
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/openpilot-docs:latest -t $DOCKER_REGISTRY/openpilot-docs:latest -f docs/docker/Dockerfile .
- name: Push docker container
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
run: |
$DOCKER_LOGIN
docker push $DOCKER_REGISTRY/openpilot-docs:latest

@ -53,6 +53,7 @@ repos:
types: [python]
exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)'
args:
- -j0
- -rn
- -sn
- --rcfile=.pylintrc

@ -327,6 +327,7 @@ qt_flags = [
qt_env['CXXFLAGS'] += qt_flags
qt_env['LIBPATH'] += ['#selfdrive/ui']
qt_env['LIBS'] = qt_libs
qt_env['QT_MOCHPREFIX'] = cache_dir + '/moc_files/moc_'
if GetOption("clazy"):
checks = [

@ -1 +1 @@
Subproject commit 5766e645f2ee2a131b145fb1ea9e3b7c55a4a740
Subproject commit 107048c83ec2f488286a1be314e7aece0a20a6b1

@ -1 +1 @@
Subproject commit 437df00bc8bc4f522a3828cbe691212ccc7ba9c6
Subproject commit 8f245e6ef5e25814d8e6e1f096221f6dfeefe86b

@ -1 +1 @@
Subproject commit 87b01b89068375e89c4eaf9d155ff3dc0651006d
Subproject commit b5362b9c2770b09552d5533cf776b4c04b09b464

@ -22,8 +22,8 @@ pre-commit uninstall || true
echo "[-] bringing master-ci and devel in sync T=$SECONDS"
cd $TARGET_DIR
git fetch origin master-ci
git fetch origin devel
git fetch --depth 1 origin master-ci
git fetch --depth 1 origin devel
git checkout -f --track origin/master-ci
git reset --hard master-ci

@ -9,6 +9,7 @@ import threading
import queue
import unittest
from datetime import datetime, timedelta
from typing import Optional
from multiprocessing import Process
from pathlib import Path
@ -46,12 +47,26 @@ class TestAthenadMethods(unittest.TestCase):
else:
os.unlink(p)
def wait_for_upload(self):
# *** test helpers ***
@staticmethod
def _wait_for_upload():
now = time.time()
while time.time() - now < 5:
if athenad.upload_queue.qsize() == 0:
break
@staticmethod
def _create_file(file: str, parent: Optional[str] = None) -> str:
fn = os.path.join(athenad.ROOT if parent is None else parent, file)
os.makedirs(os.path.dirname(fn), exist_ok=True)
Path(fn).touch()
return fn
# *** test cases ***
def test_echo(self):
assert dispatcher["echo"]("bob") == "bob"
@ -85,9 +100,7 @@ class TestAthenadMethods(unittest.TestCase):
filenames = ['qlog', 'qcamera.ts', 'rlog', 'fcamera.hevc', 'ecamera.hevc', 'dcamera.hevc']
files = [f'{route}--{s}/{f}' for s in segments for f in filenames]
for file in files:
fn = os.path.join(athenad.ROOT, file)
os.makedirs(os.path.dirname(fn), exist_ok=True)
Path(fn).touch()
self._create_file(file)
resp = dispatcher["listDataDirectory"]()
self.assertTrue(resp, 'list empty!')
@ -121,16 +134,14 @@ class TestAthenadMethods(unittest.TestCase):
self.assertCountEqual(resp, expected)
def test_strip_bz2_extension(self):
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
fn = self._create_file('qlog.bz2')
if fn.endswith('.bz2'):
self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4])
@with_http_server
def test_do_upload(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url="http://localhost:1238", headers={}, created_at=int(time.time()*1000), id='')
with self.assertRaises(requests.exceptions.ConnectionError):
@ -142,8 +153,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server
def test_uploadFileToUrl(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
fn = self._create_file('qlog.bz2')
resp = dispatcher["uploadFileToUrl"]("qlog.bz2", f"{host}/qlog.bz2", {})
self.assertEqual(resp['enqueued'], 1)
@ -154,8 +164,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server
def test_uploadFileToUrl_duplicate(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
self._create_file('qlog.bz2')
url1 = f"{host}/qlog.bz2?sig=sig1"
dispatcher["uploadFileToUrl"]("qlog.bz2", url1, {})
@ -172,8 +181,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server
def test_upload_handler(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event()
@ -182,7 +190,7 @@ class TestAthenadMethods(unittest.TestCase):
athenad.upload_queue.put_nowait(item)
try:
self.wait_for_upload()
self._wait_for_upload()
time.sleep(0.1)
# TODO: verify that upload actually succeeded
@ -195,8 +203,7 @@ class TestAthenadMethods(unittest.TestCase):
def test_upload_handler_retry(self, host, mock_put):
for status, retry in ((500, True), (412, False)):
mock_put.return_value.status_code = status
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event()
@ -205,7 +212,7 @@ class TestAthenadMethods(unittest.TestCase):
athenad.upload_queue.put_nowait(item)
try:
self.wait_for_upload()
self._wait_for_upload()
time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0)
@ -217,8 +224,7 @@ class TestAthenadMethods(unittest.TestCase):
def test_upload_handler_timeout(self):
"""When an upload times out or fails to connect it should be placed back in the queue"""
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
item_no_retry = item._replace(retry_count=MAX_RETRY_COUNT)
@ -228,14 +234,14 @@ class TestAthenadMethods(unittest.TestCase):
try:
athenad.upload_queue.put_nowait(item_no_retry)
self.wait_for_upload()
self._wait_for_upload()
time.sleep(0.1)
# Check that upload with retry count exceeded is not put back
self.assertEqual(athenad.upload_queue.qsize(), 0)
athenad.upload_queue.put_nowait(item)
self.wait_for_upload()
self._wait_for_upload()
time.sleep(0.1)
# Check that upload item was put back in the queue with incremented retry count
@ -256,7 +262,7 @@ class TestAthenadMethods(unittest.TestCase):
thread = threading.Thread(target=athenad.upload_handler, args=(end_event,))
thread.start()
try:
self.wait_for_upload()
self._wait_for_upload()
time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 0)
@ -269,8 +275,7 @@ class TestAthenadMethods(unittest.TestCase):
ts = int(t_future.strftime("%s")) * 1000
# Item that would time out if actually uploaded
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=ts, id='', allow_cellular=True)
@ -279,7 +284,7 @@ class TestAthenadMethods(unittest.TestCase):
thread.start()
try:
athenad.upload_queue.put_nowait(item)
self.wait_for_upload()
self._wait_for_upload()
time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 0)
@ -292,8 +297,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server
def test_listUploadQueueCurrent(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2')
Path(fn).touch()
fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event()
@ -302,7 +306,7 @@ class TestAthenadMethods(unittest.TestCase):
try:
athenad.upload_queue.put_nowait(item)
self.wait_for_upload()
self._wait_for_upload()
items = dispatcher["listUploadQueue"]()
self.assertEqual(len(items), 1)
@ -405,9 +409,9 @@ class TestAthenadMethods(unittest.TestCase):
def test_get_logs_to_send_sorted(self):
fl = list()
for i in range(10):
fn = os.path.join(swaglog.SWAGLOG_DIR, f'swaglog.{i:010}')
Path(fn).touch()
fl.append(os.path.basename(fn))
file = f'swaglog.{i:010}'
self._create_file(file, athenad.SWAGLOG_DIR)
fl.append(file)
# ensure the list is all logs except most recent
sl = athenad.get_logs_to_send_sorted()

@ -76,7 +76,7 @@ interfaces = load_interfaces(interface_names)
# **** for use live only ****
def fingerprint(logcan, sendcan):
def fingerprint(logcan, sendcan, num_pandas):
fixed_fingerprint = os.environ.get('FINGERPRINT', "")
skip_fw_query = os.environ.get('SKIP_FW_QUERY', False)
ecu_rx_addrs = set()
@ -100,7 +100,7 @@ def fingerprint(logcan, sendcan):
cloudlog.warning("Getting VIN & FW versions")
vin_rx_addr, vin = get_vin(logcan, sendcan, bus)
ecu_rx_addrs = get_present_ecus(logcan, sendcan)
car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs)
car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas)
cached = False
exact_fw_match, fw_candidates = match_fw_to_car(car_fw)
@ -173,8 +173,8 @@ def fingerprint(logcan, sendcan):
return car_fingerprint, finger, vin, car_fw, source, exact_match
def get_car(logcan, sendcan):
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan)
def get_car(logcan, sendcan, num_pandas=1):
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan, num_pandas)
if candidate is None:
cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints)

@ -194,14 +194,14 @@ def get_brand_ecu_matches(ecu_rx_addrs):
return brand_matches
def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=False, progress=False):
def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False):
"""Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""
all_car_fw = []
brand_matches = get_brand_ecu_matches(ecu_rx_addrs)
for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True):
car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, debug=debug, progress=progress)
car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, debug=debug, progress=progress)
all_car_fw.extend(car_fw)
# Try to match using FW returned from this brand only
matches = match_fw_to_car_exact(build_fw_dict(car_fw))
@ -211,7 +211,7 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=Fa
return all_car_fw
def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, debug=False, progress=False):
def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False):
versions = VERSIONS.copy()
# Each brand can define extra ECUs to query for data collection
@ -252,6 +252,10 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1,
for addr in tqdm(addrs, disable=not progress):
for addr_chunk in chunks(addr):
for brand, r in requests:
# Skip query if no panda available
if r.bus > num_pandas * 4 - 1:
continue
try:
addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and
(len(r.whitelist_ecus) == 0 or ecu_types[(b, a, s)] in r.whitelist_ecus)]
@ -292,6 +296,7 @@ if __name__ == "__main__":
args = parser.parse_args()
logcan = messaging.sub_sock('can')
pandaStates_sock = messaging.sub_sock('pandaStates')
sendcan = messaging.pub_sock('sendcan')
extra: Any = None
@ -305,6 +310,7 @@ if __name__ == "__main__":
extra = {"any": {"debug": extra}}
time.sleep(1.)
num_pandas = len(messaging.recv_one_retry(pandaStates_sock).pandaStates)
t = time.time()
print("Getting vin...")
@ -314,7 +320,7 @@ if __name__ == "__main__":
print()
t = time.time()
fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, debug=args.debug, progress=True)
fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, num_pandas=num_pandas, debug=args.debug, progress=True)
_, candidates = match_fw_to_car(fw_vers)
print()

@ -96,9 +96,7 @@ class CarState(CarStateBase):
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
if self.CP.networkLocation == NetworkLocation.fwdCamera:
ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS
ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0
ret.stockFcw = cam_cp.vl["ASCMActiveCruiseControlStatus"]["FCWAlert"] != 0
return ret
@ -110,7 +108,6 @@ class CarState(CarStateBase):
signals += [
("AEBCmdActive", "AEBCmd"),
("RollingCounter", "ASCMLKASteeringCmd"),
("FCWAlert", "ASCMActiveCruiseControlStatus"),
("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"),
]
checks += [

@ -90,13 +90,27 @@ class CarController:
addr, bus = 0x730, 5
can_sends.append([addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus])
# >90 degree steering fault prevention
# Count up to MAX_ANGLE_FRAMES, at which point we need to cut torque to avoid a steering fault
if CC.latActive and abs(CS.out.steeringAngleDeg) >= MAX_ANGLE:
self.angle_limit_counter += 1
else:
self.angle_limit_counter = 0
# Cut steer actuation bit for two frames and hold torque with induced temporary fault
torque_fault = CC.latActive and self.angle_limit_counter > MAX_ANGLE_FRAMES
lat_active = CC.latActive and not torque_fault
if self.angle_limit_counter >= MAX_ANGLE_FRAMES + MAX_ANGLE_CONSECUTIVE_FRAMES:
self.angle_limit_counter = 0
# CAN-FD platforms
if self.CP.carFingerprint in CANFD_CAR:
hda2 = self.CP.flags & HyundaiFlags.CANFD_HDA2
hda2_long = hda2 and self.CP.openpilotLongitudinalControl
# steering control
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, CC.enabled, CC.latActive, apply_steer))
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, CC.enabled, lat_active, apply_steer))
# disable LFA on HDA2
if self.frame % 5 == 0 and hda2:
@ -132,19 +146,6 @@ class CarController:
can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.RES_ACCEL))
self.last_button_frame = self.frame
else:
# Count up to MAX_ANGLE_FRAMES, at which point we need to cut torque to avoid a steering fault
if CC.latActive and abs(CS.out.steeringAngleDeg) >= MAX_ANGLE:
self.angle_limit_counter += 1
else:
self.angle_limit_counter = 0
# Cut steer actuation bit for two frames and hold torque with induced temporary fault
torque_fault = CC.latActive and self.angle_limit_counter > MAX_ANGLE_FRAMES
lat_active = CC.latActive and not torque_fault
if self.angle_limit_counter >= MAX_ANGLE_FRAMES + MAX_ANGLE_CONSECUTIVE_FRAMES:
self.angle_limit_counter = 0
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, lat_active,
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,

@ -0,0 +1,29 @@
#!/usr/bin/env python3
import unittest
from cereal import car
from selfdrive.car.car_helpers import get_interface_attr
from selfdrive.car.fw_versions import FW_QUERY_CONFIGS
from selfdrive.car.hyundai.values import CANFD_CAR
Ecu = car.CarParams.Ecu
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
VERSIONS = get_interface_attr("FW_VERSIONS", ignore_none=True)
class TestHyundaiFingerprint(unittest.TestCase):
def test_auxiliary_request_ecu_whitelist(self):
# Asserts only auxiliary Ecus can exist in database for CAN-FD cars
config = FW_QUERY_CONFIGS['hyundai']
whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus if r.bus > 3}
for car_model in CANFD_CAR:
ecus = {fw[0] for fw in VERSIONS['hyundai'][car_model].keys()}
ecus_not_in_whitelist = ecus - whitelisted_ecus
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_in_whitelist])
self.assertEqual(len(ecus_not_in_whitelist), 0, f'{car_model}: Car model has ECUs not in auxiliary request whitelists: {ecu_strings}')
if __name__ == "__main__":
unittest.main()

@ -300,10 +300,25 @@ FW_QUERY_CONFIG = FwQueryConfig(
Request(
[HYUNDAI_VERSION_REQUEST_LONG],
[HYUNDAI_VERSION_RESPONSE],
whitelist_ecus=[Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera],
),
Request(
[HYUNDAI_VERSION_REQUEST_MULTI],
[HYUNDAI_VERSION_RESPONSE],
whitelist_ecus=[Ecu.engine, Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar],
),
# CAN-FD queries
Request(
[HYUNDAI_VERSION_REQUEST_LONG],
[HYUNDAI_VERSION_RESPONSE],
whitelist_ecus=[Ecu.fwdRadar],
bus=4,
),
Request(
[HYUNDAI_VERSION_REQUEST_LONG],
[HYUNDAI_VERSION_RESPONSE],
whitelist_ecus=[Ecu.fwdCamera, Ecu.adas],
bus=5,
),
],
extra_ecus=[
@ -748,6 +763,7 @@ FW_VERSIONS = {
b'\xf1\x81640E0051\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00',
b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00',
b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104',
@ -770,6 +786,8 @@ FW_VERSIONS = {
b'\xf1\x87VDKLJ18675252DK6\x89vhgwwwwveVU\x88w\x87w\x99vgf\x97vXfgw_\xff\xc2\xfb\xf1\x89E25\x00\x00\x00\x00\x00\x00\x00\xf1\x82TCK0T33NB2',
b'\xf1\x87WAJTE17552812CH4vfFffvfVeT5DwvvVVdFeegeg\x88\x88o\xff\x1a]\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00TCK2T20NB1\x19\xd2\x00\x94',
b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S',
b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5',
b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5',
],
},
CAR.PALISADE: {
@ -1328,15 +1346,6 @@ FW_VERSIONS = {
],
},
CAR.KIA_EV6: {
(Ecu.abs, 0x7d1, None): [
b'\xf1\x00CV IEB \x02 101!\x10\x18 58520-CV100',
b'\xf1\x00CV IEB \x03 101!\x10\x18 58520-CV100',
b'\xf1\x8758520CV100\xf1\x00CV IEB \x02 101!\x10\x18 58520-CV100',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00CV1 MDPS R 1.00 1.04 57700-CV000 1B30',
b'\xf1\x00CV1 MDPS R 1.00 1.05 57700-CV000 2425',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ',
b'\xf1\x8799110CV000\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ',
@ -1348,16 +1357,6 @@ FW_VERSIONS = {
],
},
CAR.IONIQ_5: {
(Ecu.abs, 0x7d1, None): [
b'\xf1\x00NE1 IEB \x07 106!\x11) 58520-GI010',
b'\xf1\x8758520GI010\xf1\x00NE1 IEB \x07 106!\x11) 58520-GI010',
b'\xf1\x00NE1 IEB \x08 104!\x04\x05 58520-GI000',
b'\xf1\x8758520GI000\xf1\x00NE1 IEB \x08 104!\x04\x05 58520-GI000',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00NE MDPS R 1.00 1.06 57700GI000 4NEDR106',
b'\xf1\x8757700GI000 \xf1\x00NE MDPS R 1.00 1.06 57700GI000 4NEDR106',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ',
b'\xf1\x8799110GI000\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ',
@ -1371,15 +1370,8 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00NX4 MDPS C 1.00 1.01 56300-P0100 2228',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x87391312MND0',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x00PSBG2441 G19_Rev\x00\x00\x00SNX4T16XXHS01NS2lS\xdfa',
b'\xf1\x8795441-3D220\x00\xf1\x81G19_Rev\x00\x00\x00\xf1\x00PSBG2441 G19_Rev\x00\x00\x00SNX4T16XXHS01NS2lS\xdfa',
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ',
],
},
CAR.SANTA_CRUZ_1ST_GEN: {

@ -82,30 +82,30 @@ class Controls:
self.log_sock = messaging.sub_sock('androidLog')
if CI is None:
# wait for one pandaState and one CAN packet
print("Waiting for CAN messages...")
get_one_can(self.can_sock)
self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'])
else:
self.CI, self.CP = CI, CI.CP
params = Params()
self.joystick_mode = params.get_bool("JoystickDebugMode") or (self.CP.notCar and sm is None)
joystick_packet = ['testJoystick'] if self.joystick_mode else []
self.sm = sm
if self.sm is None:
ignore = []
ignore = ['testJoystick']
if SIMULATION:
ignore += ['driverCameraState', 'managerState']
if params.get_bool('WideCameraOnly'):
ignore += ['roadCameraState']
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters'] + self.camera_packets + joystick_packet,
ignore_alive=ignore, ignore_avg_freq=['radarState', 'longitudinalPlan'])
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', 'testJoystick'] + self.camera_packets,
ignore_alive=ignore, ignore_avg_freq=['radarState', 'longitudinalPlan', 'testJoystick'])
if CI is None:
# wait for one pandaState and one CAN packet
print("Waiting for CAN messages...")
get_one_can(self.can_sock)
num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates)
self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], num_pandas)
else:
self.CI, self.CP = CI, CI.CP
self.joystick_mode = params.get_bool("JoystickDebugMode") or (self.CP.notCar and sm is None)
# set alternative experiences from parameters
self.disengage_on_accelerator = params.get_bool("DisengageOnAccelerator")

@ -94,6 +94,9 @@ class TestStartup(unittest.TestCase):
time.sleep(2) # wait for controlsd to be ready
pm.send('can', can_list_to_can_capnp([[0, 0, b"", 0]]))
time.sleep(0.1)
msg = messaging.new_message('pandaStates', 1)
msg.pandaStates[0].pandaType = log.PandaState.PandaType.uno
pm.send('pandaStates', msg)

@ -19,7 +19,7 @@ if __name__ == "__main__":
for m in msgs:
ts[s].append(m.logMonoTime / 1e6)
if len(ts[s]):
if len(ts[s]) > 2:
d = np.diff(ts[s])
print(f"{s:25} {np.mean(d):.2f} {np.std(d):.2f} {np.max(d):.2f} {np.min(d):.2f}")
time.sleep(1)

@ -55,7 +55,7 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct
int bytes_count = 0;
// extract the message
capnp::FlatArrayMessageReader cmsg(kj::ArrayPtr<capnp::word>((capnp::word *)msg->getData(), msg->getSize()));
capnp::FlatArrayMessageReader cmsg(kj::ArrayPtr<capnp::word>((capnp::word *)msg->getData(), msg->getSize() / sizeof(capnp::word)));
auto event = cmsg.getRoot<cereal::Event>();
auto edata = (name == "driverEncodeData") ? event.getDriverEncodeData() :
((name == "wideRoadEncodeData") ? event.getWideRoadEncodeData() :

@ -1,2 +1,2 @@
ee8f830b-d6a1-42ef-9b1b-50fd0b2faae4
cac8f7b69d420506707ff7a19d573d5011ef2533
d1124586-761e-4e18-a771-6b5ef35124fe
6fec774f513a19e44d4316e46ad38277197d45ea

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:932e589e5cce66e5d9f48492426a33c74cd7f352a870d3ddafcede3e9156f30d
size 9157561
oid sha256:517262fa9f1ad3cc8049ad3722903f40356d87ea423ee5cf011226fb6cfc3d5b
size 16072278

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3587976a8b7e3be274fa86c2e2233e3e464cad713f5077c4394cd1ddd3c7c6c5
size 2636965
oid sha256:64b94659226a1e3c6594a13c2e5d029465d5803a5c3005121ec7217acdbbef20
size 4443461

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c4d37af666344af6bb218e0b939b1152ad3784c15ac79e37bcf0124643c8286a
size 58539563
oid sha256:022a830c39267f378f45204682060c93e3aa304bbd8cfa6b2dfe4fa8f419102d
size 56972617

@ -102,6 +102,7 @@ void get_testframe(int index, std::unique_ptr<zdl::DlSystem::ITensor> &input) {
fread(frame_buffer, length, 1, pFile);
// std::cout << *(frame_buffer+length/4-1) << std::endl;
std::copy(frame_buffer, frame_buffer+(length/4), input->begin());
fclose(pFile);
}
void SaveITensor(const std::string& path, const zdl::DlSystem::ITensor* tensor)

@ -29,10 +29,10 @@ class DRIVER_MONITOR_SETTINGS():
self._FACE_THRESHOLD = 0.7
self._EYE_THRESHOLD = 0.65
self._SG_THRESHOLD = 0.9
self._BLINK_THRESHOLD = 0.87
self._BLINK_THRESHOLD = 0.895
self._EE_THRESH11 = 0.75
self._EE_THRESH12 = 3.25
self._EE_THRESH11 = 0.275
self._EE_THRESH12 = 3.0
self._EE_THRESH21 = 0.01
self._EE_THRESH22 = 0.35
@ -207,11 +207,11 @@ class DriverStatus():
ee1_dist = self.eev1 > self.ee1_offseter.filtered_stat.M * self.settings._EE_THRESH12
else:
ee1_dist = self.eev1 > self.settings._EE_THRESH11
if self.ee2_calibrated:
ee2_dist = self.eev2 < self.ee2_offseter.filtered_stat.M * self.settings._EE_THRESH22
else:
ee2_dist = self.eev2 < self.settings._EE_THRESH21
if ee1_dist or ee2_dist:
# if self.ee2_calibrated:
# ee2_dist = self.eev2 < self.ee2_offseter.filtered_stat.M * self.settings._EE_THRESH22
# else:
# ee2_dist = self.eev2 < self.settings._EE_THRESH21
if ee1_dist:
distracted_types.append(DistractedType.DISTRACTED_E2E)
return distracted_types
@ -257,12 +257,11 @@ class DriverStatus():
self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD
self.blink.left_blink = driver_data.leftBlinkProb * (driver_data.leftEyeProb > self.settings._EYE_THRESHOLD) * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD)
self.blink.right_blink = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD)
self.eev1 = driver_data.notReadyProb[1]
self.eev1 = driver_data.notReadyProb[0]
self.eev2 = driver_data.readyProb[0]
self.distracted_types = self._get_distracted_types()
self.driver_distracted = (DistractedType.DISTRACTED_POSE in self.distracted_types or
DistractedType.DISTRACTED_BLINK in self.distracted_types) and \
self.driver_distracted = (DistractedType.DISTRACTED_E2E in self.distracted_types or DistractedType.DISTRACTED_POSE in self.distracted_types or DistractedType.DISTRACTED_BLINK in self.distracted_types) and \
driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std
self.driver_distraction_filter.update(self.driver_distracted)

@ -1 +1 @@
c171250d2cc013b3eca1cda4fb62f3d0dda28d4d
865885fc49b2766326568e5cc7ec06be8a3f6fad

@ -1 +1 @@
e5a86c14e2318f2dd218b3985cdbea6f875f7d83
6bb7d8baae51d88dd61f0baf561e386664ddd266

@ -120,8 +120,8 @@ class TestOnroad(unittest.TestCase):
if "DEBUG" in os.environ:
segs = filter(lambda x: os.path.exists(os.path.join(x, "rlog")), Path(ROOT).iterdir())
segs = sorted(segs, key=lambda x: x.stat().st_mtime)
print(segs[-1])
cls.lr = list(LogReader(os.path.join(segs[-1], "rlog")))
print(segs[-2])
cls.lr = list(LogReader(os.path.join(segs[-2], "rlog")))
return
# setup env
@ -187,6 +187,25 @@ class TestOnroad(unittest.TestCase):
big_logs = [f for f, n in cnt.most_common(3) if n / sum(cnt.values()) > 30.]
self.assertEqual(len(big_logs), 0, f"Log spam: {big_logs}")
def test_ui_timings(self):
result = "\n"
result += "------------------------------------------------\n"
result += "-------------- UI Draw Timing ------------------\n"
result += "------------------------------------------------\n"
ts = [m.uiDebug.drawTimeMillis for m in self.lr if m.which() == 'uiDebug']
result += f"min {min(ts):.2f}ms\n"
result += f"max {max(ts):.2f}ms\n"
result += f"std {np.std(ts):.2f}ms\n"
result += f"mean {np.mean(ts):.2f}ms\n"
result += "------------------------------------------------\n"
print(result)
self.assertGreater(len(ts), 20*50, "insufficient samples")
self.assertLess(max(ts), 30.)
self.assertLess(np.mean(ts), 10.)
self.assertLess(np.std(ts), 5.)
def test_cpu_usage(self):
proclogs = [m for m in self.lr if m.which() == 'procLog']
self.assertGreater(len(proclogs), service_list['procLog'].frequency * 45, "insufficient samples")

@ -12,11 +12,11 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) {
layout = new QStackedLayout(this);
layout->setStackingMode(QStackedLayout::StackAll);
cameraView = new CameraViewWidget("camerad", VISION_STREAM_DRIVER, true, this);
cameraView = new CameraWidget("camerad", VISION_STREAM_DRIVER, true, this);
layout->addWidget(cameraView);
scene = new DriverViewScene(this);
connect(cameraView, &CameraViewWidget::vipcThreadFrameReceived, scene, &DriverViewScene::frameUpdated);
connect(cameraView, &CameraWidget::vipcThreadFrameReceived, scene, &DriverViewScene::frameUpdated);
layout->addWidget(scene);
layout->setCurrentWidget(scene);
}

@ -42,7 +42,7 @@ protected:
void mouseReleaseEvent(QMouseEvent* e) override;
private:
CameraViewWidget *cameraView;
CameraWidget *cameraView;
DriverViewScene *scene;
QStackedLayout *layout;
};

@ -368,7 +368,7 @@ void WifiManager::updateGsmSettings(bool roaming, QString apn, bool metered) {
changes = true;
}
int meteredInt = metered ? NM_METERED_NO : NM_METERED_UNKNOWN;
int meteredInt = metered ? NM_METERED_UNKNOWN : NM_METERED_NO;
if (settings.value("connection").value("metered").toInt() != meteredInt) {
qWarning() << "Changing connection.metered to" << meteredInt;
settings["connection"]["metered"] = meteredInt;

@ -18,7 +18,7 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
stacked_layout->setStackingMode(QStackedLayout::StackAll);
main_layout->addLayout(stacked_layout);
nvg = new NvgWindow(VISION_STREAM_ROAD, this);
nvg = new AnnotatedCameraWidget(VISION_STREAM_ROAD, this);
QWidget * split_wrapper = new QWidget;
split = new QHBoxLayout(split_wrapper);
@ -27,7 +27,7 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
split->addWidget(nvg);
if (getenv("DUAL_CAMERA_VIEW")) {
CameraViewWidget *arCam = new CameraViewWidget("camerad", VISION_STREAM_ROAD, true, this);
CameraWidget *arCam = new CameraWidget("camerad", VISION_STREAM_ROAD, true, this);
split->insertWidget(0, arCam);
}
@ -173,14 +173,15 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
}
}
// NvgWindow
NvgWindow::NvgWindow(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraViewWidget("camerad", type, true, parent) {
AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) {
pm = std::make_unique<PubMaster, const std::initializer_list<const char *>>({"uiDebug"});
engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size});
dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size});
}
void NvgWindow::updateState(const UIState &s) {
void AnnotatedCameraWidget::updateState(const UIState &s) {
const int SET_SPEED_NA = 255;
const SubMaster &sm = *(s.sm);
@ -232,13 +233,13 @@ void NvgWindow::updateState(const UIState &s) {
}
if (s.scene.calibration_valid) {
CameraViewWidget::updateCalibration(s.scene.view_from_calib);
CameraWidget::updateCalibration(s.scene.view_from_calib);
} else {
CameraViewWidget::updateCalibration(DEFAULT_CALIBRATION);
CameraWidget::updateCalibration(DEFAULT_CALIBRATION);
}
}
void NvgWindow::drawHud(QPainter &p) {
void AnnotatedCameraWidget::drawHud(QPainter &p) {
p.save();
// Header gradient
@ -400,7 +401,11 @@ void NvgWindow::drawHud(QPainter &p) {
p.restore();
}
void NvgWindow::drawText(QPainter &p, int x, int y, const QString &text, int alpha) {
// Window that shows camera view and variety of
// info drawn on top
void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &text, int alpha) {
QRect real_rect = getTextRect(p, 0, text);
real_rect.moveCenter({x, y - real_rect.height() / 2});
@ -408,7 +413,7 @@ void NvgWindow::drawText(QPainter &p, int x, int y, const QString &text, int alp
p.drawText(real_rect.x(), real_rect.bottom(), text);
}
void NvgWindow::drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity) {
void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity) {
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius);
@ -417,8 +422,8 @@ void NvgWindow::drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, flo
}
void NvgWindow::initializeGL() {
CameraViewWidget::initializeGL();
void AnnotatedCameraWidget::initializeGL() {
CameraWidget::initializeGL();
qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION));
qInfo() << "OpenGL vendor:" << QString((const char*)glGetString(GL_VENDOR));
qInfo() << "OpenGL renderer:" << QString((const char*)glGetString(GL_RENDERER));
@ -428,8 +433,8 @@ void NvgWindow::initializeGL() {
setBackgroundColor(bg_colors[STATUS_DISENGAGED]);
}
void NvgWindow::updateFrameMat() {
CameraViewWidget::updateFrameMat();
void AnnotatedCameraWidget::updateFrameMat() {
CameraWidget::updateFrameMat();
UIState *s = uiState();
int w = width(), h = height();
@ -446,7 +451,7 @@ void NvgWindow::updateFrameMat() {
.translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]);
}
void NvgWindow::drawLaneLines(QPainter &painter, const UIState *s) {
void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
painter.save();
const UIScene &scene = s->scene;
@ -505,7 +510,7 @@ void NvgWindow::drawLaneLines(QPainter &painter, const UIState *s) {
painter.restore();
}
void NvgWindow::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd) {
void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd) {
painter.save();
const float speedBuff = 10.;
@ -541,11 +546,13 @@ void NvgWindow::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV
painter.restore();
}
void NvgWindow::paintGL() {
void AnnotatedCameraWidget::paintGL() {
const double start_draw_t = millis_since_boot();
UIState *s = uiState();
const cereal::ModelDataV2::Reader &model = (*s->sm)["modelV2"].getModelV2();
CameraViewWidget::setFrameId(model.getFrameId());
CameraViewWidget::paintGL();
CameraWidget::setFrameId(model.getFrameId());
CameraWidget::paintGL();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
@ -575,10 +582,16 @@ void NvgWindow::paintGL() {
LOGW("slow frame rate: %.2f fps", fps);
}
prev_draw_t = cur_draw_t;
// publish debug msg
MessageBuilder msg;
auto m = msg.initEvent().initUiDebug();
m.setDrawTimeMillis(cur_draw_t - start_draw_t);
pm->send("uiDebug", msg);
}
void NvgWindow::showEvent(QShowEvent *event) {
CameraViewWidget::showEvent(event);
void AnnotatedCameraWidget::showEvent(QShowEvent *event) {
CameraWidget::showEvent(event);
ui_update_params(uiState());
prev_draw_t = millis_since_boot();

@ -25,7 +25,7 @@ private:
};
// container window for the NVG UI
class NvgWindow : public CameraViewWidget {
class AnnotatedCameraWidget : public CameraWidget {
Q_OBJECT
Q_PROPERTY(float speed MEMBER speed);
Q_PROPERTY(QString speedUnit MEMBER speedUnit);
@ -43,7 +43,7 @@ class NvgWindow : public CameraViewWidget {
Q_PROPERTY(int status MEMBER status);
public:
explicit NvgWindow(VisionStreamType type, QWidget* parent = 0);
explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0);
void updateState(const UIState &s);
private:
@ -68,6 +68,7 @@ private:
bool has_eu_speed_limit = false;
bool v_ego_cluster_seen = false;
int status = STATUS_DISENGAGED;
std::unique_ptr<PubMaster> pm;
protected:
void paintGL() override;
@ -97,7 +98,7 @@ private:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent* e) override;
OnroadAlerts *alerts;
NvgWindow *nvg;
AnnotatedCameraWidget *nvg;
QColor bg = bg_colors[STATUS_DISENGAGED];
QWidget *map = nullptr;
QHBoxLayout* split;

@ -93,14 +93,14 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio)
} // namespace
CameraViewWidget::CameraViewWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) :
CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) :
stream_name(stream_name), stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
connect(this, &CameraViewWidget::vipcThreadConnected, this, &CameraViewWidget::vipcConnected, Qt::BlockingQueuedConnection);
connect(this, &CameraViewWidget::vipcThreadFrameReceived, this, &CameraViewWidget::vipcFrameReceived);
connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection);
connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived);
}
CameraViewWidget::~CameraViewWidget() {
CameraWidget::~CameraWidget() {
makeCurrent();
if (isValid()) {
glDeleteVertexArrays(1, &frame_vao);
@ -111,7 +111,7 @@ CameraViewWidget::~CameraViewWidget() {
doneCurrent();
}
void CameraViewWidget::initializeGL() {
void CameraWidget::initializeGL() {
initializeOpenGLFunctions();
program = std::make_unique<QOpenGLShaderProgram>(context());
@ -161,7 +161,7 @@ void CameraViewWidget::initializeGL() {
#endif
}
void CameraViewWidget::showEvent(QShowEvent *event) {
void CameraWidget::showEvent(QShowEvent *event) {
frames.clear();
if (!vipc_thread) {
vipc_thread = new QThread();
@ -171,7 +171,7 @@ void CameraViewWidget::showEvent(QShowEvent *event) {
}
}
void CameraViewWidget::hideEvent(QHideEvent *event) {
void CameraWidget::hideEvent(QHideEvent *event) {
if (vipc_thread) {
vipc_thread->requestInterruption();
vipc_thread->quit();
@ -180,7 +180,7 @@ void CameraViewWidget::hideEvent(QHideEvent *event) {
}
}
void CameraViewWidget::updateFrameMat() {
void CameraWidget::updateFrameMat() {
int w = width(), h = height();
if (zoomed_view) {
@ -224,12 +224,12 @@ void CameraViewWidget::updateFrameMat() {
}
}
void CameraViewWidget::updateCalibration(const mat3 &calib) {
void CameraWidget::updateCalibration(const mat3 &calib) {
calibration = calib;
updateFrameMat();
}
void CameraViewWidget::paintGL() {
void CameraWidget::paintGL() {
glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF());
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
@ -286,7 +286,7 @@ void CameraViewWidget::paintGL() {
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
void CameraViewWidget::vipcConnected(VisionIpcClient *vipc_client) {
void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) {
makeCurrent();
frames.clear();
stream_width = vipc_client->buffers[0].width;
@ -339,7 +339,7 @@ void CameraViewWidget::vipcConnected(VisionIpcClient *vipc_client) {
updateFrameMat();
}
void CameraViewWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) {
void CameraWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) {
frames.push_back(std::make_pair(frame_id, buf));
while (frames.size() > FRAME_BUFFER_SIZE) {
frames.pop_front();
@ -347,7 +347,7 @@ void CameraViewWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) {
update();
}
void CameraViewWidget::vipcThread() {
void CameraWidget::vipcThread() {
VisionStreamType cur_stream_type = stream_type;
std::unique_ptr<VisionIpcClient> vipc_client;
VisionIpcBufExtra meta_main = {0};

@ -23,13 +23,13 @@
const int FRAME_BUFFER_SIZE = 5;
static_assert(FRAME_BUFFER_SIZE <= YUV_BUFFER_COUNT);
class CameraViewWidget : public QOpenGLWidget, protected QOpenGLFunctions {
class CameraWidget : public QOpenGLWidget, protected QOpenGLFunctions {
Q_OBJECT
public:
using QOpenGLWidget::QOpenGLWidget;
explicit CameraViewWidget(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr);
~CameraViewWidget();
explicit CameraWidget(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr);
~CameraWidget();
void setStreamType(VisionStreamType type) { stream_type = type; }
void setBackgroundColor(const QColor &color) { bg = color; }
void setFrameId(int frame_id) { draw_frame_id = frame_id; }

@ -493,7 +493,7 @@ location set</source>
</message>
</context>
<context>
<name>NvgWindow</name>
<name>AnnotatedCameraWidget</name>
<message>
<location filename="../qt/onroad.cc" line="218"/>
<source>km/h</source>

@ -67,6 +67,29 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>AnnotatedCameraWidget</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation></translation>
</message>
<message>
<source>SPEED</source>
<translation></translation>
</message>
<message>
<source>LIMIT</source>
<translation></translation>
</message>
</context>
<context>
<name>ConfirmationDialog</name>
<message>
@ -406,29 +429,6 @@ location set</source>
<translation></translation>
</message>
</context>
<context>
<name>NvgWindow</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation></translation>
</message>
<message>
<source>SPEED</source>
<translation></translation>
</message>
<message>
<source>LIMIT</source>
<translation></translation>
</message>
</context>
<context>
<name>OffroadHome</name>
<message>

@ -60,11 +60,34 @@
</message>
<message>
<source>Cellular Metered</source>
<translation type="unfinished"></translation>
<translation> </translation>
</message>
<message>
<source>Prevent large data uploads when on a metered connection</source>
<translation type="unfinished"></translation>
<translation> </translation>
</message>
</context>
<context>
<name>AnnotatedCameraWidget</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation>MAX</translation>
</message>
<message>
<source>SPEED</source>
<translation>SPEED</translation>
</message>
<message>
<source>LIMIT</source>
<translation>LIMIT</translation>
</message>
</context>
<context>
@ -406,29 +429,6 @@ location set</source>
<translation> </translation>
</message>
</context>
<context>
<name>NvgWindow</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation>MAX</translation>
</message>
<message>
<source>SPEED</source>
<translation>SPEED</translation>
</message>
<message>
<source>LIMIT</source>
<translation>LIMIT</translation>
</message>
</context>
<context>
<name>OffroadHome</name>
<message>

@ -489,7 +489,7 @@ ingesteld</translation>
</message>
</context>
<context>
<name>NvgWindow</name>
<name>AnnotatedCameraWidget</name>
<message>
<location filename="../qt/onroad.cc" line="218"/>
<source>km/h</source>

@ -490,7 +490,7 @@ nie zostało ustawione</translation>
</message>
</context>
<context>
<name>NvgWindow</name>
<name>AnnotatedCameraWidget</name>
<message>
<location filename="../qt/onroad.cc" line="218"/>
<source>km/h</source>

@ -67,6 +67,29 @@
<translation>Evite grandes uploads de dados quando estiver em uma conexão limitada</translation>
</message>
</context>
<context>
<name>AnnotatedCameraWidget</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation>LIMITE</translation>
</message>
<message>
<source>SPEED</source>
<translation>MAX</translation>
</message>
<message>
<source>LIMIT</source>
<translation>VELO</translation>
</message>
</context>
<context>
<name>ConfirmationDialog</name>
<message>
@ -407,29 +430,6 @@ trabalho definido</translation>
<translation>Senha incorreta</translation>
</message>
</context>
<context>
<name>NvgWindow</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation>LIMITE</translation>
</message>
<message>
<source>SPEED</source>
<translation>MAX</translation>
</message>
<message>
<source>LIMIT</source>
<translation>VELO</translation>
</message>
</context>
<context>
<name>OffroadHome</name>
<message>

@ -488,7 +488,7 @@ location set</source>
</message>
</context>
<context>
<name>NvgWindow</name>
<name>AnnotatedCameraWidget</name>
<message>
<location filename="../qt/onroad.cc" line="218"/>
<source>km/h</source>

@ -67,6 +67,29 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>AnnotatedCameraWidget</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation></translation>
</message>
<message>
<source>SPEED</source>
<translation>SPEED</translation>
</message>
<message>
<source>LIMIT</source>
<translation>LIMIT</translation>
</message>
</context>
<context>
<name>ConfirmationDialog</name>
<message>
@ -404,29 +427,6 @@ location set</source>
<translation></translation>
</message>
</context>
<context>
<name>NvgWindow</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation></translation>
</message>
<message>
<source>SPEED</source>
<translation>SPEED</translation>
</message>
<message>
<source>LIMIT</source>
<translation>LIMIT</translation>
</message>
</context>
<context>
<name>OffroadHome</name>
<message>

@ -67,6 +67,29 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>AnnotatedCameraWidget</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation></translation>
</message>
<message>
<source>SPEED</source>
<translation></translation>
</message>
<message>
<source>LIMIT</source>
<translation></translation>
</message>
</context>
<context>
<name>ConfirmationDialog</name>
<message>
@ -406,29 +429,6 @@ location set</source>
<translation></translation>
</message>
</context>
<context>
<name>NvgWindow</name>
<message>
<source>km/h</source>
<translation>km/h</translation>
</message>
<message>
<source>mph</source>
<translation>mph</translation>
</message>
<message>
<source>MAX</source>
<translation></translation>
</message>
<message>
<source>SPEED</source>
<translation></translation>
</message>
<message>
<source>LIMIT</source>
<translation></translation>
</message>
</context>
<context>
<name>OffroadHome</name>
<message>

@ -19,15 +19,15 @@ int main(int argc, char *argv[]) {
{
QHBoxLayout *hlayout = new QHBoxLayout();
layout->addLayout(hlayout);
hlayout->addWidget(new CameraViewWidget("navd", VISION_STREAM_MAP, false));
hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_ROAD, false));
hlayout->addWidget(new CameraWidget("navd", VISION_STREAM_MAP, false));
hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_ROAD, false));
}
{
QHBoxLayout *hlayout = new QHBoxLayout();
layout->addLayout(hlayout);
hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_DRIVER, false));
hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_WIDE_ROAD, false));
hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_DRIVER, false));
hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_WIDE_ROAD, false));
}
return a.exec();

@ -365,6 +365,7 @@ class Updater:
["git", "checkout", "--force", "--no-recurse-submodules", "-B", branch, "FETCH_HEAD"],
["git", "reset", "--hard"],
["git", "clean", "-xdff"],
["git", "submodule", "sync"],
["git", "submodule", "init"],
["git", "submodule", "update"],
]

@ -21,7 +21,7 @@ class Proc:
PROCS = [
Proc('camerad', 2.15),
Proc('modeld', 1.15, atol=0.2),
Proc('dmonitoringmodeld', 0.35),
Proc('dmonitoringmodeld', 0.4),
Proc('encoderd', 0.23),
]

@ -12,5 +12,5 @@ else:
qt_libs = ['qt_util', 'Qt5Charts'] + base_libs
cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs
qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
'canmessages.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)

@ -0,0 +1,184 @@
#include "tools/cabana/binaryview.h"
#include <QApplication>
#include <QHeaderView>
#include <QPainter>
#include "tools/cabana/canmessages.h"
// BinaryView
const int CELL_HEIGHT = 30;
BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
model = new BinaryViewModel(this);
setModel(model);
setItemDelegate(new BinaryItemDelegate(this));
horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
horizontalHeader()->hide();
verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// replace selection model
auto old_model = selectionModel();
setSelectionModel(new BinarySelectionModel(model));
delete old_model;
QObject::connect(model, &QAbstractItemModel::modelReset, [this]() {
setFixedHeight((CELL_HEIGHT + 1) * std::min(model->rowCount(), 8) + 2);
});
}
void BinaryView::mouseReleaseEvent(QMouseEvent *event) {
QTableView::mouseReleaseEvent(event);
if (auto indexes = selectedIndexes(); !indexes.isEmpty()) {
int start_bit = indexes.first().row() * 8 + indexes.first().column();
int size = indexes.back().row() * 8 + indexes.back().column() - start_bit + 1;
emit cellsSelected(start_bit, size);
}
}
void BinaryView::setMessage(const QString &message_id) {
msg_id = message_id;
model->setMessage(message_id);
resizeRowsToContents();
clearSelection();
updateState();
}
void BinaryView::updateState() {
model->updateState();
}
// BinaryViewModel
void BinaryViewModel::setMessage(const QString &message_id) {
msg_id = message_id;
beginResetModel();
items.clear();
row_count = 0;
dbc_msg = dbc()->msg(msg_id);
if (dbc_msg) {
row_count = dbc_msg->size;
items.resize(row_count * column_count);
for (int i = 0; i < dbc_msg->sigs.size(); ++i) {
const auto &sig = dbc_msg->sigs[i];
const int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit);
const int end = start + sig.size - 1;
for (int j = start; j <= end; ++j) {
int idx = column_count * (j / 8) + j % 8;
if (idx >= items.size()) {
qWarning() << "signal " << sig.name.c_str() << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size;
break;
}
if (j == start) {
sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true;
} else if (j == end) {
sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true;
}
items[idx].bg_color = QColor(getColor(i));
}
}
}
endResetModel();
}
QModelIndex BinaryViewModel::index(int row, int column, const QModelIndex &parent) const {
return createIndex(row, column, (void *)&items[row * column_count + column]);
}
Qt::ItemFlags BinaryViewModel::flags(const QModelIndex &index) const {
return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
void BinaryViewModel::updateState() {
auto prev_items = items;
const auto &binary = can->lastMessage(msg_id).dat;
// data size may changed.
if (!dbc_msg && binary.size() != row_count) {
beginResetModel();
row_count = binary.size();
items.clear();
items.resize(row_count * column_count);
endResetModel();
}
char hex[3] = {'\0'};
for (int i = 0; i < std::min(binary.size(), row_count); ++i) {
for (int j = 0; j < column_count - 1; ++j) {
items[i * column_count + j].val = QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0');
}
hex[0] = toHex(binary[i] >> 4);
hex[1] = toHex(binary[i] & 0xf);
items[i * column_count + 8].val = hex;
}
for (int i = 0; i < items.size(); ++i) {
if (i >= prev_items.size() || prev_items[i].val != items[i].val) {
auto idx = index(i / column_count, i % column_count);
emit dataChanged(idx, idx);
}
}
}
QVariant BinaryViewModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Vertical) {
switch (role) {
case Qt::DisplayRole: return section + 1;
case Qt::SizeHintRole: return QSize(30, CELL_HEIGHT);
case Qt::TextAlignmentRole: return Qt::AlignCenter;
}
}
return {};
}
// BinarySelectionModel
void BinarySelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) {
QItemSelection new_selection = selection;
if (auto indexes = selection.indexes(); !indexes.isEmpty()) {
auto [begin_idx, end_idx] = (QModelIndex[]){indexes.first(), indexes.back()};
for (int row = begin_idx.row(); row <= end_idx.row(); ++row) {
int left_col = (row == begin_idx.row()) ? begin_idx.column() : 0;
int right_col = (row == end_idx.row()) ? end_idx.column() : 7;
new_selection.merge({model()->index(row, left_col), model()->index(row, right_col)}, command);
}
}
QItemSelectionModel::select(new_selection, command);
}
// BinaryItemDelegate
BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
// cache fonts and color
small_font.setPointSize(6);
bold_font.setBold(true);
highlight_color = QApplication::style()->standardPalette().color(QPalette::Active, QPalette::Highlight);
}
QSize BinaryItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
QSize sz = QStyledItemDelegate::sizeHint(option, index);
return {sz.width(), CELL_HEIGHT};
}
void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto item = (const BinaryViewModel::Item *)index.internalPointer();
painter->save();
// TODO: highlight signal cells on mouse over
painter->fillRect(option.rect, option.state & QStyle::State_Selected ? highlight_color : item->bg_color);
if (index.column() == 8) {
painter->setFont(bold_font);
}
painter->drawText(option.rect, Qt::AlignCenter, item->val);
if (item->is_msb || item->is_lsb) {
painter->setFont(small_font);
painter->drawText(option.rect, Qt::AlignHCenter | Qt::AlignBottom, item->is_msb ? "MSB" : "LSB");
}
painter->restore();
}

@ -0,0 +1,72 @@
#pragma once
#include <QStyledItemDelegate>
#include <QTableView>
#include "tools/cabana/dbcmanager.h"
class BinaryItemDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
BinaryItemDelegate(QObject *parent);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
private:
QFont small_font, bold_font;
QColor highlight_color;
};
class BinaryViewModel : public QAbstractTableModel {
Q_OBJECT
public:
BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {}
void setMessage(const QString &message_id);
void updateState();
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return {}; }
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; }
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; }
struct Item {
QColor bg_color = QColor(Qt::white);
bool is_msb = false;
bool is_lsb = false;
QString val = "0";
};
private:
QString msg_id;
const Msg *dbc_msg;
int row_count = 0;
const int column_count = 9;
std::vector<Item> items;
};
// the default QItemSelectionModel does not support our selection mode.
class BinarySelectionModel : public QItemSelectionModel {
public:
BinarySelectionModel(QAbstractItemModel *model = nullptr) : QItemSelectionModel(model) {}
void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override;
};
class BinaryView : public QTableView {
Q_OBJECT
public:
BinaryView(QWidget *parent = nullptr);
void mouseReleaseEvent(QMouseEvent *event) override;
void setMessage(const QString &message_id);
void updateState();
signals:
void cellsSelected(int start_bit, int size);
private:
QString msg_id;
BinaryViewModel *model;
};

@ -14,6 +14,7 @@ int main(int argc, char *argv[]) {
cmd_parser.addHelpOption();
cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai");
cmd_parser.addOption({"demo", "use a demo route instead of providing your own"});
cmd_parser.addOption({"qcam", "load qcamera"});
cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"});
cmd_parser.process(app);
const QStringList args = cmd_parser.positionalArguments();
@ -23,7 +24,7 @@ int main(int argc, char *argv[]) {
const QString route = args.empty() ? DEMO_ROUTE : args.first();
CANMessages p(&app);
if (!p.loadRoute(route, cmd_parser.value("data_dir"), true)) {
if (!p.loadRoute(route, cmd_parser.value("data_dir"), cmd_parser.isSet("qcam"))) {
return 0;
}
MainWindow w;

@ -141,11 +141,14 @@ void Settings::save() {
s.setValue("fps", fps);
s.setValue("log_size", can_msg_log_size);
s.setValue("cached_segment", cached_segment_limit);
s.setValue("chart_height", chart_height);
emit changed();
}
void Settings::load() {
QSettings s("settings", QSettings::IniFormat);
fps = s.value("fps", 10).toInt();
can_msg_log_size = s.value("log_size", 100).toInt();
cached_segment_limit = s.value("cached_segment", 3.).toInt();
cached_segment_limit = s.value("cached_segment", 3).toInt();
chart_height = s.value("chart_height", 200).toInt();
}

@ -19,6 +19,7 @@ public:
int fps = 10;
int can_msg_log_size = 100;
int cached_segment_limit = 3;
int chart_height = 200;
signals:
void changed();
@ -90,6 +91,7 @@ inline char toHex(uint value) {
}
inline const QString &getColor(int i) {
// TODO: add more colors
static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"};
return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)];
}

@ -1,8 +1,8 @@
#include "tools/cabana/chartswidget.h"
#include <QGraphicsLayout>
#include <QLabel>
#include <QRubberBand>
#include <QTimer>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
@ -14,6 +14,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
// title bar
title_bar = new QWidget(this);
title_bar->setVisible(false);
QHBoxLayout *title_layout = new QHBoxLayout(title_bar);
title_layout->setContentsMargins(0, 0, 0, 0);
title_label = new QLabel(tr("Charts"));
@ -25,13 +26,11 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
title_layout->addWidget(range_label);
reset_zoom_btn = new QPushButton("", this);
reset_zoom_btn->setVisible(false);
reset_zoom_btn->setFixedSize(30, 30);
reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)"));
title_layout->addWidget(reset_zoom_btn);
remove_all_btn = new QPushButton("", this);
remove_all_btn->setVisible(false);
remove_all_btn->setToolTip(tr("Remove all charts"));
remove_all_btn->setFixedSize(30, 30);
title_layout->addWidget(remove_all_btn);
@ -56,10 +55,20 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
main_layout->addWidget(charts_scroll);
updateTitleBar();
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeChart);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll);
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeChart);
QObject::connect(dbc(), &DBCManager::signalUpdated, [this](const Signal *sig) {
if (auto it = charts.find(sig); it != charts.end()) {
it.value()->chart_view->updateSeries();
}
});
QObject::connect(dbc(), &DBCManager::msgUpdated, [this](const QString &id) {
for (auto chart : charts) {
if (chart->id == id)
chart->updateTitle();
}
});
QObject::connect(can, &CANMessages::rangeChanged, [this]() { updateTitleBar(); });
QObject::connect(reset_zoom_btn, &QPushButton::clicked, can, &CANMessages::resetRange);
QObject::connect(remove_all_btn, &QPushButton::clicked, this, &ChartsWidget::removeAll);
@ -68,57 +77,51 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
docking = !docking;
updateTitleBar();
});
QObject::connect(&settings, &Settings::changed, [this]() {
for (auto chart : charts) {
chart->setHeight(settings.chart_height);
}
});
}
void ChartsWidget::updateTitleBar() {
if (!charts.size()) {
title_bar->setVisible(false);
return;
}
title_label->setText(tr("Charts (%1)").arg(charts.size()));
title_bar->setVisible(!charts.isEmpty());
if (charts.isEmpty()) return;
// show select range
range_label->setVisible(can->isZoomed());
reset_zoom_btn->setEnabled(can->isZoomed());
if (can->isZoomed()) {
auto [min, max] = can->range();
range_label->setText(tr("%1 - %2").arg(min, 0, 'f', 2).arg(max, 0, 'f', 2));
range_label->setVisible(true);
reset_zoom_btn->setEnabled(true);
} else {
reset_zoom_btn->setEnabled(false);
range_label->setVisible(false);
}
title_label->setText(tr("Charts (%1)").arg(charts.size()));
dock_btn->setText(docking ? "" : "");
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
remove_all_btn->setVisible(!charts.empty());
reset_zoom_btn->setVisible(!charts.empty());
title_bar->setVisible(true);
}
void ChartsWidget::addChart(const QString &id, const QString &sig_name) {
const QString char_name = id + ":" + sig_name;
if (charts.find(char_name) == charts.end()) {
auto chart = new ChartWidget(id, sig_name, this);
QObject::connect(chart, &ChartWidget::remove, [=]() {
removeChart(id, sig_name);
});
void ChartsWidget::addChart(const QString &id, const Signal *sig) {
if (!charts.contains(sig)) {
auto chart = new ChartWidget(id, sig, this);
QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(sig); });
charts_layout->insertWidget(0, chart);
charts[char_name] = chart;
charts.insert(sig, chart);
}
updateTitleBar();
}
void ChartsWidget::removeChart(const QString &id, const QString &sig_name) {
if (auto it = charts.find(id + ":" + sig_name); it != charts.end()) {
it->second->deleteLater();
charts.erase(it);
void ChartsWidget::removeChart(const Signal *sig) {
auto it = charts.find(sig);
if (it != charts.end()) {
it.value()->deleteLater();
charts.remove(sig);
}
updateTitleBar();
}
void ChartsWidget::removeAll() {
for (auto [_, chart] : charts)
for (auto chart : charts)
chart->deleteLater();
charts.clear();
updateTitleBar();
@ -134,19 +137,16 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
// ChartWidget
ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *parent) : id(id), sig_name(sig_name), QWidget(parent) {
ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent) : id(id), signal(sig), QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
QWidget *chart_widget = new QWidget(this);
QVBoxLayout *chart_layout = new QVBoxLayout(chart_widget);
chart_layout->setSpacing(0);
chart_layout->setContentsMargins(0, 0, 0, 0);
main_layout->setSpacing(0);
main_layout->setContentsMargins(0, 0, 0, 0);
QWidget *header = new QWidget(this);
header->setStyleSheet("background-color:white");
QHBoxLayout *header_layout = new QHBoxLayout(header);
header_layout->setContentsMargins(11, 11, 11, 0);
QLabel *title = new QLabel(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id));
title = new QLabel(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id));
header_layout->addWidget(title);
header_layout->addStretch();
@ -155,26 +155,28 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa
remove_btn->setToolTip(tr("Remove chart"));
QObject::connect(remove_btn, &QPushButton::clicked, this, &ChartWidget::remove);
header_layout->addWidget(remove_btn);
chart_layout->addWidget(header);
main_layout->addWidget(header);
chart_view = new ChartView(id, sig_name, this);
chart_view->setFixedHeight(300);
chart_layout->addWidget(chart_view);
chart_layout->addStretch();
main_layout->addWidget(chart_widget);
chart_view = new ChartView(id, sig, this);
chart_view->setFixedHeight(settings.chart_height);
main_layout->addWidget(chart_view);
main_layout->addStretch();
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
void ChartWidget::updateTitle() {
title->setText(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id));
}
// ChartView
ChartView::ChartView(const QString &id, const QString &sig_name, QWidget *parent)
: id(id), sig_name(sig_name), QChartView(nullptr, parent) {
ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
: id(id), signal(sig), QChartView(nullptr, parent) {
QLineSeries *series = new QLineSeries();
series->setUseOpenGL(true);
QChart *chart = new QChart();
chart->setTitle(sig_name);
chart->setTitle(sig->name.c_str());
chart->addSeries(series);
chart->createDefaultAxes();
chart->legend()->hide();
@ -201,17 +203,36 @@ ChartView::ChartView(const QString &id, const QString &sig_name, QWidget *parent
rubber->setPalette(pal);
}
QTimer *timer = new QTimer(this);
timer->setInterval(100);
timer->setSingleShot(true);
timer->callOnTimeout(this, &ChartView::adjustChartMargins);
QObject::connect(can, &CANMessages::updated, this, &ChartView::updateState);
QObject::connect(can, &CANMessages::rangeChanged, this, &ChartView::rangeChanged);
QObject::connect(can, &CANMessages::eventsMerged, this, &ChartView::updateSeries);
QObject::connect(dynamic_cast<QValueAxis *>(chart->axisX()), &QValueAxis::rangeChanged, can, &CANMessages::setRange);
QObject::connect(dbc(), &DBCManager::signalUpdated, [this](const QString &msg_id, const QString &sig_name) {
if (this->id == msg_id && this->sig_name == sig_name)
updateSeries();
QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) {
// use a singleshot timer to avoid recursion call.
timer->start();
});
updateSeries();
}
void ChartView::adjustChartMargins() {
// TODO: Remove hardcoded aligned_pos
const int aligned_pos = 60;
if (chart()->plotArea().left() != aligned_pos) {
const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left();
chart()->setMargins(QMargins(left_margin, 0, 0, 0));
}
}
void ChartWidget::setHeight(int height) {
chart_view->setFixedHeight(height);
}
void ChartView::updateState() {
auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
int x = chart()->plotArea().left() + chart()->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min());
@ -219,9 +240,9 @@ void ChartView::updateState() {
}
void ChartView::updateSeries() {
const Signal *sig = dbc()->signal(id, sig_name);
chart()->setTitle(signal->name.c_str());
auto events = can->events();
if (!sig || !events) return;
if (!events) return;
auto l = id.split(':');
int bus = l[0].toInt();
@ -235,7 +256,7 @@ void ChartView::updateSeries() {
for (auto c : evt->event.getCan()) {
if (bus == c.getSrc() && address == c.getAddress()) {
auto dat = c.getDat();
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sig);
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *signal);
double ts = (evt->mono_time / (double)1e9) - route_start_time; // seconds
vals.push_back({ts, value});
}

@ -9,7 +9,6 @@
#include <QVBoxLayout>
#include <QWidget>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include "tools/cabana/canmessages.h"
#include "tools/cabana/dbcmanager.h"
@ -20,15 +19,16 @@ class ChartView : public QChartView {
Q_OBJECT
public:
ChartView(const QString &id, const QString &sig_name, QWidget *parent = nullptr);
ChartView(const QString &id, const Signal *sig, QWidget *parent = nullptr);
void updateSeries();
private:
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *ev) override;
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void adjustChartMargins();
void updateSeries();
void rangeChanged(qreal min, qreal max);
void updateAxisY();
void updateState();
@ -38,22 +38,24 @@ private:
QGraphicsLineItem *line_marker;
QList<QPointF> vals;
QString id;
QString sig_name;
const Signal *signal;
};
class ChartWidget : public QWidget {
Q_OBJECT
public:
ChartWidget(const QString &id, const QString &sig_name, QWidget *parent);
inline QChart *chart() const { return chart_view->chart(); }
ChartWidget(const QString &id, const Signal *sig, QWidget *parent);
void updateTitle();
void setHeight(int height);
signals:
void remove();
protected:
public:
QString id;
QString sig_name;
const Signal *signal;
QLabel *title;
ChartView *chart_view = nullptr;
};
@ -62,11 +64,8 @@ class ChartsWidget : public QWidget {
public:
ChartsWidget(QWidget *parent = nullptr);
void addChart(const QString &id, const QString &sig_name);
void removeChart(const QString &id, const QString &sig_name);
inline bool hasChart(const QString &id, const QString &sig_name) {
return charts.find(id + sig_name) != charts.end();
}
void addChart(const QString &id, const Signal *sig);
void removeChart(const Signal *sig);
signals:
void dock(bool floating);
@ -85,5 +84,5 @@ private:
QPushButton *reset_zoom_btn;
QPushButton *remove_all_btn;
QVBoxLayout *charts_layout;
std::map<QString, ChartWidget *> charts;
QHash<const Signal *, ChartWidget *> charts;
};

@ -1,5 +1,6 @@
#include "tools/cabana/dbcmanager.h"
#include <sstream>
#include <QVector>
DBCManager::DBCManager(QObject *parent) : QObject(parent) {}
@ -16,6 +17,17 @@ void DBCManager::open(const QString &dbc_file_name) {
emit DBCFileChanged();
}
void DBCManager::open(const QString &name, const QString &content) {
this->dbc_name = name;
std::istringstream stream(content.toStdString());
dbc = const_cast<DBC *>(dbc_parse_from_stream(name.toStdString(), stream));
msg_map.clear();
for (auto &msg : dbc->msgs) {
msg_map[msg.address] = &msg;
}
emit DBCFileChanged();
}
void save(const QString &dbc_file_name) {
// TODO: save DBC to file
}
@ -36,14 +48,17 @@ void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size
void DBCManager::addSignal(const QString &id, const Signal &sig) {
if (Msg *m = const_cast<Msg *>(msg(id))) {
m->sigs.push_back(sig);
emit signalAdded(id, QString::fromStdString(sig.name));
emit signalAdded(&m->sigs.back());
}
}
void DBCManager::updateSignal(const QString &id, const QString &sig_name, const Signal &sig) {
if (Signal *s = const_cast<Signal *>(signal(id, sig_name))) {
*s = sig;
emit signalUpdated(id, sig_name);
if (Msg *m = const_cast<Msg *>(msg(id))) {
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); });
if (it != m->sigs.end()) {
*it = sig;
emit signalUpdated(&(*it));
}
}
}
@ -51,21 +66,12 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) {
if (Msg *m = const_cast<Msg *>(msg(id))) {
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); });
if (it != m->sigs.end()) {
emit signalRemoved(&(*it));
m->sigs.erase(it);
emit signalRemoved(id, sig_name);
}
}
}
const Signal *DBCManager::signal(const QString &id, const QString &sig_name) const {
if (auto m = msg(id)) {
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return sig_name == s.name.c_str(); });
if (it != m->sigs.end())
return &(*it);
}
return nullptr;
}
uint32_t DBCManager::addressFromId(const QString &id) {
return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16);
}

@ -12,9 +12,9 @@ public:
~DBCManager();
void open(const QString &dbc_file_name);
void open(const QString &name, const QString &content);
void save(const QString &dbc_file_name);
const Signal *signal(const QString &id, const QString &sig_name) const;
void addSignal(const QString &id, const Signal &sig);
void updateSignal(const QString &id, const QString &sig_name, const Signal &sig);
void removeSignal(const QString &id, const QString &sig_name);
@ -31,9 +31,9 @@ public:
}
signals:
void signalAdded(const QString &id, const QString &sig_name);
void signalRemoved(const QString &id, const QString &sig_name);
void signalUpdated(const QString &id, const QString &sig_name);
void signalAdded(const Signal *sig);
void signalRemoved(const Signal *sig);
void signalUpdated(const Signal *sig);
void msgUpdated(const QString &id);
void DBCFileChanged();

@ -1,12 +1,12 @@
#include "tools/cabana/detailwidget.h"
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHeaderView>
#include <QScrollBar>
#include <QMessageBox>
#include <QTimer>
#include <QVBoxLayout>
#include "tools/cabana/canmessages.h"
#include "tools/cabana/dbcmanager.h"
// DetailWidget
@ -33,60 +33,52 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) {
binary_view = new BinaryView(this);
main_layout->addWidget(binary_view, 0, Qt::AlignTop);
// signal header
signals_header = new QWidget(this);
QHBoxLayout *signals_header_layout = new QHBoxLayout(signals_header);
signals_header_layout->addWidget(new QLabel(tr("Signals")));
signals_header_layout->addStretch();
QPushButton *add_sig_btn = new QPushButton(tr("Add signal"), this);
signals_header_layout->addWidget(add_sig_btn);
signals_header->setVisible(false);
main_layout->addWidget(signals_header);
// scroll area
// signals
signals_container = new QWidget(this);
signals_container->setLayout(new QVBoxLayout);
signals_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
scroll = new ScrollArea(this);
QWidget *container = new QWidget(this);
container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QVBoxLayout *container_layout = new QVBoxLayout(container);
signal_edit_layout = new QVBoxLayout();
signal_edit_layout->setSpacing(2);
container_layout->addLayout(signal_edit_layout);
scroll->setWidget(container);
scroll->setWidget(signals_container);
scroll->setWidgetResizable(true);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
main_layout->addWidget(scroll);
// history log
history_log = new HistoryLog(this);
main_layout->addWidget(history_log);
QObject::connect(add_sig_btn, &QPushButton::clicked, this, &DetailWidget::addSignal);
QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg);
QObject::connect(binary_view, &BinaryView::cellsSelected, this, &DetailWidget::addSignal);
QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &DetailWidget::dbcMsgChanged);
}
void DetailWidget::setMessage(const QString &message_id) {
msg_id = message_id;
for (auto f : signal_forms) {
f->deleteLater();
if (msg_id != message_id) {
msg_id = message_id;
dbcMsgChanged();
}
signal_forms.clear();
}
void DetailWidget::dbcMsgChanged() {
if (msg_id.isEmpty()) return;
qDeleteAll(signals_container->findChildren<SignalEdit *>());
QString msg_name = tr("untitled");
if (auto msg = dbc()->msg(msg_id)) {
for (int i = 0; i < msg->sigs.size(); ++i) {
auto form = new SignalEdit(i, msg_id, msg->sigs[i], getColor(i));
signal_edit_layout->addWidget(form);
QObject::connect(form, &SignalEdit::showChart, this, &DetailWidget::showChart);
auto form = new SignalEdit(i, msg_id, msg->sigs[i]);
signals_container->layout()->addWidget(form);
QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]]() { emit showChart(msg_id, sig); });
QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm);
signal_forms.push_back(form);
QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal);
QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal);
}
name_label->setText(msg->name.c_str());
signals_header->setVisible(true);
} else {
name_label->setText(tr("untitled"));
signals_header->setVisible(false);
msg_name = msg->name.c_str();
}
edit_btn->setVisible(true);
name_label->setText(msg_name);
binary_view->setMessage(msg_id);
history_log->setMessage(msg_id);
@ -100,141 +92,83 @@ void DetailWidget::updateState() {
history_log->updateState();
}
void DetailWidget::editMsg() {
EditMessageDialog dlg(msg_id, this);
if (dlg.exec()) {
setMessage(msg_id);
}
}
void DetailWidget::addSignal() {
AddSignalDialog dlg(msg_id, this);
if (dlg.exec()) {
setMessage(msg_id);
}
}
void DetailWidget::showForm() {
SignalEdit *sender = qobject_cast<SignalEdit *>(QObject::sender());
if (sender->isFormVisible()) {
sender->setFormVisible(false);
} else {
for (auto f : signal_forms) {
f->setFormVisible(f == sender);
if (f == sender) {
// scroll to header
QTimer::singleShot(0, [=]() {
const QPoint p = f->mapTo(scroll, QPoint(0, 0));
scroll->verticalScrollBar()->setValue(p.y() + scroll->verticalScrollBar()->value());
});
}
for (auto f : signals_container->findChildren<SignalEdit *>()) {
f->setFormVisible(f == sender && !f->isFormVisible());
if (f == sender) {
QTimer::singleShot(0, [=]() { scroll->ensureWidgetVisible(f); });
}
}
}
// BinaryView
BinaryView::BinaryView(QWidget *parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
table = new QTableWidget(this);
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
table->horizontalHeader()->hide();
table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
main_layout->addWidget(table);
table->setColumnCount(9);
}
void BinaryView::setMessage(const QString &message_id) {
msg_id = message_id;
const Msg *msg = dbc()->msg(msg_id);
const int row_count = msg ? msg->size : can->lastMessage(msg_id).dat.size();
table->setRowCount(row_count);
table->setColumnCount(9);
for (int i = 0; i < table->rowCount(); ++i) {
for (int j = 0; j < table->columnCount(); ++j) {
auto item = new QTableWidgetItem();
item->setFlags(item->flags() ^ Qt::ItemIsEditable);
item->setTextAlignment(Qt::AlignCenter);
if (j == 8) {
QFont font;
font.setBold(true);
item->setFont(font);
}
table->setItem(i, j, item);
}
void DetailWidget::editMsg() {
auto msg = dbc()->msg(msg_id);
QString name = msg ? msg->name.c_str() : "untitled";
int size = msg ? msg->size : can->lastMessage(msg_id).dat.size();
EditMessageDialog dlg(msg_id, name, size, this);
if (dlg.exec()) {
dbc()->updateMsg(msg_id, dlg.name_edit->text(), dlg.size_spin->value());
dbcMsgChanged();
}
}
// set background color
if (msg) {
for (int i = 0; i < msg->sigs.size(); ++i) {
const auto &sig = msg->sigs[i];
int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit);
for (int j = start; j <= start + sig.size - 1; ++j) {
table->item(j / 8, j % 8)->setBackground(QColor(getColor(i)));
}
void DetailWidget::addSignal(int start_bit, int size) {
if (dbc()->msg(msg_id)) {
AddSignalDialog dlg(msg_id, start_bit, size, this);
if (dlg.exec()) {
dbc()->addSignal(msg_id, dlg.form->getSignal());
dbcMsgChanged();
}
}
table->setFixedHeight(table->rowHeight(0) * std::min(row_count, 8) + 2);
updateState();
}
void BinaryView::updateState() {
if (msg_id.isEmpty()) return;
const auto &binary = can->lastMessage(msg_id).dat;
void DetailWidget::saveSignal() {
SignalEdit *sig_form = qobject_cast<SignalEdit *>(QObject::sender());
auto s = sig_form->form->getSignal();
dbc()->updateSignal(msg_id, sig_form->sig_name, s);
// update binary view and history log
binary_view->setMessage(msg_id);
history_log->setMessage(msg_id);
}
setUpdatesEnabled(false);
char hex[3] = {'\0'};
for (int i = 0; i < binary.size(); ++i) {
for (int j = 0; j < 8; ++j) {
table->item(i, j)->setText(QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0'));
}
hex[0] = toHex(binary[i] >> 4);
hex[1] = toHex(binary[i] & 0xf);
table->item(i, 8)->setText(hex);
void DetailWidget::removeSignal() {
SignalEdit *sig_form = qobject_cast<SignalEdit *>(QObject::sender());
QString text = tr("Are you sure you want to remove signal '%1'").arg(sig_form->sig_name);
if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove signal"), text)) {
dbc()->removeSignal(msg_id, sig_form->sig_name);
dbcMsgChanged();
}
setUpdatesEnabled(true);
}
// EditMessageDialog
EditMessageDialog::EditMessageDialog(const QString &msg_id, QWidget *parent) : msg_id(msg_id), QDialog(parent) {
EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Edit message"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
QFormLayout *form_layout = new QFormLayout();
form_layout->addRow("ID", new QLabel(msg_id));
const auto msg = dbc()->msg(msg_id);
name_edit = new QLineEdit(this);
name_edit->setText(msg ? msg->name.c_str() : "untitled");
name_edit = new QLineEdit(title, this);
form_layout->addRow(tr("Name"), name_edit);
size_spin = new QSpinBox(this);
size_spin->setValue(msg ? msg->size : can->lastMessage(msg_id).dat.size());
// TODO: limit the maximum?
size_spin->setMinimum(1);
size_spin->setValue(size);
form_layout->addRow(tr("Size"), size_spin);
main_layout->addLayout(form_layout);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox);
setFixedWidth(parent->width() * 0.9);
connect(buttonBox, &QDialogButtonBox::accepted, this, &EditMessageDialog::save);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void EditMessageDialog::save() {
const QString name = name_edit->text();
if (size_spin->value() <= 0 || name_edit->text().isEmpty() || name == tr("untitled"))
return;
dbc()->updateMsg(msg_id, name, size_spin->value());
QDialog::accept();
}
// ScrollArea
bool ScrollArea::eventFilter(QObject *obj, QEvent *ev) {

@ -1,43 +1,17 @@
#pragma once
#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QTableWidget>
#include <QVBoxLayout>
#include <QWidget>
#include "opendbc/can/common.h"
#include "opendbc/can/common_dbc.h"
#include "tools/cabana/canmessages.h"
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/binaryview.h"
#include "tools/cabana/historylog.h"
#include "tools/cabana/signaledit.h"
class BinaryView : public QWidget {
Q_OBJECT
public:
BinaryView(QWidget *parent);
void setMessage(const QString &message_id);
void updateState();
private:
QString msg_id;
QTableWidget *table;
};
class EditMessageDialog : public QDialog {
Q_OBJECT
public:
EditMessageDialog(const QString &msg_id, QWidget *parent);
EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent);
protected:
void save();
QString msg_id;
QLineEdit *name_edit;
QSpinBox *size_spin;
};
@ -57,24 +31,24 @@ class DetailWidget : public QWidget {
public:
DetailWidget(QWidget *parent);
void setMessage(const QString &message_id);
void dbcMsgChanged();
signals:
void showChart(const QString &msg_id, const QString &sig_name);
private slots:
void showForm();
void showChart(const QString &msg_id, const Signal *sig);
void removeChart(const Signal *sig);
private:
void addSignal();
void addSignal(int start_bit, int size);
void saveSignal();
void removeSignal();
void editMsg();
void showForm();
void updateState();
QString msg_id;
QLabel *name_label, *time_label;
QPushButton *edit_btn;
QVBoxLayout *signal_edit_layout;
QWidget *signals_header;
QList<SignalEdit *> signal_forms;
QWidget *signals_container;
HistoryLog *history_log;
BinaryView *binary_view;
ScrollArea *scroll;

@ -1,20 +1,20 @@
#include "tools/cabana/historylog.h"
#include <QFontDatabase>
#include <QHeaderView>
#include <QVBoxLayout>
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
bool has_signal = dbc_msg && !dbc_msg->sigs.empty();
if (role == Qt::DisplayRole) {
const auto &can_msgs = can->messages(msg_id);
if (index.row() < can_msgs.size()) {
const auto &can_data = can_msgs[index.row()];
auto msg = dbc()->msg(msg_id);
if (msg && index.column() < msg->sigs.size()) {
return get_raw_value((uint8_t *)can_data.dat.begin(), can_data.dat.size(), msg->sigs[index.column()]);
} else {
return toHex(can_data.dat);
}
const auto &m = can->messages(msg_id)[index.row()];
if (index.column() == 0) {
return QString::number(m.ts, 'f', 2);
}
return has_signal ? QString::number(get_raw_value((uint8_t *)m.dat.begin(), m.dat.size(), dbc_msg->sigs[index.column() - 1]))
: toHex(m.dat);
} else if (role == Qt::FontRole && index.column() == 1 && !has_signal) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
}
return {};
}
@ -22,8 +22,8 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
void HistoryLogModel::setMessage(const QString &message_id) {
beginResetModel();
msg_id = message_id;
const auto msg = dbc()->msg(message_id);
column_count = msg && !msg->sigs.empty() ? msg->sigs.size() : 1;
dbc_msg = dbc()->msg(message_id);
column_count = (dbc_msg && !dbc_msg->sigs.empty() ? dbc_msg->sigs.size() : 1) + 1;
row_count = 0;
endResetModel();
@ -32,18 +32,14 @@ void HistoryLogModel::setMessage(const QString &message_id) {
QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal) {
auto msg = dbc()->msg(msg_id);
if (msg && section < msg->sigs.size()) {
if (role == Qt::BackgroundRole) {
return QBrush(QColor(getColor(section)));
} else if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
return QString::fromStdString(msg->sigs[section].name);
bool has_signal = dbc_msg && !dbc_msg->sigs.empty();
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
if (section == 0) {
return "Time";
}
}
} else if (role == Qt::DisplayRole) {
const auto &can_msgs = can->messages(msg_id);
if (section < can_msgs.size()) {
return QString::number(can_msgs[section].ts, 'f', 2);
return has_signal ? dbc_msg->sigs[section - 1].name.c_str() : "Data";
} else if (role == Qt::BackgroundRole && section > 0 && has_signal) {
return QBrush(QColor(getColor(section - 1)));
}
}
return {};
@ -52,9 +48,8 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i
void HistoryLogModel::updateState() {
if (msg_id.isEmpty()) return;
const auto &can_msgs = can->messages(msg_id);
int prev_row_count = row_count;
row_count = can_msgs.size();
row_count = can->messages(msg_id).size();
int delta = row_count - prev_row_count;
if (delta > 0) {
beginInsertRows({}, prev_row_count, row_count - 1);
@ -64,8 +59,7 @@ void HistoryLogModel::updateState() {
endRemoveRows();
}
if (row_count > 0) {
emit dataChanged(index(0, 0), index(row_count - 1, column_count - 1));
emit headerDataChanged(Qt::Vertical, 0, row_count - 1);
emit dataChanged(index(0, 0), index(row_count - 1, column_count - 1), {Qt::DisplayRole});
}
}
@ -75,17 +69,10 @@ HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) {
model = new HistoryLogModel(this);
table = new QTableView(this);
table->setModel(model);
table->horizontalHeader()->setStretchLastSection(true);
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
table->setColumnWidth(0, 60);
table->verticalHeader()->setVisible(false);
table->setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }");
table->verticalHeader()->setStyleSheet("QHeaderView::section {padding-left: 5px; padding-right: 5px;min-width:40px;}");
main_layout->addWidget(table);
}
void HistoryLog::setMessage(const QString &message_id) {
model->setMessage(message_id);
}
void HistoryLog::updateState() {
model->updateState();
}

@ -6,21 +6,22 @@
#include "tools/cabana/dbcmanager.h"
class HistoryLogModel : public QAbstractTableModel {
Q_OBJECT
Q_OBJECT
public:
HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {}
void setMessage(const QString &message_id);
void updateState();
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; }
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; }
private:
QString msg_id;
int row_count = 0;
int column_count = 0;
int column_count = 2;
const Msg *dbc_msg = nullptr;
};
class HistoryLog : public QWidget {
@ -28,8 +29,8 @@ class HistoryLog : public QWidget {
public:
HistoryLog(QWidget *parent);
void setMessage(const QString &message_id);
void updateState();
void setMessage(const QString &message_id) { model->setMessage(message_id); }
void updateState() { model->updateState(); }
private:
QTableView *table;

@ -5,6 +5,7 @@
#include <QHBoxLayout>
#include <QFormLayout>
#include <QScreen>
#include <QSplitter>
#include <QVBoxLayout>
MainWindow::MainWindow() : QWidget() {
@ -13,12 +14,16 @@ MainWindow::MainWindow() : QWidget() {
QHBoxLayout *h_layout = new QHBoxLayout();
main_layout->addLayout(h_layout);
QSplitter *splitter = new QSplitter(Qt::Horizontal, this);
messages_widget = new MessagesWidget(this);
h_layout->addWidget(messages_widget);
splitter->addWidget(messages_widget);
detail_widget = new DetailWidget(this);
detail_widget->setFixedWidth(600);
h_layout->addWidget(detail_widget);
splitter->addWidget(detail_widget);
splitter->setSizes({100, 500});
h_layout->addWidget(splitter);
// right widgets
QWidget *right_container = new QWidget(this);
@ -96,6 +101,12 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
cached_segment->setValue(settings.cached_segment_limit);
form_layout->addRow(tr("Cached segments limit"), cached_segment);
chart_height = new QSpinBox(this);
chart_height->setRange(100, 500);
chart_height->setSingleStep(10);
chart_height->setValue(settings.chart_height);
form_layout->addRow(tr("Chart height"), chart_height);
main_layout->addLayout(form_layout);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
@ -110,6 +121,7 @@ void SettingsDlg::save() {
settings.fps = fps->value();
settings.can_msg_log_size = log_size->value();
settings.cached_segment_limit = cached_segment->value();
settings.chart_height = chart_height->value();
settings.save();
accept();
}

@ -33,4 +33,5 @@ public:
QSpinBox *fps;
QSpinBox *log_size ;
QSpinBox *cached_segment;
QSpinBox *chart_height;
};

@ -1,11 +1,13 @@
#include "tools/cabana/messageswidget.h"
#include <QComboBox>
#include <QCompleter>
#include <QDialogButtonBox>
#include <QFontDatabase>
#include <QHeaderView>
#include <QLineEdit>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QTextEdit>
#include <QVBoxLayout>
#include "tools/cabana/dbcmanager.h"
@ -15,19 +17,23 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
// DBC file selector
QHBoxLayout *dbc_file_layout = new QHBoxLayout();
QComboBox *combo = new QComboBox(this);
dbc_combo = new QComboBox(this);
auto dbc_names = dbc()->allDBCNames();
for (const auto &name : dbc_names) {
combo->addItem(QString::fromStdString(name));
dbc_combo->addItem(QString::fromStdString(name));
}
combo->setEditable(true);
combo->setCurrentText(QString());
combo->setInsertPolicy(QComboBox::NoInsert);
combo->completer()->setCompletionMode(QCompleter::PopupCompletion);
dbc_combo->model()->sort(0);
dbc_combo->setEditable(true);
dbc_combo->setCurrentText(QString());
dbc_combo->setInsertPolicy(QComboBox::NoInsert);
dbc_combo->completer()->setCompletionMode(QCompleter::PopupCompletion);
QFont font;
font.setBold(true);
combo->lineEdit()->setFont(font);
dbc_file_layout->addWidget(combo);
dbc_combo->lineEdit()->setFont(font);
dbc_file_layout->addWidget(dbc_combo);
QPushButton *load_from_paste = new QPushButton(tr("Load from paste"), this);
dbc_file_layout->addWidget(load_from_paste);
dbc_file_layout->addStretch();
QPushButton *save_btn = new QPushButton(tr("Save DBC"), this);
@ -62,26 +68,32 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
// signals/slots
QObject::connect(filter, &QLineEdit::textChanged, proxy_model, &QSortFilterProxyModel::setFilterFixedString);
QObject::connect(can, &CANMessages::updated, model, &MessageListModel::updateState);
QObject::connect(combo, SIGNAL(activated(const QString &)), SLOT(dbcSelectionChanged(const QString &)));
QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(dbcSelectionChanged(const QString &)));
QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadFromPaste);
QObject::connect(save_btn, &QPushButton::clicked, [=]() {
// TODO: save DBC to file
});
QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) {
if (current.isValid()) {
emit msgSelectionChanged(table_widget->model()->data(current, Qt::UserRole).toString());
emit msgSelectionChanged(current.data(Qt::UserRole).toString());
}
});
// For test purpose
combo->setCurrentText("toyota_nodsu_pt_generated");
dbc_combo->setCurrentText("toyota_nodsu_pt_generated");
}
void MessagesWidget::dbcSelectionChanged(const QString &dbc_file) {
dbc()->open(dbc_file);
// update detailwidget
auto current = table_widget->selectionModel()->currentIndex();
if (current.isValid()) {
emit msgSelectionChanged(table_widget->model()->data(current, Qt::UserRole).toString());
// TODO: reset model?
table_widget->sortByColumn(0, Qt::AscendingOrder);
}
void MessagesWidget::loadFromPaste() {
LoadDBCDialog dlg(this);
if (dlg.exec()) {
dbc()->open("from paste", dlg.dbc_edit->toPlainText());
dbc_combo->setCurrentText("loaded from paste");
}
}
@ -90,9 +102,6 @@ void MessagesWidget::dbcSelectionChanged(const QString &dbc_file) {
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return (QString[]){"Name", "ID", "Count", "Bytes"}[section];
else if (orientation == Qt::Vertical && role == Qt::DisplayRole) {
// return QString::number(section);
}
return {};
}
@ -100,21 +109,23 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole) {
auto it = std::next(can->can_msgs.begin(), index.row());
if (it != can->can_msgs.end() && !it.value().empty()) {
const auto &d = it.value().front();
const QString &msg_id = it.key();
switch (index.column()) {
case 0: {
auto msg = dbc()->msg(msg_id);
QString name = msg ? msg->name.c_str() : "untitled";
return name;
return msg ? msg->name.c_str() : "untitled";
}
case 1: return msg_id;
case 2: return can->counters[msg_id];
case 3: return toHex(d.dat);
case 3: return toHex(it.value().front().dat);
}
}
} else if (role == Qt::UserRole) {
return std::next(can->can_msgs.begin(), index.row()).key();
} else if (role == Qt::FontRole) {
if (index.column() == 3) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
}
}
return {};
}
@ -132,6 +143,20 @@ void MessageListModel::updateState() {
}
if (row_count > 0) {
emit dataChanged(index(0, 0), index(row_count - 1, 3));
emit dataChanged(index(0, 0), index(row_count - 1, 3), {Qt::DisplayRole});
}
}
LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
dbc_edit = new QTextEdit(this);
dbc_edit->setAcceptRichText(false);
dbc_edit->setPlaceholderText(tr("paste DBC file here"));
main_layout->addWidget(dbc_edit);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox);
setFixedWidth(640);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}

@ -1,10 +1,21 @@
#pragma once
#include <QAbstractTableModel>
#include <QComboBox>
#include <QDialog>
#include <QTableView>
#include <QTextEdit>
#include "tools/cabana/canmessages.h"
class LoadDBCDialog : public QDialog {
Q_OBJECT
public:
LoadDBCDialog(QWidget *parent);
QTextEdit *dbc_edit;
};
class MessageListModel : public QAbstractTableModel {
Q_OBJECT
@ -28,11 +39,13 @@ public:
public slots:
void dbcSelectionChanged(const QString &dbc_file);
void loadFromPaste();
signals:
void msgSelectionChanged(const QString &message_id);
protected:
QTableView *table_widget;
QComboBox *dbc_combo;
MessageListModel *model;
};

@ -3,7 +3,6 @@
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QVBoxLayout>
// SignalForm
@ -15,18 +14,18 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start
form_layout->addRow(tr("Name"), name);
size = new QSpinBox();
size->setMinimum(1);
size->setValue(sig.size);
form_layout->addRow(tr("Size"), size);
msb = new QSpinBox();
msb->setValue(sig.msb);
form_layout->addRow(tr("Most significant bit"), msb);
endianness = new QComboBox();
endianness->addItems({"Little", "Big"});
endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1);
form_layout->addRow(tr("Endianness"), endianness);
form_layout->addRow(tr("lsb"), new QLabel(QString::number(sig.lsb)));
form_layout->addRow(tr("msb"), new QLabel(QString::number(sig.msb)));
sign = new QComboBox();
sign->addItems({"Signed", "Unsigned"});
sign->setCurrentIndex(sig.is_signed ? 0 : 1);
@ -56,7 +55,8 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start
form_layout->addRow(tr("Value descriptions"), val_desc);
}
std::optional<Signal> SignalForm::getSignal() {
Signal SignalForm::getSignal() {
// TODO: Check if the size is valid, and no duplicate name
Signal sig = {};
sig.start_bit = start_bit;
sig.name = name->text().toStdString();
@ -72,42 +72,42 @@ std::optional<Signal> SignalForm::getSignal() {
sig.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(sig.start_bit) + sig.size - 1);
sig.msb = sig.start_bit;
}
return (sig.name.empty() || sig.size <= 0) ? std::nullopt : std::optional(sig);
return sig;
}
// SignalEdit
SignalEdit::SignalEdit(int index, const QString &id, const Signal &sig, const QString &color, QWidget *parent)
: id(id), name_(sig.name.c_str()), QWidget(parent) {
SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal &sig, QWidget *parent)
: sig_name(sig.name.c_str()), QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
// title
// title bar
QHBoxLayout *title_layout = new QHBoxLayout();
icon = new QLabel(">");
icon->setFixedSize(15, 30);
icon->setStyleSheet("font-weight:bold");
title_layout->addWidget(icon);
title = new ElidedLabel(this);
title->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
title->setText(QString("%1. %2").arg(index + 1).arg(sig.name.c_str()));
title->setStyleSheet(QString("font-weight:bold; color:%1").arg(color));
title_layout->addWidget(title);
title->setText(QString("%1. %2").arg(index + 1).arg(sig_name));
title->setStyleSheet(QString("font-weight:bold; color:%1").arg(getColor(index)));
title_layout->addWidget(title, 1);
plot_btn = new QPushButton("📈");
QPushButton *plot_btn = new QPushButton("📈");
plot_btn->setToolTip(tr("Show Plot"));
plot_btn->setFixedSize(30, 30);
QObject::connect(plot_btn, &QPushButton::clicked, [=]() { emit showChart(id, name_); });
plot_btn->setFixedSize(20, 20);
QObject::connect(plot_btn, &QPushButton::clicked, this, &SignalEdit::showChart);
title_layout->addWidget(plot_btn);
main_layout->addLayout(title_layout);
// signal form
form_container = new QWidget(this);
QVBoxLayout *v_layout = new QVBoxLayout(form_container);
form = new SignalForm(sig, this);
v_layout->addWidget(form);
QHBoxLayout *h = new QHBoxLayout();
remove_btn = new QPushButton(tr("Remove Signal"));
QPushButton *remove_btn = new QPushButton(tr("Remove Signal"));
h->addWidget(remove_btn);
h->addStretch();
QPushButton *save_btn = new QPushButton(tr("Save"));
@ -117,13 +117,19 @@ SignalEdit::SignalEdit(int index, const QString &id, const Signal &sig, const QS
form_container->setVisible(false);
main_layout->addWidget(form_container);
QFrame* hline = new QFrame();
// bottom line
QFrame *hline = new QFrame();
hline->setFrameShape(QFrame::HLine);
hline->setFrameShadow(QFrame::Sunken);
main_layout->addWidget(hline);
QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove);
QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::save);
QObject::connect(save_btn, &QPushButton::clicked, [=]() {
QString new_name = form->getSignal().name.c_str();
title->setText(QString("%1. %2").arg(index + 1).arg(new_name));
emit save();
sig_name = new_name;
});
QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked);
}
@ -132,40 +138,24 @@ void SignalEdit::setFormVisible(bool visible) {
icon->setText(visible ? "" : ">");
}
void SignalEdit::save() {
if (auto s = form->getSignal())
dbc()->updateSignal(id, name_, *s);
}
void SignalEdit::remove() {
QMessageBox msgbox;
msgbox.setText(tr("Remove signal"));
msgbox.setInformativeText(tr("Are you sure you want to remove signal '%1'").arg(name_));
msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgbox.setDefaultButton(QMessageBox::Cancel);
if (msgbox.exec()) {
dbc()->removeSignal(id, name_);
deleteLater();
}
}
// AddSignalDialog
AddSignalDialog::AddSignalDialog(const QString &id, QWidget *parent) : QDialog(parent) {
AddSignalDialog::AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Add signal to %1").arg(dbc()->msg(id)->name.c_str()));
QVBoxLayout *main_layout = new QVBoxLayout(this);
Signal sig = {.name = "untitled"};
auto form = new SignalForm(sig, this);
Signal sig = {
.name = "untitled",
.start_bit = bigEndianBitIndex(start_bit),
.is_little_endian = false,
.size = size,
};
form = new SignalForm(sig, this);
main_layout->addWidget(form);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox);
setFixedWidth(parent->width() * 0.9);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(buttonBox, &QDialogButtonBox::accepted, [=]() {
if (auto signal = form->getSignal()) {
dbc()->addSignal(id, *signal);
}
QDialog::accept();
});
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
}

@ -1,7 +1,5 @@
#pragma once
#include <optional>
#include <QComboBox>
#include <QDialog>
#include <QLabel>
@ -15,14 +13,12 @@
#include "tools/cabana/dbcmanager.h"
class SignalForm : public QWidget {
Q_OBJECT
public:
SignalForm(const Signal &sig, QWidget *parent);
std::optional<Signal> getSignal();
Signal getSignal();
QLineEdit *name, *unit, *comment, *val_desc;
QSpinBox *size, *msb, *lsb, *offset;
QSpinBox *size, *offset;
QDoubleSpinBox *factor, *min_val, *max_val;
QComboBox *sign, *endianness;
int start_bit = 0;
@ -32,31 +28,26 @@ class SignalEdit : public QWidget {
Q_OBJECT
public:
SignalEdit(int index, const QString &id, const Signal &sig, const QString &color, QWidget *parent = nullptr);
SignalEdit(int index, const QString &msg_id, const Signal &sig, QWidget *parent = nullptr);
void setFormVisible(bool show);
inline bool isFormVisible() const { return form_container->isVisible(); }
void save();
QString sig_name;
SignalForm *form;
signals:
void showChart(const QString &msg_id, const QString &sig_name);
void showChart();
void showFormClicked();
protected:
void remove();
void save();
QString id;
QString name_;
QPushButton *plot_btn;
protected:
ElidedLabel *title;
SignalForm *form;
QWidget *form_container;
QPushButton *remove_btn;
QLabel *icon;
};
class AddSignalDialog : public QDialog {
Q_OBJECT
public:
AddSignalDialog(const QString &id, QWidget *parent);
AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent);
SignalForm *form;
};

@ -5,20 +5,19 @@
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QPainter>
#include <QPushButton>
#include <QStyleOptionSlider>
#include <QTimer>
#include <QVBoxLayout>
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) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
// TODO: figure out why the CameraViewWidget crashed occasionally.
cam_widget = new CameraViewWidget("camerad", VISION_STREAM_ROAD, false, this);
// TODO: figure out why the CameraWidget crashed occasionally.
cam_widget = new CameraWidget("camerad", VISION_STREAM_ROAD, false, this);
cam_widget->setFixedSize(parent->width(), parent->width() / 1.596);
main_layout->addWidget(cam_widget);
@ -39,9 +38,9 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
// btn controls
QHBoxLayout *control_layout = new QHBoxLayout();
QPushButton *play = new QPushButton("");
play->setStyleSheet("font-weight:bold");
control_layout->addWidget(play);
play_btn = new QPushButton("");
play_btn->setStyleSheet("font-weight:bold");
control_layout->addWidget(play_btn);
QButtonGroup *group = new QButtonGroup(this);
group->setExclusive(true);
@ -61,11 +60,13 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
QObject::connect(can, &CANMessages::updated, this, &VideoWidget::updateState);
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(play, &QPushButton::clicked, [=]() {
bool is_paused = can->isPaused();
play->setText(is_paused ? "" : "");
can->pause(!is_paused);
});
QObject::connect(cam_widget, &CameraWidget::clicked, [this]() { pause(!can->isPaused()); });
QObject::connect(play_btn, &QPushButton::clicked, [=]() { pause(!can->isPaused()); });
}
void VideoWidget::pause(bool pause) {
play_btn->setText(!pause ? "" : "");
can->pause(pause);
}
void VideoWidget::rangeChanged(double min, double max) {

@ -1,6 +1,7 @@
#pragma once
#include <QLabel>
#include <QPushButton>
#include <QSlider>
#include <QWidget>
@ -29,8 +30,10 @@ public:
protected:
void rangeChanged(double min, double max);
void updateState();
void pause(bool pause);
CameraViewWidget *cam_widget;
CameraWidget *cam_widget;
QLabel *end_time_label;
QPushButton *play_btn;
Slider *slider;
};

Loading…
Cancel
Save