cabana: color bytes based on activity (#26970)

* cabana: color bytes based on activity

* newlines

* fix text color when selected

* fix indent

* add colors to binary view

* no need to check contains

* whitespace

* Apply suggestions from code review

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
old-commit-hash: 5b8d124be7
beeps
Willem Melching 2 years ago committed by GitHub
parent e95c8e665b
commit 589c7d951f
  1. 8
      tools/cabana/binaryview.cc
  2. 60
      tools/cabana/canmessages.cc
  3. 2
      tools/cabana/canmessages.h
  4. 53
      tools/cabana/messageswidget.cc
  5. 8
      tools/cabana/messageswidget.h

@ -177,7 +177,9 @@ void BinaryViewModel::setMessage(const QString &message_id) {
void BinaryViewModel::updateState() {
auto prev_items = items;
const auto &binary = can->lastMessage(msg_id).dat;
const auto &last_msg = can->lastMessage(msg_id);
const auto &binary = last_msg.dat;
// data size may changed.
if (binary.size() > row_count) {
beginInsertRows({}, row_count, binary.size() - 1);
@ -193,6 +195,7 @@ void BinaryViewModel::updateState() {
hex[0] = toHex(binary[i] >> 4);
hex[1] = toHex(binary[i] & 0xf);
items[i * column_count + 8].val = hex;
items[i * column_count + 8].bg_color = last_msg.colors[i];
}
for (int i = binary.size(); i < row_count; ++i) {
for (int j = 0; j < column_count; ++j) {
@ -201,7 +204,7 @@ void BinaryViewModel::updateState() {
}
for (int i = 0; i < row_count * column_count; ++i) {
if (i >= prev_items.size() || prev_items[i].val != items[i].val) {
if (i >= prev_items.size() || prev_items[i].val != items[i].val || prev_items[i].bg_color != items[i].bg_color) {
auto idx = index(i / column_count, i % column_count);
emit dataChanged(idx, idx);
}
@ -234,6 +237,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
if (index.column() == 8) {
painter->setFont(hex_font);
painter->fillRect(option.rect, item->bg_color);
} else if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, selection_color);
painter->setPen(option.palette.color(QPalette::BrightText));

@ -18,6 +18,10 @@ static bool event_filter(const Event *e, void *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);
@ -49,6 +53,9 @@ void CANMessages::process(QHash<QString, CanData> *messages) {
bool CANMessages::eventFilter(const Event *event) {
static std::unique_ptr new_msgs = std::make_unique<QHash<QString, CanData>>();
static QHash<QString, QByteArray> prev_dat;
static QHash<QString, QList<QColor>> colors;
static QHash<QString, QList<double>> last_change_t;
static double prev_update_ts = 0;
if (event->which == cereal::Event::Which::CAN) {
@ -71,6 +78,59 @@ bool CANMessages::eventFilter(const Event *event) {
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();

@ -4,6 +4,7 @@
#include <QColor>
#include <QHash>
#include <QApplication>
#include "opendbc/can/common_dbc.h"
#include "tools/cabana/settings.h"
@ -16,6 +17,7 @@ struct CanData {
uint32_t count = 0;
uint32_t freq = 0;
QByteArray dat;
QList<QColor> colors;
};
class CANMessages : public QObject {

@ -4,6 +4,8 @@
#include <QHeaderView>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QPainter>
#include <QApplication>
#include "tools/cabana/dbcmanager.h"
@ -21,6 +23,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
table_widget = new QTableView(this);
model = new MessageListModel(this);
table_widget->setModel(model);
table_widget->setItemDelegateForColumn(4, new MessageBytesDelegate(table_widget));
table_widget->setSelectionBehavior(QAbstractItemView::SelectRows);
table_widget->setSelectionMode(QAbstractItemView::SingleSelection);
table_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
@ -31,6 +34,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
table_widget->setColumnWidth(2, 80);
table_widget->horizontalHeader()->setStretchLastSection(true);
table_widget->verticalHeader()->hide();
main_layout->addWidget(table_widget);
// signals/slots
@ -65,9 +69,10 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
}
QVariant MessageListModel::data(const QModelIndex &index, int role) const {
const auto &id = msgs[index.row()];
auto &can_data = can->lastMessage(id);
if (role == Qt::DisplayRole) {
const auto &id = msgs[index.row()];
auto &can_data = can->lastMessage(id);
switch (index.column()) {
case 0: return msgName(id);
case 1: return id;
@ -77,6 +82,13 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
}
} else if (role == Qt::FontRole && index.column() == columnCount() - 1) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
} else if (role == Qt::UserRole && index.column() == 4) {
QList<QVariant> colors;
for (int i = 0; i < can_data.dat.size(); i++){
colors.append(can_data.colors[i]);
}
return colors;
}
return {};
}
@ -146,3 +158,40 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
sortMessages();
}
}
MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) {
}
void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QList<QVariant> colors = index.data(Qt::UserRole).toList();
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
const QFont font = index.data(Qt::FontRole).value<QFont>();
painter->setFont(font);
QRect rect = opt.rect;
QString bytes = QString(opt.text);
QRect pos = rect;
QRect space = painter->boundingRect(pos, opt.displayAlignment, " ");
pos.setX(pos.x() + space.width());
if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) {
painter->setPen(option.palette.color(QPalette::HighlightedText));
} else {
painter->setPen(option.palette.color(QPalette::Text));
}
int i = 0;
for (auto &byte : bytes.split(" ")) {
QRect sz = painter->boundingRect(pos, opt.displayAlignment, byte);
const int m = space.width() / 2;
painter->fillRect(sz.marginsAdded(QMargins(m + 1, m, m, m)), colors[i].value<QColor>());
painter->drawText(pos, opt.displayAlignment, byte);
pos.setX(pos.x() + sz.width() + space.width());
i++;
}
}

@ -2,6 +2,7 @@
#include <QAbstractTableModel>
#include <QTableView>
#include <QStyledItemDelegate>
#include "tools/cabana/canmessages.h"
@ -41,3 +42,10 @@ protected:
QString current_msg_id;
MessageListModel *model;
};
class MessageBytesDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
MessageBytesDelegate(QObject *parent);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};

Loading…
Cancel
Save