#include "tools/cabana/canmessages.h" #include "tools/cabana/dbcmanager.h" CANMessages *can = nullptr; CANMessages::CANMessages(QObject *parent) : QObject(parent) { can = this; QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection); QObject::connect(&settings, &Settings::changed, this, &CANMessages::settingChanged); } CANMessages::~CANMessages() { replay->stop(); } static bool event_filter(const Event *e, void *opaque) { CANMessages *c = (CANMessages *)opaque; return c->eventFilter(e); } static QColor blend(QColor a, QColor b) { return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); } bool CANMessages::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) { replay = new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this); replay->setSegmentCacheLimit(settings.cached_segment_limit); replay->installEventFilter(event_filter, this); QObject::connect(replay, &Replay::seekedTo, this, &CANMessages::seekedTo); QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged); QObject::connect(replay, &Replay::streamStarted, this, &CANMessages::streamStarted); if (replay->load()) { const auto &segments = replay->route()->segments(); if (std::none_of(segments.begin(), segments.end(), [](auto &s) { return s.second.rlog.length() > 0; })) { qWarning() << "no rlogs in route" << route; return false; } replay->start(); return true; } return false; } void CANMessages::process(QHash *messages) { for (auto it = messages->begin(); it != messages->end(); ++it) { can_msgs[it.key()] = it.value(); } emit updated(); emit msgsReceived(messages); delete messages; processing = false; } bool CANMessages::eventFilter(const Event *event) { static std::unique_ptr new_msgs = std::make_unique>(); static QHash prev_dat; static QHash> colors; static QHash> last_change_t; static double prev_update_ts = 0; if (event->which == cereal::Event::Which::CAN) { double current_sec = replay->currentSeconds(); if (counters_begin_sec == 0 || counters_begin_sec >= current_sec) { new_msgs->clear(); counters.clear(); counters_begin_sec = current_sec; } auto can_events = event->event.getCan(); for (const auto &c : can_events) { QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16); CanData &data = (*new_msgs)[id]; data.ts = current_sec; data.src = c.getSrc(); data.address = c.getAddress(); data.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size()); data.count = ++counters[id]; if (double delta = (current_sec - counters_begin_sec); delta > 0) { data.freq = data.count / delta; } // Init colors if (colors[id].size() != data.dat.size()) { colors[id].clear(); for (int i = 0; i < data.dat.size(); i++){ colors[id].append(QColor(0, 0, 0, 0)); } } // Init last_change_t if (last_change_t[id].size() != data.dat.size()) { last_change_t[id].clear(); for (int i = 0; i < data.dat.size(); i++){ last_change_t[id].append(data.ts); } } // Compute background color for the bytes based on changes if (prev_dat[id].size() == data.dat.size()) { for (int i = 0; i < data.dat.size(); i++){ uint8_t last = prev_dat[id][i]; uint8_t cur = data.dat[i]; const int periodic_threshold = 10; const int start_alpha = 128; const float fade_time = 2.0; if (last != cur) { double delta_t = data.ts - last_change_t[id][i]; if (delta_t * data.freq > periodic_threshold) { // Last change was while ago, choose color based on delta up or down if (cur > last) { colors[id][i] = QColor(0, 187, 255, start_alpha); // Cyan } else { colors[id][i] = QColor(255, 0, 0, start_alpha); // Red } } else { // Periodic changes colors[id][i] = blend(colors[id][i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue } last_change_t[id][i] = data.ts; } else { // Fade out float alpha_delta = 1.0 / (data.freq + 1) / fade_time; colors[id][i].setAlphaF(std::max(0.0, colors[id][i].alphaF() - alpha_delta)); } } } data.colors = colors[id]; prev_dat[id] = data.dat; } double ts = millis_since_boot(); if ((ts - prev_update_ts) > (1000.0 / settings.fps) && !processing && !new_msgs->isEmpty()) { // delay posting CAN message if UI thread is busy processing = true; prev_update_ts = ts; // use pointer to avoid data copy in queued connection. emit received(new_msgs.release()); new_msgs.reset(new QHash); new_msgs->reserve(100); } } return true; } void CANMessages::seekTo(double ts) { replay->seekTo(std::max(double(0), ts), false); counters_begin_sec = 0; emit updated(); } void CANMessages::pause(bool pause) { replay->pause(pause); emit (pause ? paused() : resume()); } void CANMessages::settingChanged() { replay->setSegmentCacheLimit(settings.cached_segment_limit); }