cabana: add support for multiple DBC files (#27435)

* split out dbc file logic into separate file

* emit dbc file changed

* change to list of dbc files

* handle missing DBC file

* dbcmanager emits signal

* close existing files before loading new one

* add placeholder open for bus menu

* implement open dbc for bus

* emit signals relevant for current dbc

* move logic into findDBC

* fix use after free

* show currently loaded names in load dialog

* enable/disable load save menus

* handle saving

* add save as to recents

* handle file already open

* do not assert

* fix return and throw on file open error

* fix test maybe

* remove todo

* typo

* fix new dbc

* fix loading fingerprint on macos

* handle replacing a currently open dbc

* fix reference and handle exception

* fix indendation
old-commit-hash: 4ab2131ec9
beeps
Willem Melching 2 years ago committed by GitHub
parent c08e6fe35e
commit e0ba3902df
  1. 2
      tools/cabana/SConscript
  2. 283
      tools/cabana/dbcfile.cc
  3. 55
      tools/cabana/dbcfile.h
  4. 289
      tools/cabana/dbcmanager.cc
  5. 59
      tools/cabana/dbcmanager.h
  6. 176
      tools/cabana/mainwin.cc
  7. 18
      tools/cabana/mainwin.h
  8. 4
      tools/cabana/streams/abstractstream.h
  9. 15
      tools/cabana/tests/test_cabana.cc
  10. 2
      tools/cabana/tools/findsimilarbits.cc

@ -28,7 +28,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset
prev_moc_path = cabana_env['QT_MOCHPREFIX']
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbc.cc', 'dbcmanager.cc',
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbc.cc', 'dbcfile.cc', 'dbcmanager.cc',
'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)

@ -0,0 +1,283 @@
#include "tools/cabana/dbcfile.h"
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include <QTextStream>
#include <QVector>
#include <limits>
#include <sstream>
DBCFile::DBCFile(const QString &dbc_file_name, QObject *parent) : QObject(parent) {
QFile file(dbc_file_name);
if (file.open(QIODevice::ReadOnly)) {
name_ = QFileInfo(dbc_file_name).baseName();
// Remove auto save file extension
if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) {
filename = dbc_file_name.left(dbc_file_name.length() - AUTO_SAVE_EXTENSION.length());
} else {
filename = dbc_file_name;
}
open(file.readAll());
} else {
throw std::runtime_error("Failed to open file.");
}
}
DBCFile::DBCFile(const QString &name, const QString &content, QObject *parent) : QObject(parent), name_(name), filename("") {
// Open from clipboard
open(content);
}
void DBCFile::open(const QString &content) {
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;
sig.updatePrecision();
}
}
parseExtraInfo(content);
delete dbc;
}
bool DBCFile::save() {
assert (!filename.isEmpty());
if (writeContents(filename)) {
cleanupAutoSaveFile();
return true;
} else {
return false;
}
}
bool DBCFile::saveAs(const QString &new_filename) {
filename = new_filename;
return save();
}
bool DBCFile::autoSave() {
if (!filename.isEmpty()) {
return writeContents(filename + AUTO_SAVE_EXTENSION);
} else {
return false;
}
}
void DBCFile::cleanupAutoSaveFile() {
if (!filename.isEmpty()) {
QFile::remove(filename + AUTO_SAVE_EXTENSION);
}
}
bool DBCFile::writeContents(const QString &fn) {
QFile file(fn);
if (file.open(QIODevice::WriteOnly)) {
file.write(generateDBC().toUtf8());
return true;
} else {
return false;
}
}
cabana::Signal *DBCFile::addSignal(const MessageId &id, const cabana::Signal &sig) {
if (auto m = const_cast<cabana::Msg *>(msg(id.address))) {
m->sigs.push_back(sig);
return &m->sigs.last();
}
return nullptr;
}
cabana::Signal *DBCFile::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) {
if (auto m = const_cast<cabana::Msg *>(msg(id))) {
if (auto s = (cabana::Signal *)m->sig(sig_name)) {
*s = sig;
return s;
}
}
return nullptr;
}
cabana::Signal *DBCFile::getSignal(const MessageId &id, const QString &sig_name) {
if (auto m = const_cast<cabana::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()) {
return &(*it);
}
}
return nullptr;
}
void DBCFile::removeSignal(const MessageId &id, const QString &sig_name) {
if (auto m = const_cast<cabana::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()) {
m->sigs.erase(it);
}
}
}
void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size) {
auto &m = msgs[id.address];
m.name = name;
m.size = size;
}
void DBCFile::removeMsg(const MessageId &id) {
msgs.erase(id.address);
}
std::map<uint32_t, cabana::Msg> DBCFile::getMessages() {
return msgs;
}
const cabana::Msg *DBCFile::msg(const MessageId &id) const {
return msg(id.address);
}
const cabana::Msg *DBCFile::msg(uint32_t address) const {
auto it = msgs.find(address);
return it != msgs.end() ? &it->second : nullptr;
}
const cabana::Msg* DBCFile::msg(const QString &name) {
for (auto &[_, msg] : msgs) {
if (msg.name == name) {
return &msg;
}
}
return nullptr;
}
QStringList DBCFile::signalNames() const {
// Used for autocompletion
QStringList ret;
for (auto const& [_, msg] : msgs) {
for (auto sig: msg.getSignals()) {
ret << sig->name;
}
}
ret.sort();
ret.removeDuplicates();
return ret;
}
int DBCFile::msgCount() const {
return msgs.size();
}
QString DBCFile::name() const {
return name_;
}
void DBCFile::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) -> cabana::Signal * {
auto m = (cabana::Msg *)msg(address);
return m ? (cabana::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();
}
}
}
}
}
QString DBCFile::generateDBC() {
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 sig : m.getSignals()) {
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->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()) {
QStringList 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.join(" "));
}
}
dbc_string += "\n";
}
return dbc_string + signal_comment + val_desc;
}

