openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

239 lines
8.9 KiB

#include "tools/cabana/dbc/dbcfile.h"
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include <QTextStream>
#include <numeric>
#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();
filename = dbc_file_name;
// Remove auto save file extension
if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) {
filename.chop(AUTO_SAVE_EXTENSION.length());
}
parse(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
parse(content);
}
bool DBCFile::save() {
assert(!filename.isEmpty());
if (writeContents(filename)) {
cleanupAutoSaveFile();
return true;
}
return false;
}
bool DBCFile::saveAs(const QString &new_filename) {
filename = new_filename;
return save();
}
bool DBCFile::autoSave() {
return !filename.isEmpty() && writeContents(filename + AUTO_SAVE_EXTENSION);
}
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;
}
return false;
}
void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &comment) {
auto &m = msgs[id.address];
m.address = id.address;
m.name = name;
m.size = size;
m.comment = comment;
}
cabana::Msg *DBCFile::msg(uint32_t address) {
auto it = msgs.find(address);
return it != msgs.end() ? &it->second : nullptr;
}
cabana::Msg *DBCFile::msg(const QString &name) {
auto it = std::find_if(msgs.begin(), msgs.end(), [&name](auto &m) { return m.second.name == name; });
return it != msgs.end() ? &(it->second) : nullptr;
}
int DBCFile::signalCount() {
return std::accumulate(msgs.cbegin(), msgs.cend(), 0, [](int &n, const auto &m) { return n + m.second.sigs.size(); });
}
void DBCFile::parse(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 msg_comment_regexp(R"(^CM_ BO_ *(\w+) *\"([^"]*)\"\s*;)");
static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"([^"]*)\"\s*;)");
static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))");
int line_num = 0;
QString line;
auto dbc_assert = [&line_num, &line, this](bool condition, const QString &msg = "") {
if (!condition) throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(msg).arg(line).toStdString());
};
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;
};
msgs.clear();
QTextStream stream((QString *)&content);
cabana::Msg *current_msg = nullptr;
int multiplexor_cnt = 0;
while (!stream.atEnd()) {
++line_num;
line = stream.readLine().trimmed();
if (line.startsWith("BO_ ")) {
multiplexor_cnt = 0;
auto match = bo_regexp.match(line);
dbc_assert(match.hasMatch());
auto address = match.captured(1).toUInt();
dbc_assert(msgs.count(address) == 0, QString("Duplicate message address: %1").arg(address));
current_msg = &msgs[address];
current_msg->address = address;
current_msg->name = match.captured(2);
current_msg->size = match.captured(3).toULong();
current_msg->transmitter = match.captured(4).trimmed();
} else if (line.startsWith("SG_ ")) {
int offset = 0;
auto match = sg_regexp.match(line);
if (!match.hasMatch()) {
match = sgm_regexp.match(line);
offset = 1;
}
dbc_assert(match.hasMatch());
dbc_assert(current_msg, "No Message");
auto name = match.captured(1);
dbc_assert(current_msg->sig(name) == nullptr, "Duplicate signal name");
cabana::Signal s{};
if (offset == 1) {
auto indicator = match.captured(2);
if (indicator == "M") {
// Only one signal within a single message can be the multiplexer switch.
dbc_assert(++multiplexor_cnt < 2, "Multiple multiplexor");
s.type = cabana::Signal::Type::Multiplexor;
} else {
s.type = cabana::Signal::Type::Multiplexed;
s.multiplex_value = indicator.mid(1).toInt();
}
}
s.name = name;
s.start_bit = match.captured(offset + 2).toInt();
s.size = match.captured(offset + 3).toInt();
s.is_little_endian = match.captured(offset + 4).toInt() == 1;
s.is_signed = match.captured(offset + 5) == "-";
s.factor = match.captured(offset + 6).toDouble();
s.offset = match.captured(offset + 7).toDouble();
s.min = match.captured(8 + offset).toDouble();
s.max = match.captured(9 + offset).toDouble();
s.unit = match.captured(10 + offset);
s.receiver_name = match.captured(11 + offset).trimmed();
current_msg->sigs.push_back(new cabana::Signal(s));
} else if (line.startsWith("VAL_ ")) {
auto match = val_regexp.match(line);
dbc_assert(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.toDouble(), desc});
}
}
}
} else if (line.startsWith("CM_ BO_")) {
if (!line.endsWith("\";")) {
int pos = stream.pos() - line.length() - 1;
line = content.mid(pos, content.indexOf("\";", pos));
}
auto match = msg_comment_regexp.match(line);
dbc_assert(match.hasMatch());
if (auto m = (cabana::Msg *)msg(match.captured(1).toUInt())) {
m->comment = match.captured(2).trimmed();
}
} else if (line.startsWith("CM_ SG_ ")) {
if (!line.endsWith("\";")) {
int pos = stream.pos() - line.length() - 1;
line = content.mid(pos, content.indexOf("\";", pos));
}
auto match = sg_comment_regexp.match(line);
dbc_assert(match.hasMatch());
if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) {
s->comment = match.captured(3).trimmed();
}
}
}
for (auto &[_, m] : msgs) {
m.update();
}
}
QString DBCFile::generateDBC() {
QString dbc_string, signal_comment, message_comment, val_desc;
for (const auto &[address, m] : msgs) {
dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(m.transmitter.isEmpty() ? "XXX" : m.transmitter);
if (!m.comment.isEmpty()) {
message_comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(m.comment);
}
for (auto sig : m.getSignals()) {
QString multiplexer_indicator;
if (sig->type == cabana::Signal::Type::Multiplexor) {
multiplexer_indicator = "M ";
} else if (sig->type == cabana::Signal::Type::Multiplexed) {
multiplexer_indicator = QString("m%1 ").arg(sig->multiplex_value);
}
dbc_string += QString(" SG_ %1 %2: %3|%4@%5%6 (%7,%8) [%9|%10] \"%11\" %12\n")
.arg(sig->name)
.arg(multiplexer_indicator)
.arg(sig->start_bit)
.arg(sig->size)
.arg(sig->is_little_endian ? '1' : '0')
.arg(sig->is_signed ? '-' : '+')
.arg(doubleToString(sig->factor))
.arg(doubleToString(sig->offset))
.arg(doubleToString(sig->min))
.arg(doubleToString(sig->max))
.arg(sig->unit)
.arg(sig->receiver_name.isEmpty() ? "XXX" : sig->receiver_name);
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).arg(desc);
}
val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig->name).arg(text.join(" "));
}
}
dbc_string += "\n";
}
return dbc_string + message_comment + signal_comment + val_desc;
}