commit
ee493eed79
63 changed files with 2721 additions and 215 deletions
@ -1,2 +1,10 @@ |
||||
*.pyc |
||||
*.os |
||||
*.tmp |
||||
.*.swp |
||||
can/*.so |
||||
can/build/ |
||||
can/obj/ |
||||
can/packer_pyx.cpp |
||||
can/parser_pyx.cpp |
||||
can/packer_impl.cpp |
||||
|
@ -0,0 +1,25 @@ |
||||
from ubuntu:16.04 |
||||
|
||||
RUN apt-get update && apt-get install -y libzmq3-dev clang wget git autoconf libtool curl make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python-openssl |
||||
|
||||
RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash |
||||
ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" |
||||
RUN pyenv install 3.7.3 |
||||
RUN pyenv global 3.7.3 |
||||
RUN pyenv rehash |
||||
|
||||
COPY requirements.txt /tmp/ |
||||
RUN pip install -r /tmp/requirements.txt |
||||
|
||||
ENV PYTHONPATH=/project |
||||
|
||||
# TODO: Add tag to cereal |
||||
RUN git clone https://github.com/commaai/cereal.git /project/cereal |
||||
RUN /project/cereal/install_capnp.sh |
||||
|
||||
WORKDIR /project |
||||
|
||||
COPY SConstruct . |
||||
COPY . /project/opendbc |
||||
|
||||
RUN scons -c && scons -j$(nproc) |
@ -0,0 +1,57 @@ |
||||
import os |
||||
import subprocess |
||||
|
||||
zmq = 'zmq' |
||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() |
||||
|
||||
cereal_dir = Dir('.') |
||||
|
||||
cpppath = [ |
||||
'#', |
||||
'#cereal', |
||||
"#cereal/messaging", |
||||
"#opendbc/can", |
||||
'/usr/lib/include', |
||||
] |
||||
|
||||
AddOption('--test', |
||||
action='store_true', |
||||
help='build test files') |
||||
|
||||
AddOption('--asan', |
||||
action='store_true', |
||||
help='turn on ASAN') |
||||
|
||||
ccflags_asan = ["-fsanitize=address", "-fno-omit-frame-pointer"] if GetOption('asan') else [] |
||||
ldflags_asan = ["-fsanitize=address"] if GetOption('asan') else [] |
||||
|
||||
env = Environment( |
||||
ENV=os.environ, |
||||
CC='clang', |
||||
CXX='clang++', |
||||
CCFLAGS=[ |
||||
"-g", |
||||
"-fPIC", |
||||
"-O2", |
||||
"-Werror=implicit-function-declaration", |
||||
"-Werror=incompatible-pointer-types", |
||||
"-Werror=int-conversion", |
||||
"-Werror=return-type", |
||||
"-Werror=format-extra-args", |
||||
] + ccflags_asan, |
||||
LDFLAGS=ldflags_asan, |
||||
LINKFLAGS=ldflags_asan, |
||||
|
||||
CFLAGS="-std=gnu11", |
||||
CXXFLAGS="-std=c++14", |
||||
CPPPATH=cpppath, |
||||
) |
||||
|
||||
Export('env', 'zmq', 'arch') |
||||
|
||||
cereal = [File('#cereal/libcereal.a')] |
||||
messaging = [File('#cereal/libmessaging.a')] |
||||
Export('cereal', 'messaging') |
||||
|
||||
SConscript(['cereal/SConscript']) |
||||
SConscript(['opendbc/can/SConscript']) |
@ -0,0 +1,16 @@ |
||||
pr: none |
||||
|
||||
pool: |
||||
vmImage: 'ubuntu-16.04' |
||||
steps: |
||||
- script: | |
||||
set -e |
||||
docker build -t opendbc . |
||||
displayName: 'Build' |
||||
- script: | |
||||
docker run opendbc bash -c "python -m unittest discover opendbc" |
||||
displayName: 'Unit tests' |
||||
- script: | |
||||
docker run opendbc bash -c "cd opendbc/can/tests/linter_python; PYTHONPATH=/ ./flake8_opendbc.sh" |
||||
docker run opendbc bash -c "cd opendbc/can/tests/linter_python; PYTHONPATH=/ ./pylint_opendbc.sh" |
||||
displayName: 'Python linter' |
@ -0,0 +1,27 @@ |
||||
Import('env', 'cereal') |
||||
|
||||
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) |
||||
dbcs.append(dbc) |
||||
|
||||
|
||||
libdbc = env.SharedLibrary('libdbc', ["dbc.cc", "parser.cc", "packer.cc", "common.cc"]+dbcs, LIBS=["capnp", "kj"]) |
||||
|
||||
# packer |
||||
env.Command(['packer_pyx.so'], |
||||
[libdbc, 'packer_pyx.pyx', 'packer_pyx_setup.py'], |
||||
"cd opendbc/can && python3 packer_pyx_setup.py build_ext --inplace") |
||||
|
||||
# parser |
||||
env.Command(['parser_pyx.so'], |
||||
[libdbc, cereal, 'parser_pyx_setup.py', 'parser_pyx.pyx', 'common.pxd'], |
||||
"cd opendbc/can && python3 parser_pyx_setup.py build_ext --inplace") |
@ -0,0 +1,2 @@ |
||||
from opendbc.can.parser_pyx import CANDefine # pylint: disable=no-name-in-module, import-error |
||||
assert CANDefine |
@ -0,0 +1,165 @@ |
||||
#include "common.h" |
||||
|
||||
unsigned int honda_checksum(unsigned int address, uint64_t d, int l) { |
||||
d >>= ((8-l)*8); // remove padding
|
||||
d >>= 4; // remove checksum
|
||||
|
||||
int s = 0; |
||||
while (address) { s += (address & 0xF); address >>= 4; } |
||||
while (d) { s += (d & 0xF); d >>= 4; } |
||||
s = 8-s; |
||||
s &= 0xF; |
||||
|
||||
return s; |
||||
} |
||||
|
||||
unsigned int toyota_checksum(unsigned int address, uint64_t d, int l) { |
||||
d >>= ((8-l)*8); // remove padding
|
||||
d >>= 8; // remove checksum
|
||||
|
||||
unsigned int s = l; |
||||
while (address) { s += address & 0xff; address >>= 8; } |
||||
while (d) { s += d & 0xff; d >>= 8; } |
||||
|
||||
return s & 0xFF; |
||||
} |
||||
|
||||
// Static lookup table for fast computation of CRC8 poly 0x2F, aka 8H2F/AUTOSAR
|
||||
uint8_t crc8_lut_8h2f[256]; |
||||
|
||||
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
|
||||
printf("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
|
||||
} |
||||
|
||||
|
||||
unsigned int pedal_checksum(uint64_t d, int l) { |
||||
uint8_t crc = 0xFF; |
||||
uint8_t poly = 0xD5; // standard crc8
|
||||
|
||||
d >>= ((8-l)*8); // remove padding
|
||||
d >>= 8; // remove checksum
|
||||
|
||||
uint8_t *dat = (uint8_t *)&d; |
||||
|
||||
int i, j; |
||||
for (i = 0; i < l - 1; i++) { |
||||
crc ^= dat[i]; |
||||
for (j = 0; j < 8; j++) { |
||||
if ((crc & 0x80) != 0) { |
||||
crc = (uint8_t)((crc << 1) ^ poly); |
||||
} |
||||
else { |
||||
crc <<= 1; |
||||
} |
||||
} |
||||
} |
||||
return crc; |
||||
} |
||||
|
||||
|
||||
uint64_t read_u64_be(const uint8_t* v) { |
||||
return (((uint64_t)v[0] << 56) |
||||
| ((uint64_t)v[1] << 48) |
||||
| ((uint64_t)v[2] << 40) |
||||
| ((uint64_t)v[3] << 32) |
||||
| ((uint64_t)v[4] << 24) |
||||
| ((uint64_t)v[5] << 16) |
||||
| ((uint64_t)v[6] << 8) |
||||
| (uint64_t)v[7]); |
||||
} |
||||
|
||||
uint64_t read_u64_le(const uint8_t* v) { |
||||
return ((uint64_t)v[0] |
||||
| ((uint64_t)v[1] << 8) |
||||
| ((uint64_t)v[2] << 16) |
||||
| ((uint64_t)v[3] << 24) |
||||
| ((uint64_t)v[4] << 32) |
||||
| ((uint64_t)v[5] << 40) |
||||
| ((uint64_t)v[6] << 48) |
||||
| ((uint64_t)v[7] << 56)); |
||||
} |
@ -0,0 +1,70 @@ |
||||
#pragma once |
||||
|
||||
#include <vector> |
||||
#include <map> |
||||
#include <unordered_map> |
||||
|
||||
#include "common_dbc.h" |
||||
#include <capnp/serialize.h> |
||||
#include "cereal/gen/cpp/log.capnp.h" |
||||
|
||||
#define MAX_BAD_COUNTER 5 |
||||
|
||||
// Helper functions
|
||||
unsigned int honda_checksum(unsigned int address, uint64_t d, int l); |
||||
unsigned int toyota_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); |
||||
unsigned int pedal_checksum(uint64_t d, int l); |
||||
uint64_t read_u64_be(const uint8_t* v); |
||||
uint64_t read_u64_le(const uint8_t* v); |
||||
|
||||
class MessageState { |
||||
public: |
||||
uint32_t address; |
||||
unsigned int size; |
||||
|
||||
std::vector<Signal> parse_sigs; |
||||
std::vector<double> vals; |
||||
|
||||
uint16_t ts; |
||||
uint64_t seen; |
||||
uint64_t check_threshold; |
||||
|
||||
uint8_t counter; |
||||
uint8_t counter_fail; |
||||
|
||||
bool parse(uint64_t sec, uint16_t ts_, uint8_t * dat); |
||||
bool update_counter_generic(int64_t v, int cnt_size); |
||||
}; |
||||
|
||||
class CANParser { |
||||
private: |
||||
const int bus; |
||||
|
||||
const DBC *dbc = NULL; |
||||
std::unordered_map<uint32_t, MessageState> message_states; |
||||
|
||||
public: |
||||
bool can_valid = false; |
||||
uint64_t last_sec = 0; |
||||
|
||||
CANParser(int abus, const std::string& dbc_name, |
||||
const std::vector<MessageParseOptions> &options, |
||||
const std::vector<SignalParseOptions> &sigoptions); |
||||
void UpdateCans(uint64_t sec, const capnp::List<cereal::CanData>::Reader& cans); |
||||
void UpdateValid(uint64_t sec); |
||||
void update_string(std::string data, bool sendcan); |
||||
std::vector<SignalValue> query_latest(); |
||||
}; |
||||
|
||||
class CANPacker { |
||||
private: |
||||
const DBC *dbc = NULL; |
||||
std::map<std::pair<uint32_t, std::string>, Signal> signal_lookup; |
||||
std::map<uint32_t, Msg> message_lookup; |
||||
|
||||
public: |
||||
CANPacker(const std::string& dbc_name); |
||||
uint64_t pack(uint32_t address, const std::vector<SignalPackValue> &signals, int counter); |
||||
}; |
@ -0,0 +1,82 @@ |
||||
# distutils: language = c++ |
||||
#cython: language_level=3 |
||||
|
||||
from libc.stdint cimport uint32_t, uint64_t, uint16_t |
||||
from libcpp.vector cimport vector |
||||
from libcpp.map cimport map |
||||
from libcpp.string cimport string |
||||
from libcpp.unordered_set cimport unordered_set |
||||
from libcpp cimport bool |
||||
|
||||
|
||||
cdef extern from "common_dbc.h": |
||||
ctypedef enum SignalType: |
||||
DEFAULT, |
||||
HONDA_CHECKSUM, |
||||
HONDA_COUNTER, |
||||
TOYOTA_CHECKSUM, |
||||
PEDAL_CHECKSUM, |
||||
PEDAL_COUNTER, |
||||
VOLKSWAGEN_CHECKSUM, |
||||
VOLKSWAGEN_COUNTER |
||||
|
||||
cdef struct Signal: |
||||
const char* name |
||||
int b1, b2, bo |
||||
bool is_signed |
||||
double factor, offset |
||||
SignalType type |
||||
|
||||
cdef struct Msg: |
||||
const char* name |
||||
uint32_t address |
||||
unsigned int size |
||||
size_t num_sigs |
||||
const Signal *sigs |
||||
|
||||
cdef struct Val: |
||||
const char* name |
||||
uint32_t address |
||||
const char* def_val |
||||
const Signal *sigs |
||||
|
||||
cdef struct DBC: |
||||
const char* name |
||||
size_t num_msgs |
||||
const Msg *msgs |
||||
const Val *vals |
||||
size_t num_vals |
||||
|
||||
cdef struct SignalParseOptions: |
||||
uint32_t address |
||||
const char* name |
||||
double default_value |
||||
|
||||
|
||||
cdef struct MessageParseOptions: |
||||
uint32_t address |
||||
int check_frequency |
||||
|
||||
cdef struct SignalValue: |
||||
uint32_t address |
||||
uint16_t ts |
||||
const char* name |
||||
double value |
||||
|
||||
cdef struct SignalPackValue: |
||||
const char * name |
||||
double value |
||||
|
||||
|
||||
cdef extern from "common.h": |
||||
cdef const DBC* dbc_lookup(const string); |
||||
|
||||
cdef cppclass CANParser: |
||||
bool can_valid |
||||
CANParser(int, string, vector[MessageParseOptions], vector[SignalParseOptions]) |
||||
void update_string(string, bool) |
||||
vector[SignalValue] query_latest() |
||||
|
||||
cdef cppclass CANPacker: |
||||
CANPacker(string) |
||||
uint64_t pack(uint32_t, vector[SignalPackValue], int counter) |
@ -0,0 +1,82 @@ |
||||
#pragma once |
||||
|
||||
#include <cstddef> |
||||
#include <cstdint> |
||||
#include <string> |
||||
|
||||
#define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0])) |
||||
|
||||
struct SignalPackValue { |
||||
const char* name; |
||||
double value; |
||||
}; |
||||
|
||||
struct SignalParseOptions { |
||||
uint32_t address; |
||||
const char* name; |
||||
double default_value; |
||||
}; |
||||
|
||||
struct MessageParseOptions { |
||||
uint32_t address; |
||||
int check_frequency; |
||||
}; |
||||
|
||||
struct SignalValue { |
||||
uint32_t address; |
||||
uint16_t ts; |
||||
const char* name; |
||||
double value; |
||||
}; |
||||
|
||||
enum SignalType { |
||||
DEFAULT, |
||||
HONDA_CHECKSUM, |
||||
HONDA_COUNTER, |
||||
TOYOTA_CHECKSUM, |
||||
PEDAL_CHECKSUM, |
||||
PEDAL_COUNTER, |
||||
VOLKSWAGEN_CHECKSUM, |
||||
VOLKSWAGEN_COUNTER, |
||||
}; |
||||
|
||||
struct Signal { |
||||
const char* name; |
||||
int b1, b2, bo; |
||||
bool is_signed; |
||||
double factor, offset; |
||||
bool is_little_endian; |
||||
SignalType type; |
||||
}; |
||||
|
||||
struct Msg { |
||||
const char* name; |
||||
uint32_t address; |
||||
unsigned int size; |
||||
size_t num_sigs; |
||||
const Signal *sigs; |
||||
}; |
||||
|
||||
struct Val { |
||||
const char* name; |
||||
uint32_t address; |
||||
const char* def_val; |
||||
const Signal *sigs; |
||||
}; |
||||
|
||||
struct DBC { |
||||
const char* name; |
||||
size_t num_msgs; |
||||
const Msg *msgs; |
||||
const Val *vals; |
||||
size_t num_vals; |
||||
}; |
||||
|
||||
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); \
|
||||
} |
@ -0,0 +1,31 @@ |
||||
#include <vector> |
||||
|
||||
#include "common_dbc.h" |
||||
|
||||
namespace { |
||||
|
||||
std::vector<const DBC*>& get_dbcs() { |
||||
static std::vector<const DBC*> vec; |
||||
return vec; |
||||
} |
||||
|
||||
} |
||||
|
||||
const DBC* dbc_lookup(const std::string& dbc_name) { |
||||
for (const auto& dbci : get_dbcs()) { |
||||
if (dbc_name == dbci->name) { |
||||
return dbci; |
||||
} |
||||
} |
||||
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)); |
||||
} |
||||
} |
@ -0,0 +1,275 @@ |
||||
import re |
||||
import os |
||||
import struct |
||||
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", "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="ascii") 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) |
||||
|
||||
# lookup to bit reverse each byte |
||||
self.bits_index = [(i & ~0b111) + ((-i-1) & 0b111) for i in range(64)] |
||||
|
||||
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) |
||||
|
||||
self.msgs[ids][1].append( |
||||
DBCSignal(sgname, start_bit, signal_size, is_little_endian, |
||||
is_signed, factor, offset, tmin, tmax, units)) |
||||
|
||||
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 reverse_bytes(self, x): |
||||
return ((x & 0xff00000000000000) >> 56) | \ |
||||
((x & 0x00ff000000000000) >> 40) | \ |
||||
((x & 0x0000ff0000000000) >> 24) | \ |
||||
((x & 0x000000ff00000000) >> 8) | \ |
||||
((x & 0x00000000ff000000) << 8) | \ |
||||
((x & 0x0000000000ff0000) << 24) | \ |
||||
((x & 0x000000000000ff00) << 40) | \ |
||||
((x & 0x00000000000000ff) << 56) |
||||
|
||||
def encode(self, msg_id, dd): |
||||
"""Encode a CAN message using the dbc. |
||||
|
||||
Inputs: |
||||
msg_id: The message ID. |
||||
dd: A dictionary mapping signal name to signal data. |
||||
""" |
||||
msg_id = self.lookup_msg_id(msg_id) |
||||
|
||||
msg_def = self.msgs[msg_id] |
||||
size = msg_def[0][1] |
||||
|
||||
result = 0 |
||||
for s in msg_def[1]: |
||||
ival = dd.get(s.name) |
||||
if ival is not None: |
||||
|
||||
ival = (ival / s.factor) - s.offset |
||||
ival = int(round(ival)) |
||||
|
||||
if s.is_signed and ival < 0: |
||||
ival = (1 << s.size) + ival |
||||
|
||||
if s.is_little_endian: |
||||
shift = s.start_bit |
||||
else: |
||||
b1 = (s.start_bit // 8) * 8 + (-s.start_bit - 1) % 8 |
||||
shift = 64 - (b1 + s.size) |
||||
|
||||
mask = ((1 << s.size) - 1) << shift |
||||
dat = (ival & ((1 << s.size) - 1)) << shift |
||||
|
||||
if s.is_little_endian: |
||||
mask = self.reverse_bytes(mask) |
||||
dat = self.reverse_bytes(dat) |
||||
|
||||
result &= ~mask |
||||
result |= dat |
||||
|
||||
result = struct.pack('>Q', result) |
||||
return result[:size] |
||||
|
||||
def decode(self, x, arr=None, debug=False): |
||||
"""Decode a CAN message using the dbc. |
||||
|
||||
Inputs: |
||||
x: A collection with elements (address, time, data), where address is |
||||
the CAN address, time is the bus time, and data is the CAN data as a |
||||
hex string. |
||||
arr: Optional list of signals which should be decoded and returned. |
||||
debug: True to print debugging statements. |
||||
|
||||
Returns: |
||||
A tuple (name, data), where name is the name of the CAN message and data |
||||
is the decoded result. If arr is None, data is a dict of properties. |
||||
Otherwise data is a list of the same length as arr. |
||||
|
||||
Returns (None, None) if the message could not be decoded. |
||||
""" |
||||
|
||||
if arr is None: |
||||
out = {} |
||||
else: |
||||
out = [None]*len(arr) |
||||
|
||||
msg = self.msgs.get(x[0]) |
||||
if msg is None: |
||||
if x[0] not in self._warned_addresses: |
||||
#print("WARNING: Unknown message address {}".format(x[0])) |
||||
self._warned_addresses.add(x[0]) |
||||
return None, None |
||||
|
||||
name = msg[0][0] |
||||
if debug: |
||||
print(name) |
||||
|
||||
st = x[2].ljust(8, b'\x00') |
||||
le, be = None, None |
||||
|
||||
for s in msg[1]: |
||||
if arr is not None and s[0] not in arr: |
||||
continue |
||||
|
||||
start_bit = s[1] |
||||
signal_size = s[2] |
||||
little_endian = s[3] |
||||
signed = s[4] |
||||
factor = s[5] |
||||
offset = s[6] |
||||
|
||||
if little_endian: |
||||
if le is None: |
||||
le = struct.unpack("<Q", st)[0] |
||||
tmp = le |
||||
shift_amount = start_bit |
||||
else: |
||||
if be is None: |
||||
be = struct.unpack(">Q", st)[0] |
||||
tmp = be |
||||
b1 = (start_bit // 8) * 8 + (-start_bit - 1) % 8 |
||||
shift_amount = 64 - (b1 + signal_size) |
||||
|
||||
if shift_amount < 0: |
||||
continue |
||||
|
||||
tmp = (tmp >> shift_amount) & ((1 << signal_size) - 1) |
||||
if signed and (tmp >> (signal_size - 1)): |
||||
tmp -= (1 << signal_size) |
||||
|
||||
tmp = tmp * factor + offset |
||||
|
||||
# if debug: |
||||
# print("%40s %2d %2d %7.2f %s" % (s[0], s[1], s[2], tmp, s[-1])) |
||||
|
||||
if arr is None: |
||||
out[s[0]] = tmp |
||||
else: |
||||
out[arr.index(s[0])] = tmp |
||||
return name, out |
||||
|
||||
def get_signals(self, msg): |
||||
msg = self.lookup_msg_id(msg) |
||||
return [sgs.name for sgs in self.msgs[msg][1]] |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
from opendbc import DBC_PATH |
||||
|
||||
dbc_test = dbc(os.path.join(DBC_PATH, 'toyota_prius_2017_pt_generated.dbc')) |
||||
msg = ('STEER_ANGLE_SENSOR', {'STEER_ANGLE': -6.0, 'STEER_RATE': 4, 'STEER_FRACTION': -0.2}) |
||||
encoded = dbc_test.encode(*msg) |
||||
decoded = dbc_test.decode((0x25, 0, encoded)) |
||||
assert decoded == msg |
||||
|
||||
dbc_test = dbc(os.path.join(DBC_PATH, 'hyundai_santa_fe_2019_ccan.dbc')) |
||||
decoded = dbc_test.decode((0x2b0, 0, "\xfa\xfe\x00\x07\x12")) |
||||
assert abs(decoded[1]['SAS_Angle'] - (-26.2)) < 0.001 |
||||
|
||||
msg = ('SAS11', {'SAS_Stat': 7.0, 'MsgCount': 0.0, 'SAS_Angle': -26.200000000000003, 'SAS_Speed': 0.0, 'CheckSum': 0.0}) |
||||
encoded = dbc_test.encode(*msg) |
||||
decoded = dbc_test.decode((0x2b0, 0, encoded)) |
||||
|
||||
assert decoded == msg |
@ -0,0 +1,2 @@ |
||||
*.cc |
||||
|
@ -0,0 +1,81 @@ |
||||
#include "common_dbc.h" |
||||
|
||||
namespace { |
||||
|
||||
{% for address, msg_name, msg_size, sigs in msgs %} |
||||
const Signal sigs_{{address}}[] = { |
||||
{% for sig in sigs %} |
||||
{ |
||||
{% if sig.is_little_endian %} |
||||
{% set b1 = sig.start_bit %} |
||||
{% else %} |
||||
{% set b1 = (sig.start_bit//8)*8 + (-sig.start_bit-1) % 8 %}
|
||||
{% endif %} |
||||
.name = "{{sig.name}}", |
||||
.b1 = {{b1}}, |
||||
.b2 = {{sig.size}}, |
||||
.bo = {{64 - (b1 + sig.size)}}, |
||||
.is_signed = {{"true" if sig.is_signed else "false"}}, |
||||
.factor = {{sig.factor}}, |
||||
.offset = {{sig.offset}}, |
||||
.is_little_endian = {{"true" if sig.is_little_endian else "false"}}, |
||||
{% if checksum_type == "honda" and sig.name == "CHECKSUM" %} |
||||
.type = SignalType::HONDA_CHECKSUM, |
||||
{% elif checksum_type == "honda" and sig.name == "COUNTER" %} |
||||
.type = SignalType::HONDA_COUNTER, |
||||
{% elif checksum_type == "toyota" and sig.name == "CHECKSUM" %} |
||||
.type = SignalType::TOYOTA_CHECKSUM, |
||||
{% elif checksum_type == "volkswagen" and sig.name == "CHECKSUM" %} |
||||
.type = SignalType::VOLKSWAGEN_CHECKSUM, |
||||
{% elif checksum_type == "volkswagen" and sig.name == "COUNTER" %} |
||||
.type = SignalType::VOLKSWAGEN_COUNTER, |
||||
{% elif address in [512, 513] and sig.name == "CHECKSUM_PEDAL" %} |
||||
.type = SignalType::PEDAL_CHECKSUM, |
||||
{% elif address in [512, 513] and sig.name == "COUNTER_PEDAL" %} |
||||
.type = SignalType::PEDAL_COUNTER, |
||||
{% else %} |
||||
.type = SignalType::DEFAULT, |
||||
{% endif %} |
||||
}, |
||||
{% endfor %} |
||||
}; |
||||
{% endfor %} |
||||
|
||||
const Msg msgs[] = { |
||||
{% for address, msg_name, msg_size, sigs in msgs %} |
||||
{% set address_hex = "0x%X" % address %} |
||||
{ |
||||
.name = "{{msg_name}}", |
||||
.address = {{address_hex}}, |
||||
.size = {{msg_size}}, |
||||
.num_sigs = ARRAYSIZE(sigs_{{address}}), |
||||
.sigs = sigs_{{address}}, |
||||
}, |
||||
{% endfor %} |
||||
}; |
||||
|
||||
const Val vals[] = { |
||||
{% for address, sig in def_vals %} |
||||
{% for sg_name, def_val in sig %} |
||||
{% set address_hex = "0x%X" % address %} |
||||
{ |
||||
.name = "{{sg_name}}", |
||||
.address = {{address_hex}}, |
||||
.def_val = {{def_val}}, |
||||
.sigs = sigs_{{address}}, |
||||
}, |
||||
{% endfor %} |
||||
{% endfor %} |
||||
}; |
||||
|
||||
} |
||||
|
||||
const DBC {{dbc.name}} = { |
||||
.name = "{{dbc.name}}", |
||||
.num_msgs = ARRAYSIZE(msgs), |
||||
.msgs = msgs, |
||||
.vals = vals, |
||||
.num_vals = ARRAYSIZE(vals), |
||||
}; |
||||
|
||||
dbc_init({{dbc.name}}) |
@ -0,0 +1,108 @@ |
||||
#include <cassert> |
||||
#include <utility> |
||||
#include <algorithm> |
||||
#include <map> |
||||
#include <cmath> |
||||
|
||||
#include "common.h" |
||||
|
||||
#define WARN printf |
||||
|
||||
// this is the same as read_u64_le, but uses uint64_t as in/out
|
||||
uint64_t ReverseBytes(uint64_t x) { |
||||
return ((x & 0xff00000000000000ull) >> 56) | |
||||
((x & 0x00ff000000000000ull) >> 40) | |
||||
((x & 0x0000ff0000000000ull) >> 24) | |
||||
((x & 0x000000ff00000000ull) >> 8) | |
||||
((x & 0x00000000ff000000ull) << 8) | |
||||
((x & 0x0000000000ff0000ull) << 24) | |
||||
((x & 0x000000000000ff00ull) << 40) | |
||||
((x & 0x00000000000000ffull) << 56); |
||||
} |
||||
|
||||
uint64_t set_value(uint64_t ret, Signal sig, int64_t ival){ |
||||
int shift = sig.is_little_endian? sig.b1 : sig.bo; |
||||
uint64_t mask = ((1ULL << sig.b2)-1) << shift; |
||||
uint64_t dat = (ival & ((1ULL << sig.b2)-1)) << shift; |
||||
if (sig.is_little_endian) { |
||||
dat = ReverseBytes(dat); |
||||
mask = ReverseBytes(mask); |
||||
} |
||||
ret &= ~mask; |
||||
ret |= dat; |
||||
return ret; |
||||
} |
||||
|
||||
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; |
||||
} |
||||
} |
||||
init_crc_lookup_tables(); |
||||
} |
||||
|
||||
uint64_t CANPacker::pack(uint32_t address, const std::vector<SignalPackValue> &signals, int counter) { |
||||
uint64_t ret = 0; |
||||
for (const auto& sigval : signals) { |
||||
std::string name = std::string(sigval.name); |
||||
double value = sigval.value; |
||||
|
||||
auto sig_it = signal_lookup.find(std::make_pair(address, name)); |
||||
if (sig_it == signal_lookup.end()) { |
||||
WARN("undefined signal %s - %d\n", name.c_str(), address); |
||||
continue; |
||||
} |
||||
auto sig = sig_it->second; |
||||
|
||||
int64_t ival = (int64_t)(round((value - sig.offset) / sig.factor)); |
||||
if (ival < 0) { |
||||
ival = (1ULL << sig.b2) + ival; |
||||
} |
||||
|
||||
ret = set_value(ret, sig, ival); |
||||
} |
||||
|
||||
if (counter >= 0){ |
||||
auto sig_it = signal_lookup.find(std::make_pair(address, "COUNTER")); |
||||
if (sig_it == signal_lookup.end()) { |
||||
WARN("COUNTER not defined\n"); |
||||
return ret; |
||||
} |
||||
auto sig = sig_it->second; |
||||
|
||||
if ((sig.type != SignalType::HONDA_COUNTER) && (sig.type != SignalType::VOLKSWAGEN_COUNTER)) { |
||||
WARN("COUNTER signal type not valid\n"); |
||||
} |
||||
|
||||
ret = set_value(ret, sig, counter); |
||||
} |
||||
|
||||
auto sig_it_checksum = signal_lookup.find(std::make_pair(address, "CHECKSUM")); |
||||
if (sig_it_checksum != signal_lookup.end()) { |
||||
auto sig = sig_it_checksum->second; |
||||
if (sig.type == SignalType::HONDA_CHECKSUM) { |
||||
unsigned int chksm = honda_checksum(address, ret, message_lookup[address].size); |
||||
ret = set_value(ret, sig, chksm); |
||||
} else if (sig.type == SignalType::TOYOTA_CHECKSUM) { |
||||
unsigned int chksm = toyota_checksum(address, ret, message_lookup[address].size); |
||||
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 { |
||||
//WARN("CHECKSUM signal type not valid\n");
|
||||
} |
||||
} |
||||
|
||||
return ret; |
||||
} |
@ -0,0 +1,3 @@ |
||||
# pylint: skip-file |
||||
from opendbc.can.packer_pyx import CANPacker |
||||
assert CANPacker |
@ -0,0 +1,67 @@ |
||||
# distutils: language = c++ |
||||
# cython: c_string_encoding=ascii, language_level=3 |
||||
|
||||
from libc.stdint cimport uint32_t, uint64_t |
||||
from libcpp.vector cimport vector |
||||
from libcpp.map cimport map |
||||
from libcpp.string cimport string |
||||
from libcpp cimport bool |
||||
from posix.dlfcn cimport dlopen, dlsym, RTLD_LAZY |
||||
|
||||
from common cimport CANPacker as cpp_CANPacker |
||||
from common cimport dbc_lookup, SignalPackValue, DBC |
||||
|
||||
|
||||
cdef class CANPacker: |
||||
cdef: |
||||
cpp_CANPacker *packer |
||||
const DBC *dbc |
||||
map[string, (int, int)] name_to_address_and_size |
||||
map[int, int] address_to_size |
||||
|
||||
def __init__(self, dbc_name): |
||||
self.packer = new cpp_CANPacker(dbc_name) |
||||
self.dbc = dbc_lookup(dbc_name) |
||||
|
||||
num_msgs = self.dbc[0].num_msgs |
||||
for i in range(num_msgs): |
||||
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 |
||||
|
||||
cdef uint64_t pack(self, addr, values, counter): |
||||
cdef vector[SignalPackValue] values_thing |
||||
cdef SignalPackValue spv |
||||
|
||||
names = [] |
||||
|
||||
for name, value in values.iteritems(): |
||||
n = name.encode('utf8') |
||||
names.append(n) # TODO: find better way to keep reference to temp string arround |
||||
|
||||
spv.name = n |
||||
spv.value = value |
||||
values_thing.push_back(spv) |
||||
|
||||
return self.packer.pack(addr, values_thing, counter) |
||||
|
||||
cdef inline uint64_t ReverseBytes(self, uint64_t x): |
||||
return (((x & 0xff00000000000000ull) >> 56) | |
||||
((x & 0x00ff000000000000ull) >> 40) | |
||||
((x & 0x0000ff0000000000ull) >> 24) | |
||||
((x & 0x000000ff00000000ull) >> 8) | |
||||
((x & 0x00000000ff000000ull) << 8) | |
||||
((x & 0x0000000000ff0000ull) << 24) | |
||||
((x & 0x000000000000ff00ull) << 40) | |
||||
((x & 0x00000000000000ffull) << 56)) |
||||
|
||||
cpdef make_can_msg(self, name_or_addr, bus, values, counter=-1): |
||||
cdef int addr, size |
||||
if type(name_or_addr) == int: |
||||
addr = name_or_addr |
||||
size = self.address_to_size[name_or_addr] |
||||
else: |
||||
addr, size = self.name_to_address_and_size[name_or_addr.encode('utf8')] |
||||
cdef uint64_t val = self.pack(addr, values, counter) |
||||
val = self.ReverseBytes(val) |
||||
return [addr, 0, (<char *>&val)[:size], bus] |
@ -0,0 +1,60 @@ |
||||
import os |
||||
import sysconfig |
||||
import subprocess |
||||
from distutils.core import Extension, setup # pylint: disable=import-error,no-name-in-module |
||||
|
||||
from Cython.Build import cythonize |
||||
from Cython.Distutils import build_ext |
||||
|
||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../")) |
||||
|
||||
|
||||
def get_ext_filename_without_platform_suffix(filename): |
||||
name, ext = os.path.splitext(filename) |
||||
ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') |
||||
|
||||
if ext_suffix == ext: |
||||
return filename |
||||
|
||||
ext_suffix = ext_suffix.replace(ext, '') |
||||
idx = name.find(ext_suffix) |
||||
|
||||
if idx == -1: |
||||
return filename |
||||
else: |
||||
return name[:idx] + ext |
||||
|
||||
|
||||
class BuildExtWithoutPlatformSuffix(build_ext): |
||||
def get_ext_filename(self, ext_name): |
||||
filename = super().get_ext_filename(ext_name) |
||||
return get_ext_filename_without_platform_suffix(filename) |
||||
|
||||
|
||||
sourcefiles = ['packer_pyx.pyx'] |
||||
extra_compile_args = ["-std=c++11"] |
||||
ARCH = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() # pylint: disable=unexpected-keyword-arg |
||||
|
||||
if ARCH == "aarch64": |
||||
extra_compile_args += ["-Wno-deprecated-register"] |
||||
|
||||
|
||||
setup(name='CAN packer', |
||||
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix}, |
||||
ext_modules=cythonize( |
||||
Extension( |
||||
"packer_pyx", |
||||
language="c++", |
||||
sources=sourcefiles, |
||||
extra_compile_args=extra_compile_args, |
||||
include_dirs=[ |
||||
BASEDIR, |
||||
os.path.join(BASEDIR, 'phonelibs', 'capnp-cpp/include'), |
||||
], |
||||
extra_link_args=[ |
||||
os.path.join(BASEDIR, 'opendbc', 'can', 'libdbc.so'), |
||||
], |
||||
) |
||||
), |
||||
nthreads=4, |
||||
) |
@ -0,0 +1,239 @@ |
||||
#include <cassert> |
||||
#include <cstring> |
||||
|
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/mman.h> |
||||
#include <algorithm> |
||||
|
||||
#include "common.h" |
||||
|
||||
#define DEBUG(...) |
||||
// #define DEBUG printf
|
||||
#define INFO printf |
||||
|
||||
|
||||
bool MessageState::parse(uint64_t sec, uint16_t ts_, uint8_t * dat) { |
||||
uint64_t dat_le = read_u64_le(dat); |
||||
uint64_t dat_be = read_u64_be(dat); |
||||
|
||||
for (int i=0; i < parse_sigs.size(); i++) { |
||||
auto& sig = parse_sigs[i]; |
||||
int64_t tmp; |
||||
|
||||
if (sig.is_little_endian){ |
||||
tmp = (dat_le >> sig.b1) & ((1ULL << sig.b2)-1); |
||||
} else { |
||||
tmp = (dat_be >> sig.bo) & ((1ULL << sig.b2)-1); |
||||
} |
||||
|
||||
if (sig.is_signed) { |
||||
tmp -= (tmp >> (sig.b2-1)) ? (1ULL << sig.b2) : 0; //signed
|
||||
} |
||||
|
||||
DEBUG("parse 0x%X %s -> %lld\n", address, sig.name, tmp); |
||||
|
||||
if (sig.type == SignalType::HONDA_CHECKSUM) { |
||||
if (honda_checksum(address, dat_be, size) != tmp) { |
||||
INFO("0x%X CHECKSUM FAIL\n", address); |
||||
return false; |
||||
} |
||||
} else if (sig.type == SignalType::HONDA_COUNTER) { |
||||
if (!update_counter_generic(tmp, sig.b2)) { |
||||
return false; |
||||
} |
||||
} else if (sig.type == SignalType::TOYOTA_CHECKSUM) { |
||||
if (toyota_checksum(address, dat_be, size) != tmp) { |
||||
INFO("0x%X CHECKSUM FAIL\n", address); |
||||
return false; |
||||
} |
||||
} else if (sig.type == SignalType::VOLKSWAGEN_CHECKSUM) { |
||||
if (volkswagen_crc(address, dat_le, 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; |
||||
} |
||||
} else if (sig.type == SignalType::PEDAL_CHECKSUM) { |
||||
if (pedal_checksum(dat_be, size) != tmp) { |
||||
INFO("0x%X PEDAL CHECKSUM FAIL\n", address); |
||||
return false; |
||||
} |
||||
} else if (sig.type == SignalType::PEDAL_COUNTER) { |
||||
if (!update_counter_generic(tmp, sig.b2)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
vals[i] = tmp * sig.factor + sig.offset; |
||||
} |
||||
ts = ts_; |
||||
seen = sec; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
|
||||
bool MessageState::update_counter_generic(int64_t v, int cnt_size) { |
||||
uint8_t old_counter = counter; |
||||
counter = v; |
||||
if (((old_counter+1) & ((1 << cnt_size) -1)) != v) { |
||||
counter_fail += 1; |
||||
if (counter_fail > 1) { |
||||
INFO("0x%X COUNTER FAIL %d -- %d vs %d\n", address, counter_fail, old_counter, (int)v); |
||||
} |
||||
if (counter_fail >= MAX_BAD_COUNTER) { |
||||
return false; |
||||
} |
||||
} else if (counter_fail > 0) { |
||||
counter_fail--; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
|
||||
CANParser::CANParser(int abus, const std::string& dbc_name, |
||||
const std::vector<MessageParseOptions> &options, |
||||
const std::vector<SignalParseOptions> &sigoptions) |
||||
: bus(abus) { |
||||
|
||||
dbc = dbc_lookup(dbc_name); |
||||
assert(dbc); |
||||
init_crc_lookup_tables(); |
||||
|
||||
for (const auto& op : options) { |
||||
MessageState state = { |
||||
.address = op.address, |
||||
// .check_frequency = op.check_frequency,
|
||||
}; |
||||
|
||||
// msg is not valid if a message isn't received for 10 consecutive steps
|
||||
if (op.check_frequency > 0) { |
||||
state.check_threshold = (1000000000ULL / op.check_frequency) * 10; |
||||
} |
||||
|
||||
|
||||
const Msg* msg = NULL; |
||||
for (int i=0; i<dbc->num_msgs; i++) { |
||||
if (dbc->msgs[i].address == op.address) { |
||||
msg = &dbc->msgs[i]; |
||||
break; |
||||
} |
||||
} |
||||
if (!msg) { |
||||
fprintf(stderr, "CANParser: could not find message 0x%X in DBC %s\n", op.address, dbc_name.c_str()); |
||||
assert(false); |
||||
} |
||||
|
||||
state.size = msg->size; |
||||
|
||||
// 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); |
||||
state.vals.push_back(0); |
||||
} |
||||
} |
||||
|
||||
// track requested signals for this message
|
||||
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); |
||||
state.vals.push_back(sigop.default_value); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
message_states[state.address] = state; |
||||
} |
||||
} |
||||
|
||||
void CANParser::UpdateCans(uint64_t sec, const capnp::List<cereal::CanData>::Reader& cans) { |
||||
int msg_count = cans.size(); |
||||
uint64_t p; |
||||
|
||||
DEBUG("got %d messages\n", msg_count); |
||||
|
||||
// parse the messages
|
||||
for (int i = 0; i < msg_count; i++) { |
||||
auto cmsg = cans[i]; |
||||
if (cmsg.getSrc() != bus) { |
||||
// DEBUG("skip %d: wrong bus\n", cmsg.getAddress());
|
||||
continue; |
||||
} |
||||
auto state_it = message_states.find(cmsg.getAddress()); |
||||
if (state_it == message_states.end()) { |
||||
// DEBUG("skip %d: not specified\n", cmsg.getAddress());
|
||||
continue; |
||||
} |
||||
|
||||
if (cmsg.getDat().size() > 8) continue; //shouldnt ever happen
|
||||
uint8_t dat[8] = {0}; |
||||
memcpy(dat, cmsg.getDat().begin(), cmsg.getDat().size()); |
||||
|
||||
state_it->second.parse(sec, cmsg.getBusTime(), dat); |
||||
} |
||||
} |
||||
|
||||
void CANParser::UpdateValid(uint64_t sec) { |
||||
can_valid = true; |
||||
for (const auto& kv : message_states) { |
||||
const auto& state = kv.second; |
||||
if (state.check_threshold > 0 && (sec - state.seen) > state.check_threshold) { |
||||
if (state.seen > 0) { |
||||
DEBUG("0x%X TIMEOUT\n", state.address); |
||||
} |
||||
can_valid = false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void CANParser::update_string(std::string data, bool sendcan) { |
||||
// format for board, make copy due to alignment issues, will be freed on out of scope
|
||||
auto amsg = kj::heapArray<capnp::word>((data.length() / sizeof(capnp::word)) + 1); |
||||
memcpy(amsg.begin(), data.data(), data.length()); |
||||
|
||||
// extract the messages
|
||||
capnp::FlatArrayMessageReader cmsg(amsg); |
||||
cereal::Event::Reader event = cmsg.getRoot<cereal::Event>(); |
||||
|
||||
last_sec = event.getLogMonoTime(); |
||||
|
||||
auto cans = sendcan? event.getSendcan() : event.getCan(); |
||||
UpdateCans(last_sec, cans); |
||||
|
||||
UpdateValid(last_sec); |
||||
} |
||||
|
||||
|
||||
std::vector<SignalValue> CANParser::query_latest() { |
||||
std::vector<SignalValue> ret; |
||||
|
||||
for (const auto& kv : message_states) { |
||||
const auto& state = kv.second; |
||||
if (last_sec != 0 && state.seen != last_sec) continue; |
||||
|
||||
for (int i=0; i<state.parse_sigs.size(); i++) { |
||||
const Signal &sig = state.parse_sigs[i]; |
||||
ret.push_back((SignalValue){ |
||||
.address = state.address, |
||||
.ts = state.ts, |
||||
.name = sig.name, |
||||
.value = state.vals[i], |
||||
}); |
||||
} |
||||
} |
||||
|
||||
return ret; |
||||
} |
@ -0,0 +1,2 @@ |
||||
from opendbc.can.parser_pyx import CANParser # pylint: disable=no-name-in-module, import-error |
||||
assert CANParser |
@ -0,0 +1,188 @@ |
||||
# distutils: language = c++ |
||||
# cython: c_string_encoding=ascii, language_level=3 |
||||
|
||||
from libcpp.string cimport string |
||||
from libcpp.vector cimport vector |
||||
from libcpp cimport bool |
||||
from libcpp.unordered_set cimport unordered_set |
||||
from libc.stdint cimport uint32_t, uint64_t, uint16_t |
||||
from libcpp.map cimport map |
||||
|
||||
from collections import defaultdict |
||||
|
||||
from common cimport CANParser as cpp_CANParser |
||||
from common cimport SignalParseOptions, MessageParseOptions, dbc_lookup, SignalValue, DBC |
||||
|
||||
|
||||
from libcpp cimport bool |
||||
import os |
||||
import numbers |
||||
|
||||
cdef int CAN_INVALID_CNT = 5 |
||||
|
||||
|
||||
cdef class CANParser: |
||||
cdef: |
||||
cpp_CANParser *can |
||||
const DBC *dbc |
||||
map[string, uint32_t] msg_name_to_address |
||||
map[uint32_t, string] address_to_msg_name |
||||
vector[SignalValue] can_values |
||||
bool test_mode_enabled |
||||
|
||||
cdef public: |
||||
string dbc_name |
||||
dict vl |
||||
dict ts |
||||
bool can_valid |
||||
int can_invalid_cnt |
||||
|
||||
def __init__(self, dbc_name, signals, checks=None, bus=0): |
||||
if checks is None: |
||||
checks = [] |
||||
|
||||
self.can_valid = True |
||||
self.dbc_name = dbc_name |
||||
self.dbc = dbc_lookup(dbc_name) |
||||
self.vl = {} |
||||
self.ts = {} |
||||
|
||||
self.can_invalid_cnt = CAN_INVALID_CNT |
||||
|
||||
num_msgs = self.dbc[0].num_msgs |
||||
for i in range(num_msgs): |
||||
msg = self.dbc[0].msgs[i] |
||||
name = msg.name.decode('utf8') |
||||
|
||||
self.msg_name_to_address[name] = msg.address |
||||
self.address_to_msg_name[msg.address] = name |
||||
self.vl[msg.address] = {} |
||||
self.vl[name] = {} |
||||
self.ts[msg.address] = {} |
||||
self.ts[name] = {} |
||||
|
||||
# Convert message names into addresses |
||||
for i in range(len(signals)): |
||||
s = signals[i] |
||||
if not isinstance(s[1], numbers.Number): |
||||
name = s[1].encode('utf8') |
||||
s = (s[0], self.msg_name_to_address[name], s[2]) |
||||
signals[i] = s |
||||
|
||||
for i in range(len(checks)): |
||||
c = checks[i] |
||||
if not isinstance(c[0], numbers.Number): |
||||
name = c[0].encode('utf8') |
||||
c = (self.msg_name_to_address[name], c[1]) |
||||
checks[i] = c |
||||
|
||||
cdef vector[SignalParseOptions] signal_options_v |
||||
cdef SignalParseOptions spo |
||||
for sig_name, sig_address, sig_default in signals: |
||||
spo.address = sig_address |
||||
spo.name = sig_name |
||||
spo.default_value = sig_default |
||||
signal_options_v.push_back(spo) |
||||
|
||||
message_options = dict((address, 0) for _, address, _ in signals) |
||||
message_options.update(dict(checks)) |
||||
|
||||
cdef vector[MessageParseOptions] message_options_v |
||||
cdef MessageParseOptions mpo |
||||
for msg_address, freq in message_options.items(): |
||||
mpo.address = msg_address |
||||
mpo.check_frequency = freq |
||||
message_options_v.push_back(mpo) |
||||
|
||||
self.can = new cpp_CANParser(bus, dbc_name, message_options_v, signal_options_v) |
||||
self.update_vl() |
||||
|
||||
cdef unordered_set[uint32_t] update_vl(self): |
||||
cdef string sig_name |
||||
cdef unordered_set[uint32_t] updated_val |
||||
|
||||
can_values = self.can.query_latest() |
||||
valid = self.can.can_valid |
||||
|
||||
# Update invalid flag |
||||
self.can_invalid_cnt += 1 |
||||
if valid: |
||||
self.can_invalid_cnt = 0 |
||||
self.can_valid = self.can_invalid_cnt < CAN_INVALID_CNT |
||||
|
||||
|
||||
for cv in can_values: |
||||
# Cast char * directly to unicde |
||||
name = <unicode>self.address_to_msg_name[cv.address].c_str() |
||||
cv_name = <unicode>cv.name |
||||
|
||||
self.vl[cv.address][cv_name] = cv.value |
||||
self.ts[cv.address][cv_name] = cv.ts |
||||
|
||||
self.vl[name][cv_name] = cv.value |
||||
self.ts[name][cv_name] = cv.ts |
||||
|
||||
updated_val.insert(cv.address) |
||||
|
||||
return updated_val |
||||
|
||||
def update_string(self, dat, sendcan=False): |
||||
self.can.update_string(dat, sendcan) |
||||
return self.update_vl() |
||||
|
||||
def update_strings(self, strings, sendcan=False): |
||||
updated_vals = set() |
||||
|
||||
for s in strings: |
||||
updated_val = self.update_string(s, sendcan) |
||||
updated_vals.update(updated_val) |
||||
|
||||
return updated_vals |
||||
|
||||
cdef class CANDefine(): |
||||
cdef: |
||||
const DBC *dbc |
||||
|
||||
cdef public: |
||||
dict dv |
||||
string dbc_name |
||||
|
||||
def __init__(self, dbc_name): |
||||
self.dbc_name = dbc_name |
||||
self.dbc = dbc_lookup(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): |
||||
msg = self.dbc[0].msgs[i] |
||||
name = msg.name.decode('utf8') |
||||
address = msg.address |
||||
address_to_msg_name[address] = name |
||||
|
||||
dv = defaultdict(dict) |
||||
|
||||
for i in range(num_vals): |
||||
val = self.dbc[0].vals[i] |
||||
|
||||
sgname = val.name.decode('utf8') |
||||
address = val.address |
||||
def_val = val.def_val.decode('utf8') |
||||
|
||||
#separate definition/value pairs |
||||
def_val = def_val.split() |
||||
values = [int(v) for v in def_val[::2]] |
||||
defs = def_val[1::2] |
||||
|
||||
if address not in dv: |
||||
dv[address] = {} |
||||
msgname = address_to_msg_name[address] |
||||
dv[msgname] = {} |
||||
|
||||
# two ways to lookup: address or msg name |
||||
dv[address][sgname] = dict(zip(values, defs)) |
||||
dv[msgname][sgname] = dv[address][sgname] |
||||
|
||||
self.dv = dict(dv) |
@ -0,0 +1,59 @@ |
||||
import os |
||||
import subprocess |
||||
import sysconfig |
||||
from distutils.core import Extension, setup # pylint: disable=import-error,no-name-in-module |
||||
|
||||
from Cython.Build import cythonize |
||||
from Cython.Distutils import build_ext |
||||
|
||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../")) |
||||
|
||||
|
||||
def get_ext_filename_without_platform_suffix(filename): |
||||
name, ext = os.path.splitext(filename) |
||||
ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') |
||||
|
||||
if ext_suffix == ext: |
||||
return filename |
||||
|
||||
ext_suffix = ext_suffix.replace(ext, '') |
||||
idx = name.find(ext_suffix) |
||||
|
||||
if idx == -1: |
||||
return filename |
||||
else: |
||||
return name[:idx] + ext |
||||
|
||||
|
||||
class BuildExtWithoutPlatformSuffix(build_ext): |
||||
def get_ext_filename(self, ext_name): |
||||
filename = super().get_ext_filename(ext_name) |
||||
return get_ext_filename_without_platform_suffix(filename) |
||||
|
||||
|
||||
sourcefiles = ['parser_pyx.pyx'] |
||||
extra_compile_args = ["-std=c++11"] |
||||
ARCH = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() # pylint: disable=unexpected-keyword-arg |
||||
|
||||
if ARCH == "aarch64": |
||||
extra_compile_args += ["-Wno-deprecated-register"] |
||||
|
||||
setup(name='CAN parser', |
||||
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix}, |
||||
ext_modules=cythonize( |
||||
Extension( |
||||
"parser_pyx", |
||||
language="c++", |
||||
sources=sourcefiles, |
||||
extra_compile_args=extra_compile_args, |
||||
include_dirs=[ |
||||
BASEDIR, |
||||
os.path.join(BASEDIR, 'phonelibs', 'capnp-cpp/include'), |
||||
], |
||||
extra_link_args=[ |
||||
os.path.join(BASEDIR, 'opendbc', 'can', 'libdbc.so'), |
||||
], |
||||
) |
||||
), |
||||
nthreads=4, |
||||
) |
@ -0,0 +1,112 @@ |
||||
#!/usr/bin/env python3 |
||||
from __future__ import print_function |
||||
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) |
||||
|
||||
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] |
||||
|
||||
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 |
||||
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, msg_size, 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 endianess" % 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 endianess" % 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, len=len) |
||||
|
||||
with open(out_fn, "w") as out_f: |
||||
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 @@ |
||||
*.bz2 |
@ -0,0 +1,585 @@ |
||||
[MASTER] |
||||
|
||||
# A comma-separated list of package or module names from where C extensions may |
||||
# be loaded. Extensions are loading into the active Python interpreter and may |
||||
# run arbitrary code |
||||
extension-pkg-whitelist=scipy |
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not |
||||
# paths. |
||||
ignore=CVS |
||||
|
||||
# Add files or directories matching the regex patterns to the blacklist. The |
||||
# regex matches against base names, not paths. |
||||
ignore-patterns= |
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as |
||||
# pygtk.require(). |
||||
#init-hook= |
||||
|
||||
# Use multiple processes to speed up Pylint. |
||||
jobs=4 |
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load, |
||||
# usually to register additional checkers. |
||||
load-plugins= |
||||
|
||||
# Pickle collected data for later comparisons. |
||||
persistent=yes |
||||
|
||||
# Specify a configuration file. |
||||
#rcfile= |
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit |
||||
# user-friendly hints instead of false-positive error messages |
||||
suggestion-mode=yes |
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the |
||||
# active Python interpreter and may run arbitrary code. |
||||
unsafe-load-any-extension=no |
||||
|
||||
|
||||
[MESSAGES CONTROL] |
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show |
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED |
||||
confidence= |
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You |
||||
# can either give multiple identifiers separated by comma (,) or put this |
||||
# option multiple times (only on the command line, not in the configuration |
||||
# file where it should appear only once).You can also use "--disable=all" to |
||||
# disable everything first and then reenable specific checks. For example, if |
||||
# you want to run only the similarities checker, you can use "--disable=all |
||||
# --enable=similarities". If you want to run only the classes checker, but have |
||||
# no Warning level messages displayed, use"--disable=all --enable=classes |
||||
# --disable=W" |
||||
disable=print-statement, |
||||
parameter-unpacking, |
||||
unpacking-in-except, |
||||
old-raise-syntax, |
||||
backtick, |
||||
long-suffix, |
||||
old-ne-operator, |
||||
old-octal-literal, |
||||
import-star-module-level, |
||||
non-ascii-bytes-literal, |
||||
raw-checker-failed, |
||||
bad-inline-option, |
||||
locally-disabled, |
||||
locally-enabled, |
||||
file-ignored, |
||||
suppressed-message, |
||||
useless-suppression, |
||||
deprecated-pragma, |
||||
apply-builtin, |
||||
basestring-builtin, |
||||
buffer-builtin, |
||||
cmp-builtin, |
||||
coerce-builtin, |
||||
execfile-builtin, |
||||
file-builtin, |
||||
long-builtin, |
||||
raw_input-builtin, |
||||
reduce-builtin, |
||||
standarderror-builtin, |
||||
unicode-builtin, |
||||
xrange-builtin, |
||||
coerce-method, |
||||
delslice-method, |
||||
getslice-method, |
||||
setslice-method, |
||||
no-absolute-import, |
||||
old-division, |
||||
dict-iter-method, |
||||
dict-view-method, |
||||
next-method-called, |
||||
metaclass-assignment, |
||||
indexing-exception, |
||||
raising-string, |
||||
reload-builtin, |
||||
oct-method, |
||||
hex-method, |
||||
nonzero-method, |
||||
cmp-method, |
||||
input-builtin, |
||||
round-builtin, |
||||
intern-builtin, |
||||
unichr-builtin, |
||||
map-builtin-not-iterating, |
||||
zip-builtin-not-iterating, |
||||
range-builtin-not-iterating, |
||||
filter-builtin-not-iterating, |
||||
using-cmp-argument, |
||||
eq-without-hash, |
||||
div-method, |
||||
idiv-method, |
||||
rdiv-method, |
||||
exception-message-attribute, |
||||
invalid-str-codec, |
||||
sys-max-int, |
||||
bad-python3-import, |
||||
deprecated-string-function, |
||||
deprecated-str-translate-call, |
||||
deprecated-itertools-function, |
||||
deprecated-types-field, |
||||
next-method-defined, |
||||
dict-items-not-iterating, |
||||
dict-keys-not-iterating, |
||||
dict-values-not-iterating, |
||||
bad-indentation, |
||||
line-too-long, |
||||
missing-docstring, |
||||
multiple-statements, |
||||
bad-continuation, |
||||
invalid-name, |
||||
too-many-arguments, |
||||
too-many-locals, |
||||
superfluous-parens, |
||||
bad-whitespace, |
||||
too-many-instance-attributes, |
||||
wrong-import-position, |
||||
ungrouped-imports, |
||||
wrong-import-order, |
||||
protected-access, |
||||
trailing-whitespace, |
||||
too-many-branches, |
||||
too-few-public-methods, |
||||
too-many-statements, |
||||
trailing-newlines, |
||||
attribute-defined-outside-init, |
||||
too-many-return-statements, |
||||
too-many-public-methods, |
||||
unused-argument, |
||||
old-style-class, |
||||
no-init, |
||||
len-as-condition, |
||||
unneeded-not, |
||||
no-self-use, |
||||
multiple-imports, |
||||
no-else-return, |
||||
logging-not-lazy, |
||||
fixme, |
||||
redefined-outer-name, |
||||
unused-variable, |
||||
unsubscriptable-object, |
||||
expression-not-assigned, |
||||
too-many-boolean-expressions, |
||||
consider-using-ternary, |
||||
invalid-unary-operand-type, |
||||
relative-import, |
||||
deprecated-lambda |
||||
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can |
||||
# either give multiple identifier separated by comma (,) or put this option |
||||
# multiple time (only on the command line, not in the configuration file where |
||||
# it should appear only once). See also the "--disable" option for examples. |
||||
enable=c-extension-no-member |
||||
|
||||
|
||||
[REPORTS] |
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest |
||||
# note). You have access to the variables errors warning, statement which |
||||
# respectively contain the number of errors / warnings messages and the total |
||||
# number of statements analyzed. This is used by the global evaluation report |
||||
# (RP0004). |
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) |
||||
|
||||
# Template used to display messages. This is a python new-style format string |
||||
# used to format the message information. See doc for all details |
||||
#msg-template= |
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, json |
||||
# and msvs (visual studio).You can also give a reporter class, eg |
||||
# mypackage.mymodule.MyReporterClass. |
||||
output-format=text |
||||
|
||||
# Tells whether to display a full report or only the messages |
||||
reports=no |
||||
|
||||
# Activate the evaluation score. |
||||
score=yes |
||||
|
||||
|
||||
[REFACTORING] |
||||
|
||||
# Maximum number of nested blocks for function / method body |
||||
max-nested-blocks=5 |
||||
|
||||
# Complete name of functions that never returns. When checking for |
||||
# inconsistent-return-statements if a never returning function is called then |
||||
# it will be considered as an explicit return statement and no message will be |
||||
# printed. |
||||
never-returning-functions=optparse.Values,sys.exit |
||||
|
||||
|
||||
[LOGGING] |
||||
|
||||
# Logging modules to check that the string format arguments are in logging |
||||
# function parameter format |
||||
logging-modules=logging |
||||
|
||||
|
||||
[SPELLING] |
||||
|
||||
# Limits count of emitted suggestions for spelling mistakes |
||||
max-spelling-suggestions=4 |
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working |
||||
# install python-enchant package. |
||||
spelling-dict= |
||||
|
||||
# List of comma separated words that should not be checked. |
||||
spelling-ignore-words= |
||||
|
||||
# A path to a file that contains private dictionary; one word per line. |
||||
spelling-private-dict-file= |
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in |
||||
# --spelling-private-dict-file option instead of raising a message. |
||||
spelling-store-unknown-words=no |
||||
|
||||
|
||||
[MISCELLANEOUS] |
||||
|
||||
# List of note tags to take in consideration, separated by a comma. |
||||
notes=FIXME, |
||||
XXX, |
||||
TODO |
||||
|
||||
|
||||
[SIMILARITIES] |
||||
|
||||
# Ignore comments when computing similarities. |
||||
ignore-comments=yes |
||||
|
||||
# Ignore docstrings when computing similarities. |
||||
ignore-docstrings=yes |
||||
|
||||
# Ignore imports when computing similarities. |
||||
ignore-imports=no |
||||
|
||||
# Minimum lines number of a similarity. |
||||
min-similarity-lines=4 |
||||
|
||||
|
||||
[TYPECHECK] |
||||
|
||||
# List of decorators that produce context managers, such as |
||||
# contextlib.contextmanager. Add to this list to register other decorators that |
||||
# produce valid context managers. |
||||
contextmanager-decorators=contextlib.contextmanager |
||||
|
||||
# List of members which are set dynamically and missed by pylint inference |
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular |
||||
# expressions are accepted. |
||||
generated-members=capnp.* cereal.* pygame.* zmq.* setproctitle.* smbus2.* usb1.* serial.* cv2.* |
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A |
||||
# mixin class is detected if its name ends with "mixin" (case insensitive). |
||||
ignore-mixin-members=yes |
||||
|
||||
# This flag controls whether pylint should warn about no-member and similar |
||||
# checks whenever an opaque object is returned when inferring. The inference |
||||
# can return multiple potential results while evaluating a Python object, but |
||||
# some branches might not be evaluated, which results in partial inference. In |
||||
# that case, it might be useful to still emit no-member and other checks for |
||||
# the rest of the inferred objects. |
||||
ignore-on-opaque-inference=yes |
||||
|
||||
# List of class names for which member attributes should not be checked (useful |
||||
# for classes with dynamically set attributes). This supports the use of |
||||
# qualified names. |
||||
ignored-classes=optparse.Values,thread._local,_thread._local |
||||
|
||||
# List of module names for which member attributes should not be checked |
||||
# (useful for modules/projects where namespaces are manipulated during runtime |
||||
# and thus existing member attributes cannot be deduced by static analysis. It |
||||
# supports qualified module names, as well as Unix pattern matching. |
||||
ignored-modules=flask setproctitle usb1 flask.ext.socketio smbus2 usb1.* |
||||
|
||||
# Show a hint with possible names when a member name was not found. The aspect |
||||
# of finding the hint is based on edit distance. |
||||
missing-member-hint=yes |
||||
|
||||
# The minimum edit distance a name should have in order to be considered a |
||||
# similar match for a missing member name. |
||||
missing-member-hint-distance=1 |
||||
|
||||
# The total number of similar names that should be taken in consideration when |
||||
# showing a hint for a missing member. |
||||
missing-member-max-choices=1 |
||||
|
||||
|
||||
[VARIABLES] |
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that |
||||
# you should avoid to define new builtins when possible. |
||||
additional-builtins= |
||||
|
||||
# Tells whether unused global variables should be treated as a violation. |
||||
allow-global-unused-variables=yes |
||||
|
||||
# List of strings which can identify a callback function by name. A callback |
||||
# name must start or end with one of those strings. |
||||
callbacks=cb_, |
||||
_cb |
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly |
||||
# not used). |
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ |
||||
|
||||
# Argument names that match this expression will be ignored. Default to name |
||||
# with leading underscore |
||||
ignored-argument-names=_.*|^ignored_|^unused_ |
||||
|
||||
# Tells whether we should check for unused import in __init__ files. |
||||
init-import=no |
||||
|
||||
# List of qualified module names which can have objects that can redefine |
||||
# builtins. |
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins |
||||
|
||||
|
||||
[FORMAT] |
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. |
||||
expected-line-ending-format= |
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit. |
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$ |
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line. |
||||
indent-after-paren=4 |
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 |
||||
# tab). |
||||
indent-string=' ' |
||||
|
||||
# Maximum number of characters on a single line. |
||||
max-line-length=100 |
||||
|
||||
# Maximum number of lines in a module |
||||
max-module-lines=1000 |
||||
|
||||
# List of optional constructs for which whitespace checking is disabled. `dict- |
||||
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. |
||||
# `trailing-comma` allows a space between comma and closing bracket: (a, ). |
||||
# `empty-line` allows space-only lines. |
||||
no-space-check=trailing-comma, |
||||
dict-separator |
||||
|
||||
# Allow the body of a class to be on the same line as the declaration if body |
||||
# contains single statement. |
||||
single-line-class-stmt=no |
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no |
||||
# else. |
||||
single-line-if-stmt=no |
||||
|
||||
|
||||
[BASIC] |
||||
|
||||
# Naming style matching correct argument names |
||||
argument-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct argument names. Overrides argument- |
||||
# naming-style |
||||
#argument-rgx= |
||||
|
||||
# Naming style matching correct attribute names |
||||
attr-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct attribute names. Overrides attr-naming- |
||||
# style |
||||
#attr-rgx= |
||||
|
||||
# Bad variable names which should always be refused, separated by a comma |
||||
bad-names=foo, |
||||
bar, |
||||
baz, |
||||
toto, |
||||
tutu, |
||||
tata |
||||
|
||||
# Naming style matching correct class attribute names |
||||
class-attribute-naming-style=any |
||||
|
||||
# Regular expression matching correct class attribute names. Overrides class- |
||||
# attribute-naming-style |
||||
#class-attribute-rgx= |
||||
|
||||
# Naming style matching correct class names |
||||
class-naming-style=PascalCase |
||||
|
||||
# Regular expression matching correct class names. Overrides class-naming-style |
||||
#class-rgx= |
||||
|
||||
# Naming style matching correct constant names |
||||
const-naming-style=UPPER_CASE |
||||
|
||||
# Regular expression matching correct constant names. Overrides const-naming- |
||||
# style |
||||
#const-rgx= |
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter |
||||
# ones are exempt. |
||||
docstring-min-length=-1 |
||||
|
||||
# Naming style matching correct function names |
||||
function-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct function names. Overrides function- |
||||
# naming-style |
||||
#function-rgx= |
||||
|
||||
# Good variable names which should always be accepted, separated by a comma |
||||
good-names=i, |
||||
j, |
||||
k, |
||||
ex, |
||||
Run, |
||||
_ |
||||
|
||||
# Include a hint for the correct naming format with invalid-name |
||||
include-naming-hint=no |
||||
|
||||
# Naming style matching correct inline iteration names |
||||
inlinevar-naming-style=any |
||||
|
||||
# Regular expression matching correct inline iteration names. Overrides |
||||
# inlinevar-naming-style |
||||
#inlinevar-rgx= |
||||
|
||||
# Naming style matching correct method names |
||||
method-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct method names. Overrides method-naming- |
||||
# style |
||||
#method-rgx= |
||||
|
||||
# Naming style matching correct module names |
||||
module-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct module names. Overrides module-naming- |
||||
# style |
||||
#module-rgx= |
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when |
||||
# the name regexes allow several styles. |
||||
name-group= |
||||
|
||||
# Regular expression which should only match function or class names that do |
||||
# not require a docstring. |
||||
no-docstring-rgx=^_ |
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add |
||||
# to this list to register other decorators that produce valid properties. |
||||
property-classes=abc.abstractproperty |
||||
|
||||
# Naming style matching correct variable names |
||||
variable-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct variable names. Overrides variable- |
||||
# naming-style |
||||
#variable-rgx= |
||||
|
||||
|
||||
[DESIGN] |
||||
|
||||
# Maximum number of arguments for function / method |
||||
max-args=5 |
||||
|
||||
# Maximum number of attributes for a class (see R0902). |
||||
max-attributes=7 |
||||
|
||||
# Maximum number of boolean expressions in a if statement |
||||
max-bool-expr=5 |
||||
|
||||
# Maximum number of branch for function / method body |
||||
max-branches=12 |
||||
|
||||
# Maximum number of locals for function / method body |
||||
max-locals=15 |
||||
|
||||
# Maximum number of parents for a class (see R0901). |
||||
max-parents=7 |
||||
|
||||
# Maximum number of public methods for a class (see R0904). |
||||
max-public-methods=20 |
||||
|
||||
# Maximum number of return / yield for function / method body |
||||
max-returns=6 |
||||
|
||||
# Maximum number of statements in function / method body |
||||
max-statements=50 |
||||
|
||||
# Minimum number of public methods for a class (see R0903). |
||||
min-public-methods=2 |
||||
|
||||
|
||||
[CLASSES] |
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes. |
||||
defining-attr-methods=__init__, |
||||
__new__, |
||||
setUp |
||||
|
||||
# List of member names, which should be excluded from the protected access |
||||
# warning. |
||||
exclude-protected=_asdict, |
||||
_fields, |
||||
_replace, |
||||
_source, |
||||
_make |
||||
|
||||
# List of valid names for the first argument in a class method. |
||||
valid-classmethod-first-arg=cls |
||||
|
||||
# List of valid names for the first argument in a metaclass class method. |
||||
valid-metaclass-classmethod-first-arg=mcs |
||||
|
||||
|
||||
[IMPORTS] |
||||
|
||||
# Allow wildcard imports from modules that define __all__. |
||||
allow-wildcard-with-all=no |
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and |
||||
# 3 compatible code, which means that the block might have code that exists |
||||
# only in one or another interpreter, leading to false positives when analysed. |
||||
analyse-fallback-blocks=no |
||||
|
||||
# Deprecated modules which should not be used, separated by a comma |
||||
deprecated-modules=regsub, |
||||
TERMIOS, |
||||
Bastion, |
||||
rexec |
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must |
||||
# not be disabled) |
||||
ext-import-graph= |
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the |
||||
# given file (report RP0402 must not be disabled) |
||||
import-graph= |
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must |
||||
# not be disabled) |
||||
int-import-graph= |
||||
|
||||
# Force import order to recognize a module as part of the standard |
||||
# compatibility libraries. |
||||
known-standard-library= |
||||
|
||||
# Force import order to recognize a module as part of a third party library. |
||||
known-third-party=enchant |
||||
|
||||
|
||||
[EXCEPTIONS] |
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to |
||||
# "Exception" |
||||
overgeneral-exceptions=Exception |
@ -0,0 +1,8 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
RESULT=$(python3 -m flake8 --select=F $(find ../../../ -type f | grep "\.py$")) |
||||
if [[ $RESULT ]]; then |
||||
echo "Pyflakes found errors in the code. Please fix and try again" |
||||
echo "$RESULT" |
||||
exit 1 |
||||
fi |
@ -0,0 +1,11 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
python3 -m pylint --disable=R,C,W $(find ../../../ -type f | grep "\.py$") |
||||
|
||||
exit_status=$? |
||||
(( res = exit_status & 3 )) |
||||
|
||||
if [[ $res != 0 ]]; then |
||||
echo "Pylint found errors in the code. Please fix and try again" |
||||
exit 1 |
||||
fi |
@ -0,0 +1,27 @@ |
||||
#!/usr/bin/env python3 |
||||
import unittest |
||||
|
||||
from opendbc.can.can_define import CANDefine |
||||
|
||||
|
||||
class TestCADNDefine(unittest.TestCase): |
||||
def test_civic(self): |
||||
|
||||
dbc_file = "honda_civic_touring_2016_can_generated" |
||||
defs = CANDefine(dbc_file) |
||||
|
||||
self.assertDictEqual(defs.dv[399], defs.dv['STEER_STATUS']) |
||||
self.assertDictEqual(defs.dv[399], |
||||
{'STEER_STATUS': |
||||
{6: 'TMP_FAULT', |
||||
5: 'FAULT_1', |
||||
4: 'NO_TORQUE_ALERT_2', |
||||
3: 'LOW_SPEED_LOCKOUT', |
||||
2: 'NO_TORQUE_ALERT_1', |
||||
0: 'NORMAL'} |
||||
} |
||||
) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
unittest.main() |
@ -0,0 +1,105 @@ |
||||
#!/usr/bin/env python3 |
||||
|
||||
import unittest |
||||
|
||||
from opendbc.can.parser import CANParser |
||||
from opendbc.can.packer import CANPacker |
||||
import cereal.messaging as messaging |
||||
|
||||
|
||||
# Python implementation so we don't have to depend on boardd |
||||
def can_list_to_can_capnp(can_msgs, msgtype='can'): |
||||
dat = messaging.new_message() |
||||
dat.init(msgtype, len(can_msgs)) |
||||
|
||||
for i, can_msg in enumerate(can_msgs): |
||||
if msgtype == 'sendcan': |
||||
cc = dat.sendcan[i] |
||||
else: |
||||
cc = dat.can[i] |
||||
|
||||
cc.address = can_msg[0] |
||||
cc.busTime = can_msg[1] |
||||
cc.dat = bytes(can_msg[2]) |
||||
cc.src = can_msg[3] |
||||
|
||||
return dat.to_bytes() |
||||
|
||||
|
||||
class TestCanParserPacker(unittest.TestCase): |
||||
def test_civic(self): |
||||
|
||||
dbc_file = "honda_civic_touring_2016_can_generated" |
||||
|
||||
signals = [ |
||||
("STEER_TORQUE", "STEERING_CONTROL", 0), |
||||
("STEER_TORQUE_REQUEST", "STEERING_CONTROL", 0), |
||||
] |
||||
checks = [] |
||||
|
||||
parser = CANParser(dbc_file, signals, checks, 0) |
||||
packer = CANPacker(dbc_file) |
||||
|
||||
idx = 0 |
||||
|
||||
for steer in range(-256, 255): |
||||
for active in [1, 0]: |
||||
values = { |
||||
"STEER_TORQUE": steer, |
||||
"STEER_TORQUE_REQUEST": active, |
||||
} |
||||
|
||||
msgs = packer.make_can_msg("STEERING_CONTROL", 0, values, idx) |
||||
bts = can_list_to_can_capnp([msgs]) |
||||
|
||||
parser.update_string(bts) |
||||
|
||||
self.assertAlmostEqual(parser.vl["STEERING_CONTROL"]["STEER_TORQUE"], steer) |
||||
self.assertAlmostEqual(parser.vl["STEERING_CONTROL"]["STEER_TORQUE_REQUEST"], active) |
||||
self.assertAlmostEqual(parser.vl["STEERING_CONTROL"]["COUNTER"], idx % 4) |
||||
|
||||
idx += 1 |
||||
|
||||
def test_subaru(self): |
||||
# Subuaru is little endian |
||||
|
||||
dbc_file = "subaru_global_2017" |
||||
|
||||
signals = [ |
||||
("Counter", "ES_LKAS", 0), |
||||
("LKAS_Output", "ES_LKAS", 0), |
||||
("LKAS_Request", "ES_LKAS", 0), |
||||
("SET_1", "ES_LKAS", 0), |
||||
|
||||
] |
||||
|
||||
checks = [] |
||||
|
||||
parser = CANParser(dbc_file, signals, checks, 0) |
||||
packer = CANPacker(dbc_file) |
||||
|
||||
idx = 0 |
||||
|
||||
for steer in range(-256, 255): |
||||
for active in [1, 0]: |
||||
values = { |
||||
"Counter": idx, |
||||
"LKAS_Output": steer, |
||||
"LKAS_Request": active, |
||||
"SET_1": 1 |
||||
} |
||||
|
||||
msgs = packer.make_can_msg("ES_LKAS", 0, values) |
||||
bts = can_list_to_can_capnp([msgs]) |
||||
parser.update_string(bts) |
||||
|
||||
self.assertAlmostEqual(parser.vl["ES_LKAS"]["LKAS_Output"], steer) |
||||
self.assertAlmostEqual(parser.vl["ES_LKAS"]["LKAS_Request"], active) |
||||
self.assertAlmostEqual(parser.vl["ES_LKAS"]["SET_1"], 1) |
||||
self.assertAlmostEqual(parser.vl["ES_LKAS"]["Counter"], idx % 16) |
||||
|
||||
idx += 1 |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
unittest.main() |
@ -0,0 +1,7 @@ |
||||
Cython==0.29.14 |
||||
flake8==3.7.9 |
||||
Jinja2==2.10.3 |
||||
pycapnp==0.6.4 |
||||
pylint==2.4.3 |
||||
pyyaml==5.1.2 |
||||
scons==3.1.1 |
Loading…
Reference in new issue