@ -0,0 +1,55 @@
#pragma once
#include <map>
#include <QList>
#include <QMetaType>
#include <QObject>
#include <QString>
#include <QSet>
#include <QDebug>
#include "tools/cabana/dbc.h"
const QString AUTO_SAVE_EXTENSION = ".tmp";
class DBCFile : public QObject {
Q_OBJECT
public:
DBCFile(const QString &dbc_file_name, QObject *parent=nullptr);
DBCFile(const QString &name, const QString &content, QObject *parent=nullptr);
~DBCFile() {}
void open(const QString &content);
bool save();
bool saveAs(const QString &new_filename);
bool autoSave();
bool writeContents(const QString &fn);
void cleanupAutoSaveFile();
QString generateDBC();
cabana::Signal *addSignal(const MessageId &id, const cabana::Signal &sig);
cabana::Signal *updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig);
cabana::Signal *getSignal(const MessageId &id, const QString &sig_name);
void removeSignal(const MessageId &id, const QString &sig_name);
void updateMsg(const MessageId &id, const QString &name, uint32_t size);
void removeMsg(const MessageId &id);
std::map<uint32_t, cabana::Msg> getMessages();
const cabana::Msg *msg(const MessageId &id) const;
const cabana::Msg *msg(uint32_t address) const;
const cabana::Msg* msg(const QString &name);
QStringList signalNames() const;
int msgCount() const;
QString name() const;
QString filename;
private:
void parseExtraInfo(const QString &content);
std::map<uint32_t, cabana::Msg> msgs;
QString name_;
};

