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.

275 lines
9.6 KiB

#include "tools/cabana/dbc/dbcfile.h"
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
DBCFile::DBCFile(const QString &dbc_file_name) {
QFile file(dbc_file_name);
if (file.open(QIODevice::ReadOnly)) {
name_ = QFileInfo(dbc_file_name).baseName();
filename = dbc_file_name;
parse(file.readAll());
} else {
throw std::runtime_error("Failed to open file.");
}
}
DBCFile::DBCFile(const QString &name, const QString &content) : name_(name), filename("") {
parse(content);
}
bool DBCFile::save() {
assert(!filename.isEmpty());
return writeContents(filename);
}
bool DBCFile::saveAs(const QString &new_filename) {
filename = new_filename;
return save();
}
bool DBCFile::writeContents(const QString &fn) {
QFile file(fn);
if (file.open(QIODevice::WriteOnly)) {
return file.write(generateDBC().toUtf8()) >= 0;
}
return false;
}
void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) {
auto &m = msgs[id.address];
m.address = id.address;
m.name = name;
m.size = size;
m.transmitter = node.isEmpty() ? DEFAULT_NODE_NAME : node;
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;
}
cabana::Signal *DBCFile::signal(uint32_t address, const QString &name) {
auto m = msg(address);
return m ? (cabana::Signal *)m->sig(name) : nullptr;
}
void DBCFile::parse(const QString &content) {
msgs.clear();
int line_num = 0;
QString line;
cabana::Msg *current_msg = nullptr;
int multiplexor_cnt = 0;
bool seen_first = false;
QTextStream stream((QString *)&content);
while (!stream.atEnd()) {
++line_num;
QString raw_line = stream.readLine();
line = raw_line.trimmed();
bool seen = true;
try {
if (line.startsWith("BO_ ")) {
multiplexor_cnt = 0;
current_msg = parseBO(line);
} else if (line.startsWith("SG_ ")) {
parseSG(line, current_msg, multiplexor_cnt);
} else if (line.startsWith("VAL_ ")) {
parseVAL(line);
} else if (line.startsWith("CM_ BO_")) {
parseCM_BO(line, content, raw_line, stream);
} else if (line.startsWith("CM_ SG_ ")) {
parseCM_SG(line, content, raw_line, stream);
} else {
seen = false;
}
} catch (std::exception &e) {
throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(e.what()).arg(line).toStdString());
}
if (seen) {
seen_first = true;
} else if (!seen_first) {
header += raw_line + "\n";
}
}
for (auto &[_, m] : msgs) {
m.update();
}
}
cabana::Msg *DBCFile::parseBO(const QString &line) {
static QRegularExpression bo_regexp(R"(^BO_ (?<address>\w+) (?<name>\w+) *: (?<size>\w+) (?<transmitter>\w+))");
QRegularExpressionMatch match = bo_regexp.match(line);
if (!match.hasMatch())
throw std::runtime_error("Invalid BO_ line format");
uint32_t address = match.captured("address").toUInt();
if (msgs.count(address) > 0)
throw std::runtime_error(QString("Duplicate message address: %1").arg(address).toStdString());
// Create a new message object
cabana::Msg *msg = &msgs[address];
msg->address = address;
msg->name = match.captured("name");
msg->size = match.captured("size").toULong();
msg->transmitter = match.captured("transmitter").trimmed();
return msg;
}
void DBCFile::parseCM_BO(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream) {
static QRegularExpression msg_comment_regexp(R"(^CM_ BO_ *(?<address>\w+) *\"(?<comment>(?:[^"\\]|\\.)*)\"\s*;)");
QString parse_line = line;
if (!parse_line.endsWith("\";")) {
int pos = stream.pos() - raw_line.length() - 1;
parse_line = content.mid(pos, content.indexOf("\";", pos));
}
auto match = msg_comment_regexp.match(parse_line);
if (!match.hasMatch())
throw std::runtime_error("Invalid message comment format");
if (auto m = (cabana::Msg *)msg(match.captured("address").toUInt()))
m->comment = match.captured("comment").trimmed().replace("\\\"", "\"");
}
void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt) {
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]+)\] \"(.*)\" (.*))");
if (!current_msg)
throw std::runtime_error("No Message");
int offset = 0;
auto match = sg_regexp.match(line);
if (!match.hasMatch()) {
match = sgm_regexp.match(line);
offset = 1;
}
if (!match.hasMatch())
throw std::runtime_error("Invalid SG_ line format");
QString name = match.captured(1);
if (current_msg->sig(name) != nullptr)
throw std::runtime_error("Duplicate signal name");
cabana::Signal s{};
if (offset == 1) {
auto indicator = match.captured(2);
if (indicator == "M") {
++multiplexor_cnt;
// Only one signal within a single message can be the multiplexer switch.
if (multiplexor_cnt >= 2)
throw std::runtime_error("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));
}
void DBCFile::parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream) {
static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"((?:[^"\\]|\\.)*)\"\s*;)");
QString parse_line = line;
if (!parse_line.endsWith("\";")) {
int pos = stream.pos() - raw_line.length() - 1;
parse_line = content.mid(pos, content.indexOf("\";", pos));
}
auto match = sg_comment_regexp.match(parse_line);
if (!match.hasMatch())
throw std::runtime_error("Invalid CM_ SG_ line format");
if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) {
s->comment = match.captured(3).trimmed().replace("\\\"", "\"");
}
}
void DBCFile::parseVAL(const QString &line) {
static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))");
auto match = val_regexp.match(line);
if (!match.hasMatch())
throw std::runtime_error("invalid VAL_ line format");
if (auto s = signal(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});
}
}
}
}
QString DBCFile::generateDBC() {
QString dbc_string, comment, val_desc;
for (const auto &[address, m] : msgs) {
const QString transmitter = m.transmitter.isEmpty() ? DEFAULT_NODE_NAME : m.transmitter;
dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(transmitter);
if (!m.comment.isEmpty()) {
comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(QString(m.comment).replace("\"", "\\\""));
}
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() ? DEFAULT_NODE_NAME : sig->receiver_name);
if (!sig->comment.isEmpty()) {
comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(QString(sig->comment).replace("\"", "\\\""));
}
if (!sig->val_desc.empty()) {
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 header + dbc_string + comment + val_desc;
}