cabana: bug fixes and improvements (#32934)

bug fixes and improvements
old-commit-hash: 36815cc6d5
fix-exp-path
Dean Lee 10 months ago committed by GitHub
parent 14a31b177b
commit dbf095522f
  1. 14
      tools/cabana/chart/chart.cc
  2. 1
      tools/cabana/chart/chart.h
  3. 28
      tools/cabana/chart/chartswidget.cc
  4. 9
      tools/cabana/chart/sparkline.cc
  5. 2
      tools/cabana/chart/tiplabel.h
  6. 7
      tools/cabana/dbc/dbcmanager.cc
  7. 4
      tools/cabana/historylog.cc
  8. 48
      tools/cabana/streams/abstractstream.cc
  9. 30
      tools/cabana/streams/abstractstream.h
  10. 6
      tools/cabana/streams/livestream.cc
  11. 7
      tools/cabana/streams/livestream.h
  12. 6
      tools/cabana/streams/pandastream.h
  13. 13
      tools/cabana/streams/replaystream.cc
  14. 9
      tools/cabana/streams/replaystream.h
  15. 6
      tools/cabana/tools/findsignal.cc
  16. 6
      tools/cabana/utils/export.cc
  17. 77
      tools/cabana/videowidget.cc
  18. 27
      tools/cabana/videowidget.h
  19. 10
      tools/replay/consoleui.cc
  20. 24
      tools/replay/replay.cc
  21. 8
      tools/replay/replay.h

@ -289,10 +289,9 @@ void ChartView::appendCanEvents(const cabana::Signal *sig, const std::vector<con
step_vals.reserve(step_vals.size() + events.capacity() * 2);
double value = 0;
const uint64_t begin_mono_time = can->routeStartTime() * 1e9;
for (const CanEvent *e : events) {
if (sig->getValue(e->dat, e->size, &value)) {
const double ts = (e->mono_time - std::min(e->mono_time, begin_mono_time)) / 1e9;
const double ts = can->toSeconds(e->mono_time);
vals.emplace_back(ts, value);
if (!step_vals.empty())
step_vals.emplace_back(ts, step_vals.back().y());
@ -312,7 +311,7 @@ void ChartView::updateSeries(const cabana::Signal *sig, const MessageEventsMap *
auto it = events->find(s.msg_id);
if (it == events->end() || it->second.empty()) continue;
if (s.vals.empty() || (it->second.back()->mono_time / 1e9 - can->routeStartTime()) > s.vals.back().x()) {
if (s.vals.empty() || can->toSeconds(it->second.back()->mono_time) > s.vals.back().x()) {
appendCanEvents(s.sig, it->second, s.vals, s.step_vals);
} else {
std::vector<QPointF> vals, step_vals;
@ -500,8 +499,8 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
rubber->hide();
auto rect = rubber->geometry().normalized();
// Prevent zooming/seeking past the end of the route
double min = std::clamp(chart()->mapToValue(rect.topLeft()).x(), 0., can->totalSeconds());
double max = std::clamp(chart()->mapToValue(rect.bottomRight()).x(), 0., can->totalSeconds());
double min = std::clamp(chart()->mapToValue(rect.topLeft()).x(), can->minSeconds(), can->maxSeconds());
double max = std::clamp(chart()->mapToValue(rect.bottomRight()).x(), can->minSeconds(), can->maxSeconds());
if (rubber->width() <= 0) {
// no rubber dragged, seek to mouse position
can->seekTo(min);
@ -531,7 +530,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
// Scrubbing
if (is_scrubbing && QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) {
if (plot_area.contains(ev->pos())) {
can->seekTo(std::clamp(chart()->mapToValue(ev->pos()).x(), 0., can->totalSeconds()));
can->seekTo(std::clamp(chart()->mapToValue(ev->pos()).x(), can->minSeconds(), can->maxSeconds()));
}
}
@ -540,8 +539,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
clearTrackPoints();
if (!is_zooming && plot_area.contains(ev->pos()) && isActiveWindow()) {
const double sec = chart()->mapToValue(ev->pos()).x();
charts_widget->showValueTip(sec);
charts_widget->showValueTip(secondsAtPoint(ev->pos()));
} else if (tip_label->isVisible()) {
charts_widget->showValueTip(-1);
}

@ -39,6 +39,7 @@ public:
void showTip(double sec);
void hideTip();
void startAnimation();
double secondsAtPoint(const QPointF &pt) const { return chart()->mapToValue(pt).x(); }
struct SigItem {
MessageId msg_id;

@ -93,7 +93,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) {
current_theme = settings.theme;
column_count = std::clamp(settings.chart_column_count, 1, MAX_COLUMN_COUNT);
max_chart_range = std::clamp(settings.chart_range, 1, settings.max_cached_minutes * 60);
display_range = {0, max_chart_range};
display_range = std::make_pair(can->minSeconds(), can->minSeconds() + max_chart_range);
range_slider->setValue(max_chart_range);
updateToolBar();
@ -192,10 +192,10 @@ void ChartsWidget::updateState() {
if (!time_range.has_value()) {
double pos = (cur_sec - display_range.first) / std::max<float>(1.0, max_chart_range);
if (pos < 0 || pos > 0.8) {
display_range.first = std::max(0.0, cur_sec - max_chart_range * 0.1);
display_range.first = std::max(can->minSeconds(), cur_sec - max_chart_range * 0.1);
}
double max_sec = std::min(display_range.first + max_chart_range, can->totalSeconds());
display_range.first = std::max(0.0, max_sec - max_chart_range);
double max_sec = std::min(display_range.first + max_chart_range, can->maxSeconds());
display_range.first = std::max(can->minSeconds(), max_sec - max_chart_range);
display_range.second = display_range.first + max_chart_range;
}
@ -435,14 +435,20 @@ void ChartsWidget::alignCharts() {
bool ChartsWidget::eventFilter(QObject *o, QEvent *e) {
if (value_tip_visible_ && e->type() == QEvent::MouseMove) {
auto pos = static_cast<QMouseEvent *>(e)->globalPos();
bool outside_plot_area =std::none_of(charts.begin(), charts.end(), [&pos](auto c) {
return c->chart()->plotArea().contains(c->mapFromGlobal(pos));
});
if (outside_plot_area) {
showValueTip(-1);
bool on_tip = qobject_cast<TipLabel *>(o) != nullptr;
auto global_pos = static_cast<QMouseEvent *>(e)->globalPos();
for (const auto &c : charts) {
auto local_pos = c->mapFromGlobal(global_pos);
if (c->chart()->plotArea().contains(local_pos)) {
if (on_tip) {
showValueTip(c->secondsAtPoint(local_pos));
}
return false;
}
}
showValueTip(-1);
}
return false;
}

@ -8,10 +8,11 @@
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);
uint64_t ts = (last_msg_ts + can->routeStartTime()) * 1e9;
uint64_t first_ts = (ts > range * 1e9) ? ts - range * 1e9 : 0;
auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), first_ts, CompareCanEvent());
auto last = std::upper_bound(first, msgs.cend(), ts, CompareCanEvent());
auto range_start = can->toMonoTime(last_msg_ts - range);
auto range_end = can->toMonoTime(last_msg_ts);
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();

@ -3,6 +3,8 @@
#include <QLabel>
class TipLabel : public QLabel {
Q_OBJECT
public:
TipLabel(QWidget *parent = nullptr);
void showText(const QPoint &pt, const QString &sec, QWidget *w, const QRect &rect);

@ -1,5 +1,6 @@
#include "tools/cabana/dbc/dbcmanager.h"
#include <QSet>
#include <algorithm>
#include <numeric>
@ -124,16 +125,16 @@ cabana::Msg *DBCManager::msg(uint8_t source, const QString &name) {
QStringList DBCManager::signalNames() {
// Used for autocompletion
QStringList ret;
QSet<QString> names;
for (auto &f : allDBCFiles()) {
for (auto &[_, m] : f->getMessages()) {
for (auto sig : m.getSignals()) {
ret << sig->name;
names.insert(sig->name);
}
}
}
QStringList ret = names.values();
ret.sort();
ret.removeDuplicates();
return ret;
}

@ -13,7 +13,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
const auto &m = messages[index.row()];
const int col = index.column();
if (role == Qt::DisplayRole) {
if (col == 0) return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 3);
if (col == 0) return QString::number(can->toSeconds(m.mono_time), 'f', 3);
if (!isHexMode()) return sigs[col - 1]->formatValue(m.sig_values[col - 1], false);
} else if (role == Qt::TextAlignmentRole) {
return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter);
@ -80,7 +80,7 @@ void HistoryLogModel::updateState(bool clear) {
messages.clear();
endRemoveRows();
}
uint64_t current_time = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9 + 1;
uint64_t current_time = can->toMonoTime(can->lastMessage(msg_id).ts) + 1;
fetchData(messages.begin(), current_time, messages.empty() ? 0 : messages.front().mono_time);
}

@ -1,6 +1,5 @@
#include "tools/cabana/streams/abstractstream.h"
#include <algorithm>
#include <utility>
#include <QApplication>
@ -20,9 +19,9 @@ AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
assert(parent != nullptr);
event_buffer_ = std::make_unique<MonotonicBuffer>(EVENT_NEXT_BUFFER_SIZE);
QObject::connect(QApplication::instance(), &QCoreApplication::aboutToQuit, this, &AbstractStream::stop);
QObject::connect(this, &AbstractStream::privateUpdateLastMsgsSignal, this, &AbstractStream::updateLastMessages, Qt::QueuedConnection);
QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo);
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]() {
@ -64,14 +63,12 @@ void AbstractStream::suppressDefinedSignals(bool suppress) {
size_t AbstractStream::suppressHighlighted() {
std::lock_guard lk(mutex_);
size_t cnt = 0;
const double cur_ts = currentSec();
for (auto &[_, m] : messages_) {
for (auto &last_change : m.last_changes) {
const double dt = cur_ts - last_change.ts;
const double dt = current_sec_ - last_change.ts;
if (dt < 2.0) {
last_change.suppressed = true;
}
// clear bit change counts
last_change.bit_change_counts.fill(0);
cnt += last_change.suppressed;
}
@ -90,20 +87,16 @@ void AbstractStream::updateLastMessages() {
auto prev_src_size = sources.size();
auto prev_msg_size = last_msgs.size();
std::set<MessageId> msgs;
{
std::lock_guard lk(mutex_);
double max_sec = 0;
for (const auto &id : new_msgs_) {
const auto &can_data = messages_[id];
max_sec = std::max(max_sec, can_data.ts);
current_sec_ = std::max(current_sec_, can_data.ts);
last_msgs[id] = can_data;
sources.insert(id.source);
}
if (!new_msgs_.empty()) {
msgs = std::move(new_msgs_);
current_sec_ = max_sec;
}
msgs = std::move(new_msgs_);
}
if (time_range_ && (current_sec_ < time_range_->first || current_sec_ >= time_range_->second)) {
@ -138,7 +131,7 @@ const std::vector<const CanEvent *> &AbstractStream::events(const MessageId &id)
return it != events_.end() ? it->second : empty_events;
}
const CanData &AbstractStream::lastMessage(const MessageId &id) {
const CanData &AbstractStream::lastMessage(const MessageId &id) const {
static CanData empty_data = {};
auto it = last_msgs.find(id);
return it != last_msgs.end() ? it->second : empty_data;
@ -148,15 +141,13 @@ const CanData &AbstractStream::lastMessage(const MessageId &id) {
// updateLastMsgsTo is always called in UI thread.
void AbstractStream::updateLastMsgsTo(double sec) {
current_sec_ = sec;
uint64_t last_ts = (sec + routeStartTime()) * 1e9;
uint64_t last_ts = toMonoTime(sec);
std::unordered_map<MessageId, CanData> msgs;
msgs.reserve(events_.size());
for (const auto &[id, ev] : events_) {
auto it = std::upper_bound(ev.begin(), ev.end(), last_ts, CompareCanEvent());
if (it != ev.begin()) {
auto prev = std::prev(it);
double ts = (*prev)->mono_time / 1e9 - routeStartTime();
auto &m = msgs[id];
double freq = 0;
// Keep suppressed bits.
@ -167,7 +158,9 @@ void AbstractStream::updateLastMsgsTo(double sec) {
std::back_inserter(m.last_changes),
[](const auto &change) { return CanData::ByteLastChange{.suppressed = change.suppressed}; });
}
m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {}, freq);
auto prev = std::prev(it);
m.compute(id, (*prev)->dat, (*prev)->size, toSeconds((*prev)->mono_time), getSpeed(), {}, freq);
m.count = std::distance(ev.begin(), prev) + 1;
}
}
@ -213,7 +206,6 @@ void AbstractStream::mergeEvents(const std::vector<const CanEvent *> &events) {
all_events_.insert(pos, events.cbegin(), events.cend());
emit eventsMerged(msg_events);
}
lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time;
}
namespace {
@ -236,14 +228,18 @@ inline QColor blend(const QColor &a, const QColor &b) {
// Calculate the frequency from the past one minute data
double calc_freq(const MessageId &msg_id, double current_sec) {
const auto &events = can->events(msg_id);
uint64_t cur_mono_time = (can->routeStartTime() + current_sec) * 1e9;
uint64_t first_mono_time = std::max<int64_t>(0, cur_mono_time - 59 * 1e9);
auto first = std::lower_bound(events.begin(), events.end(), first_mono_time, CompareCanEvent());
auto second = std::lower_bound(first, events.end(), cur_mono_time, CompareCanEvent());
if (first != events.end() && second != events.end()) {
double duration = ((*second)->mono_time - (*first)->mono_time) / 1e9;
uint32_t count = std::distance(first, second);
return count / std::max(1.0, duration);
if (events.empty()) return 0.0;
auto current_mono_time = can->toMonoTime(current_sec);
auto start_mono_time = can->toMonoTime(current_sec - 59);
auto first = std::lower_bound(events.begin(), events.end(), start_mono_time, CompareCanEvent());
auto last = std::upper_bound(first, events.end(), current_mono_time, CompareCanEvent());
int count = std::distance(first, last);
if (count > 1) {
double duration = ((*std::prev(last))->mono_time - (*first)->mono_time) / 1e9;
return count / duration;
}
return 0;
}

@ -1,5 +1,6 @@
#pragma once
#include <algorithm>
#include <array>
#include <memory>
#include <mutex>
@ -28,10 +29,10 @@ struct CanData {
std::vector<QColor> colors;
struct ByteLastChange {
double ts;
int delta;
int same_delta_counter;
bool suppressed;
double ts = 0;
int delta = 0;
int same_delta_counter = 0;
bool suppressed = false;
std::array<uint32_t, 8> bit_change_counts;
};
std::vector<ByteLastChange> last_changes;
@ -51,12 +52,6 @@ struct CompareCanEvent {
constexpr bool operator()(uint64_t ts, const CanEvent *const e) const { return ts < e->mono_time; }
};
struct BusConfig {
int can_speed_kbps = 500;
int data_speed_kbps = 2000;
bool can_fd = false;
};
typedef std::unordered_map<MessageId, std::vector<const CanEvent *>> MessageEventsMap;
class AbstractStream : public QObject {
@ -66,15 +61,14 @@ public:
AbstractStream(QObject *parent);
virtual ~AbstractStream() {}
virtual void start() = 0;
virtual void stop() {}
virtual bool liveStreaming() const { return true; }
virtual void seekTo(double ts) {}
virtual QString routeName() const = 0;
virtual QString carFingerprint() const { return ""; }
virtual QDateTime beginDateTime() const { return {}; }
virtual double routeStartTime() const { return 0; }
inline double currentSec() const { return current_sec_; }
virtual double totalSeconds() const { return lastEventMonoTime() / 1e9 - routeStartTime(); }
virtual uint64_t beginMonoTime() const { return 0; }
virtual double minSeconds() const { return 0; }
virtual double maxSeconds() const { return 0; }
virtual void setSpeed(float speed) {}
virtual double getSpeed() { return 1; }
virtual bool isPaused() const { return false; }
@ -82,10 +76,14 @@ public:
void setTimeRange(const std::optional<std::pair<double, double>> &range);
const std::optional<std::pair<double, double>> &timeRange() const { return time_range_; }
inline double currentSec() const { return current_sec_; }
inline uint64_t toMonoTime(double sec) const { return beginMonoTime() + std::max(sec, 0.0) * 1e9; }
inline double toSeconds(uint64_t mono_time) const { return std::max(0.0, (mono_time - beginMonoTime()) / 1e9); }
inline const std::unordered_map<MessageId, CanData> &lastMessages() const { return last_msgs; }
inline const MessageEventsMap &eventsMap() const { return events_; }
inline const std::vector<const CanEvent *> &allEvents() const { return all_events_; }
const CanData &lastMessage(const MessageId &id);
const CanData &lastMessage(const MessageId &id) const;
const std::vector<const CanEvent *> &events(const MessageId &id) const;
size_t suppressHighlighted();
@ -111,12 +109,10 @@ protected:
void mergeEvents(const std::vector<const CanEvent *> &events);
const CanEvent *newEvent(uint64_t mono_time, const cereal::CanData::Reader &c);
void updateEvent(const MessageId &id, double sec, const uint8_t *data, uint8_t size);
uint64_t lastEventMonoTime() const { return lastest_event_ts; }
std::vector<const CanEvent *> all_events_;
double current_sec_ = 0;
std::optional<std::pair<double, double>> time_range_;
uint64_t lastest_event_ts = 0;
private:
void updateLastMessages();

@ -92,6 +92,8 @@ void LiveStream::timerEvent(QTimerEvent *event) {
// merge events received from live stream thread.
std::lock_guard lk(lock);
mergeEvents(received_events_);
uint64_t last_received_ts = !received_events_.empty() ? received_events_.back()->mono_time : 0;
lastest_event_ts = std::max(lastest_event_ts, last_received_ts);
received_events_.clear();
}
if (!all_events_.empty()) {
@ -136,8 +138,8 @@ void LiveStream::updateEvents() {
void LiveStream::seekTo(double sec) {
sec = std::max(0.0, sec);
first_update_ts = nanos_since_boot();
current_event_ts = first_event_ts = std::min<uint64_t>(sec * 1e9 + begin_event_ts, lastEventMonoTime());
post_last_event = (first_event_ts == lastEventMonoTime());
current_event_ts = first_event_ts = std::min<uint64_t>(sec * 1e9 + begin_event_ts, lastest_event_ts);
post_last_event = (first_event_ts == lastest_event_ts);
emit seekedTo((current_event_ts - begin_event_ts) / 1e9);
}

@ -1,5 +1,6 @@
#pragma once
#include <algorithm>
#include <memory>
#include <vector>
@ -14,9 +15,10 @@ public:
LiveStream(QObject *parent);
virtual ~LiveStream();
void start() override;
void stop() override;
void stop();
inline QDateTime beginDateTime() const { return begin_date_time; }
inline double routeStartTime() const override { return begin_event_ts / 1e9; }
inline uint64_t beginMonoTime() const override { return begin_event_ts; }
double maxSeconds() const override { return std::max(1.0, (lastest_event_ts - begin_event_ts) / 1e9); }
void setSpeed(float speed) override { speed_ = speed; }
double getSpeed() override { return speed_; }
bool isPaused() const override { return paused_; }
@ -41,6 +43,7 @@ private:
QDateTime begin_date_time;
uint64_t begin_event_ts = 0;
uint64_t lastest_event_ts = 0;
uint64_t current_event_ts = 0;
uint64_t first_event_ts = 0;
uint64_t first_update_ts = 0;

@ -12,6 +12,12 @@
const uint32_t speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U};
const uint32_t data_speeds[] = {10U, 20U, 50U, 100U, 125U, 250U, 500U, 1000U, 2000U, 5000U};
struct BusConfig {
int can_speed_kbps = 500;
int data_speed_kbps = 2000;
bool can_fd = false;
};
struct PandaStreamConfig {
QString serial = "";
std::vector<BusConfig> bus_config;

@ -88,16 +88,10 @@ void ReplayStream::start() {
replay->start();
}
void ReplayStream::stop() {
if (replay) {
replay->stop();
}
}
bool ReplayStream::eventFilter(const Event *event) {
static double prev_update_ts = 0;
if (event->which == cereal::Event::Which::CAN) {
double current_sec = event->mono_time / 1e9 - routeStartTime();
double current_sec = toSeconds(event->mono_time);
capnp::FlatArrayMessageReader reader(event->data);
auto e = reader.getRoot<cereal::Event>();
for (const auto &c : e.getCan()) {
@ -115,11 +109,6 @@ bool ReplayStream::eventFilter(const Event *event) {
return true;
}
void ReplayStream::seekTo(double ts) {
current_sec_ = ts;
replay->seekTo(std::max(double(0), ts), false);
}
void ReplayStream::pause(bool pause) {
replay->pause(pause);
emit(pause ? paused() : resume());

@ -16,17 +16,16 @@ class ReplayStream : public AbstractStream {
public:
ReplayStream(QObject *parent);
void start() override;
void stop() override;
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;
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }
bool liveStreaming() const override { return false; }
inline QString routeName() const override { return replay->route()->name(); }
inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); }
double totalSeconds() const override { return replay->totalSeconds(); }
double minSeconds() const override { return replay->minSeconds(); }
double maxSeconds() const { return replay->maxSeconds(); }
inline QDateTime beginDateTime() const { return replay->routeDateTime(); }
inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; }
inline const Route *route() const { return replay->route(); }
inline uint64_t beginMonoTime() const override { return replay->routeStartNanos(); }
inline void setSpeed(float speed) override { replay->setSpeed(speed); }
inline float getSpeed() const { return replay->getSpeed(); }
inline Replay *getReplay() const { return replay.get(); }

@ -46,7 +46,7 @@ void FindSignalModel::search(std::function<bool(double)> cmp) {
auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); });
if (it != last) {
auto values = s.values;
values += QString("(%1, %2)").arg((*it)->mono_time / 1e9 - can->routeStartTime(), 0, 'f', 2).arg(get_raw_value((*it)->dat, (*it)->size, s.sig));
values += QString("(%1, %2)").arg(can->toSeconds((*it)->mono_time), 0, 'f', 3).arg(get_raw_value((*it)->dat, (*it)->size, s.sig));
std::lock_guard lk(lock);
filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values});
}
@ -217,10 +217,10 @@ void FindSignalDlg::setInitialSignals() {
double first_time_val = first_time_edit->text().toDouble();
double last_time_val = last_time_edit->text().toDouble();
auto [first_sec, last_sec] = std::minmax(first_time_val, last_time_val);
uint64_t first_time = (can->routeStartTime() + first_sec) * 1e9;
uint64_t first_time = can->toMonoTime(first_sec);
model->last_time = std::numeric_limits<uint64_t>::max();
if (last_sec > 0) {
model->last_time = (can->routeStartTime() + last_sec) * 1e9;
model->last_time = can->toMonoTime(last_sec);
}
model->initial_signals.clear();

@ -10,11 +10,10 @@ namespace utils {
void exportToCSV(const QString &file_name, std::optional<MessageId> msg_id) {
QFile file(file_name);
if (file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
const uint64_t start_time = can->routeStartTime();
QTextStream stream(&file);
stream << "time,addr,bus,data\n";
for (auto e : msg_id ? can->events(*msg_id) : can->allEvents()) {
stream << QString::number((e->mono_time / 1e9) - start_time, 'f', 2) << ","
stream << QString::number(can->toSeconds(e->mono_time), 'f', 3) << ","
<< "0x" << QString::number(e->address, 16) << "," << e->src << ","
<< "0x" << QByteArray::fromRawData((const char *)e->dat, e->size).toHex().toUpper() << "\n";
}
@ -30,9 +29,8 @@ void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id) {
stream << "," << s->name;
stream << "\n";
const uint64_t start_time = can->routeStartTime();
for (auto e : can->events(msg_id)) {
stream << QString::number((e->mono_time / 1e9) - start_time, 'f', 2) << ","
stream << QString::number(can->toSeconds(e->mono_time), 'f', 3) << ","
<< "0x" << QString::number(e->address, 16) << "," << e->src;
for (auto s : msg->sigs) {
double value = 0;

@ -27,6 +27,13 @@ static const QColor timeline_colors[] = {
[(int)TimelineType::AlertCritical] = QColor(199, 0, 57),
};
static Replay *getReplay() {
auto stream = qobject_cast<ReplayStream *>(can);
if (!stream) return nullptr;
return stream->getReplay();
}
VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
auto main_layout = new QVBoxLayout(this);
@ -76,7 +83,7 @@ QHBoxLayout *VideoWidget::createPlaybackController() {
// set speed to 1.0
speed_btn->menu()->actions()[7]->setChecked(true);
can->pause(false);
can->seekTo(can->totalSeconds() + 1);
can->seekTo(can->maxSeconds() + 1);
});
}
@ -141,7 +148,7 @@ QWidget *VideoWidget::createCameraWidget() {
QStackedLayout *stacked = new QStackedLayout();
stacked->setStackingMode(QStackedLayout::StackAll);
stacked->addWidget(cam_widget = new CameraWidget("camerad", VISION_STREAM_ROAD, false));
stacked->addWidget(cam_widget = new StreamCameraView("camerad", VISION_STREAM_ROAD, false));
cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT);
cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
stacked->addWidget(alert_label = new InfoLabel(this));
@ -149,9 +156,11 @@ QWidget *VideoWidget::createCameraWidget() {
l->addWidget(slider = new Slider(w));
slider->setSingleStep(0);
slider->setTimeRange(can->minSeconds(), can->maxSeconds());
setMaximumTime(can->totalSeconds());
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); });
QObject::connect(can, &AbstractStream::paused, cam_widget, [c = cam_widget]() { c->showPausedOverlay(); });
QObject::connect(can, &AbstractStream::resume, cam_widget, [c = cam_widget]() { c->update(); });
QObject::connect(can, &AbstractStream::eventsMerged, this, [this]() { slider->update(); });
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(cam_widget, &CameraWidget::vipcAvailableStreamsUpdated, this, &VideoWidget::vipcAvailableStreamsUpdated);
@ -161,7 +170,7 @@ QWidget *VideoWidget::createCameraWidget() {
auto replay = static_cast<ReplayStream*>(can)->getReplay();
QObject::connect(replay, &Replay::qLogLoaded, slider, &Slider::parseQLog, Qt::QueuedConnection);
QObject::connect(replay, &Replay::totalSecondsUpdated, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection);
QObject::connect(replay, &Replay::minMaxTimeChanged, this, &VideoWidget::timeRangeChanged, Qt::QueuedConnection);
return w;
}
@ -185,7 +194,7 @@ void VideoWidget::vipcAvailableStreamsUpdated(std::set<VisionStreamType> streams
}
void VideoWidget::loopPlaybackClicked() {
auto replay = qobject_cast<ReplayStream *>(can)->getReplay();
auto replay = getReplay();
if (!replay) return;
if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) {
@ -197,18 +206,15 @@ void VideoWidget::loopPlaybackClicked() {
}
}
void VideoWidget::setMaximumTime(double sec) {
maximum_time = sec;
slider->setTimeRange(0, sec);
}
void VideoWidget::timeRangeChanged(const std::optional<std::pair<double, double>> &time_range) {
void VideoWidget::timeRangeChanged() {
const auto time_range = can->timeRange();
if (can->liveStreaming()) {
skip_to_end_btn->setEnabled(!time_range.has_value());
return;
}
time_range ? slider->setTimeRange(time_range->first, time_range->second)
: slider->setTimeRange(0, maximum_time);
: slider->setTimeRange(can->minSeconds(), can->maxSeconds());
updateState();
}
QString VideoWidget::formatTime(double sec, bool include_milliseconds) {
@ -219,8 +225,9 @@ QString VideoWidget::formatTime(double sec, bool include_milliseconds) {
void VideoWidget::updateState() {
if (slider) {
if (!slider->isSliderDown())
if (!slider->isSliderDown()) {
slider->setCurrentSecond(can->currentSec());
}
alert_label->showAlert(slider->alertInfo(can->currentSec()));
time_btn->setText(QString("%1 / %2").arg(formatTime(can->currentSec(), true),
formatTime(slider->maximum() / slider->factor)));
@ -242,15 +249,14 @@ Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) {
}
AlertInfo Slider::alertInfo(double seconds) {
uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9;
uint64_t mono_time = can->toMonoTime(seconds);
auto alert_it = alerts.lower_bound(mono_time);
bool has_alert = (alert_it != alerts.end()) && ((alert_it->first - mono_time) <= 1e8);
return has_alert ? alert_it->second : AlertInfo{};
}
QPixmap Slider::thumbnail(double seconds) {
uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9;
auto it = thumbnails.lowerBound(mono_time);
auto it = thumbnails.lowerBound(can->toMonoTime(seconds));
return it != thumbnails.end() ? it.value() : QPixmap();
}
@ -298,16 +304,18 @@ void Slider::paintEvent(QPaintEvent *ev) {
p.fillRect(r, color);
};
const auto replay = qobject_cast<ReplayStream *>(can)->getReplay();
for (auto [begin, end, type] : replay->getTimeline()) {
fillRange(begin, end, timeline_colors[(int)type]);
}
auto replay = getReplay();
if (replay) {
for (auto [begin, end, type] : replay->getTimeline()) {
fillRange(begin, end, timeline_colors[(int)type]);
}
QColor empty_color = palette().color(QPalette::Window);
empty_color.setAlpha(160);
for (const auto &[n, seg] : replay->segments()) {
if (!(seg && seg->isLoaded()))
fillRange(n * 60.0, (n + 1) * 60.0, empty_color);
QColor empty_color = palette().color(QPalette::Window);
empty_color.setAlpha(160);
for (const auto &[n, seg] : replay->segments()) {
if (!(seg && seg->isLoaded()))
fillRange(n * 60.0, (n + 1) * 60.0, empty_color);
}
}
QStyleOptionSlider opt;
@ -411,3 +419,22 @@ void InfoLabel::paintEvent(QPaintEvent *event) {
p.drawText(text_rect, Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, text);
}
}
StreamCameraView::StreamCameraView(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget *parent)
: CameraWidget(stream_name, stream_type, zoom, parent) {
fade_animation = new QPropertyAnimation(this, "overlayOpacity");
fade_animation->setDuration(500);
fade_animation->setStartValue(0.2f);
fade_animation->setEndValue(0.7f);
}
void StreamCameraView::paintGL() {
CameraWidget::paintGL();
if (can->isPaused()) {
QPainter p(this);
p.setPen(QColor(200, 200, 200, static_cast<int>(255 * overlay_opacity)));
p.setFont(QFont(font().family(), 16, QFont::Bold));
p.drawText(rect(), Qt::AlignCenter, tr("PAUSED"));
}
}

@ -4,10 +4,12 @@
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <QHBoxLayout>
#include <QFrame>
#include <QPropertyAnimation>
#include <QSlider>
#include <QTabBar>
@ -57,16 +59,34 @@ private:
InfoLabel *thumbnail_label;
};
class StreamCameraView : public CameraWidget {
Q_OBJECT
Q_PROPERTY(float overlayOpacity READ overlayOpacity WRITE setOverlayOpacity)
public:
StreamCameraView(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget *parent = nullptr);
void paintGL() override;
void showPausedOverlay() { fade_animation->start(); }
float overlayOpacity() const { return overlay_opacity; }
void setOverlayOpacity(float opacity) {
overlay_opacity = opacity;
update();
}
private:
float overlay_opacity;
QPropertyAnimation *fade_animation;
};
class VideoWidget : public QFrame {
Q_OBJECT
public:
VideoWidget(QWidget *parnet = nullptr);
void setMaximumTime(double sec);
protected:
QString formatTime(double sec, bool include_milliseconds = false);
void timeRangeChanged(const std::optional<std::pair<double, double>> &time_range);
void timeRangeChanged();
void updateState();
void updatePlayBtnState();
QWidget *createCameraWidget();
@ -74,8 +94,7 @@ protected:
void loopPlaybackClicked();
void vipcAvailableStreamsUpdated(std::set<VisionStreamType> streams);
CameraWidget *cam_widget;
double maximum_time = 0;
StreamCameraView *cam_widget;
QToolButton *time_btn = nullptr;
ToolButton *seek_backward_btn = nullptr;
ToolButton *play_btn = nullptr;

@ -171,7 +171,7 @@ void ConsoleUI::updateStatus() {
if (status != Status::Paused) {
auto events = replay->events();
uint64_t current_mono_time = replay->routeStartTime() + replay->currentSeconds() * 1e9;
uint64_t current_mono_time = replay->routeStartNanos() + replay->currentSeconds() * 1e9;
bool playing = !events->empty() && events->back().mono_time > current_mono_time;
status = playing ? Status::Playing : Status::Waiting;
}
@ -262,10 +262,10 @@ void ConsoleUI::updateTimeline() {
mvwhline(win, 2, 0, ' ', width);
wattroff(win, COLOR_PAIR(Color::Disengaged));
const int total_sec = replay->totalSeconds();
const int total_sec = replay->maxSeconds() - replay->minSeconds();
for (auto [begin, end, type] : replay->getTimeline()) {
int start_pos = (begin / total_sec) * width;
int end_pos = (end / total_sec) * width;
int start_pos = ((begin - replay->minSeconds()) / total_sec) * width;
int end_pos = ((end - replay->minSeconds()) / total_sec) * width;
if (type == TimelineType::Engaged) {
mvwchgat(win, 1, start_pos, end_pos - start_pos + 1, A_COLOR, Color::Engaged, NULL);
mvwchgat(win, 2, start_pos, end_pos - start_pos + 1, A_COLOR, Color::Engaged, NULL);
@ -280,7 +280,7 @@ void ConsoleUI::updateTimeline() {
}
}
int cur_pos = ((double)replay->currentSeconds() / total_sec) * width;
int cur_pos = ((replay->currentSeconds() - replay->minSeconds()) / total_sec) * width;
wattron(win, COLOR_PAIR(Color::BrightWhite));
mvwaddch(win, 0, cur_pos, ACS_VLINE);
mvwaddch(win, 3, cur_pos, ACS_VLINE);

@ -85,6 +85,7 @@ bool Replay::load() {
return false;
}
rInfo("load route %s with %zu valid segments", qPrintable(route_->name()), segments_.size());
max_seconds_ = (segments_.rbegin()->first + 1) * 60;
return true;
}
@ -108,7 +109,11 @@ void Replay::seekTo(double seconds, bool relative) {
target_time = std::max(double(0.0), target_time);
int target_segment = (int)target_time / 60;
if (segments_.count(target_segment) == 0) {
rWarning("Can't seek to %d s segment %d is invalid", (int)target_time, target_segment);
rWarning("Can't seek to %.2f s segment %d is invalid", target_time, target_segment);
return true;
}
if (target_time > max_seconds_) {
rWarning("Can't seek to %.2f s, time is invalid", target_time);
return true;
}
@ -193,15 +198,22 @@ void Replay::buildTimeline() {
}
}
if (it->first == route_segments.rbegin()->first) {
if (engaged) {
timeline.push_back({toSeconds(engaged_begin), toSeconds(log->events.back().mono_time), TimelineType::Engaged});
}
if (!alert_type.empty() && alert_size != cereal::ControlsState::AlertSize::NONE) {
timeline.push_back({toSeconds(alert_begin), toSeconds(log->events.back().mono_time), timeline_types[(int)alert_status]});
}
max_seconds_ = std::ceil(toSeconds(log->events.back().mono_time));
emit minMaxTimeChanged(route_segments.cbegin()->first * 60.0, max_seconds_);
}
{
std::lock_guard lk(timeline_lock);
timeline_.insert(timeline_.end(), timeline.begin(), timeline.end());
std::sort(timeline_.begin(), timeline_.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); });
}
if (it->first == route_segments.rbegin()->first) {
emit totalSecondsUpdated(toSeconds(log->events.back().mono_time));
}
emit qLogLoaded(log);
}
}
@ -463,7 +475,7 @@ void Replay::streamThread() {
int last_segment = segments_.rbegin()->first;
if (current_segment_ >= last_segment && isSegmentMerged(last_segment)) {
rInfo("reaches the end of route, restart from beginning");
QMetaObject::invokeMethod(this, std::bind(&Replay::seekTo, this, 0, false), Qt::QueuedConnection);
QMetaObject::invokeMethod(this, std::bind(&Replay::seekTo, this, minSeconds(), false), Qt::QueuedConnection);
}
}
}

@ -76,9 +76,10 @@ public:
inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; }
inline QDateTime routeDateTime() const { return route_date_time_; }
inline QDateTime currentDateTime() const { return route_date_time_.addSecs(currentSeconds()); }
inline uint64_t routeStartTime() const { return route_start_ts_; }
inline uint64_t routeStartNanos() const { return route_start_ts_; }
inline double toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; }
inline int totalSeconds() const { return (!segments_.empty()) ? (segments_.rbegin()->first + 1) * 60 : 0; }
inline double minSeconds() const { return !segments_.empty() ? segments_.begin()->first * 60 : 0; }
inline double maxSeconds() const { return max_seconds_; }
inline void setSpeed(float speed) { speed_ = speed; }
inline float getSpeed() const { return speed_; }
inline const std::vector<Event> *events() const { return &events_; }
@ -95,7 +96,7 @@ signals:
void seeking(double sec);
void seekedTo(double sec);
void qLogLoaded(std::shared_ptr<LogReader> qlog);
void totalSecondsUpdated(double sec);
void minMaxTimeChanged(double min_sec, double max_sec);
protected slots:
void segmentLoadFinished(bool success);
@ -133,6 +134,7 @@ protected:
QDateTime route_date_time_;
uint64_t route_start_ts_ = 0;
std::atomic<uint64_t> cur_mono_time_ = 0;
std::atomic<double> max_seconds_ = 0;
std::vector<Event> events_;
std::set<int> merged_segments_;

Loading…
Cancel
Save