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:
Shane Smiskol 2022-05-12 17:59:33 -07:00 committed by GitHub
parent cda173c9f7
commit b302f6934e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 320 additions and 367 deletions

View File

@ -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"]

View File

@ -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,4 +14,4 @@ parser = lenv.Program('parser_pyx.so', 'parser_pyx.pyx')
packer = lenv.Program('packer_pyx.so', 'packer_pyx.pyx')
lenv.Depends(parser, libdbc)
lenv.Depends(packer, libdbc)
lenv.Depends(packer, libdbc)

View File

@ -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

View File

@ -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();

View File

@ -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 it->second;
}
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 NULL;
}
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));
}
return dbcs;
}

View File

@ -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]]

View File

@ -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();

View File

@ -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

View File

@ -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({});
}

View File

@ -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')

View File

@ -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()

32
can/tests/test_can_parser.py Executable file
View File

@ -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()