cabana: add support for load&save extra dbc info (#27203)

* support extra info

* support undo/redo

* fix undo/redo

* cleanup

* fix regexp

* refactor dbcmanager

* replace text in headerview

* fix binary::refresh

* cleanup

* use QRegularExpression

* add desc validation

* edit val description in table

* cleanup
pull/27383/head
Dean Lee 2 years ago committed by GitHub
parent ce9fd785d5
commit ac0dbf74bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      tools/cabana/SConscript
  2. 21
      tools/cabana/binaryview.cc
  3. 3
      tools/cabana/binaryview.h
  4. 8
      tools/cabana/chartswidget.cc
  5. 2
      tools/cabana/chartswidget.h
  6. 16
      tools/cabana/commands.cc
  7. 4
      tools/cabana/commands.h
  8. 218
      tools/cabana/dbcmanager.cc
  9. 56
      tools/cabana/dbcmanager.h
  10. 6
      tools/cabana/detailwidget.cc
  11. 19
      tools/cabana/historylog.cc
  12. 3
      tools/cabana/historylog.h
  13. 6
      tools/cabana/messageswidget.cc
  14. 2
      tools/cabana/messageswidget.h
  15. 122
      tools/cabana/signaledit.cc
  16. 18
      tools/cabana/signaledit.h
  17. 2
      tools/cabana/streams/replaystream.cc
  18. 1
      tools/cabana/streams/replaystream.h
  19. 19
      tools/cabana/tests/test_cabana.cc
  20. 1
      tools/cabana/tools/findsimilarbits.cc
  21. 1
      tools/cabana/tools/findsimilarbits.h
  22. 2
      tools/cabana/util.cc
  23. 6
      tools/cabana/util.h
  24. 2
      tools/cabana/videowidget.h

@ -17,6 +17,8 @@ qt_libs = ['qt_util'] + base_libs
cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs
cabana_env = qt_env.Clone()
opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc").abspath)
cabana_env['CXXFLAGS'] += [opendbc_path]
prev_moc_path = cabana_env['QT_MOCHPREFIX']
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'

@ -12,7 +12,6 @@
#include "tools/cabana/commands.h"
#include "tools/cabana/signaledit.h"
#include "tools/cabana/streams/abstractstream.h"
// BinaryView
@ -170,7 +169,7 @@ void BinaryView::highlightPosition(const QPoint &pos) {
auto item = (BinaryViewModel::Item *)index.internalPointer();
const Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back();
highlight(sig);
QToolTip::showText(pos, sig ? sig->name.c_str() : "", this, rect());
QToolTip::showText(pos, sig ? sig->name : "", this, rect());
}
}
@ -246,22 +245,22 @@ std::tuple<int, int, bool> BinaryView::getSelection(QModelIndex index) {
void BinaryViewModel::refresh() {
beginResetModel();
items.clear();
if ((dbc_msg = dbc()->msg(*msg_id))) {
if (auto dbc_msg = dbc()->msg(*msg_id)) {
row_count = dbc_msg->size;
items.resize(row_count * column_count);
for (auto sig : dbc_msg->getSignals()) {
auto [start, end] = getSignalRange(sig);
for (auto &sig : dbc_msg->sigs) {
auto [start, end] = getSignalRange(&sig);
for (int j = start; j <= end; ++j) {
int bit_index = sig->is_little_endian ? bigEndianBitIndex(j) : j;
int bit_index = sig.is_little_endian ? bigEndianBitIndex(j) : j;
int idx = column_count * (bit_index / 8) + bit_index % 8;
if (idx >= items.size()) {
qWarning() << "signal " << sig->name.c_str() << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size;
qWarning() << "signal " << sig.name << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size;
break;
}
if (j == start) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true;
if (j == end) sig->is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true;
items[idx].bg_color = getColor(sig);
items[idx].sigs.push_back(sig);
if (j == start) sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true;
if (j == end) sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true;
items[idx].bg_color = getColor(&sig);
items[idx].sigs.push_back(&sig);
}
}
} else {

@ -9,6 +9,8 @@
#include <QTableView>
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
using namespace dbcmanager;
class BinaryItemDelegate : public QStyledItemDelegate {
public:
@ -49,7 +51,6 @@ public:
std::vector<Item> items;
std::optional<MessageId> msg_id;
const DBCMsg *dbc_msg = nullptr;
int row_count = 0;
const int column_count = 9;
};

@ -459,7 +459,7 @@ void ChartView::updateTitle() {
}
for (auto &s : sigs) {
auto decoration = s.series->isVisible() ? "none" : "line-through";
s.series->setName(QString("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").arg(decoration, s.sig->name.c_str(), msgName(s.msg_id), s.msg_id.toString()));
s.series->setName(QString("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").arg(decoration, s.sig->name, msgName(s.msg_id), s.msg_id.toString()));
}
}
@ -672,7 +672,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
value = QString::number(it->y());
track_pts[i] = chart()->mapToPosition(*it);
}
text_list.push_back(QString("<span style=\"color:%1;\">■ </span>%2: <b>%3</b>").arg(sigs[i].series->color().name(), sigs[i].sig->name.c_str(), value));
text_list.push_back(QString("<span style=\"color:%1;\">■ </span>%2: <b>%3</b>").arg(sigs[i].series->color().name(), sigs[i].sig->name, value));
}
auto max = std::max_element(track_pts.begin(), track_pts.end(), [](auto &a, auto &b) { return a.x() < b.x(); });
auto pt = (max == track_pts.end()) ? ev->pos() : *max;
@ -887,7 +887,7 @@ void SeriesSelector::updateAvailableList(int index) {
available_list->clear();
MessageId msg_id = msgs_combo->itemData(index).value<MessageId>();
auto selected_items = seletedItems();
for (auto &[name, s] : dbc()->msg(msg_id)->sigs) {
for (auto &s : dbc()->msg(msg_id)->sigs) {
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; });
if (!is_selected) {
addItemToList(available_list, msg_id, &s);
@ -896,7 +896,7 @@ void SeriesSelector::updateAvailableList(int index) {
}
void SeriesSelector::addItemToList(QListWidget *parent, const MessageId id, const Signal *sig, bool show_msg_name) {
QString text = QString("<span style=\"color:%0;\">■ </span> %1").arg(getColor(sig).name(), sig->name.c_str());
QString text = QString("<span style=\"color:%0;\">■ </span> %1").arg(getColor(sig).name(), sig->name);
if (show_msg_name) text += QString(" <font color=\"gray\">%0 %1</font>").arg(msgName(id), id.toString());
QLabel *label = new QLabel(text);

@ -15,7 +15,7 @@
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
using namespace dbcmanager;
using namespace QtCharts;
const int CHART_MIN_WIDTH = 300;

@ -36,7 +36,7 @@ RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) :
void RemoveMsgCommand::undo() {
if (!message.name.isEmpty()) {
dbc()->updateMsg(id, message.name, message.size);
for (auto &[name, s] : message.sigs)
for (auto &s : message.sigs)
dbc()->addSignal(id, s);
}
}
@ -50,31 +50,31 @@ void RemoveMsgCommand::redo() {
AddSigCommand::AddSigCommand(const MessageId &id, const Signal &sig, QUndoCommand *parent)
: id(id), signal(sig), QUndoCommand(parent) {
setText(QObject::tr("Add signal %1 to %2").arg(sig.name.c_str()).arg(id.address));
setText(QObject::tr("Add signal %1 to %2").arg(sig.name).arg(id.address));
}
void AddSigCommand::undo() { dbc()->removeSignal(id, signal.name.c_str()); }
void AddSigCommand::undo() { dbc()->removeSignal(id, signal.name); }
void AddSigCommand::redo() { dbc()->addSignal(id, signal); }
// RemoveSigCommand
RemoveSigCommand::RemoveSigCommand(const MessageId &id, const Signal *sig, QUndoCommand *parent)
: id(id), signal(*sig), QUndoCommand(parent) {
setText(QObject::tr("Remove signal %1 from %2").arg(signal.name.c_str()).arg(id.address));
setText(QObject::tr("Remove signal %1 from %2").arg(signal.name).arg(id.address));
}
void RemoveSigCommand::undo() { dbc()->addSignal(id, signal); }
void RemoveSigCommand::redo() { dbc()->removeSignal(id, signal.name.c_str()); }
void RemoveSigCommand::redo() { dbc()->removeSignal(id, signal.name); }
// EditSignalCommand
EditSignalCommand::EditSignalCommand(const MessageId &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent)
: id(id), old_signal(*sig), new_signal(new_sig), QUndoCommand(parent) {
setText(QObject::tr("Edit signal %1").arg(old_signal.name.c_str()));
setText(QObject::tr("Edit signal %1").arg(old_signal.name));
}
void EditSignalCommand::undo() { dbc()->updateSignal(id, new_signal.name.c_str(), old_signal); }
void EditSignalCommand::redo() { dbc()->updateSignal(id, old_signal.name.c_str(), new_signal); }
void EditSignalCommand::undo() { dbc()->updateSignal(id, new_signal.name, old_signal); }
void EditSignalCommand::redo() { dbc()->updateSignal(id, old_signal.name, new_signal); }
namespace UndoStack {

@ -4,6 +4,8 @@
#include <QUndoStack>
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
using namespace dbcmanager;
class EditMsgCommand : public QUndoCommand {
public:
@ -25,7 +27,7 @@ public:
private:
const MessageId id;
DBCMsg message;
Msg message;
};
class AddSigCommand : public QUndoCommand {

@ -1,63 +1,114 @@
#include "tools/cabana/dbcmanager.h"
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
#include <QTextStream>
#include <QVector>
#include <limits>
#include <sstream>
#include <QVector>
uint qHash(const MessageId &item) {
return qHash(item.source) ^ qHash(item.address);
}
namespace dbcmanager {
DBCManager::DBCManager(QObject *parent) : QObject(parent) {}
DBCManager::~DBCManager() {}
void DBCManager::open(const QString &dbc_file_name) {
dbc = const_cast<DBC *>(dbc_lookup(dbc_file_name.toStdString()));
initMsgMap();
void sortSignalsByAddress(QList<Signal> &sigs) {
std::sort(sigs.begin(), sigs.end(), [](auto &a, auto &b) { return a.start_bit < b.start_bit; });
}
bool DBCManager::open(const QString &name, const QString &content, QString *error) {
try {
std::istringstream stream(content.toStdString());
dbc = const_cast<DBC *>(dbc_parse_from_stream(name.toStdString(), stream));
initMsgMap();
return true;
} catch (std::exception &e) {
if (error) *error = e.what();
bool DBCManager::open(const QString &dbc_file_name, QString *error) {
QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, dbc_file_name);
QFile file(opendbc_file_path);
if (file.open(QIODevice::ReadOnly)) {
return open(dbc_file_name, file.readAll(), error);
}
return false;
}
void DBCManager::initMsgMap() {
msgs.clear();
for (auto &msg : dbc->msgs) {
auto &m = msgs[msg.address];
m.name = msg.name.c_str();
m.size = msg.size;
for (auto &s : msg.sigs)
m.sigs[QString::fromStdString(s.name)] = s;
void DBCManager::parseExtraInfo(const QString &content) {
static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))");
static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"(.*)\";)");
static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (.*);)");
auto get_sig = [this](uint32_t address, const QString &name) -> Signal * {
auto m = (Msg *)msg(address);
return m ? (Signal *)m->sig(name) : nullptr;
};
QTextStream stream((QString *)&content);
uint32_t address = 0;
while (!stream.atEnd()) {
QString line = stream.readLine().trimmed();
if (line.startsWith("BO_ ")) {
if (auto match = bo_regexp.match(line); match.hasMatch()) {
address = match.captured(1).toUInt();
}
} else if (line.startsWith("SG_ ")) {
int offset = 0;
auto match = sg_regexp.match(line);
if (!match.hasMatch()) {
match = sgm_regexp.match(line);
offset = 1;
}
if (match.hasMatch()) {
if (auto s = get_sig(address, match.captured(1))) {
s->min = match.captured(8 + offset);
s->max = match.captured(9 + offset);
s->unit = match.captured(10 + offset);
}
}
} else if (line.startsWith("VAL_ ")) {
if (auto match = val_regexp.match(line); match.hasMatch()) {
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
QStringList desc_list = match.captured(3).trimmed().split('"');
for (int i = 0; i < desc_list.size(); i += 2) {
auto val = desc_list[i].trimmed();
if (!val.isEmpty() && (i + 1) < desc_list.size()) {
auto desc = desc_list[i+1].trimmed();
s->val_desc.push_back({val, desc});
}
}
}
}
} else if (line.startsWith("CM_ SG_ ")) {
if (auto match = sg_comment_regexp.match(line); match.hasMatch()) {
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
s->comment = match.captured(3).trimmed();
}
}
}
}
emit DBCFileChanged();
}
QString DBCManager::generateDBC() {
QString dbc_string;
QString dbc_string, signal_comment, val_desc;
for (auto &[address, m] : msgs) {
dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(address).arg(m.name).arg(m.size);
for (auto &[name, sig] : m.sigs) {
dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [0|0] \"\" XXX\n")
.arg(name)
for (auto &sig : m.sigs) {
dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [%8|%9] \"%10\" XXX\n")
.arg(sig.name)
.arg(sig.start_bit)
.arg(sig.size)
.arg(sig.is_little_endian ? '1' : '0')
.arg(sig.is_signed ? '-' : '+')
.arg(sig.factor, 0, 'g', std::numeric_limits<double>::digits10)
.arg(sig.offset, 0, 'g', std::numeric_limits<double>::digits10);
.arg(sig.offset, 0, 'g', std::numeric_limits<double>::digits10)
.arg(sig.min)
.arg(sig.max)
.arg(sig.unit);
if (!sig.comment.isEmpty()) {
signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig.name).arg(sig.comment);
}
if (!sig.val_desc.isEmpty()) {
QString text;
for (auto &[val, desc] : sig.val_desc) {
text += QString("%1 \"%2\"").arg(val, desc);
}
val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig.name).arg(text);
}
}
dbc_string += "\n";
}
return dbc_string;
return dbc_string + signal_comment + val_desc;
}
void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) {
@ -73,31 +124,29 @@ void DBCManager::removeMsg(const MessageId &id) {
}
void DBCManager::addSignal(const MessageId &id, const Signal &sig) {
if (auto m = const_cast<DBCMsg *>(msg(id.address))) {
auto &s = m->sigs[sig.name.c_str()];
s = sig;
emit signalAdded(id.address, &s);
if (auto m = const_cast<Msg *>(msg(id.address))) {
m->sigs.push_back(sig);
auto s = &m->sigs.last();
sortSignalsByAddress(m->sigs);
emit signalAdded(id.address, s);
}
}
void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const Signal &sig) {
if (auto m = const_cast<DBCMsg *>(msg(id))) {
// change key name
QString new_name = QString::fromStdString(sig.name);
auto node = m->sigs.extract(sig_name);
node.key() = new_name;
auto it = m->sigs.insert(std::move(node));
auto &s = m->sigs[new_name];
s = sig;
emit signalUpdated(&s);
if (auto m = const_cast<Msg *>(msg(id))) {
if (auto s = (Signal *)m->sig(sig_name)) {
*s = sig;
sortSignalsByAddress(m->sigs);
emit signalUpdated(s);
}
}
}
void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) {
if (auto m = const_cast<DBCMsg *>(msg(id))) {
auto it = m->sigs.find(sig_name);
if (auto m = const_cast<Msg *>(msg(id))) {
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return s.name == sig_name; });
if (it != m->sigs.end()) {
emit signalRemoved(&(it->second));
emit signalRemoved(&(*it));
m->sigs.erase(it);
}
}
@ -108,16 +157,6 @@ DBCManager *dbc() {
return &dbc_manager;
}
// DBCMsg
std::vector<const Signal*> DBCMsg::getSignals() const {
std::vector<const Signal*> ret;
ret.reserve(sigs.size());
for (auto &[_, sig] : sigs) ret.push_back(&sig);
std::sort(ret.begin(), ret.end(), [](auto l, auto r) { return l->start_bit < r->start_bit; });
return ret;
}
// helper functions
static QVector<int> BIG_ENDIAN_START_BITS = []() {
@ -128,13 +167,8 @@ static QVector<int> BIG_ENDIAN_START_BITS = []() {
return ret;
}();
int bigEndianStartBitsIndex(int start_bit) {
return BIG_ENDIAN_START_BITS[start_bit];
}
int bigEndianBitIndex(int index) {
return BIG_ENDIAN_START_BITS.indexOf(index);
}
int bigEndianStartBitsIndex(int start_bit) { return BIG_ENDIAN_START_BITS[start_bit]; }
int bigEndianBitIndex(int index) { return BIG_ENDIAN_START_BITS.indexOf(index); }
double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) {
int64_t val = 0;
@ -155,8 +189,7 @@ double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) {
if (sig.is_signed) {
val -= ((val >> (sig.size - 1)) & 0x1) ? (1ULL << sig.size) : 0;
}
double value = val * sig.factor + sig.offset;
return value;
return val * sig.factor + sig.offset;
}
void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size) {
@ -182,5 +215,50 @@ bool operator==(const Signal &l, const Signal &r) {
l.start_bit == r.start_bit &&
l.msb == r.msb && l.lsb == r.lsb &&
l.is_signed == r.is_signed && l.is_little_endian == r.is_little_endian &&
l.factor == r.factor && l.offset == r.offset;
l.factor == r.factor && l.offset == r.offset &&
l.min == r.min && l.max == r.max && l.comment == r.comment && l.unit == r.unit && l.val_desc == r.val_desc;
}
} // namespace dbcmanager
#include "opendbc/can/common_dbc.h"
std::vector<std::string> dbcmanager::DBCManager::allDBCNames() { return get_dbc_names(); }
bool dbcmanager::DBCManager::open(const QString &name, const QString &content, QString *error) {
try {
std::istringstream stream(content.toStdString());
auto dbc = const_cast<DBC *>(dbc_parse_from_stream(name.toStdString(), stream));
msgs.clear();
for (auto &msg : dbc->msgs) {
auto &m = msgs[msg.address];
m.name = msg.name.c_str();
m.size = msg.size;
for (auto &s : msg.sigs) {
m.sigs.push_back({});
auto &sig = m.sigs.last();
sig.name = s.name.c_str();
sig.start_bit = s.start_bit;
sig.msb = s.msb;
sig.lsb = s.lsb;
sig.size = s.size;
sig.is_signed = s.is_signed;
sig.factor = s.factor;
sig.offset = s.offset;
sig.is_little_endian = s.is_little_endian;
}
sortSignalsByAddress(m.sigs);
}
parseExtraInfo(content);
name_ = name;
emit DBCFileChanged();
delete dbc;
} catch (std::exception &e) {
if (error) *error = e.what();
return false;
}
return true;
}
uint qHash(const MessageId &item) {
return qHash(item.source) ^ qHash(item.address);
}

@ -1,9 +1,8 @@
#pragma once
#include <map>
#include <QObject>
#include <QList>
#include <QString>
#include "opendbc/can/common_dbc.h"
struct MessageId {
uint8_t source;
@ -30,40 +29,55 @@ struct MessageId {
}
};
uint qHash(const MessageId &item);
Q_DECLARE_METATYPE(MessageId);
uint qHash(const MessageId &item);
namespace dbcmanager {
typedef QList<std::pair<QString, QString>> ValueDescription;
struct Signal {
QString name;
int start_bit, msb, lsb, size;
bool is_signed;
double factor, offset;
bool is_little_endian;
QString min, max, unit;
QString comment;
ValueDescription val_desc;
};
struct DBCMsg {
struct Msg {
QString name;
uint32_t size;
// signal must be saved as value in map to make undo stack work properly.
std::map<QString, Signal> sigs;
// return vector<signals>, sort by start_bits
std::vector<const Signal*> getSignals() const;
QList<Signal> sigs;
const Signal *sig(const QString &sig_name) const {
auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.name == sig_name; });
return it != sigs.end() ? &(*it) : nullptr;
}
};
class DBCManager : public QObject {
Q_OBJECT
public:
DBCManager(QObject *parent);
~DBCManager();
void open(const QString &dbc_file_name);
DBCManager(QObject *parent) {}
~DBCManager() {}
bool open(const QString &dbc_file_name, QString *error = nullptr);
bool open(const QString &name, const QString &content, QString *error = nullptr);
QString generateDBC();
void addSignal(const MessageId &id, const Signal &sig);
void updateSignal(const MessageId &id, const QString &sig_name, const Signal &sig);
void removeSignal(const MessageId &id, const QString &sig_name);
inline static std::vector<std::string> allDBCNames() { return get_dbc_names(); }
inline QString name() const { return dbc ? dbc->name.c_str() : ""; }
static std::vector<std::string> allDBCNames();
inline QString name() const { return name_; }
void updateMsg(const MessageId &id, const QString &name, uint32_t size);
void removeMsg(const MessageId &id);
inline const std::map<uint32_t, DBCMsg> &messages() const { return msgs; }
inline const DBCMsg *msg(const MessageId &id) const { return msg(id.address); }
inline const DBCMsg *msg(uint32_t address) const {
inline const std::map<uint32_t, Msg> &messages() const { return msgs; }
inline const Msg *msg(const MessageId &id) const { return msg(id.address); }
inline const Msg *msg(uint32_t address) const {
auto it = msgs.find(address);
return it != msgs.end() ? &it->second : nullptr;
}
@ -77,9 +91,9 @@ signals:
void DBCFileChanged();
private:
void initMsgMap();
DBC *dbc = nullptr;
std::map<uint32_t, DBCMsg> msgs;
void parseExtraInfo(const QString &content);
std::map<uint32_t, Msg> msgs;
QString name_;
};
const QString UNTITLED = "untitled";
@ -97,3 +111,5 @@ inline QString msgName(const MessageId &id) {
auto msg = dbc()->msg(id);
return msg ? msg->name : UNTITLED;
}
} // namespace dbcmanager

@ -6,8 +6,6 @@
#include <QToolButton>
#include "tools/cabana/commands.h"
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
// DetailWidget
@ -153,13 +151,13 @@ void DetailWidget::refresh() {
if (!msg_id) return;
QStringList warnings;
const DBCMsg *msg = dbc()->msg(*msg_id);
auto msg = dbc()->msg(*msg_id);
if (msg) {
if (msg->size != can->lastMessage(*msg_id).dat.size()) {
warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size));
}
for (auto s : binary_view->getOverlappingSignals()) {
warnings.push_back(tr("%1 has overlapping bits.").arg(s->name.c_str()));
warnings.push_back(tr("%1 has overlapping bits.").arg(s->name));
}
} else {
warnings.push_back(tr("Drag-Select in binary view to create new signal."));

@ -5,6 +5,7 @@
#include <QVBoxLayout>
#include "tools/cabana/commands.h"
#include "tools/cabana/util.h"
// HistoryLogModel
@ -30,7 +31,7 @@ void HistoryLogModel::refresh() {
beginResetModel();
sigs.clear();
if (auto dbc_msg = dbc()->msg(*msg_id)) {
sigs = dbc_msg->getSignals();
sigs = dbc_msg->sigs;
}
last_fetch_time = 0;
has_more_data = true;
@ -47,9 +48,9 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i
if (section == 0) {
return "Time";
}
return show_signals ? QString::fromStdString(sigs[section - 1]->name).replace('_', ' ') : "Data";
return show_signals ? sigs[section - 1].name : "Data";
} else if (role == Qt::BackgroundRole && section > 0 && show_signals) {
return QBrush(getColor(sigs[section - 1]));
return QBrush(getColor(&sigs[section - 1]));
}
}
return {};
@ -113,7 +114,7 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, I
if (msg_id->address == c.getAddress() && msg_id->source == c.getSrc()) {
const auto dat = c.getDat();
for (int i = 0; i < sigs.size(); ++i) {
values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), *(sigs[i]));
values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), sigs[i]);
}
if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) {
auto &m = msgs.emplace_back();
@ -170,8 +171,8 @@ QSize HeaderView::sectionSizeFromContents(int logicalIndex) const {
return time_col_size;
} else {
int default_size = qMax(100, (rect().width() - time_col_size.width()) / (model()->columnCount() - 1));
const QString text = model()->headerData(logicalIndex, this->orientation(), Qt::DisplayRole).toString();
const QRect rect = fontMetrics().boundingRect({0, 0, default_size, 2000}, defaultAlignment(), text);
QString text = model()->headerData(logicalIndex, this->orientation(), Qt::DisplayRole).toString();
const QRect rect = fontMetrics().boundingRect({0, 0, default_size, 2000}, defaultAlignment(), text.replace(QChar('_'), ' '));
QSize size = rect.size() + QSize{10, 6};
return QSize{qMax(size.width(), default_size), size.height()};
}
@ -183,7 +184,7 @@ void HeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalI
painter->fillRect(rect, bg_role.value<QBrush>());
}
QString text = model()->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
painter->drawText(rect.adjusted(5, 3, -5, -3), defaultAlignment(), text);
painter->drawText(rect.adjusted(5, 3, -5, -3), defaultAlignment(), text.replace(QChar('_'), ' '));
}
// LogsWidget
@ -253,8 +254,8 @@ void LogsWidget::refresh() {
bool has_signal = model->sigs.size();
if (has_signal) {
signals_cb->clear();
for (auto s : model->sigs) {
signals_cb->addItem(s->name.c_str());
for (auto &s : model->sigs) {
signals_cb->addItem(s.name);
}
}
value_edit->clear();

@ -11,6 +11,7 @@
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
using namespace dbcmanager;
class HeaderView : public QHeaderView {
public:
@ -63,7 +64,7 @@ public:
uint64_t last_fetch_time = 0;
std::function<bool(double, double)> filter_cmp = nullptr;
std::deque<Message> messages;
std::vector<const Signal*> sigs;
QList<Signal> sigs;
bool dynamic_mode = true;
bool display_signals_mode = true;
};

@ -7,8 +7,6 @@
#include <QPushButton>
#include <QVBoxLayout>
#include "tools/cabana/dbcmanager.h"
MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
@ -140,8 +138,8 @@ void MessageListModel::setFilterString(const QString &string) {
if (id.toString().contains(txt, cs) || msgName(id).contains(txt, cs)) return true;
// Search by signal name
if (const auto msg = dbc()->msg(id)) {
for (auto &signal : msg->getSignals()) {
if (QString::fromStdString(signal->name).contains(txt, cs)) return true;
for (auto &signal : msg->sigs) {
if (signal.name.contains(txt, cs)) return true;
}
}
return false;

@ -9,7 +9,9 @@
#include <QStyledItemDelegate>
#include <QTableView>
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
using namespace dbcmanager;
class MessageListModel : public QAbstractTableModel {
Q_OBJECT

@ -1,5 +1,6 @@
#include "tools/cabana/signaledit.h"
#include <QDialogButtonBox>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QHeaderView>
@ -23,9 +24,9 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p
}
void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const Signal *sig) {
Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name.c_str(), .type = Item::Sig};
Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig};
parent_item->children.insert(pos, item);
QString titles[]{"Name", "Size", "Little Endian", "Signed", "Offset", "Factor", "Extra Info", "Unit", "Comment", "Minimum", "Maximum", "Description"};
QString titles[]{"Name", "Size", "Little Endian", "Signed", "Offset", "Factor", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"};
for (int i = 0; i < std::size(titles); ++i) {
item->children.push_back(new Item{.sig = sig, .parent = item, .title = titles[i], .type = (Item::Type)(i + Item::Name)});
}
@ -47,9 +48,9 @@ void SignalModel::refresh() {
beginResetModel();
root.reset(new SignalModel::Item);
if (auto msg = dbc()->msg(msg_id)) {
for (auto &s : msg->getSignals()) {
if (filter_str.isEmpty() || QString::fromStdString(s->name).contains(filter_str, Qt::CaseInsensitive)) {
insertItem(root.get(), root->children.size(), s);
for (auto &s : msg->sigs) {
if (filter_str.isEmpty() || s.name.contains(filter_str, Qt::CaseInsensitive)) {
insertItem(root.get(), root->children.size(), &s);
}
}
}
@ -115,14 +116,25 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const {
const Item *item = getItem(index);
if (role == Qt::DisplayRole || role == Qt::EditRole) {
if (index.column() == 0) {
return item->type == Item::Sig ? QString::fromStdString(item->sig->name) : item->title;
return item->type == Item::Sig ? item->sig->name : item->title;
} else {
switch (item->type) {
case Item::Sig: return item->sig_val;
case Item::Name: return QString::fromStdString(item->sig->name);
case Item::Name: return item->sig->name;
case Item::Size: return item->sig->size;
case Item::Offset: return QString::number(item->sig->offset, 'f', 6);
case Item::Factor: return QString::number(item->sig->factor, 'f', 6);
case Item::Unit: return item->sig->unit;
case Item::Comment: return item->sig->comment;
case Item::Min: return item->sig->min;
case Item::Max: return item->sig->max;
case Item::Desc: {
QString val_desc;
for (auto &[val, desc] : item->sig->val_desc) {
val_desc += QString("%1 \"%2\"").arg(val, desc);
}
return val_desc;
}
default: break;
}
}
@ -142,12 +154,17 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r
Item *item = getItem(index);
Signal s = *item->sig;
switch (item->type) {
case Item::Name: s.name = value.toString().toStdString(); break;
case Item::Name: s.name = value.toString(); break;
case Item::Size: s.size = value.toInt(); break;
case Item::Endian: s.is_little_endian = value.toBool(); break;
case Item::Signed: s.is_signed = value.toBool(); break;
case Item::Offset: s.offset = value.toDouble(); break;
case Item::Factor: s.factor = value.toDouble(); break;
case Item::Unit: s.unit = value.toString(); break;
case Item::Comment: s.comment = value.toString(); break;
case Item::Min: s.min = value.toString(); break;
case Item::Max: s.max = value.toString(); break;
case Item::Desc: s.val_desc = value.value<ValueDescription>(); break;
default: return false;
}
bool ret = saveSignal(item->sig, s);
@ -172,8 +189,8 @@ void SignalModel::showExtraInfo(const QModelIndex &index) {
bool SignalModel::saveSignal(const Signal *origin_s, Signal &s) {
auto msg = dbc()->msg(msg_id);
if (s.name != origin_s->name && msg->sigs.count(s.name.c_str()) != 0) {
QString text = tr("There is already a signal with the same name '%1'").arg(s.name.c_str());
if (s.name != origin_s->name && msg->sig(s.name) != nullptr) {
QString text = tr("There is already a signal with the same name '%1'").arg(s.name);
QMessageBox::warning(nullptr, tr("Failed to save signal"), text);
return false;
}
@ -212,8 +229,8 @@ void SignalModel::addSignal(int start_bit, int size, bool little_endian) {
Signal sig = {.is_little_endian = little_endian, .factor = 1};
for (int i = 1; /**/; ++i) {
sig.name = "NEW_SIGNAL_" + std::to_string(i);
if (msg->sigs.count(sig.name.c_str()) == 0) break;
sig.name = QString("NEW_SIGNAL_%1").arg(i);
if (msg->sig(sig.name) == nullptr) break;
}
updateSigSizeParamsFromRange(sig, start_bit, size);
UndoStack::push(new AddSigCommand(msg_id, sig));
@ -266,8 +283,8 @@ void SignalModel::handleSignalRemoved(const Signal *sig) {
SignalItemDelegate::SignalItemDelegate(QObject *parent) {
name_validator = new NameValidator(this);
double_validator = new QDoubleValidator(this);
small_font.setPointSize(8);
double_validator->setLocale(QLocale::C); // Match locale of QString::toDouble() instead of system
small_font.setPointSize(8);
}
void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
@ -304,7 +321,8 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto item = (SignalModel::Item *)index.internalPointer();
if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset || item->type == SignalModel::Item::Factor) {
if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset ||
item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) {
QLineEdit *e = new QLineEdit(parent);
e->setFrame(false);
e->setValidator(index.row() == 0 ? name_validator : double_validator);
@ -314,6 +332,13 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie
spin->setFrame(false);
spin->setRange(1, 64);
return spin;
} else if (item->type == SignalModel::Item::Desc) {
ValueDescriptionDlg dlg(item->sig->val_desc, parent);
dlg.setWindowTitle(item->sig->name);
if (dlg.exec()) {
((QAbstractItemModel *)index.model())->setData(index, QVariant::fromValue(dlg.val_desc));
}
return nullptr;
}
return QStyledItemDelegate::createEditor(parent, option, index);
}
@ -437,7 +462,7 @@ void SignalView::expandSignal(const Signal *sig) {
void SignalView::updateChartState() {
int i = 0;
for (auto item : model->root->children) {
auto plot_btn = tree->indexWidget(model->index(i, 1))->findChildren<QToolButton*>()[0];
auto plot_btn = tree->indexWidget(model->index(i, 1))->findChildren<QToolButton *>()[0];
bool chart_opened = charts->hasSignal(msg_id, item->sig);
plot_btn->setChecked(chart_opened);
plot_btn->setToolTip(chart_opened ? tr("Close Plot") : tr("Show Plot\nSHIFT click to add to previous opened plot"));
@ -459,3 +484,70 @@ void SignalView::leaveEvent(QEvent *event) {
emit highlight(nullptr);
QWidget::leaveEvent(event);
}
// ValueDescriptionDlg
ValueDescriptionDlg::ValueDescriptionDlg(const ValueDescription &descriptions, QWidget *parent) : QDialog(parent) {
QHBoxLayout *toolbar_layout = new QHBoxLayout();
QPushButton *add = new QPushButton(utils::icon("plus"), "");
QPushButton *remove = new QPushButton(utils::icon("dash"), "");
remove->setEnabled(false);
toolbar_layout->addWidget(add);
toolbar_layout->addWidget(remove);
toolbar_layout->addStretch(0);
table = new QTableWidget(descriptions.size(), 2, this);
table->setItemDelegate(new Delegate(this));
table->setHorizontalHeaderLabels({"Value", "Description"});
table->horizontalHeader()->setStretchLastSection(true);
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->setSelectionMode(QAbstractItemView::SingleSelection);
table->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
int row = 0;
for (auto &[val, desc] : descriptions) {
table->setItem(row, 0, new QTableWidgetItem(val));
table->setItem(row, 1, new QTableWidgetItem(desc));
++row;
}
auto btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->addLayout(toolbar_layout);
main_layout->addWidget(table);
main_layout->addWidget(btn_box);
setMinimumWidth(500);
QObject::connect(btn_box, &QDialogButtonBox::accepted, this, &ValueDescriptionDlg::save);
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
QObject::connect(add, &QPushButton::clicked, [this]() {
table->setRowCount(table->rowCount() + 1);
table->setItem(table->rowCount() - 1, 0, new QTableWidgetItem);
table->setItem(table->rowCount() - 1, 1, new QTableWidgetItem);
});
QObject::connect(remove, &QPushButton::clicked, [this]() { table->removeRow(table->currentRow()); });
QObject::connect(table, &QTableWidget::itemSelectionChanged, [=]() {
remove->setEnabled(table->currentRow() != -1);
});
}
void ValueDescriptionDlg::save() {
for (int i = 0; i < table->rowCount(); ++i) {
QString val = table->item(i, 0)->text().trimmed();
QString desc = table->item(i, 1)->text().trimmed();
if (!val.isEmpty() && !desc.isEmpty()) {
val_desc.push_back({val, desc});
}
}
QDialog::accept();
}
QWidget *ValueDescriptionDlg::Delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QLineEdit *edit = new QLineEdit(parent);
edit->setFrame(false);
if (index.column() == 0) {
edit->setValidator(new QIntValidator(edit));
}
return edit;
}

@ -4,11 +4,10 @@
#include <QLabel>
#include <QLineEdit>
#include <QStyledItemDelegate>
#include <QTableWidget>
#include <QTreeView>
#include "tools/cabana/chartswidget.h"
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
class SignalModel : public QAbstractItemModel {
Q_OBJECT
@ -62,6 +61,21 @@ private:
friend class SignalView;
};
class ValueDescriptionDlg : public QDialog {
public:
ValueDescriptionDlg(const ValueDescription &descriptions, QWidget *parent);
ValueDescription val_desc;
private:
struct Delegate : public QStyledItemDelegate {
Delegate(QWidget *parent) : QStyledItemDelegate(parent) {}
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};
void save();
QTableWidget *table;
};
class SignalItemDelegate : public QStyledItemDelegate {
public:
SignalItemDelegate(QObject *parent);

@ -1,7 +1,5 @@
#include "tools/cabana/streams/replaystream.h"
#include "tools/cabana/dbcmanager.h"
ReplayStream::ReplayStream(uint32_t replay_flags, QObject *parent) : replay_flags(replay_flags), AbstractStream(parent, false) {
QObject::connect(&settings, &Settings::changed, [this]() {
if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes);

@ -1,6 +1,5 @@
#pragma once
#include "opendbc/can/common_dbc.h"
#include "tools/cabana/streams/abstractstream.h"
#include "tools/cabana/settings.h"

@ -2,8 +2,10 @@
#include "opendbc/can/common.h"
#undef INFO
#include "catch2/catch.hpp"
#include "tools/cabana/dbcmanager.h"
#include "tools/replay/logreader.h"
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
using namespace dbcmanager;
// demo route, first segment
const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/4cf7a6ad03080c90/2021-09-29--13-46-36/0/rlog.bz2";
@ -18,12 +20,13 @@ TEST_CASE("DBCManager::generateDBC") {
auto &new_msgs = dbc_from_generated.messages();
REQUIRE(msgs.size() == new_msgs.size());
for (auto &[address, m] : msgs) {
auto new_m = new_msgs.at(address);
auto &new_m = new_msgs.at(address);
REQUIRE(m.name == new_m.name);
REQUIRE(m.size == new_m.size);
REQUIRE(m.sigs.size() == new_m.sigs.size());
for (auto &[name, sig] : m.sigs)
REQUIRE(sig == new_m.sigs[name]);
for (int i = 0; i < m.sigs.size(); ++i) {
REQUIRE(m.sigs[i] == new_m.sigs[i]);
}
}
}
@ -37,13 +40,13 @@ TEST_CASE("Parse can messages") {
REQUIRE(log.events.size() > 0);
for (auto e : log.events) {
if (e->which == cereal::Event::Which::CAN) {
std::map<std::pair<uint32_t, std::string>, std::vector<double>> values_1;
std::map<std::pair<uint32_t, QString>, std::vector<double>> values_1;
for (const auto &c : e->event.getCan()) {
const auto msg = dbc.msg(c.getAddress());
if (c.getSrc() == 0 && msg) {
for (auto &[name, sig] : msg->sigs) {
for (auto &sig : msg->sigs) {
double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), sig);
values_1[{c.getAddress(), name.toStdString()}].push_back(val);
values_1[{c.getAddress(), sig.name}].push_back(val);
}
}
}
@ -53,7 +56,7 @@ TEST_CASE("Parse can messages") {
for (auto &[key, v1] : values_1) {
bool found = false;
for (auto &v2 : values_2) {
if (v2.address == key.first && v2.name == key.second) {
if (v2.address == key.first && key.second == v2.name.c_str()) {
REQUIRE(v2.all_values.size() == v1.size());
REQUIRE(v2.all_values == v1);
found = true;

@ -9,6 +9,7 @@
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
using namespace dbcmanager;
FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::WindowFlags() | Qt::Window) {
setWindowTitle(tr("Find similar bits"));

@ -7,6 +7,7 @@
#include <QTableWidget>
#include "tools/cabana/dbcmanager.h"
using namespace dbcmanager;
class FindSimilarBitsDlg : public QDialog {
Q_OBJECT

@ -106,7 +106,7 @@ QColor getColor(const Signal *sig) {
float h = 19 * (float)sig->lsb / 64.0;
h = fmod(h, 1.0);
size_t hash = qHash(QString::fromStdString(sig->name));
size_t hash = qHash(sig->name);
float s = 0.25 + 0.25 * (float)(hash & 0xff) / 255.0;
float v = 0.75 + 0.25 * (float)((hash >> 8) & 0xff) / 255.0;

@ -9,7 +9,9 @@
#include <QStyledItemDelegate>
#include <QVector>
#include "opendbc/can/common_dbc.h"
#include "tools/cabana/dbcmanager.h"
using namespace dbcmanager;
class ChangeTracker {
public:
@ -37,7 +39,7 @@ public:
inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); }
inline char toHex(uint value) { return "0123456789ABCDEF"[value & 0xF]; }
QColor getColor(const Signal *sig);
QColor getColor(const dbcmanager::Signal *sig);
class NameValidator : public QRegExpValidator {
Q_OBJECT

@ -11,7 +11,9 @@
#include "selfdrive/ui/qt/widgets/cameraview.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h"
using namespace dbcmanager;
class Slider : public QSlider {
Q_OBJECT

Loading…
Cancel
Save