@ -1,5 +1,4 @@
#include "tools/cabana/dbcmanager.h"
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
@ -8,206 +7,210 @@
#include <limits>
#include <sstream>
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;
bool DBCManager::open(SourceSet s, const QString &dbc_file_name, QString *error) {
for (int i = 0; i < dbc_files.size(); i++) {
auto [ss, dbc_file] = dbc_files[i];
// Check if file is already open, and merge sources
if (dbc_file->filename == dbc_file_name) {
ss |= s;
emit DBCFileChanged();
return true;
}
bool DBCManager::open(const QString &name, const QString &content, QString *error) {
// Check if there is already a file for this sourceset, then replace it
if (ss == s) {
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;
sig.updatePrecision();
}
}
parseExtraInfo(content);
name_ = name;
dbc_files[i] = {s, new DBCFile(dbc_file_name, this)};
delete dbc_file;
emit DBCFileChanged();
delete dbc;
return true;
} catch (std::exception &e) {
if (error) *error = e.what();
return false;
}
return true;
}
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) -> cabana::Signal * {
auto m = (cabana::Msg *)msg(address);
return m ? (cabana::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);
}
try {
dbc_files.push_back({s, new DBCFile(dbc_file_name, this)});
} catch (std::exception &e) {
if (error) *error = e.what();
return false;
}
} 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});
}
emit DBCFileChanged();
return true;
}
bool DBCManager::open(SourceSet s, const QString &name, const QString &content, QString *error) {
try {
dbc_files.push_back({s, new DBCFile(name, content, this)});
} catch (std::exception &e) {
if (error) *error = e.what();
return false;
}
emit DBCFileChanged();
return true;
}
} 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();
void DBCManager::closeAll() {
while (dbc_files.size()) {
DBCFile *dbc_file = dbc_files.back().second;
dbc_files.pop_back();
delete dbc_file;
}
emit DBCFileChanged();
}
void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) {
auto sources_dbc_file = findDBCFile(id);
assert(sources_dbc_file); // Create new DBC?
auto [dbc_sources, dbc_file] = *sources_dbc_file;
cabana::Signal *s = dbc_file->addSignal(id, sig);
if (s != nullptr) {
for (uint8_t source : dbc_sources) {
emit signalAdded({.source = source, .address = id.address}, s);
}
}
}
QString DBCManager::generateDBC() {
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 sig : m.getSignals()) {
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->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()) {
QStringList 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.join(" "));
void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) {
auto sources_dbc_file = findDBCFile(id);
assert(sources_dbc_file); // This should be impossible
auto [_, dbc_file] = *sources_dbc_file;
cabana::Signal *s = dbc_file->updateSignal(id, sig_name, sig);
if (s != nullptr) {
emit signalUpdated(s);
}
}
dbc_string += "\n";
void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) {
auto sources_dbc_file = findDBCFile(id);
assert(sources_dbc_file); // This should be impossible
auto [_, dbc_file] = *sources_dbc_file;
cabana::Signal *s = dbc_file->getSignal(id, sig_name);
if (s != nullptr) {
emit signalRemoved(s);
dbc_file->removeSignal(id, sig_name);
}
return dbc_string + signal_comment + val_desc;
}
void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) {
auto &m = msgs[id.address];
m.name = name;
m.size = size;
auto sources_dbc_file = findDBCFile(id);
assert(sources_dbc_file); // This should be impossible
auto [dbc_sources, dbc_file] = *sources_dbc_file;
// This DBC applies to all active sources, emit for every source
for (uint8_t source : sources) {
dbc_file->updateMsg(id, name, size);
for (uint8_t source : dbc_sources) {
emit msgUpdated({.source = source, .address = id.address});
}
}
void DBCManager::removeMsg(const MessageId &id) {
msgs.erase(id.address);
auto sources_dbc_file = findDBCFile(id);
assert(sources_dbc_file); // This should be impossible
auto [dbc_sources, dbc_file] = *sources_dbc_file;
dbc_file->removeMsg(id);
// This DBC applies to all active sources, emit for every source
for (uint8_t source : sources) {
for (uint8_t source : dbc_sources) {
emit msgRemoved({.source = source, .address = id.address});
}
}
void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) {
if (auto m = const_cast<cabana::Msg *>(msg(id.address))) {
m->sigs.push_back(sig);
auto s = &m->sigs.last();
std::map<MessageId, cabana::Msg> DBCManager::getMessages(uint8_t source) {
std::map<MessageId, cabana::Msg> ret;
// This DBC applies to all active sources, emit for every source
for (uint8_t source : sources) {
emit signalAdded({.source = source, .address = id.address}, s);
auto sources_dbc_file = findDBCFile({.source = source, .address = 0});
if (!sources_dbc_file) {
return ret;
}
auto [_, dbc_file] = *sources_dbc_file;
for (auto &[address, msg] : dbc_file->getMessages()) {
MessageId id = {.source = source, .address = address};
ret[id] = msg;
}
return ret;
}
void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) {
if (auto m = const_cast<cabana::Msg *>(msg(id))) {
if (auto s = (cabana::Signal *)m->sig(sig_name)) {
*s = sig;
emit signalUpdated(s);
}
const cabana::Msg *DBCManager::msg(const MessageId &id) const {
auto sources_dbc_file = findDBCFile(id);
if (!sources_dbc_file) {
return nullptr;
}
auto [_, dbc_file] = *sources_dbc_file;
return dbc_file->msg(id);
}
void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) {
if (auto m = const_cast<cabana::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));
m->sigs.erase(it);
}
const cabana::Msg* DBCManager::msg(uint8_t source, const QString &name) {
auto sources_dbc_file = findDBCFile({.source = source, .address = 0});
if (!sources_dbc_file) {
return nullptr;
}
auto [_, dbc_file] = *sources_dbc_file;
return dbc_file->msg(name);
}
QStringList DBCManager::signalNames() {
// Used for autocompletion
QStringList DBCManager::signalNames() const {
QStringList ret;
for (auto const& [_, msg] : msgs) {
for (auto sig: msg.getSignals()) {
ret << sig->name;
}
for (auto &[_, dbc_file] : dbc_files) {
ret << dbc_file->signalNames();
}
ret.sort();
ret.removeDuplicates();
return ret;
}
void DBCManager::updateSources(const QSet<uint8_t> &s) {
int DBCManager::msgCount() const {
int ret = 0;
for (auto &[_, dbc_file] : dbc_files) {
ret += dbc_file->msgCount();
}
return ret;
}
int DBCManager::dbcCount() const {
return dbc_files.size();
}
void DBCManager::updateSources(const SourceSet &s) {
sources = s;
}
std::optional<std::pair<SourceSet, DBCFile*>> DBCManager::findDBCFile(const uint8_t source) const {
// Find DBC file that matches id.source, fall back to SOURCE_ALL if no specific DBC is found
for (auto &[source_set, dbc_file] : dbc_files) {
if (source_set.contains(source)) return {{source_set, dbc_file}};
}
for (auto &[source_set, dbc_file] : dbc_files) {
if (source_set == SOURCE_ALL) return {{sources, dbc_file}};
}
return {};
}
std::optional<std::pair<SourceSet, DBCFile*>> DBCManager::findDBCFile(const MessageId &id) const {
return findDBCFile(id.source);
}
DBCManager *dbc() {
static DBCManager dbc_manager(nullptr);
return &dbc_manager;

@ -9,6 +9,10 @@
#include <QDebug>
#include "tools/cabana/dbc.h"
#include "tools/cabana/dbcfile.h"
typedef QSet<uint8_t> SourceSet;
const SourceSet SOURCE_ALL = {};
class DBCManager : public QObject {
Q_OBJECT
@ -16,39 +20,35 @@ class DBCManager : public QObject {
public:
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();
bool open(SourceSet s, const QString &dbc_file_name, QString *error = nullptr);
bool open(SourceSet s, const QString &name, const QString &content, QString *error = nullptr);
void closeAll();
void addSignal(const MessageId &id, const cabana::Signal &sig);
void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig);
void removeSignal(const MessageId &id, const QString &sig_name);
inline int msgCount() const { return msgs.size(); }
inline QString name() const { return name_; }
void updateMsg(const MessageId &id, const QString &name, uint32_t size);
void removeMsg(const MessageId &id);
inline std::map<MessageId, cabana::Msg> getMessages(uint8_t source) {
std::map<MessageId, cabana::Msg> ret;
for (auto &[address, msg] : msgs) {
MessageId id = {.source = source, .address = address};
ret[id] = msg;
}
return ret;
}
inline const cabana::Msg *msg(const MessageId &id) const { return msg(id.address); }
inline const cabana::Msg* msg(uint8_t source, const QString &name) {
for (auto &[_, msg] : msgs) {
if (msg.name == name) {
return &msg;
}
}
return nullptr;
}
QStringList signalNames();
std::map<MessageId, cabana::Msg> getMessages(uint8_t source);
const cabana::Msg *msg(const MessageId &id) const;
const cabana::Msg* msg(uint8_t source, const QString &name);
QStringList signalNames() const;
int msgCount() const;
int dbcCount() const;
std::optional<std::pair<SourceSet, DBCFile*>> findDBCFile(const uint8_t source) const;
std::optional<std::pair<SourceSet, DBCFile*>> findDBCFile(const MessageId &id) const;
QList<std::pair<SourceSet, DBCFile*>> dbc_files;
private:
SourceSet sources;
public slots:
void updateSources(const QSet<uint8_t> &s);
void updateSources(const SourceSet &s);
signals:
void signalAdded(MessageId id, const cabana::Signal *sig);
@ -57,17 +57,6 @@ signals:
void msgUpdated(MessageId id);
void msgRemoved(MessageId id);
void DBCFileChanged();
private:
void parseExtraInfo(const QString &content);
std::map<uint32_t, cabana::Msg> msgs;
QString name_;
QSet<uint8_t> sources;
inline const cabana::Msg *msg(uint32_t address) const {
auto it = msgs.find(address);
return it != msgs.end() ? &it->second : nullptr;
}
};
DBCManager *dbc();

@ -42,7 +42,7 @@ MainWindow::MainWindow() : QMainWindow() {
messages_widget->restoreHeaderState(settings.message_header_state);
qRegisterMetaType<uint64_t>("uint64_t");
qRegisterMetaType<QSet<uint8_t>>("QSet<uint8_t>");
qRegisterMetaType<SourceSet>("SourceSet");
qRegisterMetaType<ReplyMsgType>("ReplyMsgType");
installMessageHandler([this](ReplyMsgType type, const std::string msg) {
// use queued connection to recv the log messages from replay.
@ -54,9 +54,13 @@ MainWindow::MainWindow() : QMainWindow() {
main_win = this;
qInstallMessageHandler(qLogMessageHandler);
QFile json_file("./car_fingerprint_to_dbc.json");
for (const QString &fn : {"./car_fingerprint_to_dbc.json", "./tools/cabana/car_fingerprint_to_dbc.json"}) {
QFile json_file(fn);
if (json_file.open(QIODevice::ReadOnly)) {
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
break;
}
}
setStyleSheet(QString(R"(QMainWindow::separator {
@ -72,6 +76,7 @@ MainWindow::MainWindow() : QMainWindow() {
QObject::connect(can, &AbstractStream::eventsMerged, this, &MainWindow::updateStatus);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged);
QObject::connect(can, &AbstractStream::sourcesUpdated, dbc(), &DBCManager::updateSources);
QObject::connect(can, &AbstractStream::sourcesUpdated, this, &MainWindow::updateSources);
QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged);
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MainWindow::undoStackIndexChanged);
QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus);
@ -87,6 +92,9 @@ void MainWindow::createActions() {
file_menu->addAction(tr("New DBC File"), this, &MainWindow::newFile)->setShortcuts(QKeySequence::New);
file_menu->addAction(tr("Open DBC File..."), this, &MainWindow::openFile)->setShortcuts(QKeySequence::Open);
open_dbc_for_source = file_menu->addMenu(tr("Open &DBC File for Bus"));
open_dbc_for_source->setEnabled(false);
open_recent_menu = file_menu->addMenu(tr("Open &Recent"));
for (int i = 0; i < MAX_RECENT_FILES; ++i) {
recent_files_acts[i] = new QAction(this);
@ -108,9 +116,14 @@ void MainWindow::createActions() {
file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard);
file_menu->addSeparator();
file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save)->setShortcuts(QKeySequence::Save);
file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs)->setShortcuts(QKeySequence::SaveAs);
file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard);
save_dbc = file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save);
save_dbc->setShortcuts(QKeySequence::Save);
save_dbc_as = file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs);
save_dbc_as->setShortcuts(QKeySequence::SaveAs);
copy_dbc_to_clipboard = file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard);
file_menu->addSeparator();
file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption)->setShortcuts(QKeySequence::Preferences);
@ -231,7 +244,7 @@ void MainWindow::undoStackCleanChanged(bool clean) {
void MainWindow::DBCFileChanged() {
UndoStack::instance()->clear();
setWindowFilePath(QString("%1").arg(dbc()->name()));
updateLoadSaveMenus();
}
void MainWindow::openRoute() {
@ -247,7 +260,9 @@ void MainWindow::openRoute() {
void MainWindow::newFile() {
remindSaveChanges();
dbc()->open("untitled.dbc", "");
dbc()->closeAll();
dbc()->open(SOURCE_ALL, "", "");
updateLoadSaveMenus();
}
void MainWindow::openFile() {
@ -258,7 +273,19 @@ void MainWindow::openFile() {
}
}
void MainWindow::loadFile(const QString &fn) {
void MainWindow::openFileForSource() {
if (auto action = qobject_cast<QAction *>(sender())) {
uint8_t source = action->data().value<uint8_t>();
assert(source < 64);
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)");
if (!fn.isEmpty()) {
loadFile(fn, {source, uint8_t(source + 128), uint8_t(source + 192)}, false);
}
}
}
void MainWindow::loadFile(const QString &fn, SourceSet s, bool close_all) {
if (!fn.isEmpty()) {
QString dbc_fn = fn;
@ -271,20 +298,24 @@ void MainWindow::loadFile(const QString &fn) {
}
}
QFile file(dbc_fn);
if (file.open(QIODevice::ReadOnly)) {
auto dbc_name = QFileInfo(fn).baseName();
QString error;
bool ret = dbc()->open(dbc_name, file.readAll(), &error);
if (close_all) {
dbc()->closeAll();
}
bool ret = dbc()->open(s, dbc_fn, &error);
if (ret) {
setCurrentFile(fn);
updateRecentFiles(fn);
statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000);
} else {
QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC file"), tr("Failed to parse DBC file %1").arg(fn));
msg_box.setDetailedText(error);
msg_box.exec();
}
}
updateLoadSaveMenus();
}
}
@ -303,17 +334,23 @@ void MainWindow::openRecentFile() {
}
void MainWindow::loadDBCFromOpendbc(const QString &name) {
if (name != dbc()->name()) {
remindSaveChanges();
dbc()->open(name);
}
QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, name);
dbc()->closeAll();
dbc()->open(SOURCE_ALL, opendbc_file_path);
updateLoadSaveMenus();
}
void MainWindow::loadDBCFromClipboard() {
remindSaveChanges();
QString dbc_str = QGuiApplication::clipboard()->text();
QString error;
bool ret = dbc()->open("clipboard", dbc_str, &error);
dbc()->closeAll();
bool ret = dbc()->open(SOURCE_ALL, "", dbc_str, &error);
if (ret && dbc()->msgCount() > 0) {
QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!"));
} else {
@ -327,7 +364,7 @@ void MainWindow::loadDBCFromClipboard() {
void MainWindow::loadDBCFromFingerprint() {
// Don't overwrite already loaded DBC
if (!dbc()->name().isEmpty()) {
if (dbc()->msgCount()) {
return;
}
@ -349,54 +386,110 @@ void MainWindow::loadDBCFromFingerprint() {
}
void MainWindow::save() {
if (current_file.isEmpty()) {
saveAs();
} else {
saveFile(current_file);
}
saveFile();
}
void MainWindow::autoSave() {
if (!current_file.isEmpty() && !UndoStack::instance()->isClean()) {
QFile file(current_file + AUTO_SAVE_EXTENSION);
if (file.open(QIODevice::WriteOnly)) {
file.write(dbc()->generateDBC().toUtf8());
if (!UndoStack::instance()->isClean()) {
for (auto &[_, dbc_file] : dbc()->dbc_files) {
if (!dbc_file->filename.isEmpty()) {
dbc_file->autoSave();
}
}
}
}
void MainWindow::cleanupAutoSaveFile() {
if (!current_file.isEmpty()) {
QFile::remove(current_file + AUTO_SAVE_EXTENSION);
for (auto &[_, dbc_file] : dbc()->dbc_files) {
dbc_file->cleanupAutoSaveFile();
}
}
void MainWindow::saveFile() {
// Save all open DBC files
for (auto &[s, dbc_file] : dbc()->dbc_files) {
if (!dbc_file->filename.isEmpty()) {
dbc_file->save();
updateRecentFiles(dbc_file->filename);
} else {
QString fn = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)"));
if (!fn.isEmpty()) {
dbc_file->saveAs(fn);
updateRecentFiles(fn);
}
}
}
void MainWindow::saveFile(const QString &fn) {
cleanupAutoSaveFile();
QFile file(fn);
if (file.open(QIODevice::WriteOnly)) {
file.write(dbc()->generateDBC().toUtf8());
UndoStack::instance()->setClean();
setCurrentFile(fn);
statusBar()->showMessage(tr("File saved"), 2000);
}
}
void MainWindow::saveAs() {
// Assume only one file is open
assert(dbc()->dbcCount() > 0);
auto &[_, dbc_file] = dbc()->dbc_files.first();
QString fn = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)"));
if (!fn.isEmpty()) {
saveFile(fn);
dbc_file->saveAs(fn);
}
}
void MainWindow::saveDBCToClipboard() {
QGuiApplication::clipboard()->setText(dbc()->generateDBC());
// Assume only one file is open
assert(dbc()->dbcCount() > 0);
auto &[_, dbc_file] = dbc()->dbc_files.first();
QGuiApplication::clipboard()->setText(dbc_file->generateDBC());
QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!"));
}
void MainWindow::setCurrentFile(const QString &fn) {
current_file = fn;
setWindowFilePath(QString("%1").arg(fn));
void MainWindow::updateSources(const SourceSet &s) {
sources = s;
updateLoadSaveMenus();
}
void MainWindow::updateLoadSaveMenus() {
if (dbc()->dbcCount() > 1) {
save_dbc->setText(tr("Save %1 DBCs...").arg(dbc()->dbcCount()));
} else {
save_dbc->setText(tr("Save DBC..."));
}
// TODO: Support save as for multiple files
save_dbc_as->setEnabled(dbc()->dbcCount() == 1);
// TODO: Support clipboard for multiple files
copy_dbc_to_clipboard->setEnabled(dbc()->dbcCount() == 1);
QList<uint8_t> sources_sorted = sources.toList();
std::sort(sources_sorted.begin(), sources_sorted.end());
open_dbc_for_source->setEnabled(sources.size() > 0);
open_dbc_for_source->clear();
for (uint8_t source : sources_sorted) {
if (source >= 64) continue; // Sent and blocked buses are handled implicitly
QAction *action = new QAction(this);
auto d = dbc()->findDBCFile(source);
QString name = tr("no DBC");
if (d && !d->second->name().isEmpty()) {
name = tr("%1").arg(d->second->name());
} else if (d) {
name = "untitled";
}
action->setText(tr("Bus %1 (current: %2)").arg(source).arg(name));
action->setData(source);
QObject::connect(action, &QAction::triggered, this, &MainWindow::openFileForSource);
open_dbc_for_source->addAction(action);
}
}
void MainWindow::updateRecentFiles(const QString &fn) {
settings.recent_files.removeAll(fn);
settings.recent_files.prepend(fn);
while (settings.recent_files.size() > MAX_RECENT_FILES) {
@ -434,7 +527,6 @@ void MainWindow::remindSaveChanges() {
}
}
UndoStack::instance()->clear();
current_file = "";
}
void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) {

@ -8,13 +8,12 @@
#include <QStatusBar>
#include "tools/cabana/chartswidget.h"
#include "tools/cabana/dbcmanager.h"
#include "tools/cabana/detailwidget.h"
#include "tools/cabana/messageswidget.h"
#include "tools/cabana/videowidget.h"
#include "tools/cabana/tools/findsimilarbits.h"
const QString AUTO_SAVE_EXTENSION = ".tmp";
class MainWindow : public QMainWindow {
Q_OBJECT
@ -22,12 +21,13 @@ public:
MainWindow();
void dockCharts(bool dock);
void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); }
void loadFile(const QString &fn);
void loadFile(const QString &fn, SourceSet s = SOURCE_ALL, bool close_all = true);
public slots:
void openRoute();
void newFile();
void openFile();
void openFileForSource();
void openRecentFile();
void openOpendbcFile();
void loadDBCFromOpendbc(const QString &name);
@ -36,6 +36,7 @@ public slots:
void save();
void saveAs();
void saveDBCToClipboard();
void updateSources(const SourceSet &s);
signals:
void showMessage(const QString &msg, int timeout);
@ -43,10 +44,10 @@ signals:
protected:
void remindSaveChanges();
void saveFile(const QString &fn);
void saveFile();
void autoSave();
void cleanupAutoSaveFile();
void setCurrentFile(const QString &fn);
void updateRecentFiles(const QString &fn);
void updateRecentFileActions();
void createActions();
void createDockWindows();
@ -62,6 +63,7 @@ protected:
void onlineHelp();
void toggleFullScreen();
void updateStatus();
void updateLoadSaveMenus();
VideoWidget *video_widget = nullptr;
QDockWidget *video_dock;
@ -74,12 +76,16 @@ protected:
QLabel *status_label;
QJsonDocument fingerprint_to_dbc;
QSplitter *video_splitter;;
QString current_file = "";
enum { MAX_RECENT_FILES = 15 };
QAction *recent_files_acts[MAX_RECENT_FILES] = {};
QMenu *open_recent_menu = nullptr;
QMenu *open_dbc_for_source = nullptr;
QAction *save_dbc = nullptr;
QAction *save_dbc_as = nullptr;
QAction *copy_dbc_to_clipboard = nullptr;
int prev_undostack_index = 0;
int prev_undostack_count = 0;
SourceSet sources;
friend class OnlineHelp;
};

@ -64,11 +64,11 @@ signals:
void updated();
void msgsReceived(const QHash<MessageId, CanData> *);
void received(QHash<MessageId, CanData> *);
void sourcesUpdated(const QSet<uint8_t> &s);
void sourcesUpdated(const SourceSet &s);
public:
QHash<MessageId, CanData> last_msgs;
QSet<uint8_t> sources;
SourceSet sources;
protected:
virtual void process(QHash<MessageId, CanData> *);

@ -9,15 +9,14 @@
// 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";
TEST_CASE("DBCManager::generateDBC") {
DBCManager dbc_origin(nullptr);
dbc_origin.open("toyota_new_mc_pt_generated");
DBCManager dbc_from_generated(nullptr);
dbc_from_generated.open("", dbc_origin.generateDBC());
TEST_CASE("DBCFile::generateDBC") {
QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "toyota_new_mc_pt_generated");
DBCFile dbc_origin(fn);
DBCFile dbc_from_generated("", dbc_origin.generateDBC());
REQUIRE(dbc_origin.msgCount() == dbc_from_generated.msgCount());
auto msgs = dbc_origin.getMessages(0);
auto new_msgs = dbc_from_generated.getMessages(0);
auto msgs = dbc_origin.getMessages();
auto new_msgs = dbc_from_generated.getMessages();
for (auto &[id, m] : msgs) {
auto &new_m = new_msgs.at(id);
REQUIRE(m.name == new_m.name);
@ -33,7 +32,7 @@ TEST_CASE("DBCManager::generateDBC") {
TEST_CASE("Parse can messages") {
DBCManager dbc(nullptr);
dbc.open("toyota_new_mc_pt_generated");
dbc.open({0}, "toyota_new_mc_pt_generated");
CANParser can_parser(0, "toyota_new_mc_pt_generated", {}, {});
LogReader log;

@ -20,7 +20,7 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi
QHBoxLayout *src_layout = new QHBoxLayout();
src_bus_combo = new QComboBox(this);
find_bus_combo = new QComboBox(this);
QSet<uint8_t> bus_set;
SourceSet bus_set;
for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) {
bus_set << it.key().source;
}

Loading…
Cancel
Save