mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-09-30 11:23:44 +08:00
Merge remote-tracking branch 'origin/master' into master3
This commit is contained in:
+1
-1
Submodule body updated: 0e74db67ae...c433da944f
+1
-1
Submodule panda updated: aa18bc85bb...4e9425715e
+1
-1
@@ -107,7 +107,6 @@ dev = [
|
||||
"parameterized >=0.8, <0.9",
|
||||
#pprofile = "*"
|
||||
"pyautogui",
|
||||
"pygame",
|
||||
"pyopencl; platform_machine != 'aarch64'", # broken on arm64
|
||||
"pywinctl",
|
||||
"pyprof2calltree",
|
||||
@@ -115,6 +114,7 @@ dev = [
|
||||
"tabulate",
|
||||
"types-requests",
|
||||
"types-tabulate",
|
||||
"hexdump",
|
||||
|
||||
# this is only pinned since 5.15.11 is broken
|
||||
"pyqt5 ==5.15.2; platform_machine == 'x86_64'", # no aarch64 wheels for macOS/linux
|
||||
|
||||
+1
-1
Submodule rednose_repo updated: 72b3479bab...023a6195db
@@ -1,15 +1,40 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
#!/bin/bash -e
|
||||
|
||||
SRC=/tmp/openpilot/
|
||||
SRC_CLONE=/tmp/openpilot-clone/
|
||||
OUT=/tmp/openpilot-tiny/
|
||||
|
||||
LOG_FILE=$DIR/git-rewrite-log-$(date +"%Y-%m-%dT%H:%M:%S%z").txt
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
REWRITE_IGNORE_BRANCHES=(
|
||||
dashcam3
|
||||
devel
|
||||
master-ci
|
||||
nightly
|
||||
release2
|
||||
release3
|
||||
release3-staging
|
||||
)
|
||||
|
||||
VALIDATE_IGNORE_FILES=(
|
||||
".github/ISSUE_TEMPLATE/bug_report.md"
|
||||
".github/pull_request_template.md"
|
||||
)
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
|
||||
LOGS_DIR=$DIR/git-rewrite-$(date +"%Y-%m-%dT%H:%M:%S%z")
|
||||
mkdir -p $LOGS_DIR
|
||||
|
||||
GIT_REWRITE_LOG=$LOGS_DIR/git-rewrite-log.txt
|
||||
BRANCH_DIFF_LOG=$LOGS_DIR/branch-diff-log.txt
|
||||
COMMIT_DIFF_LOG=$LOGS_DIR/commit-diff-log.txt
|
||||
|
||||
START_TIME=$(date +%s)
|
||||
exec > >(while IFS= read -r line; do
|
||||
CURRENT_TIME=$(date +%s)
|
||||
ELAPSED_TIME=$((CURRENT_TIME - START_TIME))
|
||||
echo "[${ELAPSED_TIME}s] $line"
|
||||
done | tee -a "$GIT_REWRITE_LOG") 2>&1
|
||||
|
||||
# INSTALL git-filter-repo
|
||||
if [ ! -f /tmp/git-filter-repo ]; then
|
||||
@@ -40,7 +65,7 @@ if [ ! -d $SRC ]; then
|
||||
git push --prune $ARCHIVE_REPO +refs/heads/*:refs/heads/* # 956.39 MiB (110725 objects)
|
||||
git push --prune $ARCHIVE_REPO +refs/tags/*:refs/tags/* # 1.75 GiB (21694 objects)
|
||||
# git push --mirror $ARCHIVE_REPO || true # fails to push refs/pull/* (deny updating a hidden ref) for pull requests
|
||||
# we fail and continue - more reading: https://stackoverflow.com/a/34266401/639708
|
||||
# we fail and continue - more reading: https://stackoverflow.com/a/34266401/639708 and https://blog.plataformatec.com.br/2013/05/how-to-properly-mirror-a-git-repository/
|
||||
fi
|
||||
|
||||
# REWRITE master and tags
|
||||
@@ -52,7 +77,7 @@ if [ ! -d $SRC_CLONE ]; then
|
||||
|
||||
echo "Checking out old history..."
|
||||
|
||||
git checkout tags/v0.7.1
|
||||
git checkout tags/v0.7.1 > /dev/null 2>&1
|
||||
# checkout as main, since we need master ref later
|
||||
git checkout -b main
|
||||
|
||||
@@ -60,22 +85,26 @@ if [ ! -d $SRC_CLONE ]; then
|
||||
|
||||
# rm these so we don't get conflicts later
|
||||
git rm -r cereal opendbc panda selfdrive/ui/ui > /dev/null
|
||||
git commit -m "removed conflicting files"
|
||||
git commit -m "removed conflicting files" > /dev/null
|
||||
|
||||
# skip-smudge to get rid of some lfs errors that it can't find the reference of some lfs files
|
||||
# we don't care about fetching/pushing lfs right now
|
||||
git lfs install --skip-smudge --local
|
||||
|
||||
# squash initial setup commits
|
||||
git cherry-pick -n -X theirs 6c33a5c..59b3d06
|
||||
git commit -m "switching to master" -m "$(git log --reverse --format=%B 6c33a5c..59b3d06)"
|
||||
git cherry-pick -n -X theirs 6c33a5c..59b3d06 > /dev/null
|
||||
git commit -m "switching to master" > /dev/null
|
||||
|
||||
# squash the two commits
|
||||
git reset --soft HEAD~2
|
||||
git commit -m "switching to master" -m "$(git log --reverse --format=%B 6c33a5c..59b3d06)" -m "removed conflicting files" > /dev/null
|
||||
|
||||
# get commits we want to cherry-pick
|
||||
# will start with the next commit after #59b3d06 tools is local now
|
||||
COMMITS=$(git rev-list --reverse 59b3d06..master)
|
||||
|
||||
# we need this for logging
|
||||
TOTAL_COMMITS=$(echo $COMMITS | wc -w)
|
||||
TOTAL_COMMITS=$(echo $COMMITS | wc -w | xargs)
|
||||
CURRENT_COMMIT_NUMBER=0
|
||||
|
||||
# empty this file
|
||||
@@ -85,7 +114,8 @@ if [ ! -d $SRC_CLONE ]; then
|
||||
|
||||
for COMMIT in $COMMITS; do
|
||||
CURRENT_COMMIT_NUMBER=$((CURRENT_COMMIT_NUMBER + 1))
|
||||
echo -ne "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Cherry-picking commit: $COMMIT"\\r
|
||||
# echo -ne "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Cherry-picking commit: $COMMIT"\\r
|
||||
echo "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Cherry-picking commit: $COMMIT"
|
||||
|
||||
# set environment variables to preserve author/committer and dates
|
||||
export GIT_AUTHOR_NAME=$(git show -s --format='%an' $COMMIT)
|
||||
@@ -119,6 +149,12 @@ if [ ! -d $SRC_CLONE ]; then
|
||||
# append the old commit ID to the commit message
|
||||
git commit --amend -m "$(git log -1 --pretty=%B)" -m "Former-commit-id: $COMMIT" > /dev/null
|
||||
fi
|
||||
|
||||
# prune every 3000 commits to avoid gc errors
|
||||
if [ $((CURRENT_COMMIT_NUMBER % 3000)) -eq 0 ]; then
|
||||
echo "Pruning repo..."
|
||||
git gc
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Rewriting tags..."
|
||||
@@ -137,7 +173,7 @@ if [ ! -d $SRC_CLONE ]; then
|
||||
NEW_COMMIT=$OLD_COMMIT
|
||||
fi
|
||||
|
||||
printf "Rewriting tag %s from commit %s\n" "$TAG" "$NEW_COMMIT"
|
||||
echo "Rewriting tag $TAG from commit $NEW_COMMIT"
|
||||
git tag -f "$TAG" "$NEW_COMMIT"
|
||||
done < "$DIR/tag-commit-map.txt"
|
||||
|
||||
@@ -153,19 +189,20 @@ if [ ! -d $SRC_CLONE ]; then
|
||||
fi
|
||||
|
||||
# REWRITE branches based on master
|
||||
if [ ! -f "$SRC_CLONE/branch-diff.txt" ]; then
|
||||
if [ ! -f "$SRC_CLONE/rewrite-branches-done" ]; then
|
||||
cd $SRC_CLONE
|
||||
> rewrite-branches-done
|
||||
|
||||
# empty file
|
||||
> branch-diff.txt
|
||||
> $BRANCH_DIFF_LOG
|
||||
|
||||
echo "Rewriting branches based on master..."
|
||||
|
||||
# will store raw diffs here, if exist
|
||||
mkdir -p differences
|
||||
|
||||
# get a list of all branches except master
|
||||
BRANCHES=$(git branch -r | grep -v ' -> ' | sed 's/origin\///' | grep -v '^master$')
|
||||
# get a list of all branches except master and REWRITE_IGNORE_BRANCHES
|
||||
BRANCHES=$(git branch -r | grep -v ' -> ' | sed 's/.*origin\///' | grep -v '^master$' | grep -v -f <(echo "${REWRITE_IGNORE_BRANCHES[*]}" | tr ' ' '\n'))
|
||||
|
||||
for BRANCH in $BRANCHES; do
|
||||
# check if the branch is based on master history
|
||||
@@ -176,7 +213,7 @@ if [ ! -f "$SRC_CLONE/branch-diff.txt" ]; then
|
||||
# create a new branch based on the new master
|
||||
NEW_MERGE_BASE=$(grep "^$MERGE_BASE " "commit-map.txt" | awk '{print $2}')
|
||||
if [ -z "$NEW_MERGE_BASE" ]; then
|
||||
echo "Error: could not find new merge base for branch $BRANCH" >> branch-diff.txt
|
||||
echo "Error: could not find new merge base for branch $BRANCH" >> $BRANCH_DIFF_LOG
|
||||
continue
|
||||
fi
|
||||
git checkout -b ${BRANCH}_new $NEW_MERGE_BASE
|
||||
@@ -208,8 +245,8 @@ if [ ! -f "$SRC_CLONE/branch-diff.txt" ]; then
|
||||
echo "$COMMIT EMPTY" >> commit-map.txt
|
||||
else
|
||||
# handle other errors or conflicts
|
||||
echo "Cherry-pick of ${BRANCH} branch failed. Removing branch upstream..." >> branch-diff.txt
|
||||
echo "$GIT_OUTPUT" > "differences/branch-${BRANCH}"
|
||||
echo "Cherry-pick of ${BRANCH} branch failed. Removing branch upstream..." >> $BRANCH_DIFF_LOG
|
||||
echo "$GIT_OUTPUT" > "$LOGS_DIR/branch-${BRANCH}"
|
||||
git cherry-pick --abort
|
||||
git push --delete origin ${BRANCH}
|
||||
HAS_ERROR=1
|
||||
@@ -239,28 +276,24 @@ if [ ! -f "$SRC_CLONE/branch-diff.txt" ]; then
|
||||
git checkout master > /dev/null
|
||||
git branch -D ${BRANCH}_new > /dev/null
|
||||
else
|
||||
# echo "Skipping branch $BRANCH as it's not based on master history" >> branch-diff.txt
|
||||
echo "Deleting branch $BRANCH as it's not based on master history" >> branch-diff.txt
|
||||
git push --delete origin ${BRANCH}
|
||||
echo "Deleting branch $BRANCH as it's not based on master history" >> $BRANCH_DIFF_LOG
|
||||
git push --delete origin ${BRANCH}
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# VALIDATE cherry-pick
|
||||
if [ ! -f "$SRC_CLONE/commit-diff.txt" ]; then
|
||||
if [ ! -f "$SRC_CLONE/validation-done" ]; then
|
||||
cd $SRC_CLONE
|
||||
> validation-done
|
||||
|
||||
TOTAL_COMMITS=$(grep -cve '^\s*$' commit-map.txt)
|
||||
CURRENT_COMMIT_NUMBER=0
|
||||
COUNT_SAME=0
|
||||
COUNT_DIFF=0
|
||||
VALIDATE_IGNORE_FILES=(
|
||||
".github/ISSUE_TEMPLATE/bug_report.md"
|
||||
".github/pull_request_template.md"
|
||||
)
|
||||
|
||||
# empty file
|
||||
> commit-diff.txt
|
||||
> $COMMIT_DIFF_LOG
|
||||
|
||||
echo "Validating commits..."
|
||||
|
||||
@@ -273,7 +306,7 @@ if [ ! -f "$SRC_CLONE/commit-diff.txt" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ "$OLD_COMMIT" == "BRANCH" ]; then
|
||||
echo "Branch ${NEW_COMMIT} below:" >> commit-diff.txt
|
||||
echo "Branch ${NEW_COMMIT} below:" >> $COMMIT_DIFF_LOG
|
||||
continue
|
||||
fi
|
||||
CURRENT_COMMIT_NUMBER=$((CURRENT_COMMIT_NUMBER + 1))
|
||||
@@ -283,7 +316,8 @@ if [ ! -f "$SRC_CLONE/commit-diff.txt" ]; then
|
||||
OLD_DATE=$(git show -s --format='%cd' $OLD_COMMIT)
|
||||
NEW_DATE=$(git show -s --format='%cd' $NEW_COMMIT)
|
||||
|
||||
echo -ne "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Comparing old commit $OLD_COMMIT_SHORT ($OLD_DATE) with new commit $NEW_COMMIT_SHORT ($NEW_DATE)"\\r
|
||||
# echo -ne "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Comparing old commit $OLD_COMMIT_SHORT ($OLD_DATE) with new commit $NEW_COMMIT_SHORT ($NEW_DATE)"\\r
|
||||
echo "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Comparing old commit $OLD_COMMIT_SHORT ($OLD_DATE) with new commit $NEW_COMMIT_SHORT ($NEW_DATE)"
|
||||
|
||||
# generate lists of files and their hashes for the old and new commits, excluding ignored files
|
||||
OLD_FILES=$(git ls-tree -r $OLD_COMMIT | grep -vE "$(IFS='|'; echo "${VALIDATE_IGNORE_FILES[*]}")")
|
||||
@@ -294,17 +328,17 @@ if [ ! -f "$SRC_CLONE/commit-diff.txt" ]; then
|
||||
# echo "Old commit $OLD_COMMIT_SHORT and new commit $NEW_COMMIT_SHORT are equivalent."
|
||||
COUNT_SAME=$((COUNT_SAME + 1))
|
||||
else
|
||||
echo "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Difference found between old commit $OLD_COMMIT_SHORT and new commit $NEW_COMMIT_SHORT" >> commit-diff.txt
|
||||
echo "[$CURRENT_COMMIT_NUMBER/$TOTAL_COMMITS] Difference found between old commit $OLD_COMMIT_SHORT and new commit $NEW_COMMIT_SHORT" >> $COMMIT_DIFF_LOG
|
||||
COUNT_DIFF=$((COUNT_DIFF + 1))
|
||||
set +e
|
||||
diff -u <(echo "$OLD_FILES") <(echo "$NEW_FILES") > "differences/$CURRENT_COMMIT_NUMBER-$OLD_COMMIT_SHORT-$NEW_COMMIT_SHORT"
|
||||
diff -u <(echo "$OLD_FILES") <(echo "$NEW_FILES") > "$LOGS_DIR/commit-$CURRENT_COMMIT_NUMBER-$OLD_COMMIT_SHORT-$NEW_COMMIT_SHORT"
|
||||
set -e
|
||||
fi
|
||||
done < "commit-map.txt"
|
||||
|
||||
echo "Summary:" >> commit-diff.txt
|
||||
echo "Equivalent commits: $COUNT_SAME" >> commit-diff.txt
|
||||
echo "Different commits: $COUNT_DIFF" >> commit-diff.txt
|
||||
echo "Summary:" >> $COMMIT_DIFF_LOG
|
||||
echo "Equivalent commits: $COUNT_SAME" >> $COMMIT_DIFF_LOG
|
||||
echo "Different commits: $COUNT_DIFF" >> $COMMIT_DIFF_LOG
|
||||
fi
|
||||
|
||||
if [ ! -d $OUT ]; then
|
||||
@@ -313,7 +347,6 @@ if [ ! -d $OUT ]; then
|
||||
cd $OUT
|
||||
|
||||
# remove all non-master branches
|
||||
# TODO: need to see if we "redo" the other branches (except master, master-ci, devel, devel-staging, release3, release3-staging, dashcam3, dashcam3-staging, testing-closet*, hotfix-*)
|
||||
# git branch | grep -v "^ master$" | grep -v "\*" | xargs git branch -D
|
||||
|
||||
# echo "cleaning up refs"
|
||||
@@ -322,7 +355,8 @@ if [ ! -d $OUT ]; then
|
||||
|
||||
echo "importing new lfs files"
|
||||
# import "almost" everything to lfs
|
||||
git lfs migrate import --everything --include="*.dlc,*.onnx,*.svg,*.png,*.gif,*.ttf,*.wav,selfdrive/car/tests/test_models_segs.txt,system/hardware/tici/updater,selfdrive/ui/qt/spinner_larch64,selfdrive/ui/qt/text_larch64,third_party/**/*.a,third_party/**/*.so,third_party/**/*.so.*,third_party/**/*.dylib,third_party/acados/*/t_renderer,third_party/qt5/larch64/bin/lrelease,third_party/qt5/larch64/bin/lupdate,third_party/catch2/include/catch2/catch.hpp,*.apk,*.apkpatch,*.jar,*.pdf,*.jpg,*.mp3,*.thneed,*.tar.gz,*.npy,*.csv,*.a,*.so*,*.dylib,*.o,*.b64,selfdrive/hardware/tici/updater,selfdrive/boardd/tests/test_boardd,selfdrive/ui/qt/spinner_aarch64,installer/updater/updater,selfdrive/debug/profiling/simpleperf/**/*,selfdrive/hardware/eon/updater,selfdrive/ui/qt/text_aarch64,selfdrive/debug/profiling/pyflame/**/*,installer/installers/installer_openpilot,installer/installers/installer_dashcam,selfdrive/ui/text/text,selfdrive/ui/android/text/text,selfdrive/ui/spinner/spinner,selfdrive/visiond/visiond,selfdrive/loggerd/loggerd,selfdrive/sensord/sensord,selfdrive/sensord/gpsd,selfdrive/ui/android/spinner/spinner,selfdrive/ui/qt/spinner,selfdrive/ui/qt/text,_stringdefs.py,dfu-util-aarch64-linux,dfu-util-aarch64,dfu-util-x86_64-linux,dfu-util-x86_64,stb_image.h,clpeak3,clwaste,apk/**/*,external/**/*,phonelibs/**/*,third_party/boringssl/**/*,flask/**/*,panda/**/*,board/**/*,messaging/**/*,opendbc/**/*,tools/cabana/chartswidget.cc,third_party/nanovg/**/*,selfdrive/controls/lib/lateral_mpc/lib_mpc_export/**/*,selfdrive/ui/paint.cc,werkzeug/**/*,pyextra/**/*,third_party/android_hardware_libhardware/**/*,selfdrive/controls/lib/lead_mpc_lib/lib_mpc_export/**/*,selfdrive/locationd/laikad.py,selfdrive/locationd/test/test_laikad.py,tools/gpstest/test_laikad.py,selfdrive/locationd/laikad_helpers.py,tools/nui/**/*,jsonrpc/**/*,selfdrive/controls/lib/longitudinal_mpc/lib_mpc_export/**/*,selfdrive/controls/lib/lateral_mpc/mpc_export/**/*,selfdrive/camerad/cameras/camera_qcom.cc,selfdrive/manager.py,selfdrive/modeld/models/driving.cc,third_party/curl/**/*,selfdrive/modeld/thneed/debug/**/*,selfdrive/modeld/thneed/include/**/*,third_party/openmax/**/*,selfdrive/controls/lib/longitudinal_mpc/mpc_export/**/*,selfdrive/controls/lib/longitudinal_mpc_model/lib_mpc_export/**/*,Pipfile,Pipfile.lock,gunicorn/**/*,*.qm,jinja2/**/*,click/**/*,dbcs/**/*,websocket/**/*"
|
||||
BRANCHES=$(git for-each-ref --format='%(refname)' refs/heads/ | sed 's%refs/heads/%%g' | grep -v -f <(echo "${REWRITE_IGNORE_BRANCHES[*]}" | tr ' ' '\n') | tr '\n' ' ')
|
||||
git lfs migrate import --include="*.dlc,*.onnx,*.svg,*.png,*.gif,*.ttf,*.wav,selfdrive/car/tests/test_models_segs.txt,system/hardware/tici/updater,selfdrive/ui/qt/spinner_larch64,selfdrive/ui/qt/text_larch64,third_party/**/*.a,third_party/**/*.so,third_party/**/*.so.*,third_party/**/*.dylib,third_party/acados/*/t_renderer,third_party/qt5/larch64/bin/lrelease,third_party/qt5/larch64/bin/lupdate,third_party/catch2/include/catch2/catch.hpp,*.apk,*.apkpatch,*.jar,*.pdf,*.jpg,*.mp3,*.thneed,*.tar.gz,*.npy,*.csv,*.a,*.so*,*.dylib,*.o,*.b64,selfdrive/hardware/tici/updater,selfdrive/boardd/tests/test_boardd,selfdrive/ui/qt/spinner_aarch64,installer/updater/updater,selfdrive/debug/profiling/simpleperf/**/*,selfdrive/hardware/eon/updater,selfdrive/ui/qt/text_aarch64,selfdrive/debug/profiling/pyflame/**/*,installer/installers/installer_openpilot,installer/installers/installer_dashcam,selfdrive/ui/text/text,selfdrive/ui/android/text/text,selfdrive/ui/spinner/spinner,selfdrive/visiond/visiond,selfdrive/loggerd/loggerd,selfdrive/sensord/sensord,selfdrive/sensord/gpsd,selfdrive/ui/android/spinner/spinner,selfdrive/ui/qt/spinner,selfdrive/ui/qt/text,_stringdefs.py,dfu-util-aarch64-linux,dfu-util-aarch64,dfu-util-x86_64-linux,dfu-util-x86_64,stb_image.h,clpeak3,clwaste,apk/**/*,external/**/*,phonelibs/**/*,third_party/boringssl/**/*,flask/**/*,panda/**/*,board/**/*,messaging/**/*,opendbc/**/*,tools/cabana/chartswidget.cc,third_party/nanovg/**/*,selfdrive/controls/lib/lateral_mpc/lib_mpc_export/**/*,selfdrive/ui/paint.cc,werkzeug/**/*,pyextra/**/*,third_party/android_hardware_libhardware/**/*,selfdrive/controls/lib/lead_mpc_lib/lib_mpc_export/**/*,selfdrive/locationd/laikad.py,selfdrive/locationd/test/test_laikad.py,tools/gpstest/test_laikad.py,selfdrive/locationd/laikad_helpers.py,tools/nui/**/*,jsonrpc/**/*,selfdrive/controls/lib/longitudinal_mpc/lib_mpc_export/**/*,selfdrive/controls/lib/lateral_mpc/mpc_export/**/*,selfdrive/camerad/cameras/camera_qcom.cc,selfdrive/manager.py,selfdrive/modeld/models/driving.cc,third_party/curl/**/*,selfdrive/modeld/thneed/debug/**/*,selfdrive/modeld/thneed/include/**/*,third_party/openmax/**/*,selfdrive/controls/lib/longitudinal_mpc/mpc_export/**/*,selfdrive/controls/lib/longitudinal_mpc_model/lib_mpc_export/**/*,Pipfile,Pipfile.lock,gunicorn/**/*,*.qm,jinja2/**/*,click/**/*,dbcs/**/*,websocket/**/*" $BRANCHES
|
||||
|
||||
echo "reflog and gc"
|
||||
# this is needed after lfs import
|
||||
@@ -351,7 +385,6 @@ git lfs fetch --all || true
|
||||
|
||||
# final push - will also push lfs
|
||||
# TODO: switch to git@github.com:commaai/openpilot.git when ready
|
||||
# if using mirror, it will also delete non-master branches (master-ci, nightly, devel, release3, release3-staging, dashcam3, release2)
|
||||
# git push --mirror git@github.com:commaai/openpilot-tiny.git
|
||||
# using this instead - since this is also what --mirror does - https://blog.plataformatec.com.br/2013/05/how-to-properly-mirror-a-git-repository/
|
||||
git push git@github.com:commaai/openpilot-tiny.git +refs/heads/*:refs/heads/* +refs/tags/*:refs/tags/*
|
||||
# using this instead to ignore refs/pull/* - since this is also what --mirror does - https://blog.plataformatec.com.br/2013/05/how-to-properly-mirror-a-git-repository/
|
||||
git push --prune git@github.com:commaai/openpilot-tiny.git +refs/heads/*:refs/heads/* +refs/tags/*:refs/tags/*
|
||||
|
||||
@@ -51,22 +51,21 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
|
||||
|
||||
|
||||
def get_accel_from_plan(CP, speeds, accels):
|
||||
if len(speeds) == CONTROL_N:
|
||||
v_target_now = interp(DT_MDL, CONTROL_N_T_IDX, speeds)
|
||||
a_target_now = interp(DT_MDL, CONTROL_N_T_IDX, accels)
|
||||
if len(speeds) == CONTROL_N:
|
||||
v_target_now = interp(DT_MDL, CONTROL_N_T_IDX, speeds)
|
||||
a_target_now = interp(DT_MDL, CONTROL_N_T_IDX, accels)
|
||||
|
||||
v_target = interp(CP.longitudinalActuatorDelay + DT_MDL, CONTROL_N_T_IDX, speeds)
|
||||
a_target = 2 * (v_target - v_target_now) / CP.longitudinalActuatorDelay - a_target_now
|
||||
v_target = interp(CP.longitudinalActuatorDelay + DT_MDL, CONTROL_N_T_IDX, speeds)
|
||||
a_target = 2 * (v_target - v_target_now) / CP.longitudinalActuatorDelay - a_target_now
|
||||
|
||||
v_target_1sec = interp(CP.longitudinalActuatorDelay + DT_MDL + 1.0, CONTROL_N_T_IDX, speeds)
|
||||
else:
|
||||
v_target = 0.0
|
||||
v_target_now = 0.0
|
||||
v_target_1sec = 0.0
|
||||
a_target = 0.0
|
||||
should_stop = (v_target < CP.vEgoStopping and
|
||||
v_target_1sec < CP.vEgoStopping)
|
||||
return a_target, should_stop
|
||||
v_target_1sec = interp(CP.longitudinalActuatorDelay + DT_MDL + 1.0, CONTROL_N_T_IDX, speeds)
|
||||
else:
|
||||
v_target = 0.0
|
||||
v_target_1sec = 0.0
|
||||
a_target = 0.0
|
||||
should_stop = (v_target < CP.vEgoStopping and
|
||||
v_target_1sec < CP.vEgoStopping)
|
||||
return a_target, should_stop
|
||||
|
||||
|
||||
class LongitudinalPlanner:
|
||||
@@ -95,8 +94,8 @@ class LongitudinalPlanner:
|
||||
@staticmethod
|
||||
def parse_model(model_msg, model_error):
|
||||
if (len(model_msg.position.x) == ModelConstants.IDX_N and
|
||||
len(model_msg.velocity.x) == ModelConstants.IDX_N and
|
||||
len(model_msg.acceleration.x) == ModelConstants.IDX_N):
|
||||
len(model_msg.velocity.x) == ModelConstants.IDX_N and
|
||||
len(model_msg.acceleration.x) == ModelConstants.IDX_N):
|
||||
x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC
|
||||
v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error
|
||||
a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x)
|
||||
|
||||
@@ -177,8 +177,11 @@ class TorqueEstimator(ParameterEstimator):
|
||||
if len(self.raw_points['steer_torque']) == self.hist_len:
|
||||
yaw_rate = msg.angularVelocityCalibrated.value[2]
|
||||
roll = msg.orientationNED.value[0]
|
||||
lat_active = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t, DT_MDL), self.raw_points['carControl_t'], self.raw_points['lat_active']).astype(bool)
|
||||
steer_override = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t, DT_MDL), self.raw_points['carState_t'], self.raw_points['steer_override']).astype(bool)
|
||||
# check lat active up to now (without lag compensation)
|
||||
lat_active = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t + self.lag, DT_MDL),
|
||||
self.raw_points['carControl_t'], self.raw_points['lat_active']).astype(bool)
|
||||
steer_override = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t + self.lag, DT_MDL),
|
||||
self.raw_points['carState_t'], self.raw_points['steer_override']).astype(bool)
|
||||
vego = np.interp(t, self.raw_points['carState_t'], self.raw_points['vego'])
|
||||
steer = np.interp(t, self.raw_points['carOutput_t'], self.raw_points['steer_torque']).item()
|
||||
lateral_acc = (vego * yaw_rate) - (np.sin(roll) * ACCELERATION_DUE_TO_GRAVITY).item()
|
||||
|
||||
@@ -26,8 +26,6 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
|
||||
|
||||
hw_type = get_hw_type();
|
||||
can_reset_communications();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool Panda::connected() {
|
||||
|
||||
@@ -1 +1 @@
|
||||
f6ff3601bd0496e78d8bc3b019d58bb7739f096b
|
||||
0adff03d45c99dcfb330c48b2aa9d2093ce674a2
|
||||
|
||||
@@ -60,11 +60,13 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
const auto cs = sm["controlsState"].getControlsState();
|
||||
const auto car_state = sm["carState"].getCarState();
|
||||
|
||||
is_metric = s.scene.is_metric;
|
||||
|
||||
// Handle older routes where vCruiseCluster is not set
|
||||
float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster();
|
||||
setSpeed = cs_alive ? v_cruise : SET_SPEED_NA;
|
||||
is_cruise_set = setSpeed > 0 && (int)setSpeed != SET_SPEED_NA;
|
||||
if (is_cruise_set && !s.scene.is_metric) {
|
||||
if (is_cruise_set && !is_metric) {
|
||||
setSpeed *= KM_TO_MILE;
|
||||
}
|
||||
|
||||
@@ -72,10 +74,9 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0;
|
||||
float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo();
|
||||
speed = cs_alive ? std::max<float>(0.0, v_ego) : 0.0;
|
||||
speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH;
|
||||
speed *= is_metric ? MS_TO_KPH : MS_TO_MPH;
|
||||
|
||||
is_metric = s.scene.is_metric;
|
||||
speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph");
|
||||
speedUnit = is_metric ? tr("km/h") : tr("mph");
|
||||
hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
|
||||
status = s.status;
|
||||
|
||||
|
||||
@@ -235,8 +235,8 @@ def hardware_thread(end_event, hw_queue) -> None:
|
||||
onroad_conditions["ignition"] = False
|
||||
cloudlog.error("panda timed out onroad")
|
||||
|
||||
# Run at 2Hz, plus rising edge of ignition
|
||||
ign_edge = started_ts is None and onroad_conditions["ignition"]
|
||||
# Run at 2Hz, plus either edge of ignition
|
||||
ign_edge = (started_ts is not None) != onroad_conditions["ignition"]
|
||||
if (sm.frame % round(SERVICE_LIST['pandaStates'].frequency * DT_HW) != 0) and not ign_edge:
|
||||
continue
|
||||
|
||||
|
||||
+69
-1
@@ -29,4 +29,72 @@ Arguments:
|
||||
connect.comma.ai
|
||||
```
|
||||
|
||||
See [openpilot wiki](https://github.com/commaai/openpilot/wiki/Cabana)
|
||||
## Examples
|
||||
|
||||
### Running Cabana in Demo Mode
|
||||
To run Cabana using a built-in demo route, use the following command:
|
||||
|
||||
```shell
|
||||
cabana --demo
|
||||
```
|
||||
|
||||
### Loading a Specific Route
|
||||
|
||||
To load a specific route for replay, provide the route as an argument:
|
||||
|
||||
```shell
|
||||
cabana "a2a0ccea32023010|2023-07-27--13-01-19"
|
||||
```
|
||||
|
||||
Replace "0ccea32023010|2023-07-27--13-01-19" with your desired route identifier.
|
||||
|
||||
|
||||
### Running Cabana with multiple cameras
|
||||
To run Cabana with multiple cameras, use the following command:
|
||||
|
||||
```shell
|
||||
cabana "a2a0ccea32023010|2023-07-27--13-01-19" --dcam --ecam
|
||||
```
|
||||
|
||||
### Streaming CAN Messages from a comma Device
|
||||
|
||||
[SSH into your device](https://github.com/commaai/openpilot/wiki/SSH) and start the bridge with the following command:
|
||||
|
||||
```shell
|
||||
cd /data/openpilot/cereal/messaging/
|
||||
./bridge &
|
||||
```
|
||||
|
||||
Then Run Cabana with the device's IP address:
|
||||
|
||||
```shell
|
||||
cabana --stream <ipaddress>
|
||||
```
|
||||
|
||||
Replace <ipaddress> with your comma device's IP address.
|
||||
|
||||
While streaming from the device, Cabana will log the CAN messages to a local directory. By default, this directory is ~/cabana_live_stream/. You can change the log directory in Cabana by navigating to menu -> tools -> settings.
|
||||
|
||||
After disconnecting from the device, you can replay the logged CAN messages from the stream selector dialog -> browse local route.
|
||||
|
||||
### Streaming CAN Messages from Panda
|
||||
|
||||
To read CAN messages from a connected Panda, use the following command:
|
||||
|
||||
```shell
|
||||
cabana --panda
|
||||
```
|
||||
|
||||
### Using the Stream Selector Dialog
|
||||
|
||||
If you run Cabana without any arguments, a stream selector dialog will pop up, allowing you to choose the stream.
|
||||
|
||||
```shell
|
||||
cabana
|
||||
```
|
||||
|
||||
## Additional Information
|
||||
|
||||
For more information, see the [openpilot wiki](https://github.com/commaai/openpilot/wiki/Cabana)
|
||||
|
||||
If you encounter any issues or have feature requests, please contribute bug reports or discussions at: [new cabana feedback](https://github.com/commaai/openpilot/discussions/26091)
|
||||
+6
-23
@@ -38,26 +38,19 @@ int main(int argc, char *argv[]) {
|
||||
cmd_parser.addOption({"dbc", "dbc file to open", "dbc"});
|
||||
cmd_parser.process(app);
|
||||
|
||||
QString dbc_file = cmd_parser.isSet("dbc") ? cmd_parser.value("dbc") : "";
|
||||
|
||||
AbstractStream *stream = nullptr;
|
||||
|
||||
if (cmd_parser.isSet("stream")) {
|
||||
stream = new DeviceStream(&app, cmd_parser.value("zmq"));
|
||||
} else if (cmd_parser.isSet("panda") || cmd_parser.isSet("panda-serial")) {
|
||||
PandaStreamConfig config = {};
|
||||
if (cmd_parser.isSet("panda-serial")) {
|
||||
config.serial = cmd_parser.value("panda-serial");
|
||||
}
|
||||
try {
|
||||
stream = new PandaStream(&app, config);
|
||||
stream = new PandaStream(&app, {.serial = cmd_parser.value("panda-serial")});
|
||||
} catch (std::exception &e) {
|
||||
qWarning() << e.what();
|
||||
return 0;
|
||||
}
|
||||
} else if (cmd_parser.isSet("socketcan")) {
|
||||
SocketCanStreamConfig config = {};
|
||||
config.device = cmd_parser.value("socketcan");
|
||||
stream = new SocketCanStream(&app, config);
|
||||
stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan")});
|
||||
} else {
|
||||
uint32_t replay_flags = REPLAY_FLAG_NONE;
|
||||
if (cmd_parser.isSet("ecam")) replay_flags |= REPLAY_FLAG_ECAM;
|
||||
@@ -73,24 +66,14 @@ int main(int argc, char *argv[]) {
|
||||
route = DEMO_ROUTE;
|
||||
}
|
||||
if (!route.isEmpty()) {
|
||||
auto replay_stream = new ReplayStream(&app);
|
||||
auto replay_stream = std::make_unique<ReplayStream>(&app);
|
||||
if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
|
||||
return 0;
|
||||
}
|
||||
stream = replay_stream;
|
||||
stream = replay_stream.release();
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow w;
|
||||
if (stream) {
|
||||
stream->start();
|
||||
if (!dbc_file.isEmpty()) {
|
||||
w.loadFile(dbc_file);
|
||||
}
|
||||
} else {
|
||||
w.openStream();
|
||||
}
|
||||
w.show();
|
||||
|
||||
MainWindow w(stream, cmd_parser.value("dbc"));
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -155,11 +155,10 @@ void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) {
|
||||
}
|
||||
|
||||
void ChartView::signalUpdated(const cabana::Signal *sig) {
|
||||
if (std::any_of(sigs.cbegin(), sigs.cend(), [=](auto &s) { return s.sig == sig; })) {
|
||||
for (const auto &s : sigs) {
|
||||
if (s.sig == sig && s.series->color() != sig->color) {
|
||||
setSeriesColor(s.series, sig->color);
|
||||
}
|
||||
auto it = std::find_if(sigs.begin(), sigs.end(), [sig](auto &s) { return s.sig == sig; });
|
||||
if (it != sigs.end()) {
|
||||
if (it->series->color() != sig->color) {
|
||||
setSeriesColor(it->series, sig->color);
|
||||
}
|
||||
updateTitle();
|
||||
updateSeries(sig);
|
||||
|
||||
@@ -110,7 +110,6 @@ private:
|
||||
QTimer *align_timer;
|
||||
int current_theme = 0;
|
||||
bool value_tip_visible_ = false;
|
||||
friend class ZoomCommand;
|
||||
friend class ChartView;
|
||||
friend class ChartsContainer;
|
||||
};
|
||||
|
||||
@@ -83,7 +83,8 @@ void SignalSelector::updateAvailableList(int index) {
|
||||
MessageId msg_id = msgs_combo->itemData(index).value<MessageId>();
|
||||
auto selected_items = seletedItems();
|
||||
for (auto s : dbc()->msg(msg_id)->getSignals()) {
|
||||
bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig = s](auto it) { return it->msg_id == msg_id && it->sig == sig; });
|
||||
bool is_selected = std::any_of(selected_items.begin(), selected_items.end(),
|
||||
[sig = s, &msg_id](auto it) { return it->msg_id == msg_id && it->sig == sig; });
|
||||
if (!is_selected) {
|
||||
addItemToList(available_list, msg_id, s);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include <limits>
|
||||
#include <QPainter>
|
||||
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size) {
|
||||
const auto &msgs = can->events(msg_id);
|
||||
|
||||
@@ -14,11 +12,6 @@ void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, doubl
|
||||
auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), range_start, CompareCanEvent());
|
||||
auto last = std::upper_bound(first, msgs.cend(), range_end, CompareCanEvent());
|
||||
|
||||
if (first == last || size.isEmpty()) {
|
||||
pixmap = QPixmap();
|
||||
return;
|
||||
}
|
||||
|
||||
points.clear();
|
||||
double value = 0;
|
||||
for (auto it = first; it != last; ++it) {
|
||||
@@ -27,7 +20,7 @@ void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, doubl
|
||||
}
|
||||
}
|
||||
|
||||
if (points.empty()) {
|
||||
if (points.empty() || size.isEmpty()) {
|
||||
pixmap = QPixmap();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "tools/cabana/dbc/dbc.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
class Sparkline {
|
||||
public:
|
||||
|
||||
@@ -9,10 +9,6 @@ DBCFile::DBCFile(const QString &dbc_file_name) {
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
name_ = QFileInfo(dbc_file_name).baseName();
|
||||
filename = dbc_file_name;
|
||||
// Remove auto save file extension
|
||||
if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) {
|
||||
filename.chop(AUTO_SAVE_EXTENSION.length());
|
||||
}
|
||||
parse(file.readAll());
|
||||
} else {
|
||||
throw std::runtime_error("Failed to open file.");
|
||||
@@ -20,17 +16,12 @@ DBCFile::DBCFile(const QString &dbc_file_name) {
|
||||
}
|
||||
|
||||
DBCFile::DBCFile(const QString &name, const QString &content) : name_(name), filename("") {
|
||||
// Open from clipboard
|
||||
parse(content);
|
||||
}
|
||||
|
||||
bool DBCFile::save() {
|
||||
assert(!filename.isEmpty());
|
||||
if (writeContents(filename)) {
|
||||
cleanupAutoSaveFile();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return writeContents(filename);
|
||||
}
|
||||
|
||||
bool DBCFile::saveAs(const QString &new_filename) {
|
||||
@@ -38,16 +29,6 @@ bool DBCFile::saveAs(const QString &new_filename) {
|
||||
return save();
|
||||
}
|
||||
|
||||
bool DBCFile::autoSave() {
|
||||
return !filename.isEmpty() && writeContents(filename + AUTO_SAVE_EXTENSION);
|
||||
}
|
||||
|
||||
void DBCFile::cleanupAutoSaveFile() {
|
||||
if (!filename.isEmpty()) {
|
||||
QFile::remove(filename + AUTO_SAVE_EXTENSION);
|
||||
}
|
||||
}
|
||||
|
||||
bool DBCFile::writeContents(const QString &fn) {
|
||||
QFile file(fn);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
@@ -75,7 +56,7 @@ cabana::Msg *DBCFile::msg(const QString &name) {
|
||||
return it != msgs.end() ? &(it->second) : nullptr;
|
||||
}
|
||||
|
||||
cabana::Signal *DBCFile::signal(uint32_t address, const QString name) {
|
||||
cabana::Signal *DBCFile::signal(uint32_t address, const QString &name) {
|
||||
auto m = msg(address);
|
||||
return m ? (cabana::Signal *)m->sig(name) : nullptr;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "tools/cabana/dbc/dbc.h"
|
||||
|
||||
const QString AUTO_SAVE_EXTENSION = ".tmp";
|
||||
|
||||
class DBCFile {
|
||||
public:
|
||||
DBCFile(const QString &dbc_file_name);
|
||||
@@ -15,9 +13,7 @@ public:
|
||||
|
||||
bool save();
|
||||
bool saveAs(const QString &new_filename);
|
||||
bool autoSave();
|
||||
bool writeContents(const QString &fn);
|
||||
void cleanupAutoSaveFile();
|
||||
QString generateDBC();
|
||||
|
||||
void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment);
|
||||
@@ -27,7 +23,7 @@ public:
|
||||
cabana::Msg *msg(uint32_t address);
|
||||
cabana::Msg *msg(const QString &name);
|
||||
inline cabana::Msg *msg(const MessageId &id) { return msg(id.address); }
|
||||
cabana::Signal *signal(uint32_t address, const QString name);
|
||||
cabana::Signal *signal(uint32_t address, const QString &name);
|
||||
|
||||
inline QString name() const { return name_.isEmpty() ? "untitled" : name_; }
|
||||
inline bool isEmpty() const { return msgs.empty() && name_.isEmpty(); }
|
||||
|
||||
+72
-135
@@ -12,6 +12,7 @@
|
||||
#include <QJsonObject>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QResizeEvent>
|
||||
#include <QShortcut>
|
||||
#include <QTextDocument>
|
||||
@@ -23,9 +24,8 @@
|
||||
#include "tools/cabana/streamselector.h"
|
||||
#include "tools/cabana/tools/findsignal.h"
|
||||
#include "tools/cabana/utils/export.h"
|
||||
#include "tools/replay/replay.h"
|
||||
|
||||
MainWindow::MainWindow() : QMainWindow() {
|
||||
MainWindow::MainWindow(AbstractStream *stream, const QString &dbc_file) : QMainWindow() {
|
||||
loadFingerprints();
|
||||
createDockWindows();
|
||||
setCentralWidget(center_widget = new CenterWidget(this));
|
||||
@@ -65,10 +65,10 @@ MainWindow::MainWindow() : QMainWindow() {
|
||||
QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged);
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged);
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MainWindow::undoStackIndexChanged);
|
||||
QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus);
|
||||
QObject::connect(StreamNotifier::instance(), &StreamNotifier::changingStream, this, &MainWindow::changingStream);
|
||||
QObject::connect(StreamNotifier::instance(), &StreamNotifier::streamStarted, this, &MainWindow::streamStarted);
|
||||
|
||||
QTimer::singleShot(0, this, [=]() { stream ? openStream(stream, dbc_file) : selectAndOpenStream(); });
|
||||
show();
|
||||
}
|
||||
|
||||
void MainWindow::loadFingerprints() {
|
||||
@@ -76,16 +76,12 @@ void MainWindow::loadFingerprints() {
|
||||
if (json_file.open(QIODevice::ReadOnly)) {
|
||||
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
|
||||
}
|
||||
// get opendbc names
|
||||
for (auto fn : QDir(OPENDBC_FILE_PATH).entryList({"*.dbc"}, QDir::Files, QDir::Name)) {
|
||||
opendbc_names << QFileInfo(fn).baseName();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::createActions() {
|
||||
// File menu
|
||||
QMenu *file_menu = menuBar()->addMenu(tr("&File"));
|
||||
file_menu->addAction(tr("Open Stream..."), this, &MainWindow::openStream);
|
||||
file_menu->addAction(tr("Open Stream..."), this, &MainWindow::selectAndOpenStream);
|
||||
close_stream_act = file_menu->addAction(tr("Close stream"), this, &MainWindow::closeStream);
|
||||
export_to_csv_act = file_menu->addAction(tr("Export to CSV..."), this, &MainWindow::exportToCSV);
|
||||
close_stream_act->setEnabled(false);
|
||||
@@ -96,20 +92,15 @@ void MainWindow::createActions() {
|
||||
file_menu->addAction(tr("Open DBC File..."), [this]() { openFile(); }, QKeySequence::Open);
|
||||
|
||||
manage_dbcs_menu = file_menu->addMenu(tr("Manage &DBC Files"));
|
||||
QObject::connect(manage_dbcs_menu, &QMenu::aboutToShow, this, &MainWindow::updateLoadSaveMenus);
|
||||
|
||||
open_recent_menu = file_menu->addMenu(tr("Open &Recent"));
|
||||
for (int i = 0; i < MAX_RECENT_FILES; ++i) {
|
||||
recent_files_acts[i] = new QAction(this);
|
||||
recent_files_acts[i]->setVisible(false);
|
||||
QObject::connect(recent_files_acts[i], &QAction::triggered, this, &MainWindow::openRecentFile);
|
||||
open_recent_menu->addAction(recent_files_acts[i]);
|
||||
}
|
||||
updateRecentFileActions();
|
||||
QObject::connect(open_recent_menu, &QMenu::aboutToShow, this, &MainWindow::updateRecentFileMenu);
|
||||
|
||||
file_menu->addSeparator();
|
||||
QMenu *load_opendbc_menu = file_menu->addMenu(tr("Load DBC from commaai/opendbc"));
|
||||
// load_opendbc_menu->setStyleSheet("QMenu { menu-scrollable: true; }");
|
||||
for (const auto &dbc_name : opendbc_names) {
|
||||
for (const auto &dbc_name : QDir(OPENDBC_FILE_PATH).entryList({"*.dbc"}, QDir::Files, QDir::Name)) {
|
||||
load_opendbc_menu->addAction(dbc_name, [this, name = dbc_name]() { loadDBCFromOpendbc(name); });
|
||||
}
|
||||
|
||||
@@ -180,6 +171,7 @@ void MainWindow::createDockWidgets() {
|
||||
messages_widget = new MessagesWidget(this);
|
||||
messages_dock->setWidget(messages_widget);
|
||||
QObject::connect(messages_widget, &MessagesWidget::titleChanged, messages_dock, &QDockWidget::setWindowTitle);
|
||||
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, center_widget, &CenterWidget::setMessage);
|
||||
|
||||
// right panel
|
||||
charts_widget = new ChartsWidget(this);
|
||||
@@ -209,67 +201,52 @@ void MainWindow::createStatusBar() {
|
||||
progress_bar->setVisible(false);
|
||||
statusBar()->addWidget(new QLabel(tr("For Help, Press F1")));
|
||||
statusBar()->addPermanentWidget(progress_bar);
|
||||
|
||||
statusBar()->addPermanentWidget(status_label = new QLabel(this));
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void MainWindow::createShortcuts() {
|
||||
auto shortcut = new QShortcut(QKeySequence(Qt::Key_Space), this, nullptr, nullptr, Qt::ApplicationShortcut);
|
||||
QObject::connect(shortcut, &QShortcut::activated, []() { can->pause(!can->isPaused()); });
|
||||
QObject::connect(shortcut, &QShortcut::activated, this, []() {
|
||||
if (can) can->pause(!can->isPaused());
|
||||
});
|
||||
// TODO: add more shortcuts here.
|
||||
}
|
||||
|
||||
void MainWindow::undoStackIndexChanged(int index) {
|
||||
int count = UndoStack::instance()->count();
|
||||
if (count >= 0) {
|
||||
QString command_text;
|
||||
if (index == count) {
|
||||
command_text = (count == prev_undostack_count ? "Redo " : "") + UndoStack::instance()->text(index - 1);
|
||||
} else if (index < prev_undostack_index) {
|
||||
command_text = tr("Undo %1").arg(UndoStack::instance()->text(index));
|
||||
} else if (index > prev_undostack_index) {
|
||||
command_text = tr("Redo %1").arg(UndoStack::instance()->text(index - 1));
|
||||
}
|
||||
statusBar()->showMessage(command_text, 2000);
|
||||
}
|
||||
prev_undostack_index = index;
|
||||
prev_undostack_count = count;
|
||||
autoSave();
|
||||
updateLoadSaveMenus();
|
||||
}
|
||||
|
||||
void MainWindow::undoStackCleanChanged(bool clean) {
|
||||
if (clean) {
|
||||
prev_undostack_index = 0;
|
||||
prev_undostack_count = 0;
|
||||
}
|
||||
setWindowModified(!clean);
|
||||
}
|
||||
|
||||
void MainWindow::DBCFileChanged() {
|
||||
UndoStack::instance()->clear();
|
||||
updateLoadSaveMenus();
|
||||
|
||||
// Update file menu
|
||||
int cnt = dbc()->nonEmptyDBCCount();
|
||||
save_dbc->setText(cnt > 1 ? tr("Save %1 DBCs...").arg(cnt) : tr("Save DBC..."));
|
||||
save_dbc->setEnabled(cnt > 0);
|
||||
save_dbc_as->setEnabled(cnt == 1);
|
||||
// TODO: Support clipboard for multiple files
|
||||
copy_dbc_to_clipboard->setEnabled(cnt == 1);
|
||||
manage_dbcs_menu->setEnabled(dynamic_cast<DummyStream *>(can) == nullptr);
|
||||
|
||||
QStringList title;
|
||||
for (auto f : dbc()->allDBCFiles()) {
|
||||
title.push_back(tr("(%1) %2").arg(toString(dbc()->sources(f)), f->name()));
|
||||
}
|
||||
setWindowFilePath(title.join(" | "));
|
||||
}
|
||||
|
||||
void MainWindow::openStream() {
|
||||
AbstractStream *stream = nullptr;
|
||||
StreamSelector dlg(&stream, this);
|
||||
void MainWindow::selectAndOpenStream() {
|
||||
StreamSelector dlg(this);
|
||||
if (dlg.exec()) {
|
||||
if (!dlg.dbcFile().isEmpty()) {
|
||||
loadFile(dlg.dbcFile());
|
||||
}
|
||||
stream->start();
|
||||
statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000);
|
||||
openStream(dlg.stream(), dlg.dbcFile());
|
||||
} else if (!can) {
|
||||
stream = new DummyStream(this);
|
||||
stream->start();
|
||||
openStream(new DummyStream(this));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::closeStream() {
|
||||
AbstractStream *stream = new DummyStream(this);
|
||||
stream->start();
|
||||
openStream(new DummyStream(this));
|
||||
if (dbc()->nonEmptyDBCCount() > 0) {
|
||||
emit dbc()->DBCFileChanged();
|
||||
}
|
||||
@@ -301,18 +278,8 @@ void MainWindow::loadFile(const QString &fn, SourceSet s) {
|
||||
if (!fn.isEmpty()) {
|
||||
closeFile(s);
|
||||
|
||||
QString dbc_fn = fn;
|
||||
// Prompt user to load auto saved file if it exists.
|
||||
if (QFile::exists(fn + AUTO_SAVE_EXTENSION)) {
|
||||
auto ret = QMessageBox::question(this, tr("Auto saved DBC found"), tr("Auto saved DBC file from previous session found. Do you want to load it instead?"));
|
||||
if (ret == QMessageBox::Yes) {
|
||||
dbc_fn += AUTO_SAVE_EXTENSION;
|
||||
UndoStack::instance()->resetClean(); // Force user to save on close so the auto saved file is not lost
|
||||
}
|
||||
}
|
||||
|
||||
QString error;
|
||||
if (dbc()->open(s, dbc_fn, &error)) {
|
||||
if (dbc()->open(s, fn, &error)) {
|
||||
updateRecentFiles(fn);
|
||||
statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000);
|
||||
} else {
|
||||
@@ -323,15 +290,8 @@ void MainWindow::loadFile(const QString &fn, SourceSet s) {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::openRecentFile() {
|
||||
if (auto action = qobject_cast<QAction *>(sender())) {
|
||||
loadFile(action->data().toString());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::loadDBCFromOpendbc(const QString &name) {
|
||||
QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, name);
|
||||
loadFile(opendbc_file_path);
|
||||
loadFile(QString("%1/%2").arg(OPENDBC_FILE_PATH, name));
|
||||
}
|
||||
|
||||
void MainWindow::loadFromClipboard(SourceSet s, bool close_all) {
|
||||
@@ -349,13 +309,19 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::changingStream() {
|
||||
void MainWindow::openStream(AbstractStream *stream, const QString &dbc_file) {
|
||||
center_widget->clear();
|
||||
delete messages_widget;
|
||||
delete video_splitter;
|
||||
}
|
||||
|
||||
void MainWindow::streamStarted() {
|
||||
delete can;
|
||||
can = stream;
|
||||
can->setParent(this); // take ownership
|
||||
can->start();
|
||||
|
||||
loadFile(dbc_file);
|
||||
statusBar()->showMessage(tr("Stream [%1] started").arg(can->routeName()), 2000);
|
||||
|
||||
bool has_stream = dynamic_cast<DummyStream *>(can) == nullptr;
|
||||
close_stream_act->setEnabled(has_stream);
|
||||
export_to_csv_act->setEnabled(has_stream);
|
||||
@@ -372,9 +338,20 @@ void MainWindow::streamStarted() {
|
||||
newFile();
|
||||
}
|
||||
|
||||
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, center_widget, &CenterWidget::setMessage);
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, this, &MainWindow::eventsMerged);
|
||||
QObject::connect(can, &AbstractStream::sourcesUpdated, this, &MainWindow::updateLoadSaveMenus);
|
||||
|
||||
if (has_stream) {
|
||||
auto wait_dlg = new QProgressDialog(
|
||||
can->liveStreaming() ? tr("Waiting for the live stream to start...") : tr("Loading segment data..."),
|
||||
tr("&Abort"), 0, 100, this);
|
||||
wait_dlg->setWindowModality(Qt::WindowModal);
|
||||
wait_dlg->setFixedSize(400, wait_dlg->sizeHint().height());
|
||||
QObject::connect(wait_dlg, &QProgressDialog::canceled, this, &MainWindow::close);
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, wait_dlg, &QProgressDialog::deleteLater);
|
||||
QObject::connect(this, &MainWindow::updateProgressBar, wait_dlg, [=](uint64_t cur, uint64_t total, bool success) {
|
||||
wait_dlg->setValue((int)((cur / (double)total) * 100));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::eventsMerged() {
|
||||
@@ -383,12 +360,8 @@ void MainWindow::eventsMerged() {
|
||||
.arg(can->routeName())
|
||||
.arg(car_fingerprint.isEmpty() ? tr("Unknown Car") : car_fingerprint));
|
||||
// Don't overwrite already loaded DBC
|
||||
if (!dbc()->nonEmptyDBCCount() && !car_fingerprint.isEmpty()) {
|
||||
auto dbc_name = fingerprint_to_dbc[car_fingerprint];
|
||||
if (dbc_name != QJsonValue::Undefined) {
|
||||
// Prevent dialog that load autosaved file from blocking replay->start().
|
||||
QTimer::singleShot(0, this, [dbc_name, this]() { loadDBCFromOpendbc(dbc_name.toString()); });
|
||||
}
|
||||
if (!dbc()->nonEmptyDBCCount() && fingerprint_to_dbc.object().contains(car_fingerprint)) {
|
||||
QTimer::singleShot(0, this, [this]() { loadDBCFromOpendbc(fingerprint_to_dbc[car_fingerprint].toString() + ".dbc"); });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,22 +382,6 @@ void MainWindow::saveAs() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::autoSave() {
|
||||
if (!UndoStack::instance()->isClean()) {
|
||||
for (auto dbc_file : dbc()->allDBCFiles()) {
|
||||
if (!dbc_file->filename.isEmpty()) {
|
||||
dbc_file->autoSave();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::cleanupAutoSaveFile() {
|
||||
for (auto dbc_file : dbc()->allDBCFiles()) {
|
||||
dbc_file->cleanupAutoSaveFile();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::closeFile(SourceSet s) {
|
||||
remindSaveChanges();
|
||||
if (s == SOURCE_ALL) {
|
||||
@@ -448,7 +405,6 @@ void MainWindow::saveFile(DBCFile *dbc_file) {
|
||||
assert(dbc_file != nullptr);
|
||||
if (!dbc_file->filename.isEmpty()) {
|
||||
dbc_file->save();
|
||||
updateLoadSaveMenus();
|
||||
UndoStack::instance()->setClean();
|
||||
statusBar()->showMessage(tr("File saved"), 2000);
|
||||
} else if (!dbc_file->isEmpty()) {
|
||||
@@ -464,7 +420,6 @@ void MainWindow::saveFileAs(DBCFile *dbc_file) {
|
||||
UndoStack::instance()->setClean();
|
||||
statusBar()->showMessage(tr("File saved as %1").arg(fn), 2000);
|
||||
updateRecentFiles(fn);
|
||||
updateLoadSaveMenus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,16 +438,7 @@ void MainWindow::saveFileToClipboard(DBCFile *dbc_file) {
|
||||
}
|
||||
|
||||
void MainWindow::updateLoadSaveMenus() {
|
||||
int cnt = dbc()->nonEmptyDBCCount();
|
||||
save_dbc->setText(cnt > 1 ? tr("Save %1 DBCs...").arg(cnt) : tr("Save DBC..."));
|
||||
save_dbc->setEnabled(cnt > 0);
|
||||
save_dbc_as->setEnabled(cnt == 1);
|
||||
|
||||
// TODO: Support clipboard for multiple files
|
||||
copy_dbc_to_clipboard->setEnabled(cnt == 1);
|
||||
|
||||
manage_dbcs_menu->clear();
|
||||
manage_dbcs_menu->setEnabled(dynamic_cast<DummyStream *>(can) == nullptr);
|
||||
|
||||
for (int source : can->sources) {
|
||||
if (source >= 64) continue; // Sent and blocked buses are handled implicitly
|
||||
@@ -505,8 +451,8 @@ void MainWindow::updateLoadSaveMenus() {
|
||||
bus_menu->addAction(tr("Load DBC From Clipboard..."), [=]() { loadFromClipboard(ss, false); });
|
||||
|
||||
// Show sub-menu for each dbc for this source.
|
||||
QString file_name = "No DBCs loaded";
|
||||
if (auto dbc_file = dbc()->findDBCFile(source)) {
|
||||
auto dbc_file = dbc()->findDBCFile(source);
|
||||
if (dbc_file) {
|
||||
bus_menu->addSeparator();
|
||||
bus_menu->addAction(dbc_file->name() + " (" + toString(dbc()->sources(dbc_file)) + ")")->setEnabled(false);
|
||||
bus_menu->addAction(tr("Save..."), [=]() { saveFile(dbc_file); });
|
||||
@@ -514,19 +460,11 @@ void MainWindow::updateLoadSaveMenus() {
|
||||
bus_menu->addAction(tr("Copy to Clipboard..."), [=]() { saveFileToClipboard(dbc_file); });
|
||||
bus_menu->addAction(tr("Remove from this bus..."), [=]() { closeFile(ss); });
|
||||
bus_menu->addAction(tr("Remove from all buses..."), [=]() { closeFile(dbc_file); });
|
||||
|
||||
file_name = dbc_file->name();
|
||||
}
|
||||
bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(dbc_file ? dbc_file->name() : "No DBCs loaded"));
|
||||
|
||||
manage_dbcs_menu->addMenu(bus_menu);
|
||||
bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(file_name));
|
||||
}
|
||||
|
||||
QStringList title;
|
||||
for (auto f : dbc()->allDBCFiles()) {
|
||||
title.push_back(tr("(%1) %2").arg(toString(dbc()->sources(f)), f->name()));
|
||||
}
|
||||
setWindowFilePath(title.join(" | "));
|
||||
}
|
||||
|
||||
void MainWindow::updateRecentFiles(const QString &fn) {
|
||||
@@ -536,21 +474,21 @@ void MainWindow::updateRecentFiles(const QString &fn) {
|
||||
settings.recent_files.removeLast();
|
||||
}
|
||||
settings.last_dir = QFileInfo(fn).absolutePath();
|
||||
updateRecentFileActions();
|
||||
}
|
||||
|
||||
void MainWindow::updateRecentFileActions() {
|
||||
void MainWindow::updateRecentFileMenu() {
|
||||
open_recent_menu->clear();
|
||||
|
||||
int num_recent_files = std::min<int>(settings.recent_files.size(), MAX_RECENT_FILES);
|
||||
if (!num_recent_files) {
|
||||
open_recent_menu->addAction(tr("No Recent Files"))->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_recent_files; ++i) {
|
||||
QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(settings.recent_files[i]).fileName());
|
||||
recent_files_acts[i]->setText(text);
|
||||
recent_files_acts[i]->setData(settings.recent_files[i]);
|
||||
recent_files_acts[i]->setVisible(true);
|
||||
open_recent_menu->addAction(text, this, [this, file = settings.recent_files[i]]() { loadFile(file); });
|
||||
}
|
||||
for (int i = num_recent_files; i < MAX_RECENT_FILES; ++i) {
|
||||
recent_files_acts[i]->setVisible(false);
|
||||
}
|
||||
open_recent_menu->setEnabled(num_recent_files > 0);
|
||||
}
|
||||
|
||||
void MainWindow::remindSaveChanges() {
|
||||
@@ -606,7 +544,6 @@ void MainWindow::toggleChartsDocking() {
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
cleanupAutoSaveFile();
|
||||
remindSaveChanges();
|
||||
|
||||
installDownloadProgressHandler(nullptr);
|
||||
@@ -618,7 +555,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
// save states
|
||||
settings.geometry = saveGeometry();
|
||||
settings.window_state = saveState();
|
||||
if (!can->liveStreaming()) {
|
||||
if (can && !can->liveStreaming()) {
|
||||
settings.video_splitter_state = video_splitter->saveState();
|
||||
}
|
||||
settings.message_header_state = messages_widget->saveHeaderState();
|
||||
|
||||
+4
-13
@@ -20,22 +20,20 @@ class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow();
|
||||
MainWindow(AbstractStream *stream, const QString &dbc_file);
|
||||
void toggleChartsDocking();
|
||||
void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); }
|
||||
void loadFile(const QString &fn, SourceSet s = SOURCE_ALL);
|
||||
ChartsWidget *charts_widget = nullptr;
|
||||
|
||||
public slots:
|
||||
void openStream();
|
||||
void selectAndOpenStream();
|
||||
void openStream(AbstractStream *stream, const QString &dbc_file = {});
|
||||
void closeStream();
|
||||
void exportToCSV();
|
||||
void changingStream();
|
||||
void streamStarted();
|
||||
|
||||
void newFile(SourceSet s = SOURCE_ALL);
|
||||
void openFile(SourceSet s = SOURCE_ALL);
|
||||
void openRecentFile();
|
||||
void loadDBCFromOpendbc(const QString &name);
|
||||
void save();
|
||||
void saveAs();
|
||||
@@ -55,10 +53,8 @@ protected:
|
||||
void saveFileToClipboard(DBCFile *dbc_file);
|
||||
void loadFingerprints();
|
||||
void loadFromClipboard(SourceSet s = SOURCE_ALL, bool close_all = true);
|
||||
void autoSave();
|
||||
void cleanupAutoSaveFile();
|
||||
void updateRecentFiles(const QString &fn);
|
||||
void updateRecentFileActions();
|
||||
void updateRecentFileMenu();
|
||||
void createActions();
|
||||
void createDockWindows();
|
||||
void createStatusBar();
|
||||
@@ -70,7 +66,6 @@ protected:
|
||||
void findSimilarBits();
|
||||
void findSignal();
|
||||
void undoStackCleanChanged(bool clean);
|
||||
void undoStackIndexChanged(int index);
|
||||
void onlineHelp();
|
||||
void toggleFullScreen();
|
||||
void updateStatus();
|
||||
@@ -88,10 +83,8 @@ protected:
|
||||
QProgressBar *progress_bar;
|
||||
QLabel *status_label;
|
||||
QJsonDocument fingerprint_to_dbc;
|
||||
QStringList opendbc_names;
|
||||
QSplitter *video_splitter = nullptr;
|
||||
enum { MAX_RECENT_FILES = 15 };
|
||||
QAction *recent_files_acts[MAX_RECENT_FILES] = {};
|
||||
QMenu *open_recent_menu = nullptr;
|
||||
QMenu *manage_dbcs_menu = nullptr;
|
||||
QMenu *tools_menu = nullptr;
|
||||
@@ -101,8 +94,6 @@ protected:
|
||||
QAction *save_dbc_as = nullptr;
|
||||
QAction *copy_dbc_to_clipboard = nullptr;
|
||||
QString car_fingerprint;
|
||||
int prev_undostack_index = 0;
|
||||
int prev_undostack_count = 0;
|
||||
QByteArray default_state;
|
||||
};
|
||||
|
||||
|
||||
@@ -494,6 +494,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
|
||||
}
|
||||
|
||||
void SignalView::setMessage(const MessageId &id) {
|
||||
max_value_width = 0;
|
||||
filter_edit->clear();
|
||||
model->setMessage(id);
|
||||
}
|
||||
@@ -617,7 +618,6 @@ void SignalView::updateState(const std::set<MessageId> *msgs) {
|
||||
const auto &last_msg = can->lastMessage(model->msg_id);
|
||||
if (model->rowCount() == 0 || (msgs && !msgs->count(model->msg_id)) || last_msg.dat.size() == 0) return;
|
||||
|
||||
int max_value_width = 0;
|
||||
for (auto item : model->root->children) {
|
||||
double value = 0;
|
||||
if (item->sig->getValue(last_msg.dat.data(), last_msg.dat.size(), &value)) {
|
||||
|
||||
@@ -135,6 +135,7 @@ private:
|
||||
QTreeView::leaveEvent(event);
|
||||
}
|
||||
};
|
||||
int max_value_width = 0;
|
||||
int value_column_width = 0;
|
||||
TreeView *tree;
|
||||
QLabel *sparkline_label;
|
||||
|
||||
@@ -10,11 +10,6 @@ static const int EVENT_NEXT_BUFFER_SIZE = 6 * 1024 * 1024; // 6MB
|
||||
|
||||
AbstractStream *can = nullptr;
|
||||
|
||||
StreamNotifier *StreamNotifier::instance() {
|
||||
static StreamNotifier notifier;
|
||||
return ¬ifier;
|
||||
}
|
||||
|
||||
AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
|
||||
assert(parent != nullptr);
|
||||
event_buffer_ = std::make_unique<MonotonicBuffer>(EVENT_NEXT_BUFFER_SIZE);
|
||||
@@ -24,12 +19,6 @@ AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
|
||||
QObject::connect(this, &AbstractStream::seeking, this, [this](double sec) { current_sec_ = sec; });
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &AbstractStream::updateMasks);
|
||||
QObject::connect(dbc(), &DBCManager::maskUpdated, this, &AbstractStream::updateMasks);
|
||||
QObject::connect(this, &AbstractStream::streamStarted, [this]() {
|
||||
emit StreamNotifier::instance()->changingStream();
|
||||
delete can;
|
||||
can = this;
|
||||
emit StreamNotifier::instance()->streamStarted();
|
||||
});
|
||||
}
|
||||
|
||||
void AbstractStream::updateMasks() {
|
||||
|
||||
@@ -96,7 +96,6 @@ signals:
|
||||
void seeking(double sec);
|
||||
void seekedTo(double sec);
|
||||
void timeRangeChanged(const std::optional<std::pair<double, double>> &range);
|
||||
void streamStarted();
|
||||
void eventsMerged(const MessageEventsMap &events_map);
|
||||
void msgsReceived(const std::set<MessageId> *new_msgs, bool has_new_ids);
|
||||
void sourcesUpdated(const SourceSet &s);
|
||||
@@ -133,15 +132,11 @@ private:
|
||||
class AbstractOpenStreamWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AbstractOpenStreamWidget(AbstractStream **stream, QWidget *parent = nullptr) : stream(stream), QWidget(parent) {}
|
||||
virtual bool open() = 0;
|
||||
virtual QString title() = 0;
|
||||
AbstractOpenStreamWidget(QWidget *parent = nullptr) : QWidget(parent) {}
|
||||
virtual AbstractStream *open() = 0;
|
||||
|
||||
signals:
|
||||
void enableOpenButton(bool);
|
||||
|
||||
protected:
|
||||
AbstractStream **stream = nullptr;
|
||||
};
|
||||
|
||||
class DummyStream : public AbstractStream {
|
||||
@@ -149,17 +144,7 @@ class DummyStream : public AbstractStream {
|
||||
public:
|
||||
DummyStream(QObject *parent) : AbstractStream(parent) {}
|
||||
QString routeName() const override { return tr("No Stream"); }
|
||||
void start() override { emit streamStarted(); }
|
||||
};
|
||||
|
||||
class StreamNotifier : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
StreamNotifier(QObject *parent = nullptr) : QObject(parent) {}
|
||||
static StreamNotifier* instance();
|
||||
signals:
|
||||
void streamStarted();
|
||||
void changingStream();
|
||||
void start() override {}
|
||||
};
|
||||
|
||||
// A global pointer referring to the unique AbstractStream object
|
||||
|
||||
@@ -33,13 +33,9 @@ void DeviceStream::streamThread() {
|
||||
}
|
||||
}
|
||||
|
||||
AbstractOpenStreamWidget *DeviceStream::widget(AbstractStream **stream) {
|
||||
return new OpenDeviceWidget(stream);
|
||||
}
|
||||
|
||||
// OpenDeviceWidget
|
||||
|
||||
OpenDeviceWidget::OpenDeviceWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
OpenDeviceWidget::OpenDeviceWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QRadioButton *msgq = new QRadioButton(tr("MSGQ"));
|
||||
QRadioButton *zmq = new QRadioButton(tr("ZMQ"));
|
||||
ip_address = new QLineEdit(this);
|
||||
@@ -62,9 +58,8 @@ OpenDeviceWidget::OpenDeviceWidget(AbstractStream **stream) : AbstractOpenStream
|
||||
zmq->setChecked(true);
|
||||
}
|
||||
|
||||
bool OpenDeviceWidget::open() {
|
||||
AbstractStream *OpenDeviceWidget::open() {
|
||||
QString ip = ip_address->text().isEmpty() ? "127.0.0.1" : ip_address->text();
|
||||
bool msgq = group->checkedId() == 0;
|
||||
*stream = new DeviceStream(qApp, msgq ? "" : ip);
|
||||
return true;
|
||||
return new DeviceStream(qApp, msgq ? "" : ip);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ class DeviceStream : public LiveStream {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DeviceStream(QObject *parent, QString address = {});
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
inline QString routeName() const override {
|
||||
return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address);
|
||||
}
|
||||
@@ -20,9 +19,8 @@ class OpenDeviceWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenDeviceWidget(AbstractStream **stream);
|
||||
bool open() override;
|
||||
QString title() override { return tr("&Device"); }
|
||||
OpenDeviceWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
QLineEdit *ip_address;
|
||||
|
||||
@@ -53,7 +53,6 @@ void LiveStream::startUpdateTimer() {
|
||||
}
|
||||
|
||||
void LiveStream::start() {
|
||||
emit streamStarted();
|
||||
stream_thread->start();
|
||||
startUpdateTimer();
|
||||
begin_date_time = QDateTime::currentDateTime();
|
||||
|
||||
@@ -77,13 +77,9 @@ void PandaStream::streamThread() {
|
||||
}
|
||||
}
|
||||
|
||||
AbstractOpenStreamWidget *PandaStream::widget(AbstractStream **stream) {
|
||||
return new OpenPandaWidget(stream);
|
||||
}
|
||||
|
||||
// OpenPandaWidget
|
||||
|
||||
OpenPandaWidget::OpenPandaWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
OpenPandaWidget::OpenPandaWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
form_layout = new QFormLayout(this);
|
||||
if (can && dynamic_cast<PandaStream *>(can) != nullptr) {
|
||||
form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(can->routeName())));
|
||||
@@ -182,12 +178,11 @@ void OpenPandaWidget::buildConfigForm() {
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenPandaWidget::open() {
|
||||
AbstractStream *OpenPandaWidget::open() {
|
||||
try {
|
||||
*stream = new PandaStream(qApp, config);
|
||||
return true;
|
||||
return new PandaStream(qApp, config);
|
||||
} catch (std::exception &e) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what()));
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ class PandaStream : public LiveStream {
|
||||
public:
|
||||
PandaStream(QObject *parent, PandaStreamConfig config_ = {});
|
||||
~PandaStream() { stop(); }
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
inline QString routeName() const override {
|
||||
return QString("Panda: %1").arg(config.serial);
|
||||
}
|
||||
@@ -45,9 +44,8 @@ class OpenPandaWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenPandaWidget(AbstractStream **stream);
|
||||
bool open() override;
|
||||
QString title() override { return tr("&Panda"); }
|
||||
OpenPandaWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
void refreshSerials();
|
||||
|
||||
@@ -83,11 +83,6 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint
|
||||
return success;
|
||||
}
|
||||
|
||||
void ReplayStream::start() {
|
||||
emit streamStarted();
|
||||
replay->start();
|
||||
}
|
||||
|
||||
bool ReplayStream::eventFilter(const Event *event) {
|
||||
static double prev_update_ts = 0;
|
||||
if (event->which == cereal::Event::Which::CAN) {
|
||||
@@ -115,13 +110,9 @@ void ReplayStream::pause(bool pause) {
|
||||
}
|
||||
|
||||
|
||||
AbstractOpenStreamWidget *ReplayStream::widget(AbstractStream **stream) {
|
||||
return new OpenReplayWidget(stream);
|
||||
}
|
||||
|
||||
// OpenReplayWidget
|
||||
|
||||
OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
OpenReplayWidget::OpenReplayWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QGridLayout *grid_layout = new QGridLayout(this);
|
||||
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
|
||||
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
|
||||
@@ -154,7 +145,7 @@ OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStream
|
||||
});
|
||||
}
|
||||
|
||||
bool OpenReplayWidget::open() {
|
||||
AbstractStream *OpenReplayWidget::open() {
|
||||
QString route = route_edit->text();
|
||||
QString data_dir;
|
||||
if (int idx = route.lastIndexOf('/'); idx != -1 && util::file_exists(route.toStdString())) {
|
||||
@@ -173,8 +164,8 @@ bool OpenReplayWidget::open() {
|
||||
if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC;
|
||||
|
||||
if (replay_stream->loadRoute(route, data_dir, flags)) {
|
||||
*stream = replay_stream.release();
|
||||
return replay_stream.release();
|
||||
}
|
||||
}
|
||||
return *stream != nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class ReplayStream : public AbstractStream {
|
||||
|
||||
public:
|
||||
ReplayStream(QObject *parent);
|
||||
void start() override;
|
||||
void start() override { replay->start(); }
|
||||
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
|
||||
bool eventFilter(const Event *event);
|
||||
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }
|
||||
@@ -31,7 +31,6 @@ public:
|
||||
inline Replay *getReplay() const { return replay.get(); }
|
||||
inline bool isPaused() const override { return replay->isPaused(); }
|
||||
void pause(bool pause) override;
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
|
||||
private:
|
||||
void mergeSegments();
|
||||
@@ -44,9 +43,8 @@ class OpenReplayWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenReplayWidget(AbstractStream **stream);
|
||||
bool open() override;
|
||||
QString title() override { return tr("&Replay"); }
|
||||
OpenReplayWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
QLineEdit *route_edit;
|
||||
|
||||
@@ -66,11 +66,7 @@ void SocketCanStream::streamThread() {
|
||||
}
|
||||
}
|
||||
|
||||
AbstractOpenStreamWidget *SocketCanStream::widget(AbstractStream **stream) {
|
||||
return new OpenSocketCanWidget(stream);
|
||||
}
|
||||
|
||||
OpenSocketCanWidget::OpenSocketCanWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
|
||||
OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addStretch(1);
|
||||
|
||||
@@ -104,12 +100,11 @@ void OpenSocketCanWidget::refreshDevices() {
|
||||
}
|
||||
|
||||
|
||||
bool OpenSocketCanWidget::open() {
|
||||
AbstractStream *OpenSocketCanWidget::open() {
|
||||
try {
|
||||
*stream = new SocketCanStream(qApp, config);
|
||||
return new SocketCanStream(qApp, config);
|
||||
} catch (std::exception &e) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to SocketCAN device: '%1'").arg(e.what()));
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ class SocketCanStream : public LiveStream {
|
||||
public:
|
||||
SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {});
|
||||
~SocketCanStream() { stop(); }
|
||||
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
|
||||
static bool available();
|
||||
|
||||
inline QString routeName() const override {
|
||||
@@ -37,9 +36,8 @@ class OpenSocketCanWidget : public AbstractOpenStreamWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenSocketCanWidget(AbstractStream **stream);
|
||||
bool open() override;
|
||||
QString title() override { return tr("&SocketCAN"); }
|
||||
OpenSocketCanWidget(QWidget *parent = nullptr);
|
||||
AbstractStream *open() override;
|
||||
|
||||
private:
|
||||
void refreshDevices();
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
#include "tools/cabana/streams/socketcanstream.h"
|
||||
|
||||
StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDialog(parent) {
|
||||
StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle(tr("Open stream"));
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
tab = new QTabWidget(this);
|
||||
@@ -33,17 +33,17 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial
|
||||
btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(btn_box);
|
||||
|
||||
addStreamWidget(ReplayStream::widget(stream));
|
||||
addStreamWidget(PandaStream::widget(stream));
|
||||
addStreamWidget(new OpenReplayWidget, tr("&Replay"));
|
||||
addStreamWidget(new OpenPandaWidget, tr("&Panda"));
|
||||
if (SocketCanStream::available()) {
|
||||
addStreamWidget(SocketCanStream::widget(stream));
|
||||
addStreamWidget(new OpenSocketCanWidget, tr("&SocketCAN"));
|
||||
}
|
||||
addStreamWidget(DeviceStream::widget(stream));
|
||||
addStreamWidget(new OpenDeviceWidget, tr("&Device"));
|
||||
|
||||
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
QObject::connect(btn_box, &QDialogButtonBox::accepted, [=]() {
|
||||
setEnabled(false);
|
||||
if (((AbstractOpenStreamWidget *)tab->currentWidget())->open()) {
|
||||
if (stream_ = ((AbstractOpenStreamWidget *)tab->currentWidget())->open(); stream_) {
|
||||
accept();
|
||||
}
|
||||
setEnabled(true);
|
||||
@@ -57,8 +57,8 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial
|
||||
});
|
||||
}
|
||||
|
||||
void StreamSelector::addStreamWidget(AbstractOpenStreamWidget *w) {
|
||||
tab->addTab(w, w->title());
|
||||
void StreamSelector::addStreamWidget(AbstractOpenStreamWidget *w, const QString &title) {
|
||||
tab->addTab(w, title);
|
||||
auto open_btn = btn_box->button(QDialogButtonBox::Open);
|
||||
QObject::connect(w, &AbstractOpenStreamWidget::enableOpenButton, open_btn, &QPushButton::setEnabled);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@ class StreamSelector : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StreamSelector(AbstractStream **stream, QWidget *parent = nullptr);
|
||||
void addStreamWidget(AbstractOpenStreamWidget *w);
|
||||
StreamSelector(QWidget *parent = nullptr);
|
||||
void addStreamWidget(AbstractOpenStreamWidget *w, const QString &title);
|
||||
QString dbcFile() const { return dbc_file->text(); }
|
||||
AbstractStream *stream() const { return stream_; }
|
||||
|
||||
private:
|
||||
AbstractStream *stream_ = nullptr;
|
||||
QLineEdit *dbc_file;
|
||||
QTabWidget *tab;
|
||||
QDialogButtonBox *btn_box;
|
||||
|
||||
@@ -1,45 +1,193 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<root>
|
||||
<tabbed_widget name="Main Window" parent="main_window">
|
||||
<Tab tab_name="tab1" containers="1">
|
||||
<tabbed_widget parent="main_window" name="Main Window">
|
||||
<Tab containers="1" tab_name="Lateral Plan Conformance">
|
||||
<Container>
|
||||
<DockSplitter sizes="0.250298;0.250298;0.249106;0.250298" orientation="-" count="4">
|
||||
<DockArea name="...">
|
||||
<plot style="Lines" flip_x="false" flip_y="false" mode="TimeSeries">
|
||||
<range bottom="-2.900899" top="3.526047" left="825.563261" right="1415.827546"/>
|
||||
<DockSplitter count="4" sizes="0.25;0.25;0.25;0.25" orientation="-">
|
||||
<DockArea name="desired vs actual lateral acceleration (closer means better conformance to plan)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="3.586831" right="269.643117" left="0.000140" bottom="-2.354077"/>
|
||||
<limitY/>
|
||||
<curve name="/controlsState/lateralControlState/torqueState/actualLateralAccel" color="#1f77b4"/>
|
||||
<curve name="/controlsState/lateralControlState/torqueState/desiredLateralAccel" color="#d62728"/>
|
||||
<curve color="#1f77b4" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
|
||||
<curve color="#d62728" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="...">
|
||||
<plot style="Lines" flip_x="false" flip_y="false" mode="TimeSeries">
|
||||
<range bottom="-4.577789" top="3.642392" left="825.563261" right="1415.827546"/>
|
||||
<DockArea name="desired vs actual lateral acceleration, road-roll factored out (closer means better conformance to plan)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="3.445087" right="269.643117" left="0.000140" bottom="-2.654874"/>
|
||||
<limitY/>
|
||||
<curve name="Actual lateral accel (roll compensated)" color="#1ac938"/>
|
||||
<curve name="Desired lateral accel (roll compensated)" color="#ff7f0e"/>
|
||||
<curve color="#1ac938" name="Actual lateral accel (roll compensated)"/>
|
||||
<curve color="#ff7f0e" name="Desired lateral accel (roll compensated)"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="...">
|
||||
<plot style="Lines" flip_x="false" flip_y="false" mode="TimeSeries">
|
||||
<range bottom="-1.134948" top="1.052072" left="825.563261" right="1415.827546"/>
|
||||
<DockArea name="controller feed-forward vs actuator output (closer means controller prediction is more accurate)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="1.082031" right="269.643117" left="0.000140" bottom="-1.050781"/>
|
||||
<limitY/>
|
||||
<curve name="/carOutput/actuatorsOutput/steer" color="#9467bd">
|
||||
<transform name="Scale/Offset" alias="/carOutput/actuatorsOutput/steer[Scale/Offset]">
|
||||
<options time_offset="0" value_scale="-1" value_offset="0"/>
|
||||
<curve color="#9467bd" name="/carOutput/actuatorsOutput/steer">
|
||||
<transform alias="/carOutput/actuatorsOutput/steer[Scale/Offset]" name="Scale/Offset">
|
||||
<options value_scale="-1" time_offset="0" value_offset="0"/>
|
||||
</transform>
|
||||
</curve>
|
||||
<curve name="/controlsState/lateralControlState/torqueState/f" color="#1f77b4"/>
|
||||
<curve name="/carState/steeringPressed" color="#ff000f"/>
|
||||
<curve color="#1f77b4" name="/controlsState/lateralControlState/torqueState/f"/>
|
||||
<curve color="#ff000f" name="/carState/steeringPressed"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="...">
|
||||
<plot style="Lines" flip_x="false" flip_y="false" mode="TimeSeries">
|
||||
<range bottom="-1.373608" top="56.208012" left="825.563261" right="1415.827546"/>
|
||||
<DockArea name="vehicle speed">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="101.506277" right="269.643117" left="0.000140" bottom="-2.475763"/>
|
||||
<limitY/>
|
||||
<curve name="carState.vEgo mph" color="#d62728"/>
|
||||
<curve name="carState.vEgo kmh" color="#1ac938"/>
|
||||
<curve name="/carState/vEgo" color="#ff7f0e"/>
|
||||
<curve color="#d62728" name="carState.vEgo mph"/>
|
||||
<curve color="#1ac938" name="carState.vEgo kmh"/>
|
||||
<curve color="#ff7f0e" name="/carState/vEgo"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
</DockSplitter>
|
||||
</Container>
|
||||
</Tab>
|
||||
<Tab containers="1" tab_name="Actuator Performance">
|
||||
<Container>
|
||||
<DockSplitter count="3" sizes="0.33361;0.33278;0.33361" orientation="-">
|
||||
<DockArea name="offline-calculated vs online-learned lateral accel scaling factor, accel obtained from 100% actuator output">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="4.186453" right="269.490213" left="0.000000" bottom="3.175940"/>
|
||||
<limitY/>
|
||||
<curve color="#1f77b4" name="/liveTorqueParameters/latAccelFactorFiltered"/>
|
||||
<curve color="#d62728" name="/liveTorqueParameters/latAccelFactorRaw"/>
|
||||
<curve color="#1c9222" name="/carParams/lateralTuning/torque/latAccelFactor"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="learned lateral accel offset, vehicle-specific compensation to obtain true zero lateral accel">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="0.003035" right="269.490213" left="0.000000" bottom="-0.124417"/>
|
||||
<limitY/>
|
||||
<curve color="#1ac938" name="/liveTorqueParameters/latAccelOffsetFiltered"/>
|
||||
<curve color="#ff7f0e" name="/liveTorqueParameters/latAccelOffsetRaw"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="offline-calculated vs online-learned EPS friction factor, necessary to start moving the steering wheel">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="0.121143" right="269.490213" left="0.000000" bottom="-0.002955"/>
|
||||
<limitY/>
|
||||
<curve color="#f14cc1" name="/liveTorqueParameters/frictionCoefficientFiltered"/>
|
||||
<curve color="#9467bd" name="/liveTorqueParameters/frictionCoefficientRaw"/>
|
||||
<curve color="#1c9222" name="/carParams/lateralTuning/torque/friction"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
</DockSplitter>
|
||||
</Container>
|
||||
</Tab>
|
||||
<Tab containers="1" tab_name="Vehicle Dynamics">
|
||||
<Container>
|
||||
<DockSplitter count="3" sizes="0.33361;0.33278;0.33361" orientation="-">
|
||||
<DockArea name="configured-initial vs online-learned steerRatio, set configured value to match learned">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="12.903705" right="269.638801" left="0.000000" bottom="12.748092"/>
|
||||
<limitY/>
|
||||
<curve color="#1f77b4" name="/carParams/steerRatio"/>
|
||||
<curve color="#1ac938" name="/liveParameters/steerRatio"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="configured-initial vs online-learned tireStiffnessRatio, set configured value to match learned">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="1.000520" right="269.638801" left="0.000000" bottom="0.999718"/>
|
||||
<limitY/>
|
||||
<curve color="#d62728" name="/carParams/tireStiffnessFactor"/>
|
||||
<curve color="#ff7f0e" name="/liveParameters/stiffnessFactor"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="live steering angle offsets for straight-ahead driving, large values here may indicate alignment problems">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="-0.332067" right="269.638801" left="0.000000" bottom="-3.149970"/>
|
||||
<limitY/>
|
||||
<curve color="#f14cc1" name="/liveParameters/angleOffsetAverageDeg"/>
|
||||
<curve color="#9467bd" name="/liveParameters/angleOffsetDeg"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
</DockSplitter>
|
||||
</Container>
|
||||
</Tab>
|
||||
<Tab containers="1" tab_name="Controller PIF Terms">
|
||||
<Container>
|
||||
<DockSplitter count="3" sizes="0.33361;0.33278;0.33361" orientation="-">
|
||||
<DockArea name="controller feed-forward vs actuator output (closer means controller prediction is more accurate)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="1.082031" right="269.643117" left="0.000140" bottom="-1.050781"/>
|
||||
<limitY/>
|
||||
<curve color="#9467bd" name="/carOutput/actuatorsOutput/steer">
|
||||
<transform alias="/carOutput/actuatorsOutput/steer[Scale/Offset]" name="Scale/Offset">
|
||||
<options value_scale="-1.0" time_offset="0" value_offset="0"/>
|
||||
</transform>
|
||||
</curve>
|
||||
<curve color="#1f77b4" name="/controlsState/lateralControlState/torqueState/f"/>
|
||||
<curve color="#ff000f" name="/carState/steeringPressed"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="proportional, integral, and feed-forward terms (actuator output = sum of PIF terms)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="1.572946" right="269.643117" left="0.000140" bottom="-3.822608"/>
|
||||
<limitY/>
|
||||
<curve color="#0ab027" name="/controlsState/lateralControlState/torqueState/f"/>
|
||||
<curve color="#d62728" name="/controlsState/lateralControlState/torqueState/p"/>
|
||||
<curve color="#ffaf00" name="/controlsState/lateralControlState/torqueState/i"/>
|
||||
<curve color="#756a6a" name="Zero"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="road roll angle, from openpilot localizer">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="0.059127" right="269.643117" left="0.000140" bottom="-0.031841"/>
|
||||
<limitY/>
|
||||
<curve color="#f14cc1" name="/liveParameters/roll"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
</DockSplitter>
|
||||
</Container>
|
||||
</Tab>
|
||||
<Tab containers="1" tab_name="Actuator Delay Estimation">
|
||||
<Container>
|
||||
<DockSplitter count="4" sizes="0.25;0.25;0.25;0.25" orientation="-">
|
||||
<DockArea name="desired vs actual lateral acceleration (baseline)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="3.586831" right="269.943117" left="0.134774" bottom="-2.354077"/>
|
||||
<limitY/>
|
||||
<curve color="#d62728" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel"/>
|
||||
<curve color="#1f77b4" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="desired vs actual lateral acceleration (desired shifted by +0.1s)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="3.586831" right="269.943117" left="0.134774" bottom="-2.354077"/>
|
||||
<limitY/>
|
||||
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
|
||||
<transform alias="/controlsState/lateralControlState/torqueState/desiredLateralAccel[Scale/Offset]" name="Scale/Offset">
|
||||
<options value_scale="1.0" time_offset="0.1" value_offset="0"/>
|
||||
</transform>
|
||||
</curve>
|
||||
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="desired vs actual lateral acceleration (desired shifted by +0.2s)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="3.586831" right="269.943117" left="0.134774" bottom="-2.354077"/>
|
||||
<limitY/>
|
||||
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
|
||||
<transform alias="/controlsState/lateralControlState/torqueState/desiredLateralAccel[Scale/Offset]" name="Scale/Offset">
|
||||
<options value_scale="1.0" time_offset="0.2" value_offset="0"/>
|
||||
</transform>
|
||||
</curve>
|
||||
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
<DockArea name="desired vs actual lateral acceleration (desired shifted by +0.3s)">
|
||||
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
|
||||
<range top="3.586831" right="269.943117" left="0.134774" bottom="-2.354077"/>
|
||||
<limitY/>
|
||||
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
|
||||
<transform alias="/controlsState/lateralControlState/torqueState/desiredLateralAccel[Scale/Offset]" name="Scale/Offset">
|
||||
<options value_scale="1.0" time_offset="0.3" value_offset="0"/>
|
||||
</transform>
|
||||
</curve>
|
||||
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
|
||||
</plot>
|
||||
</DockArea>
|
||||
</DockSplitter>
|
||||
@@ -57,24 +205,10 @@
|
||||
<!-- - - - - - - - - - - - - - - -->
|
||||
<!-- - - - - - - - - - - - - - - -->
|
||||
<customMathEquations>
|
||||
<snippet name="carState.vEgo kmh">
|
||||
<snippet name="Zero">
|
||||
<global></global>
|
||||
<function>return value * 3.6</function>
|
||||
<linked_source>/carState/vEgo</linked_source>
|
||||
</snippet>
|
||||
<snippet name="carState.vEgo mph">
|
||||
<global></global>
|
||||
<function>return value * 2.23694</function>
|
||||
<linked_source>/carState/vEgo</linked_source>
|
||||
</snippet>
|
||||
<snippet name="Desired lateral accel (roll compensated)">
|
||||
<global></global>
|
||||
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
|
||||
<linked_source>/controlsState/desiredCurvature</linked_source>
|
||||
<additional_sources>
|
||||
<v1>/carState/vEgo</v1>
|
||||
<v2>/liveParameters/roll</v2>
|
||||
</additional_sources>
|
||||
<function>return (0)</function>
|
||||
<linked_source>/carState/canValid</linked_source>
|
||||
</snippet>
|
||||
<snippet name="Actual lateral accel (roll compensated)">
|
||||
<global></global>
|
||||
@@ -85,6 +219,25 @@
|
||||
<v2>/liveParameters/roll</v2>
|
||||
</additional_sources>
|
||||
</snippet>
|
||||
<snippet name="Desired lateral accel (roll compensated)">
|
||||
<global></global>
|
||||
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
|
||||
<linked_source>/controlsState/desiredCurvature</linked_source>
|
||||
<additional_sources>
|
||||
<v1>/carState/vEgo</v1>
|
||||
<v2>/liveParameters/roll</v2>
|
||||
</additional_sources>
|
||||
</snippet>
|
||||
<snippet name="carState.vEgo mph">
|
||||
<global></global>
|
||||
<function>return value * 2.23694</function>
|
||||
<linked_source>/carState/vEgo</linked_source>
|
||||
</snippet>
|
||||
<snippet name="carState.vEgo kmh">
|
||||
<global></global>
|
||||
<function>return value * 3.6</function>
|
||||
<linked_source>/carState/vEgo</linked_source>
|
||||
</snippet>
|
||||
</customMathEquations>
|
||||
<snippets/>
|
||||
<!-- - - - - - - - - - - - - - - -->
|
||||
|
||||
+15
-2
@@ -19,8 +19,10 @@ tools/replay/replay --demo
|
||||
# watch the replay with the normal openpilot UI
|
||||
cd selfdrive/ui && ./ui
|
||||
|
||||
# or try out a debug visualizer:
|
||||
python replay/ui.py
|
||||
# or try out radar point visualization in Rerun:
|
||||
python replay/rp_visualization.py
|
||||
|
||||
# NOTE: To visualize radar points, make sure tools/replay/replay is running.
|
||||
```
|
||||
|
||||
## usage
|
||||
@@ -34,10 +36,21 @@ Options:
|
||||
-h, --help Displays this help.
|
||||
-a, --allow <allow> whitelist of services to send
|
||||
-b, --block <block> blacklist of services to send
|
||||
-c, --cache <n> cache <n> segments in memory. default is 5
|
||||
-s, --start <seconds> start from <seconds>
|
||||
-x <speed> playback <speed>. between 0.2 - 3
|
||||
--demo use a demo route instead of providing your own
|
||||
--data_dir <data_dir> local directory with routes
|
||||
--prefix <prefix> set OPENPILOT_PREFIX
|
||||
--dcam load driver camera
|
||||
--ecam load wide road camera
|
||||
--no-loop stop at the end of the route
|
||||
--no-cache turn off local cache
|
||||
--qcam load qcamera
|
||||
--no-hw-decoder disable HW video decoding
|
||||
--no-vipc do not output video
|
||||
--all do output all messages including uiDebug, userFlag.
|
||||
this may causes issues when used along with UI
|
||||
|
||||
Arguments:
|
||||
route the drive to replay. find your drives at
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import numpy as np
|
||||
from openpilot.selfdrive.controls.radard import RADAR_TO_CAMERA
|
||||
|
||||
# Color palette used for rerun AnnotationContext
|
||||
rerunColorPalette = [(96, "red", (255, 0, 0)),
|
||||
(100, "pink", (255, 36, 0)),
|
||||
(124, "yellow", (255, 255, 0)),
|
||||
(230, "vibrantpink", (255, 36, 170)),
|
||||
(240, "orange", (255, 146, 0)),
|
||||
(255, "white", (255, 255, 255)),
|
||||
(110, "carColor", (255,0,127))]
|
||||
|
||||
|
||||
class UIParams:
|
||||
lidar_x, lidar_y, lidar_zoom = 384, 960, 6
|
||||
lidar_car_x, lidar_car_y = lidar_x / 2., lidar_y / 1.1
|
||||
car_hwidth = 1.7272 / 2 * lidar_zoom
|
||||
car_front = 2.6924 * lidar_zoom
|
||||
car_back = 1.8796 * lidar_zoom
|
||||
car_color = rerunColorPalette[6][0]
|
||||
UP = UIParams
|
||||
|
||||
|
||||
def to_topdown_pt(y, x):
|
||||
px, py = x * UP.lidar_zoom + UP.lidar_car_x, -y * UP.lidar_zoom + UP.lidar_car_y
|
||||
if px > 0 and py > 0 and px < UP.lidar_x and py < UP.lidar_y:
|
||||
return int(px), int(py)
|
||||
return -1, -1
|
||||
|
||||
|
||||
def draw_path(path, lid_overlay, lid_color=None):
|
||||
x, y = np.asarray(path.x), np.asarray(path.y)
|
||||
# draw lidar path point on lidar
|
||||
if lid_color is not None and lid_overlay is not None:
|
||||
for i in range(len(x)):
|
||||
px, py = to_topdown_pt(x[i], y[i])
|
||||
if px != -1:
|
||||
lid_overlay[px, py] = lid_color
|
||||
|
||||
|
||||
def plot_model(m, lid_overlay):
|
||||
if lid_overlay is None:
|
||||
return
|
||||
for lead in m.leadsV3:
|
||||
if lead.prob < 0.5:
|
||||
continue
|
||||
x, y = lead.x[0], lead.y[0]
|
||||
x_std = lead.xStd[0]
|
||||
x -= RADAR_TO_CAMERA
|
||||
_, py_top = to_topdown_pt(x + x_std, y)
|
||||
px, py_bottom = to_topdown_pt(x - x_std, y)
|
||||
lid_overlay[int(round(px - 4)):int(round(px + 4)), py_top:py_bottom] = rerunColorPalette[2][0]
|
||||
|
||||
for path in m.laneLines:
|
||||
draw_path(path, lid_overlay, rerunColorPalette[2][0])
|
||||
for edge in m.roadEdges:
|
||||
draw_path(edge, lid_overlay, rerunColorPalette[0][0])
|
||||
draw_path(m.position, lid_overlay, rerunColorPalette[0][0])
|
||||
|
||||
|
||||
def plot_lead(rs, lid_overlay):
|
||||
for lead in [rs.leadOne, rs.leadTwo]:
|
||||
if not lead.status:
|
||||
continue
|
||||
x = lead.dRel
|
||||
px_left, py = to_topdown_pt(x, -10)
|
||||
px_right, _ = to_topdown_pt(x, 10)
|
||||
lid_overlay[px_left:px_right, py] = rerunColorPalette[0][0]
|
||||
|
||||
|
||||
def maybe_update_radar_points(lt, lid_overlay):
|
||||
ar_pts = []
|
||||
if lt is not None:
|
||||
ar_pts = {}
|
||||
for track in lt:
|
||||
ar_pts[track.trackId] = [track.dRel, track.yRel, track.vRel, track.aRel, track.oncoming, track.stationary]
|
||||
for ids, pt in ar_pts.items():
|
||||
# negative here since radar is left positive
|
||||
px, py = to_topdown_pt(pt[0], -pt[1])
|
||||
if px != -1:
|
||||
if pt[-1]:
|
||||
color = rerunColorPalette[4][0]
|
||||
elif pt[-2]:
|
||||
color = rerunColorPalette[3][0]
|
||||
else:
|
||||
color = rerunColorPalette[5][0]
|
||||
if int(ids) == 1:
|
||||
lid_overlay[px - 2:px + 2, py - 10:py + 10] = rerunColorPalette[1][0]
|
||||
else:
|
||||
lid_overlay[px - 2:px + 2, py - 2:py + 2] = color
|
||||
|
||||
|
||||
def get_blank_lid_overlay(UP):
|
||||
lid_overlay = np.zeros((UP.lidar_x, UP.lidar_y), 'uint8')
|
||||
# Draw the car.
|
||||
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int(
|
||||
round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y -
|
||||
UP.car_front))] = UP.car_color
|
||||
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int(
|
||||
round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y +
|
||||
UP.car_back))] = UP.car_color
|
||||
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)), int(
|
||||
round(UP.lidar_car_y - UP.car_front)):int(round(
|
||||
UP.lidar_car_y + UP.car_back))] = UP.car_color
|
||||
lid_overlay[int(round(UP.lidar_car_x + UP.car_hwidth)), int(
|
||||
round(UP.lidar_car_y - UP.car_front)):int(round(
|
||||
UP.lidar_car_y + UP.car_back))] = UP.car_color
|
||||
return lid_overlay
|
||||
@@ -1,231 +0,0 @@
|
||||
import itertools
|
||||
from typing import Any
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pygame
|
||||
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
||||
|
||||
from openpilot.common.transformations.camera import get_view_frame_from_calib_frame
|
||||
from openpilot.selfdrive.controls.radard import RADAR_TO_CAMERA
|
||||
|
||||
|
||||
RED = (255, 0, 0)
|
||||
GREEN = (0, 255, 0)
|
||||
BLUE = (0, 0, 255)
|
||||
YELLOW = (255, 255, 0)
|
||||
BLACK = (0, 0, 0)
|
||||
WHITE = (255, 255, 255)
|
||||
|
||||
class UIParams:
|
||||
lidar_x, lidar_y, lidar_zoom = 384, 960, 6
|
||||
lidar_car_x, lidar_car_y = lidar_x / 2., lidar_y / 1.1
|
||||
car_hwidth = 1.7272 / 2 * lidar_zoom
|
||||
car_front = 2.6924 * lidar_zoom
|
||||
car_back = 1.8796 * lidar_zoom
|
||||
car_color = 110
|
||||
UP = UIParams
|
||||
|
||||
METER_WIDTH = 20
|
||||
|
||||
class Calibration:
|
||||
def __init__(self, num_px, rpy, intrinsic, calib_scale):
|
||||
self.intrinsic = intrinsic
|
||||
self.extrinsics_matrix = get_view_frame_from_calib_frame(rpy[0], rpy[1], rpy[2], 0.0)[:,:3]
|
||||
self.zoom = calib_scale
|
||||
|
||||
def car_space_to_ff(self, x, y, z):
|
||||
car_space_projective = np.column_stack((x, y, z)).T
|
||||
|
||||
ep = self.extrinsics_matrix.dot(car_space_projective)
|
||||
kep = self.intrinsic.dot(ep)
|
||||
return (kep[:-1, :] / kep[-1, :]).T
|
||||
|
||||
def car_space_to_bb(self, x, y, z):
|
||||
pts = self.car_space_to_ff(x, y, z)
|
||||
return pts / self.zoom
|
||||
|
||||
|
||||
_COLOR_CACHE : dict[tuple[int, int, int], Any] = {}
|
||||
def find_color(lidar_surface, color):
|
||||
if color in _COLOR_CACHE:
|
||||
return _COLOR_CACHE[color]
|
||||
tcolor = 0
|
||||
ret = 255
|
||||
for x in lidar_surface.get_palette():
|
||||
if x[0:3] == color:
|
||||
ret = tcolor
|
||||
break
|
||||
tcolor += 1
|
||||
_COLOR_CACHE[color] = ret
|
||||
return ret
|
||||
|
||||
|
||||
def to_topdown_pt(y, x):
|
||||
px, py = x * UP.lidar_zoom + UP.lidar_car_x, -y * UP.lidar_zoom + UP.lidar_car_y
|
||||
if px > 0 and py > 0 and px < UP.lidar_x and py < UP.lidar_y:
|
||||
return int(px), int(py)
|
||||
return -1, -1
|
||||
|
||||
|
||||
def draw_path(path, color, img, calibration, top_down, lid_color=None, z_off=0):
|
||||
x, y, z = np.asarray(path.x), np.asarray(path.y), np.asarray(path.z) + z_off
|
||||
pts = calibration.car_space_to_bb(x, y, z)
|
||||
pts = np.round(pts).astype(int)
|
||||
|
||||
# draw lidar path point on lidar
|
||||
# find color in 8 bit
|
||||
if lid_color is not None and top_down is not None:
|
||||
tcolor = find_color(top_down[0], lid_color)
|
||||
for i in range(len(x)):
|
||||
px, py = to_topdown_pt(x[i], y[i])
|
||||
if px != -1:
|
||||
top_down[1][px, py] = tcolor
|
||||
|
||||
height, width = img.shape[:2]
|
||||
for x, y in pts:
|
||||
if 1 < x < width - 1 and 1 < y < height - 1:
|
||||
for a, b in itertools.permutations([-1, 0, -1], 2):
|
||||
img[y + a, x + b] = color
|
||||
|
||||
|
||||
def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles):
|
||||
color_palette = { "r": (1, 0, 0),
|
||||
"g": (0, 1, 0),
|
||||
"b": (0, 0, 1),
|
||||
"k": (0, 0, 0),
|
||||
"y": (1, 1, 0),
|
||||
"p": (0, 1, 1),
|
||||
"m": (1, 0, 1)}
|
||||
|
||||
dpi = 90
|
||||
fig = plt.figure(figsize=(575 / dpi, 600 / dpi), dpi=dpi)
|
||||
canvas = FigureCanvasAgg(fig)
|
||||
|
||||
fig.set_facecolor((0.2, 0.2, 0.2))
|
||||
|
||||
axs = []
|
||||
for pn in range(len(plot_ylims)):
|
||||
ax = fig.add_subplot(len(plot_ylims), 1, len(axs)+1)
|
||||
ax.set_xlim(plot_xlims[pn][0], plot_xlims[pn][1])
|
||||
ax.set_ylim(plot_ylims[pn][0], plot_ylims[pn][1])
|
||||
ax.patch.set_facecolor((0.4, 0.4, 0.4))
|
||||
axs.append(ax)
|
||||
|
||||
plots, idxs, plot_select = [], [], []
|
||||
for i, pl_list in enumerate(plot_names):
|
||||
for j, item in enumerate(pl_list):
|
||||
plot, = axs[i].plot(arr[:, name_to_arr_idx[item]],
|
||||
label=item,
|
||||
color=color_palette[plot_colors[i][j]],
|
||||
linestyle=plot_styles[i][j])
|
||||
plots.append(plot)
|
||||
idxs.append(name_to_arr_idx[item])
|
||||
plot_select.append(i)
|
||||
axs[i].set_title(", ".join(f"{nm} ({cl})"
|
||||
for (nm, cl) in zip(pl_list, plot_colors[i], strict=False)), fontsize=10)
|
||||
axs[i].tick_params(axis="x", colors="white")
|
||||
axs[i].tick_params(axis="y", colors="white")
|
||||
axs[i].title.set_color("white")
|
||||
|
||||
if i < len(plot_ylims) - 1:
|
||||
axs[i].set_xticks([])
|
||||
|
||||
canvas.draw()
|
||||
|
||||
def draw_plots(arr):
|
||||
for ax in axs:
|
||||
ax.draw_artist(ax.patch)
|
||||
for i in range(len(plots)):
|
||||
plots[i].set_ydata(arr[:, idxs[i]])
|
||||
axs[plot_select[i]].draw_artist(plots[i])
|
||||
|
||||
raw_data = canvas.buffer_rgba()
|
||||
plot_surface = pygame.image.frombuffer(raw_data, canvas.get_width_height(), "RGBA").convert()
|
||||
return plot_surface
|
||||
|
||||
return draw_plots
|
||||
|
||||
|
||||
def pygame_modules_have_loaded():
|
||||
return pygame.display.get_init() and pygame.font.get_init()
|
||||
|
||||
|
||||
def plot_model(m, img, calibration, top_down):
|
||||
if calibration is None or top_down is None:
|
||||
return
|
||||
|
||||
for lead in m.leadsV3:
|
||||
if lead.prob < 0.5:
|
||||
continue
|
||||
|
||||
x, y = lead.x[0], lead.y[0]
|
||||
x_std = lead.xStd[0]
|
||||
x -= RADAR_TO_CAMERA
|
||||
|
||||
_, py_top = to_topdown_pt(x + x_std, y)
|
||||
px, py_bottom = to_topdown_pt(x - x_std, y)
|
||||
top_down[1][int(round(px - 4)):int(round(px + 4)), py_top:py_bottom] = find_color(top_down[0], YELLOW)
|
||||
|
||||
for path, prob, _ in zip(m.laneLines, m.laneLineProbs, m.laneLineStds, strict=True):
|
||||
color = (0, int(255 * prob), 0)
|
||||
draw_path(path, color, img, calibration, top_down, YELLOW)
|
||||
|
||||
for edge, std in zip(m.roadEdges, m.roadEdgeStds, strict=True):
|
||||
prob = max(1 - std, 0)
|
||||
color = (int(255 * prob), 0, 0)
|
||||
draw_path(edge, color, img, calibration, top_down, RED)
|
||||
|
||||
color = (255, 0, 0)
|
||||
draw_path(m.position, color, img, calibration, top_down, RED, 1.22)
|
||||
|
||||
|
||||
def plot_lead(rs, top_down):
|
||||
for lead in [rs.leadOne, rs.leadTwo]:
|
||||
if not lead.status:
|
||||
continue
|
||||
|
||||
x = lead.dRel
|
||||
px_left, py = to_topdown_pt(x, -10)
|
||||
px_right, _ = to_topdown_pt(x, 10)
|
||||
top_down[1][px_left:px_right, py] = find_color(top_down[0], RED)
|
||||
|
||||
|
||||
def maybe_update_radar_points(lt, lid_overlay):
|
||||
ar_pts = []
|
||||
if lt is not None:
|
||||
ar_pts = {}
|
||||
for track in lt:
|
||||
ar_pts[track.trackId] = [track.dRel, track.yRel, track.vRel, track.aRel, track.oncoming, track.stationary]
|
||||
for ids, pt in ar_pts.items():
|
||||
# negative here since radar is left positive
|
||||
px, py = to_topdown_pt(pt[0], -pt[1])
|
||||
if px != -1:
|
||||
if pt[-1]:
|
||||
color = 240
|
||||
elif pt[-2]:
|
||||
color = 230
|
||||
else:
|
||||
color = 255
|
||||
if int(ids) == 1:
|
||||
lid_overlay[px - 2:px + 2, py - 10:py + 10] = 100
|
||||
else:
|
||||
lid_overlay[px - 2:px + 2, py - 2:py + 2] = color
|
||||
|
||||
def get_blank_lid_overlay(UP):
|
||||
lid_overlay = np.zeros((UP.lidar_x, UP.lidar_y), 'uint8')
|
||||
# Draw the car.
|
||||
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int(
|
||||
round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y -
|
||||
UP.car_front))] = UP.car_color
|
||||
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int(
|
||||
round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y +
|
||||
UP.car_back))] = UP.car_color
|
||||
lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)), int(
|
||||
round(UP.lidar_car_y - UP.car_front)):int(round(
|
||||
UP.lidar_car_y + UP.car_back))] = UP.car_color
|
||||
lid_overlay[int(round(UP.lidar_car_x + UP.car_hwidth)), int(
|
||||
round(UP.lidar_car_y - UP.car_front)):int(round(
|
||||
UP.lidar_car_y + UP.car_back))] = UP.car_color
|
||||
return lid_overlay
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import numpy as np
|
||||
import rerun as rr
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.tools.replay.lib.rp_helpers import (UP, rerunColorPalette,
|
||||
get_blank_lid_overlay,
|
||||
maybe_update_radar_points, plot_lead,
|
||||
plot_model)
|
||||
from msgq.visionipc import VisionIpcClient, VisionStreamType
|
||||
|
||||
os.environ['BASEDIR'] = BASEDIR
|
||||
|
||||
UP.lidar_zoom = 6
|
||||
|
||||
def visualize(addr):
|
||||
sm = messaging.SubMaster(['radarState', 'liveTracks', 'modelV2'], addr=addr)
|
||||
vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_ROAD, True)
|
||||
while True:
|
||||
if not vipc_client.is_connected():
|
||||
vipc_client.connect(True)
|
||||
new_data = vipc_client.recv()
|
||||
if new_data is None or not new_data.data.any():
|
||||
continue
|
||||
|
||||
sm.update(0)
|
||||
lid_overlay = get_blank_lid_overlay(UP)
|
||||
if sm.recv_frame['modelV2']:
|
||||
plot_model(sm['modelV2'], lid_overlay)
|
||||
if sm.recv_frame['radarState']:
|
||||
plot_lead(sm['radarState'], lid_overlay)
|
||||
liveTracksTime = sm.logMonoTime['liveTracks']
|
||||
maybe_update_radar_points(sm['liveTracks'], lid_overlay)
|
||||
rr.set_time_nanos("TIMELINE", liveTracksTime)
|
||||
rr.log("tracks", rr.SegmentationImage(np.flip(np.rot90(lid_overlay, k=-1), axis=1)))
|
||||
|
||||
|
||||
def get_arg_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Show replay data in a UI.",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("ip_address", nargs="?", default="127.0.0.1",
|
||||
help="The ip address on which to receive zmq messages.")
|
||||
parser.add_argument("--frame-address", default=None,
|
||||
help="The frame address (fully qualified ZMQ endpoint for frames) on which to receive zmq messages.")
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = get_arg_parser().parse_args(sys.argv[1:])
|
||||
if args.ip_address != "127.0.0.1":
|
||||
os.environ["ZMQ"] = "1"
|
||||
messaging.context = messaging.Context()
|
||||
rr.init("RadarPoints", spawn= True)
|
||||
rr.log("tracks", rr.AnnotationContext(rerunColorPalette), static=True)
|
||||
visualize(args.ip_address)
|
||||
@@ -1,238 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import pygame
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.numpy_fast import clip
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||
from openpilot.tools.replay.lib.ui_helpers import (UP,
|
||||
BLACK, GREEN,
|
||||
YELLOW, Calibration,
|
||||
get_blank_lid_overlay, init_plots,
|
||||
maybe_update_radar_points, plot_lead,
|
||||
plot_model,
|
||||
pygame_modules_have_loaded)
|
||||
from msgq.visionipc import VisionIpcClient, VisionStreamType
|
||||
|
||||
os.environ['BASEDIR'] = BASEDIR
|
||||
|
||||
ANGLE_SCALE = 5.0
|
||||
|
||||
def ui_thread(addr):
|
||||
cv2.setNumThreads(1)
|
||||
pygame.init()
|
||||
pygame.font.init()
|
||||
assert pygame_modules_have_loaded()
|
||||
|
||||
disp_info = pygame.display.Info()
|
||||
max_height = disp_info.current_h
|
||||
|
||||
hor_mode = os.getenv("HORIZONTAL") is not None
|
||||
hor_mode = True if max_height < 960+300 else hor_mode
|
||||
|
||||
if hor_mode:
|
||||
size = (640+384+640, 960)
|
||||
write_x = 5
|
||||
write_y = 680
|
||||
else:
|
||||
size = (640+384, 960+300)
|
||||
write_x = 645
|
||||
write_y = 970
|
||||
|
||||
pygame.display.set_caption("openpilot debug UI")
|
||||
screen = pygame.display.set_mode(size, pygame.DOUBLEBUF)
|
||||
|
||||
alert1_font = pygame.font.SysFont("arial", 30)
|
||||
alert2_font = pygame.font.SysFont("arial", 20)
|
||||
info_font = pygame.font.SysFont("arial", 15)
|
||||
|
||||
camera_surface = pygame.surface.Surface((640, 480), 0, 24).convert()
|
||||
top_down_surface = pygame.surface.Surface((UP.lidar_x, UP.lidar_y), 0, 8)
|
||||
|
||||
sm = messaging.SubMaster(['carState', 'longitudinalPlan', 'carControl', 'radarState', 'liveCalibration', 'controlsState',
|
||||
'liveTracks', 'modelV2', 'liveParameters', 'roadCameraState'], addr=addr)
|
||||
|
||||
img = np.zeros((480, 640, 3), dtype='uint8')
|
||||
imgff = None
|
||||
num_px = 0
|
||||
calibration = None
|
||||
|
||||
lid_overlay_blank = get_blank_lid_overlay(UP)
|
||||
|
||||
# plots
|
||||
name_to_arr_idx = { "gas": 0,
|
||||
"computer_gas": 1,
|
||||
"user_brake": 2,
|
||||
"computer_brake": 3,
|
||||
"v_ego": 4,
|
||||
"v_pid": 5,
|
||||
"angle_steers_des": 6,
|
||||
"angle_steers": 7,
|
||||
"angle_steers_k": 8,
|
||||
"steer_torque": 9,
|
||||
"v_override": 10,
|
||||
"v_cruise": 11,
|
||||
"a_ego": 12,
|
||||
"a_target": 13}
|
||||
|
||||
plot_arr = np.zeros((100, len(name_to_arr_idx.values())))
|
||||
|
||||
plot_xlims = [(0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0])]
|
||||
plot_ylims = [(-0.1, 1.1), (-ANGLE_SCALE, ANGLE_SCALE), (0., 75.), (-3.0, 2.0)]
|
||||
plot_names = [["gas", "computer_gas", "user_brake", "computer_brake"],
|
||||
["angle_steers", "angle_steers_des", "angle_steers_k", "steer_torque"],
|
||||
["v_ego", "v_override", "v_pid", "v_cruise"],
|
||||
["a_ego", "a_target"]]
|
||||
plot_colors = [["b", "b", "g", "r", "y"],
|
||||
["b", "g", "y", "r"],
|
||||
["b", "g", "r", "y"],
|
||||
["b", "r"]]
|
||||
plot_styles = [["-", "-", "-", "-", "-"],
|
||||
["-", "-", "-", "-"],
|
||||
["-", "-", "-", "-"],
|
||||
["-", "-"]]
|
||||
|
||||
draw_plots = init_plots(plot_arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles)
|
||||
|
||||
vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_ROAD, True)
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
|
||||
screen.fill((64, 64, 64))
|
||||
lid_overlay = lid_overlay_blank.copy()
|
||||
top_down = top_down_surface, lid_overlay
|
||||
|
||||
# ***** frame *****
|
||||
if not vipc_client.is_connected():
|
||||
vipc_client.connect(True)
|
||||
|
||||
yuv_img_raw = vipc_client.recv()
|
||||
if yuv_img_raw is None or not yuv_img_raw.data.any():
|
||||
continue
|
||||
|
||||
sm.update(0)
|
||||
|
||||
camera = DEVICE_CAMERAS[("tici", str(sm['roadCameraState'].sensor))]
|
||||
|
||||
imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8).reshape((len(yuv_img_raw.data) // vipc_client.stride, vipc_client.stride))
|
||||
num_px = vipc_client.width * vipc_client.height
|
||||
rgb = cv2.cvtColor(imgff[:vipc_client.height * 3 // 2, :vipc_client.width], cv2.COLOR_YUV2RGB_NV12)
|
||||
|
||||
qcam = "QCAM" in os.environ
|
||||
bb_scale = (528 if qcam else camera.fcam.width) / 640.
|
||||
calib_scale = camera.fcam.width / 640.
|
||||
zoom_matrix = np.asarray([
|
||||
[bb_scale, 0., 0.],
|
||||
[0., bb_scale, 0.],
|
||||
[0., 0., 1.]])
|
||||
cv2.warpAffine(rgb, zoom_matrix[:2], (img.shape[1], img.shape[0]), dst=img, flags=cv2.WARP_INVERSE_MAP)
|
||||
|
||||
intrinsic_matrix = camera.fcam.intrinsics
|
||||
|
||||
w = sm['controlsState'].lateralControlState.which()
|
||||
if w == 'lqrStateDEPRECATED':
|
||||
angle_steers_k = sm['controlsState'].lateralControlState.lqrStateDEPRECATED.steeringAngleDeg
|
||||
elif w == 'indiState':
|
||||
angle_steers_k = sm['controlsState'].lateralControlState.indiState.steeringAngleDeg
|
||||
else:
|
||||
angle_steers_k = np.inf
|
||||
|
||||
plot_arr[:-1] = plot_arr[1:]
|
||||
plot_arr[-1, name_to_arr_idx['angle_steers']] = sm['carState'].steeringAngleDeg
|
||||
plot_arr[-1, name_to_arr_idx['angle_steers_des']] = sm['carControl'].actuators.steeringAngleDeg
|
||||
plot_arr[-1, name_to_arr_idx['angle_steers_k']] = angle_steers_k
|
||||
plot_arr[-1, name_to_arr_idx['gas']] = sm['carState'].gas
|
||||
# TODO gas is deprecated
|
||||
plot_arr[-1, name_to_arr_idx['computer_gas']] = clip(sm['carControl'].actuators.accel/4.0, 0.0, 1.0)
|
||||
plot_arr[-1, name_to_arr_idx['user_brake']] = sm['carState'].brake
|
||||
plot_arr[-1, name_to_arr_idx['steer_torque']] = sm['carControl'].actuators.steer * ANGLE_SCALE
|
||||
# TODO brake is deprecated
|
||||
plot_arr[-1, name_to_arr_idx['computer_brake']] = clip(-sm['carControl'].actuators.accel/4.0, 0.0, 1.0)
|
||||
plot_arr[-1, name_to_arr_idx['v_ego']] = sm['carState'].vEgo
|
||||
plot_arr[-1, name_to_arr_idx['v_cruise']] = sm['carState'].cruiseState.speed
|
||||
plot_arr[-1, name_to_arr_idx['a_ego']] = sm['carState'].aEgo
|
||||
|
||||
if len(sm['longitudinalPlan'].accels):
|
||||
plot_arr[-1, name_to_arr_idx['a_target']] = sm['longitudinalPlan'].accels[0]
|
||||
|
||||
if sm.recv_frame['modelV2']:
|
||||
plot_model(sm['modelV2'], img, calibration, top_down)
|
||||
|
||||
if sm.recv_frame['radarState']:
|
||||
plot_lead(sm['radarState'], top_down)
|
||||
|
||||
# draw all radar points
|
||||
maybe_update_radar_points(sm['liveTracks'], top_down[1])
|
||||
|
||||
if sm.updated['liveCalibration'] and num_px:
|
||||
rpyCalib = np.asarray(sm['liveCalibration'].rpyCalib)
|
||||
calibration = Calibration(num_px, rpyCalib, intrinsic_matrix, calib_scale)
|
||||
|
||||
# *** blits ***
|
||||
pygame.surfarray.blit_array(camera_surface, img.swapaxes(0, 1))
|
||||
screen.blit(camera_surface, (0, 0))
|
||||
|
||||
# display alerts
|
||||
alert_line1 = alert1_font.render(sm['controlsState'].alertText1, True, (255, 0, 0))
|
||||
alert_line2 = alert2_font.render(sm['controlsState'].alertText2, True, (255, 0, 0))
|
||||
screen.blit(alert_line1, (180, 150))
|
||||
screen.blit(alert_line2, (180, 190))
|
||||
|
||||
if hor_mode:
|
||||
screen.blit(draw_plots(plot_arr), (640+384, 0))
|
||||
else:
|
||||
screen.blit(draw_plots(plot_arr), (0, 600))
|
||||
|
||||
pygame.surfarray.blit_array(*top_down)
|
||||
screen.blit(top_down[0], (640, 0))
|
||||
|
||||
SPACING = 25
|
||||
|
||||
lines = [
|
||||
info_font.render("ENABLED", True, GREEN if sm['controlsState'].enabled else BLACK),
|
||||
info_font.render("SPEED: " + str(round(sm['carState'].vEgo, 1)) + " m/s", True, YELLOW),
|
||||
info_font.render("LONG CONTROL STATE: " + str(sm['controlsState'].longControlState), True, YELLOW),
|
||||
info_font.render("LONG MPC SOURCE: " + str(sm['longitudinalPlan'].longitudinalPlanSource), True, YELLOW),
|
||||
None,
|
||||
info_font.render("ANGLE OFFSET (AVG): " + str(round(sm['liveParameters'].angleOffsetAverageDeg, 2)) + " deg", True, YELLOW),
|
||||
info_font.render("ANGLE OFFSET (INSTANT): " + str(round(sm['liveParameters'].angleOffsetDeg, 2)) + " deg", True, YELLOW),
|
||||
info_font.render("STIFFNESS: " + str(round(sm['liveParameters'].stiffnessFactor * 100., 2)) + " %", True, YELLOW),
|
||||
info_font.render("STEER RATIO: " + str(round(sm['liveParameters'].steerRatio, 2)), True, YELLOW)
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if line is not None:
|
||||
screen.blit(line, (write_x, write_y + i * SPACING))
|
||||
|
||||
# this takes time...vsync or something
|
||||
pygame.display.flip()
|
||||
|
||||
def get_arg_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Show replay data in a UI.",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument("ip_address", nargs="?", default="127.0.0.1",
|
||||
help="The ip address on which to receive zmq messages.")
|
||||
|
||||
parser.add_argument("--frame-address", default=None,
|
||||
help="The frame address (fully qualified ZMQ endpoint for frames) on which to receive zmq messages.")
|
||||
return parser
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = get_arg_parser().parse_args(sys.argv[1:])
|
||||
|
||||
if args.ip_address != "127.0.0.1":
|
||||
os.environ["ZMQ"] = "1"
|
||||
messaging.context = messaging.Context()
|
||||
|
||||
ui_thread(args.ip_address)
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -f launch_openpilot.sh ]; then
|
||||
if [ ! -d openpilot ]; then
|
||||
git clone --single-branch --recurse-submodules https://github.com/commaai/openpilot.git
|
||||
fi
|
||||
cd openpilot
|
||||
fi
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
tools/mac_setup.sh
|
||||
else
|
||||
tools/ubuntu_setup.sh
|
||||
fi
|
||||
|
||||
git lfs pull
|
||||
|
||||
source .venv/bin/activate
|
||||
|
||||
echo "Building openpilot"
|
||||
scons -u -j$(nproc)
|
||||
|
||||
echo
|
||||
echo "---- OPENPILOT BUILDING DONE ----"
|
||||
echo "To push changes to your fork, run the following commands:"
|
||||
echo "git remote remove origin"
|
||||
echo "git remote add origin git@github.com:<YOUR_USERNAME>/openpilot.git"
|
||||
echo "git fetch"
|
||||
echo "git commit -m \"first commit\""
|
||||
echo "git push"
|
||||
Reference in New Issue
Block a user