Merge remote-tracking branch 'origin/master' into master3

This commit is contained in:
Rick Lan
2024-07-15 14:22:47 +08:00
45 changed files with 969 additions and 1140 deletions
+1 -1
Submodule body updated: 0e74db67ae...c433da944f
+1 -1
Submodule panda updated: aa18bc85bb...4e9425715e
+1 -1
View File
@@ -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
+76 -43
View File
@@ -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/*
+15 -16
View File
@@ -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)
+5 -2
View File
@@ -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()
-2
View File
@@ -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
View File
@@ -1 +1 @@
f6ff3601bd0496e78d8bc3b019d58bb7739f096b
0adff03d45c99dcfb330c48b2aa9d2093ce674a2
+5 -4
View File
@@ -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;
+2 -2
View File
@@ -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
View File
@@ -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 &lt;ipaddress&gt; 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
View File
@@ -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();
}
+4 -5
View File
@@ -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);
-1
View File
@@ -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;
};
+2 -1
View File
@@ -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);
}
+1 -8
View File
@@ -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;
}
+1
View File
@@ -5,6 +5,7 @@
#include <vector>
#include "tools/cabana/dbc/dbc.h"
#include "tools/cabana/streams/abstractstream.h"
class Sparkline {
public:
+2 -21
View File
@@ -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;
}
+1 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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;
};
+1 -1
View File
@@ -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)) {
+1
View File
@@ -135,6 +135,7 @@ private:
QTreeView::leaveEvent(event);
}
};
int max_value_width = 0;
int value_column_width = 0;
TreeView *tree;
QLabel *sparkline_label;
-11
View File
@@ -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 &notifier;
}
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() {
+3 -18
View File
@@ -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
+3 -8
View File
@@ -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);
}
+2 -4
View File
@@ -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;
-1
View File
@@ -53,7 +53,6 @@ void LiveStream::startUpdateTimer() {
}
void LiveStream::start() {
emit streamStarted();
stream_thread->start();
startUpdateTimer();
begin_date_time = QDateTime::currentDateTime();
+4 -9
View File
@@ -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;
}
}
+2 -4
View File
@@ -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();
+4 -13
View File
@@ -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;
}
+3 -5
View File
@@ -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;
+4 -9
View File
@@ -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;
}
+2 -4
View File
@@ -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();
+8 -8
View File
@@ -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);
}
+4 -2
View File
@@ -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;
+197 -44
View File
@@ -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
View File
@@ -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
+108
View File
@@ -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
-231
View File
@@ -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
+59
View File
@@ -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)
-238
View File
@@ -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
View File
@@ -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"
Generated
+247 -241
View File
File diff suppressed because it is too large Load Diff