opendbc/can/dbc.cc

249 lines
9.5 KiB
C++

#include <algorithm>
#include <filesystem>
#include <fstream>
#include <map>
#include <regex>
#include <set>
#include <sstream>
#include <vector>
#include <mutex>
#include <cstring>
#include <iterator>
#include "opendbc/can/common.h"
#include "opendbc/can/common_dbc.h"
std::regex bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))");
std::regex sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
std::regex sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
std::regex val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))");
std::regex val_split_regexp{R"([\"]+)"}; // split on "
#define DBC_ASSERT(condition, message) \
do { \
if (!(condition)) { \
std::stringstream is; \
is << "[" << dbc_name << ":" << line_num << "] " << message; \
throw std::runtime_error(is.str()); \
} \
} while (false)
inline bool startswith(const std::string& str, const char* prefix) {
return str.find(prefix, 0) == 0;
}
inline bool startswith(const std::string& str, std::initializer_list<const char*> prefix_list) {
for (auto prefix : prefix_list) {
if (startswith(str, prefix)) return true;
}
return false;
}
inline bool endswith(const std::string& str, const char* suffix) {
return str.find(suffix, 0) == (str.length() - strlen(suffix));
}
inline std::string& trim(std::string& s, const char* t = " \t\n\r\f\v") {
s.erase(s.find_last_not_of(t) + 1);
return s.erase(0, s.find_first_not_of(t));
}
ChecksumState* get_checksum(const std::string& dbc_name) {
ChecksumState* s = nullptr;
if (startswith(dbc_name, {"honda_", "acura_"})) {
s = new ChecksumState({4, 2, 3, 5, false, HONDA_CHECKSUM, &honda_checksum});
} else if (startswith(dbc_name, {"toyota_", "lexus_"})) {
s = new ChecksumState({8, -1, 7, -1, false, TOYOTA_CHECKSUM, &toyota_checksum});
} else if (startswith(dbc_name, "hyundai_canfd")) {
s = new ChecksumState({16, -1, 0, -1, true, HKG_CAN_FD_CHECKSUM, &hkg_can_fd_checksum});
} else if (startswith(dbc_name, "vw_mqb_2010")) {
s = new ChecksumState({8, 4, 0, 0, true, VOLKSWAGEN_MQB_CHECKSUM, &volkswagen_mqb_checksum});
} else if (startswith(dbc_name, "vw_golf_mk4")) {
s = new ChecksumState({8, 4, 0, -1, true, XOR_CHECKSUM, &xor_checksum});
} else if (startswith(dbc_name, "subaru_global_")) {
s = new ChecksumState({8, -1, 0, -1, true, SUBARU_CHECKSUM, &subaru_checksum});
} else if (startswith(dbc_name, "chrysler_")) {
s = new ChecksumState({8, -1, 7, -1, false, CHRYSLER_CHECKSUM, &chrysler_checksum});
} else if (startswith(dbc_name, "comma_body")) {
s = new ChecksumState({8, 4, 7, 3, false, PEDAL_CHECKSUM, &pedal_checksum});
}
return s;
}
void set_signal_type(Signal& s, ChecksumState* chk, const std::string& dbc_name, int line_num) {
s.calc_checksum = nullptr;
if (chk) {
if (s.name == "CHECKSUM") {
DBC_ASSERT(chk->checksum_size == -1 || s.size == chk->checksum_size, "CHECKSUM is not " << chk->checksum_size << " bits long");
DBC_ASSERT(chk->checksum_start_bit == -1 || (s.start_bit % 8) == chk->checksum_start_bit, " CHECKSUM starts at wrong bit");
DBC_ASSERT(s.is_little_endian == chk->little_endian, "CHECKSUM has wrong endianness");
DBC_ASSERT(chk->calc_checksum != nullptr, "CHECKSUM calculate function not supplied");
s.type = chk->checksum_type;
s.calc_checksum = chk->calc_checksum;
} else if (s.name == "COUNTER") {
DBC_ASSERT(chk->counter_size == -1 || s.size == chk->counter_size, "COUNTER is not " << chk->counter_size << " bits long");
DBC_ASSERT(chk->counter_start_bit == -1 || (s.start_bit % 8) == chk->counter_start_bit, "COUNTER starts at wrong bit");
DBC_ASSERT(chk->little_endian == s.is_little_endian, "COUNTER has wrong endianness");
s.type = COUNTER;
}
}
// TODO: CAN packer/parser shouldn't know anything about interceptors or pedals
if (s.name == "CHECKSUM_PEDAL") {
DBC_ASSERT(s.size == 8, "INTERCEPTOR CHECKSUM is not 8 bits long");
s.type = PEDAL_CHECKSUM;
} else if (s.name == "COUNTER_PEDAL") {
DBC_ASSERT(s.size == 4, "INTERCEPTOR COUNTER is not 4 bits long");
s.type = COUNTER;
}
}
DBC* dbc_parse_from_stream(const std::string &dbc_name, std::istream &stream, ChecksumState *checksum) {
uint32_t address = 0;
std::set<uint32_t> address_set;
std::set<std::string> msg_name_set;
std::map<uint32_t, std::vector<Signal>> signals;
DBC* dbc = new DBC;
dbc->name = dbc_name;
// used to find big endian LSB from MSB and size
std::vector<int> be_bits;
for (int i = 0; i < 64; i++) {
for (int j = 7; j >= 0; j--) {
be_bits.push_back(j + i * 8);
}
}
std::string line;
int line_num = 0;
std::smatch match;
// TODO: see if we can speed up the regex statements in this loop, SG_ is specifically the slowest
while (std::getline(stream, line)) {
line = trim(line);
line_num += 1;
if (startswith(line, "BO_ ")) {
// new group
bool ret = std::regex_match(line, match, bo_regexp);
DBC_ASSERT(ret, "bad BO: " << line);
Msg& msg = dbc->msgs.emplace_back();
address = msg.address = std::stoul(match[1].str()); // could be hex
msg.name = match[2].str();
msg.size = std::stoul(match[3].str());
// check for duplicates
DBC_ASSERT(address_set.find(address) == address_set.end(), "Duplicate message address: " << address << " (" << msg.name << ")");
address_set.insert(address);
DBC_ASSERT(msg_name_set.find(msg.name) == msg_name_set.end(), "Duplicate message name: " << msg.name);
msg_name_set.insert(msg.name);
} else if (startswith(line, "SG_ ")) {
// new signal
int offset = 0;
if (!std::regex_search(line, match, sg_regexp)) {
bool ret = std::regex_search(line, match, sgm_regexp);
DBC_ASSERT(ret, "bad SG: " << line);
offset = 1;
}
Signal& sig = signals[address].emplace_back();
sig.name = match[1].str();
sig.start_bit = std::stoi(match[offset + 2].str());
sig.size = std::stoi(match[offset + 3].str());
sig.is_little_endian = std::stoi(match[offset + 4].str()) == 1;
sig.is_signed = match[offset + 5].str() == "-";
sig.factor = std::stod(match[offset + 6].str());
sig.offset = std::stod(match[offset + 7].str());
set_signal_type(sig, checksum, dbc_name, line_num);
if (sig.is_little_endian) {
sig.lsb = sig.start_bit;
sig.msb = sig.start_bit + sig.size - 1;
} else {
auto it = find(be_bits.begin(), be_bits.end(), sig.start_bit);
sig.lsb = be_bits[(it - be_bits.begin()) + sig.size - 1];
sig.msb = sig.start_bit;
}
DBC_ASSERT(sig.lsb < (64 * 8) && sig.msb < (64 * 8), "Signal out of bounds: " << line);
} else if (startswith(line, "VAL_ ")) {
// new signal value/definition
bool ret = std::regex_search(line, match, val_regexp);
DBC_ASSERT(ret, "bad VAL: " << line);
auto& val = dbc->vals.emplace_back();
val.address = std::stoul(match[1].str()); // could be hex
val.name = match[2].str();
auto defvals = match[3].str();
std::sregex_token_iterator it{defvals.begin(), defvals.end(), val_split_regexp, -1};
// convert strings to UPPER_CASE_WITH_UNDERSCORES
std::vector<std::string> words{it, {}};
for (auto& w : words) {
w = trim(w);
std::transform(w.begin(), w.end(), w.begin(), ::toupper);
std::replace(w.begin(), w.end(), ' ', '_');
}
// join string
std::stringstream s;
std::copy(words.begin(), words.end(), std::ostream_iterator<std::string>(s, " "));
val.def_val = s.str();
val.def_val = trim(val.def_val);
}
}
for (auto& m : dbc->msgs) {
m.sigs = signals[m.address];
}
for (auto& v : dbc->vals) {
v.sigs = signals[v.address];
}
return dbc;
}
DBC* dbc_parse(const std::string& dbc_path) {
std::ifstream infile(dbc_path);
if (!infile) return nullptr;
const std::string dbc_name = std::filesystem::path(dbc_path).filename();
std::unique_ptr<ChecksumState> checksum(get_checksum(dbc_name));
return dbc_parse_from_stream(dbc_name, infile, checksum.get());
}
const std::string get_dbc_root_path() {
char *basedir = std::getenv("BASEDIR");
if (basedir != NULL) {
return std::string(basedir) + "/opendbc";
} else {
return DBC_FILE_PATH;
}
}
const DBC* dbc_lookup(const std::string& dbc_name) {
static std::mutex lock;
static std::map<std::string, DBC*> dbcs;
std::string dbc_file_path = dbc_name;
if (!std::filesystem::exists(dbc_file_path)) {
dbc_file_path = get_dbc_root_path() + "/" + dbc_name + ".dbc";
}
std::unique_lock lk(lock);
auto it = dbcs.find(dbc_name);
if (it == dbcs.end()) {
it = dbcs.insert(it, {dbc_name, dbc_parse(dbc_file_path)});
}
return it->second;
}
std::vector<std::string> get_dbc_names() {
static const std::string& dbc_file_path = get_dbc_root_path();
std::vector<std::string> dbcs;
for (std::filesystem::directory_iterator i(dbc_file_path), end; i != end; i++) {
if (!is_directory(i->path())) {
std::string filename = i->path().filename();
if (!startswith(filename, "_") && endswith(filename, ".dbc")) {
dbcs.push_back(filename.substr(0, filename.length() - 4));
}
}
}
return dbcs;
}