commit
ee493eed79
63 changed files with 2721 additions and 215 deletions
@ -1,2 +1,10 @@ |
|||||||
*.pyc |
*.pyc |
||||||
|
*.os |
||||||
|
*.tmp |
||||||
.*.swp |
.*.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