mirror of https://github.com/rusefi/opendbc.git
Dynamically parse DBCs (#602)
* initial commit, bring over from deanlee's PR based on newer commit Co-authored-by: Shane Smiskol <shane@smiskol.com> * fix msb, lsb, size * fix lsb * clean up * need this for CI * add missing body checksum/counter * get dir from BASEDIR first, then $HOME * fix CI * doesn't work * just hardcode when compiling * remove process_dbc * add test for startup time * test can parsing * fix * better * bye bye dbc.py * fix startswith * add function to get dbc names for plotjuggler * check DBC_FILE_PATH * revert * rename * slightly more consistent at ~0.57 seconds * make sure the tests make sense * test ms per line * use ctre * Revert "use ctre" This reverts commit 5e1a4440e465c901403a9717bbdef5b573e9838e. * compile regex at import time * add debug print and lower test time * add comment * 0.02 ms per line max * better error messages * only include what we need, and make it explicit * check total time check total time * no global * always a list * not a c loop any more * default to list * use hex * make static (only calculate path once) * seems to be more consistent now (30-38 ms) Co-authored-by: Dean Lee <deanlee3@gmail.com>
This commit is contained in:
parent
cda173c9f7
commit
b302f6934e
|
@ -47,7 +47,7 @@ env = Environment(
|
|||
"#opendbc/can/",
|
||||
],
|
||||
CFLAGS="-std=gnu11",
|
||||
CXXFLAGS="-std=c++1z",
|
||||
CXXFLAGS=["-std=c++1z"],
|
||||
CPPPATH=cpppath,
|
||||
CYTHONCFILESUFFIX=".cpp",
|
||||
tools=["default", "cython"]
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
Import('env', 'envCython', 'cereal', 'common')
|
||||
|
||||
import os
|
||||
from opendbc.can.process_dbc import process
|
||||
|
||||
dbcs = []
|
||||
for x in sorted(os.listdir('../')):
|
||||
if x.endswith(".dbc"):
|
||||
def compile_dbc(target, source, env):
|
||||
process(source[0].path, target[0].path)
|
||||
in_fn = [os.path.join('../', x), 'dbc_template.cc']
|
||||
out_fn = os.path.join('dbc_out', x.replace(".dbc", ".cc"))
|
||||
dbc = env.Command(out_fn, in_fn, compile_dbc)
|
||||
env.Depends(dbc, ["dbc.py", "process_dbc.py"])
|
||||
dbcs.append(dbc)
|
||||
|
||||
libdbc = env.SharedLibrary('libdbc', ["dbc.cc", "parser.cc", "packer.cc", "common.cc"]+dbcs, LIBS=[common, "capnp", "kj", "zmq"])
|
||||
envDBC = env.Clone()
|
||||
dbc_file_path = '-DDBC_FILE_PATH=\'"%s"\'' % (envDBC.Dir("..").abspath)
|
||||
envDBC['CXXFLAGS'] += [dbc_file_path]
|
||||
libdbc = envDBC.SharedLibrary('libdbc', ["dbc.cc", "parser.cc", "packer.cc", "common.cc"], LIBS=[common, "capnp", "kj", "zmq"])
|
||||
|
||||
# Build packer and parser
|
||||
lenv = envCython.Clone()
|
||||
|
|
|
@ -23,35 +23,33 @@ cdef extern from "common_dbc.h":
|
|||
CHRYSLER_CHECKSUM
|
||||
|
||||
cdef struct Signal:
|
||||
const char* name
|
||||
string name
|
||||
int start_bit, msb, lsb, size
|
||||
bool is_signed
|
||||
double factor, offset
|
||||
bool is_little_endian
|
||||
SignalType type
|
||||
|
||||
cdef struct Msg:
|
||||
const char* name
|
||||
string name
|
||||
uint32_t address
|
||||
unsigned int size
|
||||
size_t num_sigs
|
||||
const Signal *sigs
|
||||
vector[Signal] sigs
|
||||
|
||||
cdef struct Val:
|
||||
const char* name
|
||||
string name
|
||||
uint32_t address
|
||||
const char* def_val
|
||||
const Signal *sigs
|
||||
string def_val
|
||||
vector[Signal] sigs
|
||||
|
||||
cdef struct DBC:
|
||||
const char* name
|
||||
size_t num_msgs
|
||||
const Msg *msgs
|
||||
const Val *vals
|
||||
size_t num_vals
|
||||
string name
|
||||
vector[Msg] msgs
|
||||
vector[Val] vals
|
||||
|
||||
cdef struct SignalParseOptions:
|
||||
uint32_t address
|
||||
const char* name
|
||||
string name
|
||||
|
||||
|
||||
cdef struct MessageParseOptions:
|
||||
|
@ -60,7 +58,7 @@ cdef extern from "common_dbc.h":
|
|||
|
||||
cdef struct SignalValue:
|
||||
uint32_t address
|
||||
const char* name
|
||||
string name
|
||||
double value
|
||||
vector[double] all_values
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ struct SignalPackValue {
|
|||
|
||||
struct SignalParseOptions {
|
||||
uint32_t address;
|
||||
const char* name;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct MessageParseOptions {
|
||||
|
@ -24,7 +24,7 @@ struct MessageParseOptions {
|
|||
|
||||
struct SignalValue {
|
||||
uint32_t address;
|
||||
const char* name;
|
||||
std::string name;
|
||||
double value; // latest value
|
||||
std::vector<double> all_values; // all values from this cycle
|
||||
};
|
||||
|
@ -43,7 +43,7 @@ enum SignalType {
|
|||
};
|
||||
|
||||
struct Signal {
|
||||
const char* name;
|
||||
std::string name;
|
||||
int start_bit, msb, lsb, size;
|
||||
bool is_signed;
|
||||
double factor, offset;
|
||||
|
@ -52,34 +52,25 @@ struct Signal {
|
|||
};
|
||||
|
||||
struct Msg {
|
||||
const char* name;
|
||||
std::string name;
|
||||
uint32_t address;
|
||||
unsigned int size;
|
||||
size_t num_sigs;
|
||||
const Signal *sigs;
|
||||
std::vector<Signal> sigs;
|
||||
};
|
||||
|
||||
struct Val {
|
||||
const char* name;
|
||||
std::string name;
|
||||
uint32_t address;
|
||||
const char* def_val;
|
||||
const Signal *sigs;
|
||||
std::string def_val;
|
||||
std::vector<Signal> sigs;
|
||||
};
|
||||
|
||||
struct DBC {
|
||||
const char* name;
|
||||
size_t num_msgs;
|
||||
const Msg *msgs;
|
||||
const Val *vals;
|
||||
size_t num_vals;
|
||||
std::string name;
|
||||
std::vector<Msg> msgs;
|
||||
std::vector<Val> vals;
|
||||
};
|
||||
|
||||
std::vector<const DBC*>& get_dbcs();
|
||||
DBC* dbc_parse(const std::string& dbc_name, const std::string& dbc_file_path);
|
||||
const DBC* dbc_lookup(const std::string& dbc_name);
|
||||
|
||||
void dbc_register(const DBC* dbc);
|
||||
|
||||
#define dbc_init(dbc) \
|
||||
static void __attribute__((constructor)) do_dbc_init_ ## dbc(void) { \
|
||||
dbc_register(&dbc); \
|
||||
}
|
||||
std::vector<std::string> get_dbc_names();
|
||||
|
|
248
can/dbc.cc
248
can/dbc.cc
|
@ -1,27 +1,245 @@
|
|||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
|
||||
#include "common_dbc.h"
|
||||
|
||||
std::vector<const DBC*>& get_dbcs() {
|
||||
static std::vector<const DBC*> vec;
|
||||
return vec;
|
||||
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 << "] " << 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));
|
||||
}
|
||||
|
||||
typedef struct ChecksumState {
|
||||
int checksum_size;
|
||||
int counter_size;
|
||||
int checksum_start_bit;
|
||||
int counter_start_bit;
|
||||
bool little_endian;
|
||||
SignalType checksum_type;
|
||||
SignalType counter_type;
|
||||
} ChecksumState;
|
||||
|
||||
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_COUNTER});
|
||||
} else if (startswith(dbc_name, {"toyota_", "lexus_"})) {
|
||||
s = new ChecksumState({8, -1, 7, -1, false, TOYOTA_CHECKSUM});
|
||||
} else if (startswith(dbc_name, {"vw_", "volkswagen_", "audi_", "seat_", "skoda_"})) {
|
||||
s = new ChecksumState({8, 4, 0, 0, true, VOLKSWAGEN_CHECKSUM, VOLKSWAGEN_COUNTER});
|
||||
} else if (startswith(dbc_name, "subaru_global_")) {
|
||||
s = new ChecksumState({8, -1, 0, -1, true, SUBARU_CHECKSUM});
|
||||
} else if (startswith(dbc_name, "chrysler_")) {
|
||||
s = new ChecksumState({8, -1, 7, -1, false, CHRYSLER_CHECKSUM});
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void set_signal_type(Signal& s, uint32_t address, ChecksumState* chk, const std::string& dbc_name) {
|
||||
if (chk) {
|
||||
if (s.name == "CHECKSUM") {
|
||||
DBC_ASSERT(s.size == chk->checksum_size, "CHECKSUM is not " << chk->checksum_size << " bits long");
|
||||
DBC_ASSERT((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");
|
||||
s.type = chk->checksum_type;
|
||||
} 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 = chk->counter_type;
|
||||
}
|
||||
}
|
||||
// TODO: replace hardcoded addresses with signal names. prefix with COMMA_PEDAL_?
|
||||
if (address == 0x200 || address == 0x201) {
|
||||
if (s.name == "CHECKSUM_PEDAL") {
|
||||
DBC_ASSERT(s.size == 8, "PEDAL CHECKSUM is not 8 bits long");
|
||||
s.type = PEDAL_CHECKSUM;
|
||||
} else if (s.name == "COUNTER_PEDAL") {
|
||||
DBC_ASSERT(s.size == 4, "PEDAL COUNTER is not 4 bits long");
|
||||
s.type = PEDAL_COUNTER;
|
||||
}
|
||||
} else if (address == 0x250) {
|
||||
if (s.name == "CHECKSUM") {
|
||||
DBC_ASSERT(s.size == 8, "BODY CHECKSUM is not 8 bits long");
|
||||
s.type = PEDAL_CHECKSUM;
|
||||
} else if (s.name == "COUNTER") {
|
||||
DBC_ASSERT(s.size == 4, "BODY COUNTER is not 4 bits long");
|
||||
s.type = PEDAL_COUNTER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DBC* dbc_parse(const std::string& dbc_name, const std::string& dbc_file_path) {
|
||||
std::ifstream infile(dbc_file_path + "/" + dbc_name + ".dbc");
|
||||
if (!infile) return nullptr;
|
||||
|
||||
std::unique_ptr<ChecksumState> checksum(get_checksum(dbc_name));
|
||||
|
||||
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;
|
||||
std::smatch match;
|
||||
// TODO: see if we can speed up the regex statements in this loop, SG_ is specifically the slowest
|
||||
while (std::getline(infile, line)) {
|
||||
line = trim(line);
|
||||
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, address, checksum.get(), dbc_name);
|
||||
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;
|
||||
}
|
||||
|
||||
const std::string get_dbc_file_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) {
|
||||
for (const auto& dbci : get_dbcs()) {
|
||||
if (dbc_name == dbci->name) {
|
||||
return dbci;
|
||||
static std::mutex lock;
|
||||
static std::map<std::string, DBC*> dbcs;
|
||||
static const std::string& dbc_file_path = get_dbc_file_path();
|
||||
|
||||
std::unique_lock lk(lock);
|
||||
auto it = dbcs.find(dbc_name);
|
||||
if (it == dbcs.end()) {
|
||||
it = dbcs.insert(it, {dbc_name, dbc_parse(dbc_name, dbc_file_path)});
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void dbc_register(const DBC* dbc) {
|
||||
get_dbcs().push_back(dbc);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
const DBC* dbc_lookup(const char* dbc_name) {
|
||||
return dbc_lookup(std::string(dbc_name));
|
||||
std::vector<std::string> get_dbc_names() {
|
||||
static const std::string& dbc_file_path = get_dbc_file_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;
|
||||
}
|
||||
|
|
134
can/dbc.py
134
can/dbc.py
|
@ -1,134 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import numbers
|
||||
from collections import namedtuple, defaultdict
|
||||
|
||||
def int_or_float(s):
|
||||
# return number, trying to maintain int format
|
||||
if s.isdigit():
|
||||
return int(s, 10)
|
||||
else:
|
||||
return float(s)
|
||||
|
||||
|
||||
DBCSignal = namedtuple("DBCSignal", ["name", "start_bit", "msb", "lsb", "size", "is_little_endian", "is_signed",
|
||||
"factor", "offset", "tmin", "tmax", "units"])
|
||||
|
||||
|
||||
class dbc():
|
||||
def __init__(self, fn):
|
||||
self.name, _ = os.path.splitext(os.path.basename(fn))
|
||||
with open(fn, encoding="utf-8") as f:
|
||||
self.txt = f.readlines()
|
||||
self._warned_addresses = set()
|
||||
|
||||
# regexps from https://github.com/ebroecker/canmatrix/blob/master/canmatrix/importdbc.py
|
||||
bo_regexp = re.compile(r"^BO\_ (\w+) (\w+) *: (\w+) (\w+)")
|
||||
sg_regexp = re.compile(r"^SG\_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*)")
|
||||
sgm_regexp = re.compile(r"^SG\_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*)")
|
||||
val_regexp = re.compile(r"VAL\_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*)")
|
||||
|
||||
# A dictionary which maps message ids to tuples ((name, size), signals).
|
||||
# name is the ASCII name of the message.
|
||||
# size is the size of the message in bytes.
|
||||
# signals is a list signals contained in the message.
|
||||
# signals is a list of DBCSignal in order of increasing start_bit.
|
||||
self.msgs = {}
|
||||
|
||||
# A dictionary which maps message ids to a list of tuples (signal name, definition value pairs)
|
||||
self.def_vals = defaultdict(list)
|
||||
|
||||
# used to find big endian LSB from MSB and size
|
||||
be_bits = [(j + i*8) for i in range(64) for j in range(7, -1, -1)]
|
||||
|
||||
for l in self.txt:
|
||||
l = l.strip()
|
||||
|
||||
if l.startswith("BO_ "):
|
||||
# new group
|
||||
dat = bo_regexp.match(l)
|
||||
|
||||
if dat is None:
|
||||
print("bad BO {0}".format(l))
|
||||
|
||||
name = dat.group(2)
|
||||
size = int(dat.group(3))
|
||||
ids = int(dat.group(1), 0) # could be hex
|
||||
if ids in self.msgs:
|
||||
sys.exit("Duplicate address detected %d %s" % (ids, self.name))
|
||||
|
||||
self.msgs[ids] = ((name, size), [])
|
||||
|
||||
if l.startswith("SG_ "):
|
||||
# new signal
|
||||
dat = sg_regexp.match(l)
|
||||
go = 0
|
||||
if dat is None:
|
||||
dat = sgm_regexp.match(l)
|
||||
go = 1
|
||||
|
||||
if dat is None:
|
||||
print("bad SG {0}".format(l))
|
||||
|
||||
sgname = dat.group(1)
|
||||
start_bit = int(dat.group(go + 2))
|
||||
signal_size = int(dat.group(go + 3))
|
||||
is_little_endian = int(dat.group(go + 4)) == 1
|
||||
is_signed = dat.group(go + 5) == '-'
|
||||
factor = int_or_float(dat.group(go + 6))
|
||||
offset = int_or_float(dat.group(go + 7))
|
||||
tmin = int_or_float(dat.group(go + 8))
|
||||
tmax = int_or_float(dat.group(go + 9))
|
||||
units = dat.group(go + 10)
|
||||
|
||||
if is_little_endian:
|
||||
lsb = start_bit
|
||||
msb = start_bit + signal_size - 1
|
||||
else:
|
||||
lsb = be_bits[be_bits.index(start_bit) + signal_size - 1]
|
||||
msb = start_bit
|
||||
|
||||
self.msgs[ids][1].append(
|
||||
DBCSignal(sgname, start_bit, msb, lsb, signal_size, is_little_endian,
|
||||
is_signed, factor, offset, tmin, tmax, units))
|
||||
|
||||
assert lsb < (64*8) and msb < (64*8), f"Signal out of bounds: {msb=} {lsb=}"
|
||||
|
||||
if l.startswith("VAL_ "):
|
||||
# new signal value/definition
|
||||
dat = val_regexp.match(l)
|
||||
|
||||
if dat is None:
|
||||
print("bad VAL {0}".format(l))
|
||||
|
||||
ids = int(dat.group(1), 0) # could be hex
|
||||
sgname = dat.group(2)
|
||||
defvals = dat.group(3)
|
||||
|
||||
defvals = defvals.replace("?", r"\?") # escape sequence in C++
|
||||
defvals = defvals.split('"')[:-1]
|
||||
|
||||
# convert strings to UPPER_CASE_WITH_UNDERSCORES
|
||||
defvals[1::2] = [d.strip().upper().replace(" ", "_") for d in defvals[1::2]]
|
||||
defvals = '"' + "".join(str(i) for i in defvals) + '"'
|
||||
|
||||
self.def_vals[ids].append((sgname, defvals))
|
||||
|
||||
for msg in self.msgs.values():
|
||||
msg[1].sort(key=lambda x: x.start_bit)
|
||||
|
||||
self.msg_name_to_address = {}
|
||||
for address, m in self.msgs.items():
|
||||
name = m[0][0]
|
||||
self.msg_name_to_address[name] = address
|
||||
|
||||
def lookup_msg_id(self, msg_id):
|
||||
if not isinstance(msg_id, numbers.Number):
|
||||
msg_id = self.msg_name_to_address[msg_id]
|
||||
return msg_id
|
||||
|
||||
def get_signals(self, msg):
|
||||
msg = self.lookup_msg_id(msg)
|
||||
return [sgs.name for sgs in self.msgs[msg][1]]
|
|
@ -31,12 +31,10 @@ CANPacker::CANPacker(const std::string& dbc_name) {
|
|||
dbc = dbc_lookup(dbc_name);
|
||||
assert(dbc);
|
||||
|
||||
for (int i = 0; i < dbc->num_msgs; i++) {
|
||||
const Msg* msg = &dbc->msgs[i];
|
||||
message_lookup[msg->address] = *msg;
|
||||
for (int j = 0; j < msg->num_sigs; j++) {
|
||||
const Signal* sig = &msg->sigs[j];
|
||||
signal_lookup[std::make_pair(msg->address, std::string(sig->name))] = *sig;
|
||||
for (const auto& msg : dbc->msgs) {
|
||||
message_lookup[msg.address] = msg;
|
||||
for (const auto& sig : msg.sigs) {
|
||||
signal_lookup[std::make_pair(msg.address, std::string(sig.name))] = sig;
|
||||
}
|
||||
}
|
||||
init_crc_lookup_tables();
|
||||
|
|
|
@ -25,8 +25,7 @@ cdef class CANPacker:
|
|||
raise RuntimeError(f"Can't lookup {dbc_name}")
|
||||
|
||||
self.packer = new cpp_CANPacker(dbc_name)
|
||||
num_msgs = self.dbc[0].num_msgs
|
||||
for i in range(num_msgs):
|
||||
for i in range(self.dbc[0].msgs.size()):
|
||||
msg = self.dbc[0].msgs[i]
|
||||
self.name_to_address_and_size[string(msg.name)] = (msg.address, msg.size)
|
||||
self.address_to_size[msg.address] = msg.size
|
||||
|
|
|
@ -125,9 +125,9 @@ CANParser::CANParser(int abus, const std::string& dbc_name,
|
|||
}
|
||||
|
||||
const Msg* msg = NULL;
|
||||
for (int i = 0; i < dbc->num_msgs; i++) {
|
||||
if (dbc->msgs[i].address == op.address) {
|
||||
msg = &dbc->msgs[i];
|
||||
for (const auto& m : dbc->msgs) {
|
||||
if (m.address == op.address) {
|
||||
msg = &m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -140,10 +140,9 @@ CANParser::CANParser(int abus, const std::string& dbc_name,
|
|||
assert(state.size < 64); // max signal size is 64 bytes
|
||||
|
||||
// track checksums and counters for this message
|
||||
for (int i = 0; i < msg->num_sigs; i++) {
|
||||
const Signal *sig = &msg->sigs[i];
|
||||
if (sig->type != SignalType::DEFAULT) {
|
||||
state.parse_sigs.push_back(*sig);
|
||||
for (const auto& sig : msg->sigs) {
|
||||
if (sig.type != SignalType::DEFAULT) {
|
||||
state.parse_sigs.push_back(sig);
|
||||
state.vals.push_back(0);
|
||||
state.all_vals.push_back({});
|
||||
}
|
||||
|
@ -153,11 +152,9 @@ CANParser::CANParser(int abus, const std::string& dbc_name,
|
|||
for (const auto& sigop : sigoptions) {
|
||||
if (sigop.address != op.address) continue;
|
||||
|
||||
for (int i = 0; i < msg->num_sigs; i++) {
|
||||
const Signal *sig = &msg->sigs[i];
|
||||
if (strcmp(sig->name, sigop.name) == 0
|
||||
&& sig->type == SignalType::DEFAULT) {
|
||||
state.parse_sigs.push_back(*sig);
|
||||
for (const auto& sig : msg->sigs) {
|
||||
if (sig.name == sigop.name && sig.type == SignalType::DEFAULT) {
|
||||
state.parse_sigs.push_back(sig);
|
||||
state.vals.push_back(0);
|
||||
state.all_vals.push_back({});
|
||||
break;
|
||||
|
@ -175,18 +172,16 @@ CANParser::CANParser(int abus, const std::string& dbc_name, bool ignore_checksum
|
|||
assert(dbc);
|
||||
init_crc_lookup_tables();
|
||||
|
||||
for (int i = 0; i < dbc->num_msgs; i++) {
|
||||
const Msg* msg = &dbc->msgs[i];
|
||||
for (const auto& msg : dbc->msgs) {
|
||||
MessageState state = {
|
||||
.address = msg->address,
|
||||
.size = msg->size,
|
||||
.address = msg.address,
|
||||
.size = msg.size,
|
||||
.ignore_checksum = ignore_checksum,
|
||||
.ignore_counter = ignore_counter,
|
||||
};
|
||||
|
||||
for (int j = 0; j < msg->num_sigs; j++) {
|
||||
const Signal *sig = &msg->sigs[j];
|
||||
state.parse_sigs.push_back(*sig);
|
||||
for (const auto& sig : msg.sigs) {
|
||||
state.parse_sigs.push_back(sig);
|
||||
state.vals.push_back(0);
|
||||
state.all_vals.push_back({});
|
||||
}
|
||||
|
|
|
@ -48,9 +48,7 @@ cdef class CANParser:
|
|||
self.can_valid = False
|
||||
self.can_invalid_cnt = CAN_INVALID_CNT
|
||||
|
||||
cdef int i
|
||||
cdef int num_msgs = self.dbc[0].num_msgs
|
||||
for i in range(num_msgs):
|
||||
for i in range(self.dbc[0].msgs.size()):
|
||||
msg = self.dbc[0].msgs[i]
|
||||
name = msg.name.decode('utf8')
|
||||
|
||||
|
@ -156,12 +154,9 @@ cdef class CANDefine():
|
|||
if not self.dbc:
|
||||
raise RuntimeError(f"Can't find DBC: '{dbc_name}'")
|
||||
|
||||
num_vals = self.dbc[0].num_vals
|
||||
|
||||
address_to_msg_name = {}
|
||||
|
||||
num_msgs = self.dbc[0].num_msgs
|
||||
for i in range(num_msgs):
|
||||
for i in range(self.dbc[0].msgs.size()):
|
||||
msg = self.dbc[0].msgs[i]
|
||||
name = msg.name.decode('utf8')
|
||||
address = msg.address
|
||||
|
@ -169,7 +164,7 @@ cdef class CANDefine():
|
|||
|
||||
dv = defaultdict(dict)
|
||||
|
||||
for i in range(num_vals):
|
||||
for i in range(self.dbc[0].vals.size()):
|
||||
val = self.dbc[0].vals[i]
|
||||
|
||||
sgname = val.name.decode('utf8')
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
|
||||
import jinja2
|
||||
|
||||
from collections import Counter
|
||||
from opendbc.can.dbc import dbc
|
||||
|
||||
def process(in_fn, out_fn):
|
||||
dbc_name = os.path.split(out_fn)[-1].replace('.cc', '')
|
||||
# print("processing %s: %s -> %s" % (dbc_name, in_fn, out_fn))
|
||||
|
||||
template_fn = os.path.join(os.path.dirname(__file__), "dbc_template.cc")
|
||||
|
||||
with open(template_fn, "r") as template_f:
|
||||
template = jinja2.Template(template_f.read(), trim_blocks=True, lstrip_blocks=True)
|
||||
|
||||
can_dbc = dbc(in_fn)
|
||||
|
||||
# process counter and checksums first
|
||||
msgs = [(address, msg_name, msg_size, sorted(msg_sigs, key=lambda s: s.name not in ("COUNTER", "CHECKSUM")))
|
||||
for address, ((msg_name, msg_size), msg_sigs) in sorted(can_dbc.msgs.items()) if msg_sigs]
|
||||
|
||||
def_vals = {a: sorted(set(b)) for a, b in can_dbc.def_vals.items()} # remove duplicates
|
||||
def_vals = sorted(def_vals.items())
|
||||
|
||||
if can_dbc.name.startswith(("honda_", "acura_")):
|
||||
checksum_type = "honda"
|
||||
checksum_size = 4
|
||||
counter_size = 2
|
||||
checksum_start_bit = 3
|
||||
counter_start_bit = 5
|
||||
little_endian = False
|
||||
elif can_dbc.name.startswith(("toyota_", "lexus_")):
|
||||
checksum_type = "toyota"
|
||||
checksum_size = 8
|
||||
counter_size = None
|
||||
checksum_start_bit = 7
|
||||
counter_start_bit = None
|
||||
little_endian = False
|
||||
elif can_dbc.name.startswith(("vw_", "volkswagen_", "audi_", "seat_", "skoda_")):
|
||||
checksum_type = "volkswagen"
|
||||
checksum_size = 8
|
||||
counter_size = 4
|
||||
checksum_start_bit = 0
|
||||
counter_start_bit = 0
|
||||
little_endian = True
|
||||
elif can_dbc.name.startswith(("subaru_global_")):
|
||||
checksum_type = "subaru"
|
||||
checksum_size = 8
|
||||
counter_size = None
|
||||
checksum_start_bit = 0
|
||||
counter_start_bit = None
|
||||
little_endian = True
|
||||
elif can_dbc.name.startswith(("chrysler_", "stellantis_")):
|
||||
checksum_type = "chrysler"
|
||||
checksum_size = 8
|
||||
counter_size = None
|
||||
checksum_start_bit = 7
|
||||
counter_start_bit = None
|
||||
little_endian = False
|
||||
else:
|
||||
checksum_type = None
|
||||
checksum_size = None
|
||||
counter_size = None
|
||||
checksum_start_bit = None
|
||||
counter_start_bit = None
|
||||
little_endian = None
|
||||
|
||||
# sanity checks on expected COUNTER and CHECKSUM rules, as packer and parser auto-compute those signals
|
||||
for address, msg_name, _, sigs in msgs:
|
||||
dbc_msg_name = dbc_name + " " + msg_name
|
||||
for sig in sigs:
|
||||
if checksum_type is not None:
|
||||
# checksum rules
|
||||
if sig.name == "CHECKSUM":
|
||||
if sig.size != checksum_size:
|
||||
sys.exit("%s: CHECKSUM is not %d bits long" % (dbc_msg_name, checksum_size))
|
||||
if sig.start_bit % 8 != checksum_start_bit:
|
||||
sys.exit("%s: CHECKSUM starts at wrong bit" % dbc_msg_name)
|
||||
if little_endian != sig.is_little_endian:
|
||||
sys.exit("%s: CHECKSUM has wrong endianness" % dbc_msg_name)
|
||||
# counter rules
|
||||
if sig.name == "COUNTER":
|
||||
if counter_size is not None and sig.size != counter_size:
|
||||
sys.exit("%s: COUNTER is not %d bits long" % (dbc_msg_name, counter_size))
|
||||
if counter_start_bit is not None and sig.start_bit % 8 != counter_start_bit:
|
||||
print(counter_start_bit, sig.start_bit)
|
||||
sys.exit("%s: COUNTER starts at wrong bit" % dbc_msg_name)
|
||||
if little_endian != sig.is_little_endian:
|
||||
sys.exit("%s: COUNTER has wrong endianness" % dbc_msg_name)
|
||||
# pedal rules
|
||||
if address in [0x200, 0x201]:
|
||||
if sig.name == "COUNTER_PEDAL" and sig.size != 4:
|
||||
sys.exit("%s: PEDAL COUNTER is not 4 bits long" % dbc_msg_name)
|
||||
if sig.name == "CHECKSUM_PEDAL" and sig.size != 8:
|
||||
sys.exit("%s: PEDAL CHECKSUM is not 8 bits long" % dbc_msg_name)
|
||||
|
||||
# Fail on duplicate message names
|
||||
c = Counter([msg_name for address, msg_name, msg_size, sigs in msgs])
|
||||
for name, count in c.items():
|
||||
if count > 1:
|
||||
sys.exit("%s: Duplicate message name in DBC file %s" % (dbc_name, name))
|
||||
|
||||
parser_code = template.render(dbc=can_dbc, checksum_type=checksum_type, msgs=msgs, def_vals=def_vals)
|
||||
|
||||
with open(out_fn, "a+") as out_f:
|
||||
out_f.seek(0)
|
||||
if out_f.read() != parser_code:
|
||||
out_f.seek(0)
|
||||
out_f.truncate()
|
||||
out_f.write(parser_code)
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print("usage: %s dbc_directory output_filename" % (sys.argv[0],))
|
||||
sys.exit(0)
|
||||
|
||||
dbc_dir = sys.argv[1]
|
||||
out_fn = sys.argv[2]
|
||||
|
||||
dbc_name = os.path.split(out_fn)[-1].replace('.cc', '')
|
||||
in_fn = os.path.join(dbc_dir, dbc_name + '.dbc')
|
||||
|
||||
process(in_fn, out_fn)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env python3
|
||||
import glob
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from opendbc import DBC_PATH
|
||||
from opendbc.can.parser import CANParser
|
||||
|
||||
|
||||
class TestCANParser(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.dbcs = {}
|
||||
for dbc in glob.glob(f"{DBC_PATH}/*.dbc"):
|
||||
dbc_name = os.path.basename(dbc).split('.')[0]
|
||||
with open(dbc, 'r') as f:
|
||||
cls.dbcs[dbc_name] = len(f.readlines())
|
||||
|
||||
def test_dbc_parsing_speed(self):
|
||||
for dbc, lines in self.dbcs.items():
|
||||
start_time = time.monotonic()
|
||||
CANParser(dbc, [], [], 0)
|
||||
total_time_ms = (time.monotonic() - start_time) * 1000
|
||||
|
||||
self.assertTrue(total_time_ms < 45, f'{dbc}: total parsing time too high: {total_time_ms} ms >= 45 ms')
|
||||
ms_per_line = total_time_ms / lines
|
||||
self.assertTrue(ms_per_line < 0.02, f'{dbc}: max ms/line time too high: {ms_per_line} ms >= 0.02 ms')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue