4f82d01e gitignore 5cb83454 Honda FCM: diagnostic signals d309cdce Added linter to opendbc (#203) d452706f add requirements.txt ec3b4595 deterministic dependency order a265d351 Azure pipelines ci (#202) bce9a2e1 packer depends on libdbc 5d5fdd6a no more python version of libdbc, everything through cython 541705bf move CANDefine to parser code da25c52a add test for can define 0ba7926b unify can packer and parser 25d88009 consistent naming a5c640a5 fix linter be210fef remove obsolete make file ffd9dca7 opendbc needs cereal b559f63d remove more make d0929496 seems to work now 41e80836 don't make 3254d1fc think scons works eb78f6aa scons sort of working 0ef1e35d fix gitignore e155e017 Can migration (#199) 3eded83a Honda: correct steering torque sensor sign to be consistent with standard convention (left+) 32f70e2f Fix outback endianness consistency (#196) a7da471f Update subaru_outback_2015_eyesight.dbc (#195) git-subtree-dir: opendbc git-subtree-split: 4f82d01ebc78109888954d9807d320e3c27896fdpull/898/head^2
parent
67c4121f62
commit
683b6151ce
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