Add core OP support for CRC validation, with extra support for Volkswagen MQB (#836)

* Generalized core OP CRC support plus extra bits for Volkswagen MQB.
old-commit-hash: d953b6a7aa
commatwo_master
Jason Young 6 years ago committed by rbiasini
parent 2479a4a9af
commit a4a53b7e7d
  1. 4
      opendbc/honda_accord_touring_2016_can.dbc
  2. 6
      selfdrive/can/common.h
  3. 4
      selfdrive/can/dbc_template.cc
  4. 2
      selfdrive/can/libdbc_py.py
  5. 17
      selfdrive/can/packer.cc
  6. 4
      selfdrive/can/packer_impl.pyx
  7. 117
      selfdrive/can/parser.cc
  8. 4
      selfdrive/can/parser_pyx.pxd
  9. 69
      selfdrive/can/process_dbc.py

@ -177,14 +177,14 @@ BO_ 597 ROUGH_WHEEL_SPEED: 8 VSA
SG_ SET_TO_X55_2 : 47|8@0+ (1,0) [0|255] "" NEO SG_ SET_TO_X55_2 : 47|8@0+ (1,0) [0|255] "" NEO
SG_ LONG_COUNTER : 55|8@0+ (1,0) [0|255] "" XXX SG_ LONG_COUNTER : 55|8@0+ (1,0) [0|255] "" XXX
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" XXX SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" XXX
SG_ CHECKSUM : 59|4@1+ (1,0) [0|15] "" XXX SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" XXX
BO_ 660 SCM_COMMANDS: 8 SCM BO_ 660 SCM_COMMANDS: 8 SCM
SG_ RIGHT_BLINKER : 6|1@0+ (1,0) [0|1] "" NEO SG_ RIGHT_BLINKER : 6|1@0+ (1,0) [0|1] "" NEO
SG_ LEFT_BLINKER : 5|1@0+ (1,0) [0|1] "" NEO SG_ LEFT_BLINKER : 5|1@0+ (1,0) [0|1] "" NEO
SG_ WIPERS_SPEED : 4|2@0+ (1,0) [0|3] "" NEO SG_ WIPERS_SPEED : 4|2@0+ (1,0) [0|3] "" NEO
SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" XXX SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" XXX
SG_ CHECKSUM : 59|4@1+ (1,0) [0|15] "" XXX SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" XXX
BO_ 661 XXX_10: 4 XXX BO_ 661 XXX_10: 4 XXX
SG_ COUNTER : 29|2@0+ (1,0) [0|3] "" XXX SG_ COUNTER : 29|2@0+ (1,0) [0|3] "" XXX

@ -7,11 +7,13 @@
#define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0])) #define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0]))
unsigned int honda_checksum(unsigned int address, uint64_t d, int l); unsigned int honda_checksum(unsigned int address, uint64_t d, int l);
unsigned int toyota_checksum(unsigned int address, uint64_t d, int l); unsigned int toyota_checksum(unsigned int address, uint64_t d, int l);
unsigned int pedal_checksum(unsigned int address, uint64_t d, int l); unsigned int pedal_checksum(unsigned int address, uint64_t d, int l);
void init_crc_lookup_tables();
unsigned int volkswagen_crc(unsigned int address, uint64_t d, int l);
struct SignalPackValue { struct SignalPackValue {
const char* name; const char* name;
double value; double value;
@ -44,6 +46,8 @@ enum SignalType {
TOYOTA_CHECKSUM, TOYOTA_CHECKSUM,
PEDAL_CHECKSUM, PEDAL_CHECKSUM,
PEDAL_COUNTER, PEDAL_COUNTER,
VOLKSWAGEN_CHECKSUM,
VOLKSWAGEN_COUNTER,
}; };
struct Signal { struct Signal {

@ -31,6 +31,10 @@ const Signal sigs_{{address}}[] = {
.type = SignalType::PEDAL_CHECKSUM, .type = SignalType::PEDAL_CHECKSUM,
{% elif address in [512, 513] and sig.name == "COUNTER_PEDAL" %} {% elif address in [512, 513] and sig.name == "COUNTER_PEDAL" %}
.type = SignalType::PEDAL_COUNTER, .type = SignalType::PEDAL_COUNTER,
{% elif checksum_type == "volkswagen" and sig.name == "CHECKSUM" %}
.type = SignalType::VOLKSWAGEN_CHECKSUM,
{% elif checksum_type == "volkswagen" and sig.name == "COUNTER" %}
.type = SignalType::VOLKSWAGEN_COUNTER,
{% else %} {% else %}
.type = SignalType::DEFAULT, .type = SignalType::DEFAULT,
{% endif %} {% endif %}

@ -41,6 +41,8 @@ typedef enum {
TOYOTA_CHECKSUM, TOYOTA_CHECKSUM,
PEDAL_CHECKSUM, PEDAL_CHECKSUM,
PEDAL_COUNTER, PEDAL_COUNTER,
VOLKSWAGEN_CHECKSUM,
VOLKSWAGEN_COUNTER,
} SignalType; } SignalType;
typedef struct { typedef struct {

@ -51,6 +51,8 @@ namespace {
signal_lookup[std::make_pair(msg->address, std::string(sig->name))] = *sig; signal_lookup[std::make_pair(msg->address, std::string(sig->name))] = *sig;
} }
} }
init_crc_lookup_tables();
} }
uint64_t pack(uint32_t address, const std::vector<SignalPackValue> &signals, int counter) { uint64_t pack(uint32_t address, const std::vector<SignalPackValue> &signals, int counter) {
@ -82,16 +84,16 @@ namespace {
} }
auto sig = sig_it->second; auto sig = sig_it->second;
if (sig.type != SignalType::HONDA_COUNTER){ if ((sig.type != SignalType::HONDA_COUNTER) && (sig.type != SignalType::VOLKSWAGEN_COUNTER)) {
WARN("COUNTER signal type not valid\n"); WARN("COUNTER signal type not valid\n");
} }
ret = set_value(ret, sig, counter); ret = set_value(ret, sig, counter);
} }
auto sig_it = signal_lookup.find(std::make_pair(address, "CHECKSUM")); auto sig_it_checksum = signal_lookup.find(std::make_pair(address, "CHECKSUM"));
if (sig_it != signal_lookup.end()) { if (sig_it_checksum != signal_lookup.end()) {
auto sig = sig_it->second; auto sig = sig_it_checksum->second;
if (sig.type == SignalType::HONDA_CHECKSUM) { if (sig.type == SignalType::HONDA_CHECKSUM) {
unsigned int chksm = honda_checksum(address, ret, message_lookup[address].size); unsigned int chksm = honda_checksum(address, ret, message_lookup[address].size);
ret = set_value(ret, sig, chksm); ret = set_value(ret, sig, chksm);
@ -99,6 +101,13 @@ namespace {
else if (sig.type == SignalType::TOYOTA_CHECKSUM) { else if (sig.type == SignalType::TOYOTA_CHECKSUM) {
unsigned int chksm = toyota_checksum(address, ret, message_lookup[address].size); unsigned int chksm = toyota_checksum(address, ret, message_lookup[address].size);
ret = set_value(ret, sig, chksm); ret = set_value(ret, sig, chksm);
}
else if (sig.type == SignalType::VOLKSWAGEN_CHECKSUM) {
// FIXME: Hackish fix for an endianness issue. The message is in reverse byte order
// until later in the pack process. Checksums can be run backwards, CRCs not so much.
// The correct fix is unclear but this works for the moment.
unsigned int chksm = volkswagen_crc(address, ReverseBytes(ret), message_lookup[address].size);
ret = set_value(ret, sig, chksm);
} else { } else {
//WARN("CHECKSUM signal type not valid\n"); //WARN("CHECKSUM signal type not valid\n");
} }

@ -20,7 +20,9 @@ ctypedef enum SignalType:
HONDA_COUNTER, HONDA_COUNTER,
TOYOTA_CHECKSUM, TOYOTA_CHECKSUM,
PEDAL_CHECKSUM, PEDAL_CHECKSUM,
PEDAL_COUNTER PEDAL_COUNTER,
VOLKSWAGEN_CHECKSUM,
VOLKSWAGEN_COUNTER
cdef struct Signal: cdef struct Signal:
const char* name const char* name

@ -24,9 +24,11 @@
// #define DEBUG printf // #define DEBUG printf
#define INFO printf #define INFO printf
#define MAX_BAD_COUNTER 5 #define MAX_BAD_COUNTER 5
// Static lookup table for fast computation of CRC8 poly 0x2F, aka 8H2F/AUTOSAR
uint8_t crc8_lut_8h2f[256];
unsigned int honda_checksum(unsigned int address, uint64_t d, int l) { unsigned int honda_checksum(unsigned int address, uint64_t d, int l) {
d >>= ((8-l)*8); // remove padding d >>= ((8-l)*8); // remove padding
d >>= 4; // remove checksum d >>= 4; // remove checksum
@ -75,6 +77,98 @@ unsigned int pedal_checksum(unsigned int address, uint64_t d, int l) {
return crc; return crc;
} }
void gen_crc_lookup_table(uint8_t poly, uint8_t crc_lut[])
{
uint8_t crc;
int i, j;
for (i = 0; i < 256; i++) {
crc = i;
for (j = 0; j < 8; j++) {
if ((crc & 0x80) != 0)
crc = (uint8_t)((crc << 1) ^ poly);
else
crc <<= 1;
}
crc_lut[i] = crc;
}
}
void init_crc_lookup_tables()
{
// At init time, set up static lookup tables for fast CRC computation.
gen_crc_lookup_table(0x2F, crc8_lut_8h2f); // CRC-8 8H2F/AUTOSAR for Volkswagen
}
unsigned int volkswagen_crc(unsigned int address, uint64_t d, int l)
{
// Volkswagen uses standard CRC8 8H2F/AUTOSAR, but they compute it with
// a magic variable padding byte tacked onto the end of the payload.
// https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf
uint8_t *dat = (uint8_t *)&d;
uint8_t crc = 0xFF; // Standard init value for CRC8 8H2F/AUTOSAR
// CRC the payload first, skipping over the first byte where the CRC lives.
for (int i = 1; i < l; i++) {
crc ^= dat[i];
crc = crc8_lut_8h2f[crc];
}
// Look up and apply the magic final CRC padding byte, which permutes by CAN
// address, and additionally (for SOME addresses) by the message counter.
uint8_t counter = dat[1] & 0x0F;
switch(address) {
case 0x86: // LWI_01 Steering Angle
crc ^= (uint8_t[]){0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86}[counter];
break;
case 0x9F: // EPS_01 Electric Power Steering
crc ^= (uint8_t[]){0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5}[counter];
break;
case 0xAD: // Getriebe_11 Automatic Gearbox
crc ^= (uint8_t[]){0x3F,0x69,0x39,0xDC,0x94,0xF9,0x14,0x64,0xD8,0x6A,0x34,0xCE,0xA2,0x55,0xB5,0x2C}[counter];
break;
case 0xFD: // ESP_21 Electronic Stability Program
crc ^= (uint8_t[]){0xB4,0xEF,0xF8,0x49,0x1E,0xE5,0xC2,0xC0,0x97,0x19,0x3C,0xC9,0xF1,0x98,0xD6,0x61}[counter];
break;
case 0x106: // ESP_05 Electronic Stability Program
crc ^= (uint8_t[]){0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07}[counter];
break;
case 0x117: // ACC_10 Automatic Cruise Control
crc ^= (uint8_t[]){0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC,0xAC}[counter];
break;
case 0x122: // ACC_06 Automatic Cruise Control
crc ^= (uint8_t[]){0x37,0x7D,0xF3,0xA9,0x18,0x46,0x6D,0x4D,0x3D,0x71,0x92,0x9C,0xE5,0x32,0x10,0xB9}[counter];
break;
case 0x126: // HCA_01 Heading Control Assist
crc ^= (uint8_t[]){0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA}[counter];
break;
case 0x12B: // GRA_ACC_01 Steering wheel controls for ACC
crc ^= (uint8_t[]){0x6A,0x38,0xB4,0x27,0x22,0xEF,0xE1,0xBB,0xF8,0x80,0x84,0x49,0xC7,0x9E,0x1E,0x2B}[counter];
break;
case 0x187: // EV_Gearshift "Gear" selection data for EVs with no gearbox
crc ^= (uint8_t[]){0x7F,0xED,0x17,0xC2,0x7C,0xEB,0x44,0x21,0x01,0xFA,0xDB,0x15,0x4A,0x6B,0x23,0x05}[counter];
break;
case 0x30C: // ACC_02 Automatic Cruise Control
crc ^= (uint8_t[]){0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F}[counter];
break;
case 0x3C0: // Klemmen_Status_01 ignition and starting status
crc ^= (uint8_t[]){0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3}[counter];
break;
case 0x65D: // ESP_20 Electronic Stability Program
crc ^= (uint8_t[]){0xAC,0xB3,0xAB,0xEB,0x7A,0xE1,0x3B,0xF7,0x73,0xBA,0x7C,0x9E,0x06,0x5F,0x02,0xD9}[counter];
break;
default: // As-yet undefined CAN message, CRC check expected to fail
INFO("Attempt to CRC check undefined Volkswagen message 0x%02X\n", address);
crc ^= (uint8_t[]){0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}[counter];
break;
}
crc = crc8_lut_8h2f[crc];
return crc ^ 0xFF; // Return after standard final XOR for CRC8 8H2F/AUTOSAR
}
namespace { namespace {
uint64_t read_u64_be(const uint8_t* v) { uint64_t read_u64_be(const uint8_t* v) {
@ -129,11 +223,11 @@ struct MessageState {
tmp -= (tmp >> (sig.b2-1)) ? (1ULL << sig.b2) : 0; //signed tmp -= (tmp >> (sig.b2-1)) ? (1ULL << sig.b2) : 0; //signed
} }
DEBUG("parse %X %s -> %lld\n", address, sig.name, tmp); DEBUG("parse 0x%X %s -> %lld\n", address, sig.name, tmp);
if (sig.type == SignalType::HONDA_CHECKSUM) { if (sig.type == SignalType::HONDA_CHECKSUM) {
if (honda_checksum(address, dat, size) != tmp) { if (honda_checksum(address, dat, size) != tmp) {
INFO("%X CHECKSUM FAIL\n", address); INFO("0x%X CHECKSUM FAIL\n", address);
return false; return false;
} }
} else if (sig.type == SignalType::HONDA_COUNTER) { } else if (sig.type == SignalType::HONDA_COUNTER) {
@ -142,12 +236,21 @@ struct MessageState {
} }
} else if (sig.type == SignalType::TOYOTA_CHECKSUM) { } else if (sig.type == SignalType::TOYOTA_CHECKSUM) {
if (toyota_checksum(address, dat, size) != tmp) { if (toyota_checksum(address, dat, size) != tmp) {
INFO("%X CHECKSUM FAIL\n", address); INFO("0x%X CHECKSUM FAIL\n", address);
return false;
}
} else if (sig.type == SignalType::VOLKSWAGEN_CHECKSUM) {
if (volkswagen_crc(address, dat, size) != tmp) {
INFO("0x%X CRC FAIL\n", address);
return false;
}
} else if (sig.type == SignalType::VOLKSWAGEN_COUNTER) {
if (!update_counter_generic(tmp, sig.b2)) {
return false; return false;
} }
} else if (sig.type == SignalType::PEDAL_CHECKSUM) { } else if (sig.type == SignalType::PEDAL_CHECKSUM) {
if (pedal_checksum(address, dat, size) != tmp) { if (pedal_checksum(address, dat, size) != tmp) {
INFO("%X PEDAL CHECKSUM FAIL\n", address); INFO("0x%X PEDAL CHECKSUM FAIL\n", address);
return false; return false;
} }
} else if (sig.type == SignalType::PEDAL_COUNTER) { } else if (sig.type == SignalType::PEDAL_COUNTER) {
@ -171,7 +274,7 @@ struct MessageState {
if (((old_counter+1) & ((1 << cnt_size) -1)) != v) { if (((old_counter+1) & ((1 << cnt_size) -1)) != v) {
counter_fail += 1; counter_fail += 1;
if (counter_fail > 1) { if (counter_fail > 1) {
INFO("%X COUNTER FAIL %d -- %d vs %d\n", address, counter_fail, old_counter, (int)v); INFO("0x%X COUNTER FAIL %d -- %d vs %d\n", address, counter_fail, old_counter, (int)v);
} }
if (counter_fail >= MAX_BAD_COUNTER) { if (counter_fail >= MAX_BAD_COUNTER) {
return false; return false;
@ -224,6 +327,8 @@ class CANParser {
dbc = dbc_lookup(dbc_name); dbc = dbc_lookup(dbc_name);
assert(dbc); assert(dbc);
init_crc_lookup_tables();
for (const auto& op : options) { for (const auto& op : options) {
MessageState state = { MessageState state = {
.address = op.address, .address = op.address,

@ -14,7 +14,9 @@ ctypedef enum SignalType:
HONDA_COUNTER, HONDA_COUNTER,
TOYOTA_CHECKSUM, TOYOTA_CHECKSUM,
PEDAL_CHECKSUM, PEDAL_CHECKSUM,
PEDAL_COUNTER PEDAL_COUNTER,
VOLKSWAGEN_CHECKSUM,
VOLKSWAGEN_COUNTER
cdef struct Signal: cdef struct Signal:
const char* name const char* name

@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import print_function
import os import os
import glob import glob
import sys import sys
@ -39,50 +38,78 @@ def main():
if dbc_mtime < out_mtime and template_mtime < out_mtime and this_file_mtime < out_mtime: if dbc_mtime < out_mtime and template_mtime < out_mtime and this_file_mtime < out_mtime:
continue #skip output is newer than template and dbc continue #skip output is newer than template and dbc
msgs = [(address, msg_name, msg_size, sorted(msg_sigs, key=lambda s: s.name not in (b"COUNTER", b"CHECKSUM"))) # process counter and checksums first msgs = [(address, msg_name, msg_size, sorted(msg_sigs, key=lambda s: s.name not in ("COUNTER", "CHECKSUM"))) # process counter and checksums first
for address, ((msg_name, msg_size), msg_sigs) in sorted(can_dbc.msgs.items()) if msg_sigs] for address, ((msg_name, msg_size), msg_sigs) in sorted(can_dbc.msgs.items()) if msg_sigs]
def_vals = {a: set(b) for a,b in can_dbc.def_vals.items()} #remove duplicates def_vals = {a: set(b) for a,b in can_dbc.def_vals.items()} #remove duplicates
def_vals = [(address, sig) for address, sig in sorted(def_vals.items())] def_vals = [(address, sig) for address, sig in sorted(def_vals.items())]
if can_dbc.name.startswith("honda") or can_dbc.name.startswith("acura"): if can_dbc.name.startswith(("honda_", "acura_")):
checksum_type = "honda" checksum_type = "honda"
checksum_size = 4 checksum_size = 4
elif can_dbc.name.startswith("toyota") or can_dbc.name.startswith("lexus"): 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_type = "toyota"
checksum_size = 8 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
else: else:
checksum_type = None 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, msg_size, sigs in msgs: for address, msg_name, msg_size, sigs in msgs:
dbc_msg_name = dbc_name + " " + msg_name
for sig in sigs: for sig in sigs:
if checksum_type is not None and sig.name == b"CHECKSUM": if checksum_type is not None:
# checksum rules
if sig.name == "CHECKSUM":
if sig.size != checksum_size: if sig.size != checksum_size:
sys.exit("CHECKSUM is not %d bits longs %s" % (checksum_size, msg_name)) sys.exit("%s: CHECKSUM is not %d bits long" % (dbc_msg_name, checksum_size))
if checksum_type == "honda" and sig.start_bit % 8 != 3: if sig.start_bit % 8 != checksum_start_bit:
sys.exit("CHECKSUM starts at wrong bit %s" % msg_name) sys.exit("%s: CHECKSUM starts at wrong bit" % dbc_msg_name)
if checksum_type == "toyota" and sig.start_bit % 8 != 7: if little_endian != sig.is_little_endian:
sys.exit("CHECKSUM starts at wrong bit %s" % msg_name) sys.exit("%s: CHECKSUM has wrong endianess" % dbc_msg_name)
if checksum_type == "honda" and sig.name == b"COUNTER": # counter rules
if sig.size != 2: if sig.name == "COUNTER":
sys.exit("COUNTER is not 2 bits longs %s" % msg_name) if counter_size is not None and sig.size != counter_size:
if sig.start_bit % 8 != 5: sys.exit("%s: COUNTER is not %d bits long" % (dbc_msg_name, counter_size))
sys.exit("COUNTER starts at wrong bit %s" % msg_name) 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 endianess" % dbc_msg_name)
# pedal rules
if address in [0x200, 0x201]: if address in [0x200, 0x201]:
if sig.name == b"COUNTER_PEDAL" and sig.size != 4: if sig.name == "COUNTER_PEDAL" and sig.size != 4:
sys.exit("PEDAL COUNTER is not 4 bits longs %s" % msg_name) sys.exit("%s: PEDAL COUNTER is not 4 bits long" % dbc_msg_name)
if sig.name == b"CHECKSUM_PEDAL" and sig.size != 8: if sig.name == "CHECKSUM_PEDAL" and sig.size != 8:
sys.exit("PEDAL CHECKSUM is not 8 bits longs %s" % msg_name) sys.exit("%s: PEDAL CHECKSUM is not 8 bits long" % dbc_msg_name)
# Fail on duplicate message names # Fail on duplicate message names
c = Counter([msg_name for address, msg_name, msg_size, sigs in msgs]) c = Counter([msg_name for address, msg_name, msg_size, sigs in msgs])
for name, count in c.items(): for name, count in c.items():
if count > 1: if count > 1:
sys.exit("Duplicate message name in DBC file %s" % name) 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, len=len) parser_code = template.render(dbc=can_dbc, checksum_type=checksum_type, msgs=msgs, def_vals=def_vals, len=len)
with open(out_fn, "w") as out_f: with open(out_fn, "w") as out_f:
out_f.write(parser_code) out_f.write(parser_code)

Loading…
Cancel
Save