mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-18 21:14:01 +08:00
cabana: bug fixes and improvements (#32934)
bug fixes and improvements
old-commit-hash: 36815cc6d5
This commit is contained in:
@@ -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));
|
||||
});
|
||||
bool on_tip = qobject_cast<TipLabel *>(o) != nullptr;
|
||||
auto global_pos = static_cast<QMouseEvent *>(e)->globalPos();
|
||||
|
||||
if (outside_plot_area) {
|
||||
showValueTip(-1);
|
||||
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_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user