wip
This commit is contained in:
28
opendbc/can/SConscript
Normal file
28
opendbc/can/SConscript
Normal file
@@ -0,0 +1,28 @@
|
||||
Import('env', 'envCython', 'cereal', 'common')
|
||||
|
||||
import os
|
||||
|
||||
envDBC = env.Clone()
|
||||
dbc_file_path = '-DDBC_FILE_PATH=\'"%s"\'' % (envDBC.Dir("..").abspath)
|
||||
envDBC['CXXFLAGS'] += [dbc_file_path]
|
||||
src = ["dbc.cc", "parser.cc", "packer.cc", "common.cc"]
|
||||
libs = [common, "capnp", "kj", "zmq"]
|
||||
|
||||
# shared library for openpilot
|
||||
libdbc = envDBC.SharedLibrary('libdbc', src, LIBS=libs)
|
||||
|
||||
# static library for tools like cabana
|
||||
envDBC.Library('libdbc_static', src, LIBS=libs)
|
||||
|
||||
# Build packer and parser
|
||||
lenv = envCython.Clone()
|
||||
lenv["LINKFLAGS"] += [libdbc[0].get_labspath()]
|
||||
parser = lenv.Program('parser_pyx.so', 'parser_pyx.pyx')
|
||||
packer = lenv.Program('packer_pyx.so', 'packer_pyx.pyx')
|
||||
|
||||
lenv.Depends(parser, libdbc)
|
||||
lenv.Depends(packer, libdbc)
|
||||
|
||||
opendbc_python = Alias("opendbc_python", [parser, packer])
|
||||
|
||||
Export('opendbc_python')
|
||||
0
opendbc/can/__init__.py
Normal file
0
opendbc/can/__init__.py
Normal file
2
opendbc/can/can_define.py
Normal file
2
opendbc/can/can_define.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from opendbc.can.parser_pyx import CANDefine # pylint: disable=no-name-in-module, import-error
|
||||
assert CANDefine
|
||||
246
opendbc/can/common.cc
Normal file
246
opendbc/can/common.cc
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "opendbc/can/common.h"
|
||||
|
||||
|
||||
unsigned int honda_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
|
||||
int s = 0;
|
||||
bool extended = address > 0x7FF;
|
||||
while (address) { s += (address & 0xF); address >>= 4; }
|
||||
for (int i = 0; i < d.size(); i++) {
|
||||
uint8_t x = d[i];
|
||||
if (i == d.size()-1) x >>= 4; // remove checksum
|
||||
s += (x & 0xF) + (x >> 4);
|
||||
}
|
||||
s = 8-s;
|
||||
if (extended) s += 3; // extended can
|
||||
|
||||
return s & 0xF;
|
||||
}
|
||||
|
||||
unsigned int toyota_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
|
||||
unsigned int s = d.size();
|
||||
while (address) { s += address & 0xFF; address >>= 8; }
|
||||
for (int i = 0; i < d.size() - 1; i++) { s += d[i]; }
|
||||
|
||||
return s & 0xFF;
|
||||
}
|
||||
|
||||
unsigned int subaru_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
|
||||
unsigned int s = 0;
|
||||
while (address) { s += address & 0xFF; address >>= 8; }
|
||||
|
||||
// skip checksum in first byte
|
||||
for (int i = 1; i < d.size(); i++) { s += d[i]; }
|
||||
|
||||
return s & 0xFF;
|
||||
}
|
||||
|
||||
unsigned int chrysler_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
|
||||
// jeep chrysler canbus checksum from http://illmatics.com/Remote%20Car%20Hacking.pdf
|
||||
uint8_t checksum = 0xFF;
|
||||
for (int j = 0; j < (d.size() - 1); j++) {
|
||||
uint8_t shift = 0x80;
|
||||
uint8_t curr = d[j];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uint8_t bit_sum = curr & shift;
|
||||
uint8_t temp_chk = checksum & 0x80U;
|
||||
if (bit_sum != 0U) {
|
||||
bit_sum = 0x1C;
|
||||
if (temp_chk != 0U) {
|
||||
bit_sum = 1;
|
||||
}
|
||||
checksum = checksum << 1;
|
||||
temp_chk = checksum | 1U;
|
||||
bit_sum ^= temp_chk;
|
||||
} else {
|
||||
if (temp_chk != 0U) {
|
||||
bit_sum = 0x1D;
|
||||
}
|
||||
checksum = checksum << 1;
|
||||
bit_sum ^= checksum;
|
||||
}
|
||||
checksum = bit_sum;
|
||||
shift = shift >> 1;
|
||||
}
|
||||
}
|
||||
return ~checksum & 0xFF;
|
||||
}
|
||||
|
||||
// Static lookup table for fast computation of CRCs
|
||||
uint8_t crc8_lut_8h2f[256]; // CRC8 poly 0x2F, aka 8H2F/AUTOSAR
|
||||
uint16_t crc16_lut_xmodem[256]; // CRC16 poly 0x1021, aka XMODEM
|
||||
|
||||
void gen_crc_lookup_table_8(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 gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]) {
|
||||
uint16_t crc;
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < 256; i++) {
|
||||
crc = i << 8;
|
||||
for (j = 0; j < 8; j++) {
|
||||
if ((crc & 0x8000) != 0) {
|
||||
crc = (uint16_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_8(0x2F, crc8_lut_8h2f); // CRC-8 8H2F/AUTOSAR for Volkswagen
|
||||
gen_crc_lookup_table_16(0x1021, crc16_lut_xmodem); // CRC-16 XMODEM for HKG CAN FD
|
||||
}
|
||||
|
||||
unsigned int volkswagen_mqb_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
|
||||
// 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 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 < d.size(); i++) {
|
||||
crc ^= d[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 = d[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: // LH_EPS_03 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[]){0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16}[counter];
|
||||
break;
|
||||
case 0x120: // TSK_06 Drivetrain Coordinator
|
||||
crc ^= (uint8_t[]){0xC4, 0xE2, 0x4F, 0xE4, 0xF8, 0x2F, 0x56, 0x81, 0x9F, 0xE5, 0x83, 0x44, 0x05, 0x3F, 0x97, 0xDF}[counter];
|
||||
break;
|
||||
case 0x121: // Motor_20 Driver Throttle Inputs
|
||||
crc ^= (uint8_t[]){0xE9, 0x65, 0xAE, 0x6B, 0x7B, 0x35, 0xE5, 0x5F, 0x4E, 0xC7, 0x86, 0xA2, 0xBB, 0xDD, 0xEB, 0xB4}[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 0x12E: // ACC_07 Automatic Cruise Control
|
||||
crc ^= (uint8_t[]){0xF8, 0xE5, 0x97, 0xC9, 0xD6, 0x07, 0x47, 0x21, 0x66, 0xDD, 0xCF, 0x6F, 0xA1, 0x94, 0x74, 0x63}[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 0x30F: // SWA_01 Lane Change Assist (SpurWechselAssistent)
|
||||
crc ^= (uint8_t[]){0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C}[counter];
|
||||
break;
|
||||
case 0x324: // ACC_04 Automatic Cruise Control
|
||||
crc ^= (uint8_t[]){0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27}[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 xor_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
|
||||
uint8_t checksum = 0;
|
||||
int checksum_byte = sig.start_bit / 8;
|
||||
|
||||
// Simple XOR over the payload, except for the byte where the checksum lives.
|
||||
for (int i = 0; i < d.size(); i++) {
|
||||
if (i != checksum_byte) {
|
||||
checksum ^= d[i];
|
||||
}
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
unsigned int pedal_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
|
||||
uint8_t crc = 0xFF;
|
||||
uint8_t poly = 0xD5; // standard crc8
|
||||
|
||||
// skip checksum byte
|
||||
for (int i = d.size()-2; i >= 0; i--) {
|
||||
crc ^= d[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if ((crc & 0x80) != 0) {
|
||||
crc = (uint8_t)((crc << 1) ^ poly);
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
unsigned int hkg_can_fd_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
|
||||
uint16_t crc = 0;
|
||||
|
||||
for (int i = 2; i < d.size(); i++) {
|
||||
crc = (crc << 8) ^ crc16_lut_xmodem[(crc >> 8) ^ d[i]];
|
||||
}
|
||||
|
||||
// Add address to crc
|
||||
crc = (crc << 8) ^ crc16_lut_xmodem[(crc >> 8) ^ ((address >> 0) & 0xFF)];
|
||||
crc = (crc << 8) ^ crc16_lut_xmodem[(crc >> 8) ^ ((address >> 8) & 0xFF)];
|
||||
|
||||
if (d.size() == 8) {
|
||||
crc ^= 0x5f29;
|
||||
} else if (d.size() == 16) {
|
||||
crc ^= 0x041d;
|
||||
} else if (d.size() == 24) {
|
||||
crc ^= 0x819d;
|
||||
} else if (d.size() == 32) {
|
||||
crc ^= 0x9f5b;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
102
opendbc/can/common.h
Normal file
102
opendbc/can/common.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <capnp/dynamic.h>
|
||||
#include <capnp/serialize.h>
|
||||
|
||||
#ifndef DYNAMIC_CAPNP
|
||||
#include "cereal/gen/cpp/log.capnp.h"
|
||||
#endif
|
||||
|
||||
#include "opendbc/can/common_dbc.h"
|
||||
|
||||
#define INFO printf
|
||||
#define WARN printf
|
||||
#define DEBUG(...)
|
||||
//#define DEBUG printf
|
||||
|
||||
#define MAX_BAD_COUNTER 5
|
||||
#define CAN_INVALID_CNT 5
|
||||
|
||||
void init_crc_lookup_tables();
|
||||
|
||||
// Car specific functions
|
||||
unsigned int honda_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
unsigned int toyota_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
unsigned int subaru_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
unsigned int chrysler_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
unsigned int volkswagen_mqb_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
unsigned int xor_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
unsigned int hkg_can_fd_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
unsigned int pedal_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
|
||||
class MessageState {
|
||||
public:
|
||||
std::string name;
|
||||
uint32_t address;
|
||||
unsigned int size;
|
||||
|
||||
std::vector<Signal> parse_sigs;
|
||||
std::vector<double> vals;
|
||||
std::vector<std::vector<double>> all_vals;
|
||||
|
||||
uint64_t last_seen_nanos;
|
||||
uint64_t check_threshold;
|
||||
|
||||
uint8_t counter;
|
||||
uint8_t counter_fail;
|
||||
|
||||
bool ignore_checksum = false;
|
||||
bool ignore_counter = false;
|
||||
|
||||
bool parse(uint64_t nanos, const std::vector<uint8_t> &dat);
|
||||
bool update_counter_generic(int64_t v, int cnt_size);
|
||||
};
|
||||
|
||||
class CANParser {
|
||||
private:
|
||||
const int bus;
|
||||
kj::Array<capnp::word> aligned_buf;
|
||||
|
||||
const DBC *dbc = NULL;
|
||||
std::unordered_map<uint32_t, MessageState> message_states;
|
||||
|
||||
public:
|
||||
bool can_valid = false;
|
||||
bool bus_timeout = false;
|
||||
uint64_t first_nanos = 0;
|
||||
uint64_t last_nanos = 0;
|
||||
uint64_t last_nonempty_nanos = 0;
|
||||
uint64_t bus_timeout_threshold = 0;
|
||||
uint64_t can_invalid_cnt = CAN_INVALID_CNT;
|
||||
|
||||
CANParser(int abus, const std::string& dbc_name,
|
||||
const std::vector<std::pair<uint32_t, int>> &messages);
|
||||
CANParser(int abus, const std::string& dbc_name, bool ignore_checksum, bool ignore_counter);
|
||||
#ifndef DYNAMIC_CAPNP
|
||||
void update_string(const std::string &data, bool sendcan);
|
||||
void update_strings(const std::vector<std::string> &data, std::vector<SignalValue> &vals, bool sendcan);
|
||||
void UpdateCans(uint64_t nanos, const capnp::List<cereal::CanData>::Reader& cans);
|
||||
#endif
|
||||
void UpdateCans(uint64_t nanos, const capnp::DynamicStruct::Reader& cans);
|
||||
void UpdateValid(uint64_t nanos);
|
||||
void query_latest(std::vector<SignalValue> &vals, uint64_t last_ts = 0);
|
||||
};
|
||||
|
||||
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;
|
||||
std::map<uint32_t, uint32_t> counters;
|
||||
|
||||
public:
|
||||
CANPacker(const std::string& dbc_name);
|
||||
std::vector<uint8_t> pack(uint32_t address, const std::vector<SignalPackValue> &values);
|
||||
Msg* lookup_message(uint32_t address);
|
||||
};
|
||||
75
opendbc/can/common.pxd
Normal file
75
opendbc/can/common.pxd
Normal file
@@ -0,0 +1,75 @@
|
||||
# distutils: language = c++
|
||||
# cython: language_level=3
|
||||
|
||||
from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t
|
||||
from libcpp cimport bool
|
||||
from libcpp.pair cimport pair
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
|
||||
|
||||
ctypedef unsigned int (*calc_checksum_type)(uint32_t, const Signal&, const vector[uint8_t] &)
|
||||
|
||||
cdef extern from "common_dbc.h":
|
||||
ctypedef enum SignalType:
|
||||
DEFAULT,
|
||||
COUNTER,
|
||||
HONDA_CHECKSUM,
|
||||
TOYOTA_CHECKSUM,
|
||||
PEDAL_CHECKSUM,
|
||||
VOLKSWAGEN_MQB_CHECKSUM,
|
||||
XOR_CHECKSUM,
|
||||
SUBARU_CHECKSUM,
|
||||
CHRYSLER_CHECKSUM
|
||||
HKG_CAN_FD_CHECKSUM,
|
||||
|
||||
cdef struct Signal:
|
||||
string name
|
||||
int start_bit, msb, lsb, size
|
||||
bool is_signed
|
||||
double factor, offset
|
||||
bool is_little_endian
|
||||
SignalType type
|
||||
calc_checksum_type calc_checksum
|
||||
|
||||
cdef struct Msg:
|
||||
string name
|
||||
uint32_t address
|
||||
unsigned int size
|
||||
vector[Signal] sigs
|
||||
|
||||
cdef struct Val:
|
||||
string name
|
||||
uint32_t address
|
||||
string def_val
|
||||
vector[Signal] sigs
|
||||
|
||||
cdef struct DBC:
|
||||
string name
|
||||
vector[Msg] msgs
|
||||
vector[Val] vals
|
||||
|
||||
cdef struct SignalValue:
|
||||
uint32_t address
|
||||
uint64_t ts_nanos
|
||||
string name
|
||||
double value
|
||||
vector[double] all_values
|
||||
|
||||
cdef struct SignalPackValue:
|
||||
string name
|
||||
double value
|
||||
|
||||
|
||||
cdef extern from "common.h":
|
||||
cdef const DBC* dbc_lookup(const string) except +
|
||||
|
||||
cdef cppclass CANParser:
|
||||
bool can_valid
|
||||
bool bus_timeout
|
||||
CANParser(int, string, vector[pair[uint32_t, int]]) except +
|
||||
void update_strings(vector[string]&, vector[SignalValue]&, bool) except +
|
||||
|
||||
cdef cppclass CANPacker:
|
||||
CANPacker(string)
|
||||
vector[uint8_t] pack(uint32_t, vector[SignalPackValue]&)
|
||||
77
opendbc/can/common_dbc.h
Normal file
77
opendbc/can/common_dbc.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct SignalPackValue {
|
||||
std::string name;
|
||||
double value;
|
||||
};
|
||||
|
||||
struct SignalValue {
|
||||
uint32_t address;
|
||||
uint64_t ts_nanos;
|
||||
std::string name;
|
||||
double value; // latest value
|
||||
std::vector<double> all_values; // all values from this cycle
|
||||
};
|
||||
|
||||
enum SignalType {
|
||||
DEFAULT,
|
||||
COUNTER,
|
||||
HONDA_CHECKSUM,
|
||||
TOYOTA_CHECKSUM,
|
||||
PEDAL_CHECKSUM,
|
||||
VOLKSWAGEN_MQB_CHECKSUM,
|
||||
XOR_CHECKSUM,
|
||||
SUBARU_CHECKSUM,
|
||||
CHRYSLER_CHECKSUM,
|
||||
HKG_CAN_FD_CHECKSUM,
|
||||
};
|
||||
|
||||
struct Signal {
|
||||
std::string name;
|
||||
int start_bit, msb, lsb, size;
|
||||
bool is_signed;
|
||||
double factor, offset;
|
||||
bool is_little_endian;
|
||||
SignalType type;
|
||||
unsigned int (*calc_checksum)(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
};
|
||||
|
||||
struct Msg {
|
||||
std::string name;
|
||||
uint32_t address;
|
||||
unsigned int size;
|
||||
std::vector<Signal> sigs;
|
||||
};
|
||||
|
||||
struct Val {
|
||||
std::string name;
|
||||
uint32_t address;
|
||||
std::string def_val;
|
||||
std::vector<Signal> sigs;
|
||||
};
|
||||
|
||||
struct DBC {
|
||||
std::string name;
|
||||
std::vector<Msg> msgs;
|
||||
std::vector<Val> vals;
|
||||
};
|
||||
|
||||
typedef struct ChecksumState {
|
||||
int checksum_size;
|
||||
int counter_size;
|
||||
int checksum_start_bit;
|
||||
int counter_start_bit;
|
||||
bool little_endian;
|
||||
SignalType checksum_type;
|
||||
unsigned int (*calc_checksum)(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d);
|
||||
} ChecksumState;
|
||||
|
||||
DBC* dbc_parse(const std::string& dbc_path);
|
||||
DBC* dbc_parse_from_stream(const std::string &dbc_name, std::istream &stream, ChecksumState *checksum = nullptr, bool allow_duplicate_msg_name=false);
|
||||
const DBC* dbc_lookup(const std::string& dbc_name);
|
||||
std::vector<std::string> get_dbc_names();
|
||||
258
opendbc/can/dbc.cc
Normal file
258
opendbc/can/dbc.cc
Normal file
@@ -0,0 +1,258 @@
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <iterator>
|
||||
#include <cstring>
|
||||
#include <clocale>
|
||||
|
||||
#include "opendbc/can/common.h"
|
||||
#include "opendbc/can/common_dbc.h"
|
||||
|
||||
std::regex bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))");
|
||||
std::regex sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
std::regex sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
|
||||
std::regex val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))");
|
||||
std::regex val_split_regexp{R"([\"]+)"}; // split on "
|
||||
|
||||
#define DBC_ASSERT(condition, message) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
std::stringstream is; \
|
||||
is << "[" << dbc_name << ":" << line_num << "] " << message; \
|
||||
throw std::runtime_error(is.str()); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
inline bool startswith(const std::string& str, const char* prefix) {
|
||||
return str.find(prefix, 0) == 0;
|
||||
}
|
||||
|
||||
inline bool startswith(const std::string& str, std::initializer_list<const char*> prefix_list) {
|
||||
for (auto prefix : prefix_list) {
|
||||
if (startswith(str, prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool endswith(const std::string& str, const char* suffix) {
|
||||
return str.find(suffix, 0) == (str.length() - strlen(suffix));
|
||||
}
|
||||
|
||||
inline std::string& trim(std::string& s, const char* t = " \t\n\r\f\v") {
|
||||
s.erase(s.find_last_not_of(t) + 1);
|
||||
return s.erase(0, s.find_first_not_of(t));
|
||||
}
|
||||
|
||||
ChecksumState* get_checksum(const std::string& dbc_name) {
|
||||
ChecksumState* s = nullptr;
|
||||
if (startswith(dbc_name, {"honda_", "acura_"})) {
|
||||
s = new ChecksumState({4, 2, 3, 5, false, HONDA_CHECKSUM, &honda_checksum});
|
||||
} else if (startswith(dbc_name, {"toyota_", "lexus_"})) {
|
||||
s = new ChecksumState({8, -1, 7, -1, false, TOYOTA_CHECKSUM, &toyota_checksum});
|
||||
} else if (startswith(dbc_name, "hyundai_canfd")) {
|
||||
s = new ChecksumState({16, -1, 0, -1, true, HKG_CAN_FD_CHECKSUM, &hkg_can_fd_checksum});
|
||||
} else if (startswith(dbc_name, "vw_mqb_2010")) {
|
||||
s = new ChecksumState({8, 4, 0, 0, true, VOLKSWAGEN_MQB_CHECKSUM, &volkswagen_mqb_checksum});
|
||||
} else if (startswith(dbc_name, "vw_golf_mk4")) {
|
||||
s = new ChecksumState({8, 4, 0, -1, true, XOR_CHECKSUM, &xor_checksum});
|
||||
} else if (startswith(dbc_name, "subaru_global_")) {
|
||||
s = new ChecksumState({8, -1, 0, -1, true, SUBARU_CHECKSUM, &subaru_checksum});
|
||||
} else if (startswith(dbc_name, "chrysler_")) {
|
||||
s = new ChecksumState({8, -1, 7, -1, false, CHRYSLER_CHECKSUM, &chrysler_checksum});
|
||||
} else if (startswith(dbc_name, "comma_body")) {
|
||||
s = new ChecksumState({8, 4, 7, 3, false, PEDAL_CHECKSUM, &pedal_checksum});
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void set_signal_type(Signal& s, ChecksumState* chk, const std::string& dbc_name, int line_num) {
|
||||
s.calc_checksum = nullptr;
|
||||
if (chk) {
|
||||
if (s.name == "CHECKSUM") {
|
||||
DBC_ASSERT(chk->checksum_size == -1 || s.size == chk->checksum_size, "CHECKSUM is not " << chk->checksum_size << " bits long");
|
||||
DBC_ASSERT(chk->checksum_start_bit == -1 || (s.start_bit % 8) == chk->checksum_start_bit, " CHECKSUM starts at wrong bit");
|
||||
DBC_ASSERT(s.is_little_endian == chk->little_endian, "CHECKSUM has wrong endianness");
|
||||
DBC_ASSERT(chk->calc_checksum != nullptr, "CHECKSUM calculate function not supplied");
|
||||
s.type = chk->checksum_type;
|
||||
s.calc_checksum = chk->calc_checksum;
|
||||
} else if (s.name == "COUNTER") {
|
||||
DBC_ASSERT(chk->counter_size == -1 || s.size == chk->counter_size, "COUNTER is not " << chk->counter_size << " bits long");
|
||||
DBC_ASSERT(chk->counter_start_bit == -1 || (s.start_bit % 8) == chk->counter_start_bit, "COUNTER starts at wrong bit");
|
||||
DBC_ASSERT(chk->little_endian == s.is_little_endian, "COUNTER has wrong endianness");
|
||||
s.type = COUNTER;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: CAN packer/parser shouldn't know anything about interceptors or pedals
|
||||
if (s.name == "CHECKSUM_PEDAL") {
|
||||
DBC_ASSERT(s.size == 8, "INTERCEPTOR CHECKSUM is not 8 bits long");
|
||||
s.type = PEDAL_CHECKSUM;
|
||||
} else if (s.name == "COUNTER_PEDAL") {
|
||||
DBC_ASSERT(s.size == 4, "INTERCEPTOR COUNTER is not 4 bits long");
|
||||
s.type = COUNTER;
|
||||
}
|
||||
}
|
||||
|
||||
DBC* dbc_parse_from_stream(const std::string &dbc_name, std::istream &stream, ChecksumState *checksum, bool allow_duplicate_msg_name) {
|
||||
uint32_t address = 0;
|
||||
std::set<uint32_t> address_set;
|
||||
std::set<std::string> msg_name_set;
|
||||
std::map<uint32_t, std::set<std::string>> signal_name_sets;
|
||||
std::map<uint32_t, std::vector<Signal>> signals;
|
||||
DBC* dbc = new DBC;
|
||||
dbc->name = dbc_name;
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
// used to find big endian LSB from MSB and size
|
||||
std::vector<int> be_bits;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
for (int j = 7; j >= 0; j--) {
|
||||
be_bits.push_back(j + i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
std::string line;
|
||||
int line_num = 0;
|
||||
std::smatch match;
|
||||
// TODO: see if we can speed up the regex statements in this loop, SG_ is specifically the slowest
|
||||
while (std::getline(stream, line)) {
|
||||
line = trim(line);
|
||||
line_num += 1;
|
||||
if (startswith(line, "BO_ ")) {
|
||||
// new group
|
||||
bool ret = std::regex_match(line, match, bo_regexp);
|
||||
DBC_ASSERT(ret, "bad BO: " << line);
|
||||
|
||||
Msg& msg = dbc->msgs.emplace_back();
|
||||
address = msg.address = std::stoul(match[1].str()); // could be hex
|
||||
msg.name = match[2].str();
|
||||
msg.size = std::stoul(match[3].str());
|
||||
|
||||
// check for duplicates
|
||||
DBC_ASSERT(address_set.find(address) == address_set.end(), "Duplicate message address: " << address << " (" << msg.name << ")");
|
||||
address_set.insert(address);
|
||||
|
||||
if (!allow_duplicate_msg_name) {
|
||||
DBC_ASSERT(msg_name_set.find(msg.name) == msg_name_set.end(), "Duplicate message name: " << msg.name);
|
||||
msg_name_set.insert(msg.name);
|
||||
}
|
||||
} else if (startswith(line, "SG_ ")) {
|
||||
// new signal
|
||||
int offset = 0;
|
||||
if (!std::regex_search(line, match, sg_regexp)) {
|
||||
bool ret = std::regex_search(line, match, sgm_regexp);
|
||||
DBC_ASSERT(ret, "bad SG: " << line);
|
||||
offset = 1;
|
||||
}
|
||||
Signal& sig = signals[address].emplace_back();
|
||||
sig.name = match[1].str();
|
||||
sig.start_bit = std::stoi(match[offset + 2].str());
|
||||
sig.size = std::stoi(match[offset + 3].str());
|
||||
sig.is_little_endian = std::stoi(match[offset + 4].str()) == 1;
|
||||
sig.is_signed = match[offset + 5].str() == "-";
|
||||
sig.factor = std::stod(match[offset + 6].str());
|
||||
sig.offset = std::stod(match[offset + 7].str());
|
||||
set_signal_type(sig, checksum, dbc_name, line_num);
|
||||
if (sig.is_little_endian) {
|
||||
sig.lsb = sig.start_bit;
|
||||
sig.msb = sig.start_bit + sig.size - 1;
|
||||
} else {
|
||||
auto it = find(be_bits.begin(), be_bits.end(), sig.start_bit);
|
||||
sig.lsb = be_bits[(it - be_bits.begin()) + sig.size - 1];
|
||||
sig.msb = sig.start_bit;
|
||||
}
|
||||
DBC_ASSERT(sig.lsb < (64 * 8) && sig.msb < (64 * 8), "Signal out of bounds: " << line);
|
||||
|
||||
// Check for duplicate signal names
|
||||
DBC_ASSERT(signal_name_sets[address].find(sig.name) == signal_name_sets[address].end(), "Duplicate signal name: " << sig.name);
|
||||
signal_name_sets[address].insert(sig.name);
|
||||
} else if (startswith(line, "VAL_ ")) {
|
||||
// new signal value/definition
|
||||
bool ret = std::regex_search(line, match, val_regexp);
|
||||
DBC_ASSERT(ret, "bad VAL: " << line);
|
||||
|
||||
auto& val = dbc->vals.emplace_back();
|
||||
val.address = std::stoul(match[1].str()); // could be hex
|
||||
val.name = match[2].str();
|
||||
|
||||
auto defvals = match[3].str();
|
||||
std::sregex_token_iterator it{defvals.begin(), defvals.end(), val_split_regexp, -1};
|
||||
// convert strings to UPPER_CASE_WITH_UNDERSCORES
|
||||
std::vector<std::string> words{it, {}};
|
||||
for (auto& w : words) {
|
||||
w = trim(w);
|
||||
std::transform(w.begin(), w.end(), w.begin(), ::toupper);
|
||||
std::replace(w.begin(), w.end(), ' ', '_');
|
||||
}
|
||||
// join string
|
||||
std::stringstream s;
|
||||
std::copy(words.begin(), words.end(), std::ostream_iterator<std::string>(s, " "));
|
||||
val.def_val = s.str();
|
||||
val.def_val = trim(val.def_val);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& m : dbc->msgs) {
|
||||
m.sigs = signals[m.address];
|
||||
}
|
||||
for (auto& v : dbc->vals) {
|
||||
v.sigs = signals[v.address];
|
||||
}
|
||||
return dbc;
|
||||
}
|
||||
|
||||
DBC* dbc_parse(const std::string& dbc_path) {
|
||||
std::ifstream infile(dbc_path);
|
||||
if (!infile) return nullptr;
|
||||
|
||||
const std::string dbc_name = std::filesystem::path(dbc_path).filename();
|
||||
|
||||
std::unique_ptr<ChecksumState> checksum(get_checksum(dbc_name));
|
||||
return dbc_parse_from_stream(dbc_name, infile, checksum.get());
|
||||
}
|
||||
|
||||
const std::string get_dbc_root_path() {
|
||||
char *basedir = std::getenv("BASEDIR");
|
||||
if (basedir != NULL) {
|
||||
return std::string(basedir) + "/opendbc";
|
||||
} else {
|
||||
return DBC_FILE_PATH;
|
||||
}
|
||||
}
|
||||
|
||||
const DBC* dbc_lookup(const std::string& dbc_name) {
|
||||
static std::mutex lock;
|
||||
static std::map<std::string, DBC*> dbcs;
|
||||
|
||||
std::string dbc_file_path = dbc_name;
|
||||
if (!std::filesystem::exists(dbc_file_path)) {
|
||||
dbc_file_path = get_dbc_root_path() + "/" + dbc_name + ".dbc";
|
||||
}
|
||||
|
||||
std::unique_lock lk(lock);
|
||||
auto it = dbcs.find(dbc_name);
|
||||
if (it == dbcs.end()) {
|
||||
it = dbcs.insert(it, {dbc_name, dbc_parse(dbc_file_path)});
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_dbc_names() {
|
||||
static const std::string& dbc_file_path = get_dbc_root_path();
|
||||
std::vector<std::string> dbcs;
|
||||
for (std::filesystem::directory_iterator i(dbc_file_path), end; i != end; i++) {
|
||||
if (!is_directory(i->path())) {
|
||||
std::string filename = i->path().filename();
|
||||
if (!startswith(filename, "_") && endswith(filename, ".dbc")) {
|
||||
dbcs.push_back(filename.substr(0, filename.length() - 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbcs;
|
||||
}
|
||||
98
opendbc/can/packer.cc
Normal file
98
opendbc/can/packer.cc
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "opendbc/can/common.h"
|
||||
|
||||
|
||||
void set_value(std::vector<uint8_t> &msg, const Signal &sig, int64_t ival) {
|
||||
int i = sig.lsb / 8;
|
||||
int bits = sig.size;
|
||||
if (sig.size < 64) {
|
||||
ival &= ((1ULL << sig.size) - 1);
|
||||
}
|
||||
|
||||
while (i >= 0 && i < msg.size() && bits > 0) {
|
||||
int shift = (int)(sig.lsb / 8) == i ? sig.lsb % 8 : 0;
|
||||
int size = std::min(bits, 8 - shift);
|
||||
|
||||
msg[i] &= ~(((1ULL << size) - 1) << shift);
|
||||
msg[i] |= (ival & ((1ULL << size) - 1)) << shift;
|
||||
|
||||
bits -= size;
|
||||
ival >>= size;
|
||||
i = sig.is_little_endian ? i+1 : i-1;
|
||||
}
|
||||
}
|
||||
|
||||
CANPacker::CANPacker(const std::string& dbc_name) {
|
||||
dbc = dbc_lookup(dbc_name);
|
||||
assert(dbc);
|
||||
|
||||
for (const auto& msg : dbc->msgs) {
|
||||
message_lookup[msg.address] = msg;
|
||||
for (const auto& sig : msg.sigs) {
|
||||
signal_lookup[std::make_pair(msg.address, sig.name)] = sig;
|
||||
}
|
||||
}
|
||||
init_crc_lookup_tables();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> CANPacker::pack(uint32_t address, const std::vector<SignalPackValue> &signals) {
|
||||
std::vector<uint8_t> ret(message_lookup[address].size, 0);
|
||||
|
||||
// set all values for all given signal/value pairs
|
||||
bool counter_set = false;
|
||||
for (const auto& sigval : signals) {
|
||||
auto sig_it = signal_lookup.find(std::make_pair(address, sigval.name));
|
||||
if (sig_it == signal_lookup.end()) {
|
||||
// TODO: do something more here. invalid flag like CANParser?
|
||||
WARN("undefined signal %s - %d\n", sigval.name.c_str(), address);
|
||||
continue;
|
||||
}
|
||||
const auto &sig = sig_it->second;
|
||||
|
||||
int64_t ival = (int64_t)(round((sigval.value - sig.offset) / sig.factor));
|
||||
if (ival < 0) {
|
||||
ival = (1ULL << sig.size) + ival;
|
||||
}
|
||||
set_value(ret, sig, ival);
|
||||
|
||||
counter_set = counter_set || (sigval.name == "COUNTER");
|
||||
if (counter_set) {
|
||||
counters[address] = sigval.value;
|
||||
}
|
||||
}
|
||||
|
||||
// set message counter
|
||||
auto sig_it_counter = signal_lookup.find(std::make_pair(address, "COUNTER"));
|
||||
if (!counter_set && sig_it_counter != signal_lookup.end()) {
|
||||
const auto& sig = sig_it_counter->second;
|
||||
|
||||
if (counters.find(address) == counters.end()) {
|
||||
counters[address] = 0;
|
||||
}
|
||||
set_value(ret, sig, counters[address]);
|
||||
counters[address] = (counters[address] + 1) % (1 << sig.size);
|
||||
}
|
||||
|
||||
// set message checksum
|
||||
auto sig_it_checksum = signal_lookup.find(std::make_pair(address, "CHECKSUM"));
|
||||
if (sig_it_checksum != signal_lookup.end()) {
|
||||
const auto &sig = sig_it_checksum->second;
|
||||
if (sig.calc_checksum != nullptr) {
|
||||
unsigned int checksum = sig.calc_checksum(address, sig, ret);
|
||||
set_value(ret, sig, checksum);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This function has a definition in common.h and is used in PlotJuggler
|
||||
Msg* CANPacker::lookup_message(uint32_t address) {
|
||||
return &message_lookup[address];
|
||||
}
|
||||
2
opendbc/can/packer.py
Normal file
2
opendbc/can/packer.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from opendbc.can.packer_pyx import CANPacker # pylint: disable=no-name-in-module, import-error
|
||||
assert CANPacker
|
||||
53
opendbc/can/packer_pyx.pyx
Normal file
53
opendbc/can/packer_pyx.pyx
Normal file
@@ -0,0 +1,53 @@
|
||||
# distutils: language = c++
|
||||
# cython: c_string_encoding=ascii, language_level=3
|
||||
|
||||
from libc.stdint cimport uint8_t
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp.map cimport map
|
||||
from libcpp.string cimport string
|
||||
|
||||
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] name_to_address
|
||||
|
||||
def __init__(self, dbc_name):
|
||||
self.dbc = dbc_lookup(dbc_name)
|
||||
if not self.dbc:
|
||||
raise RuntimeError(f"Can't lookup {dbc_name}")
|
||||
|
||||
self.packer = new cpp_CANPacker(dbc_name)
|
||||
for i in range(self.dbc[0].msgs.size()):
|
||||
msg = self.dbc[0].msgs[i]
|
||||
self.name_to_address[string(msg.name)] = msg.address
|
||||
|
||||
def __dealloc__(self):
|
||||
if self.packer:
|
||||
del self.packer
|
||||
|
||||
cdef vector[uint8_t] pack(self, addr, values):
|
||||
cdef vector[SignalPackValue] values_thing
|
||||
values_thing.reserve(len(values))
|
||||
cdef SignalPackValue spv
|
||||
|
||||
for name, value in values.iteritems():
|
||||
spv.name = name.encode("utf8")
|
||||
spv.value = value
|
||||
values_thing.push_back(spv)
|
||||
|
||||
return self.packer.pack(addr, values_thing)
|
||||
|
||||
cpdef make_can_msg(self, name_or_addr, bus, values):
|
||||
cdef int addr
|
||||
if isinstance(name_or_addr, int):
|
||||
addr = name_or_addr
|
||||
else:
|
||||
addr = self.name_to_address[name_or_addr.encode("utf8")]
|
||||
|
||||
cdef vector[uint8_t] val = self.pack(addr, values)
|
||||
return [addr, 0, (<char *>&val[0])[:val.size()], bus]
|
||||
326
opendbc/can/parser.cc
Normal file
326
opendbc/can/parser.cc
Normal file
@@ -0,0 +1,326 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "cereal/logger/logger.h"
|
||||
#include "opendbc/can/common.h"
|
||||
|
||||
int64_t get_raw_value(const std::vector<uint8_t> &msg, const Signal &sig) {
|
||||
int64_t ret = 0;
|
||||
|
||||
int i = sig.msb / 8;
|
||||
int bits = sig.size;
|
||||
while (i >= 0 && i < msg.size() && bits > 0) {
|
||||
int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i*8;
|
||||
int msb = (int)(sig.msb / 8) == i ? sig.msb : (i+1)*8 - 1;
|
||||
int size = msb - lsb + 1;
|
||||
|
||||
uint64_t d = (msg[i] >> (lsb - (i*8))) & ((1ULL << size) - 1);
|
||||
ret |= d << (bits - size);
|
||||
|
||||
bits -= size;
|
||||
i = sig.is_little_endian ? i-1 : i+1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool MessageState::parse(uint64_t nanos, const std::vector<uint8_t> &dat) {
|
||||
std::vector<double> tmp_vals(parse_sigs.size());
|
||||
bool checksum_failed = false;
|
||||
bool counter_failed = false;
|
||||
|
||||
for (int i = 0; i < parse_sigs.size(); i++) {
|
||||
const auto &sig = parse_sigs[i];
|
||||
|
||||
int64_t tmp = get_raw_value(dat, sig);
|
||||
if (sig.is_signed) {
|
||||
tmp -= ((tmp >> (sig.size-1)) & 0x1) ? (1ULL << sig.size) : 0;
|
||||
}
|
||||
|
||||
//DEBUG("parse 0x%X %s -> %ld\n", address, sig.name, tmp);
|
||||
|
||||
if (!ignore_checksum) {
|
||||
if (sig.calc_checksum != nullptr && sig.calc_checksum(address, sig, dat) != tmp) {
|
||||
checksum_failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignore_counter) {
|
||||
if (sig.type == SignalType::COUNTER && !update_counter_generic(tmp, sig.size)) {
|
||||
counter_failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
tmp_vals[i] = tmp * sig.factor + sig.offset;
|
||||
}
|
||||
|
||||
// only update values if both checksum and counter are valid
|
||||
if (checksum_failed || counter_failed) {
|
||||
LOGE("0x%X message checks failed, checksum failed %d, counter failed %d", address, checksum_failed, counter_failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < parse_sigs.size(); i++) {
|
||||
vals[i] = tmp_vals[i];
|
||||
all_vals[i].push_back(vals[i]);
|
||||
}
|
||||
last_seen_nanos = nanos;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool MessageState::update_counter_generic(int64_t v, int cnt_size) {
|
||||
if (((counter + 1) & ((1 << cnt_size) -1)) != v) {
|
||||
counter_fail = std::min(counter_fail + 1, MAX_BAD_COUNTER);
|
||||
if (counter_fail > 1) {
|
||||
INFO("0x%X COUNTER FAIL #%d -- %d -> %d\n", address, counter_fail, counter, (int)v);
|
||||
}
|
||||
} else if (counter_fail > 0) {
|
||||
counter_fail--;
|
||||
}
|
||||
counter = v;
|
||||
return counter_fail < MAX_BAD_COUNTER;
|
||||
}
|
||||
|
||||
|
||||
CANParser::CANParser(int abus, const std::string& dbc_name, const std::vector<std::pair<uint32_t, int>> &messages)
|
||||
: bus(abus), aligned_buf(kj::heapArray<capnp::word>(1024)) {
|
||||
dbc = dbc_lookup(dbc_name);
|
||||
assert(dbc);
|
||||
init_crc_lookup_tables();
|
||||
|
||||
bus_timeout_threshold = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
for (const auto& [address, frequency] : messages) {
|
||||
// disallow duplicate message checks
|
||||
if (message_states.find(address) != message_states.end()) {
|
||||
std::stringstream is;
|
||||
is << "Duplicate Message Check: " << address;
|
||||
throw std::runtime_error(is.str());
|
||||
}
|
||||
|
||||
MessageState &state = message_states[address];
|
||||
state.address = address;
|
||||
// state.check_frequency = op.check_frequency,
|
||||
|
||||
// msg is not valid if a message isn't received for 10 consecutive steps
|
||||
if (frequency > 0) {
|
||||
state.check_threshold = (1000000000ULL / frequency) * 10;
|
||||
|
||||
// bus timeout threshold should be 10x the fastest msg
|
||||
bus_timeout_threshold = std::min(bus_timeout_threshold, state.check_threshold);
|
||||
}
|
||||
|
||||
const Msg* msg = NULL;
|
||||
for (const auto& m : dbc->msgs) {
|
||||
if (m.address == address) {
|
||||
msg = &m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!msg) {
|
||||
fprintf(stderr, "CANParser: could not find message 0x%X in DBC %s\n", address, dbc_name.c_str());
|
||||
assert(false);
|
||||
}
|
||||
|
||||
state.name = msg->name;
|
||||
state.size = msg->size;
|
||||
assert(state.size <= 64); // max signal size is 64 bytes
|
||||
|
||||
// track all signals for this message
|
||||
state.parse_sigs = msg->sigs;
|
||||
state.vals.resize(msg->sigs.size());
|
||||
state.all_vals.resize(msg->sigs.size());
|
||||
}
|
||||
}
|
||||
|
||||
CANParser::CANParser(int abus, const std::string& dbc_name, bool ignore_checksum, bool ignore_counter)
|
||||
: bus(abus) {
|
||||
// Add all messages and signals
|
||||
|
||||
dbc = dbc_lookup(dbc_name);
|
||||
assert(dbc);
|
||||
init_crc_lookup_tables();
|
||||
|
||||
for (const auto& msg : dbc->msgs) {
|
||||
MessageState state = {
|
||||
.name = msg.name,
|
||||
.address = msg.address,
|
||||
.size = msg.size,
|
||||
.ignore_checksum = ignore_checksum,
|
||||
.ignore_counter = ignore_counter,
|
||||
};
|
||||
|
||||
for (const auto& sig : msg.sigs) {
|
||||
state.parse_sigs.push_back(sig);
|
||||
state.vals.push_back(0);
|
||||
state.all_vals.push_back({});
|
||||
}
|
||||
|
||||
message_states[state.address] = state;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef DYNAMIC_CAPNP
|
||||
void CANParser::update_string(const std::string &data, bool sendcan) {
|
||||
// format for board, make copy due to alignment issues.
|
||||
const size_t buf_size = (data.length() / sizeof(capnp::word)) + 1;
|
||||
if (aligned_buf.size() < buf_size) {
|
||||
aligned_buf = kj::heapArray<capnp::word>(buf_size);
|
||||
}
|
||||
memcpy(aligned_buf.begin(), data.data(), data.length());
|
||||
|
||||
// extract the messages
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.slice(0, buf_size));
|
||||
cereal::Event::Reader event = cmsg.getRoot<cereal::Event>();
|
||||
|
||||
if (first_nanos == 0) {
|
||||
first_nanos = event.getLogMonoTime();
|
||||
}
|
||||
last_nanos = event.getLogMonoTime();
|
||||
|
||||
auto cans = sendcan ? event.getSendcan() : event.getCan();
|
||||
UpdateCans(last_nanos, cans);
|
||||
|
||||
UpdateValid(last_nanos);
|
||||
}
|
||||
|
||||
void CANParser::update_strings(const std::vector<std::string> &data, std::vector<SignalValue> &vals, bool sendcan) {
|
||||
uint64_t current_nanos = 0;
|
||||
for (const auto &d : data) {
|
||||
update_string(d, sendcan);
|
||||
if (current_nanos == 0) {
|
||||
current_nanos = last_nanos;
|
||||
}
|
||||
}
|
||||
query_latest(vals, current_nanos);
|
||||
}
|
||||
|
||||
void CANParser::UpdateCans(uint64_t nanos, const capnp::List<cereal::CanData>::Reader& cans) {
|
||||
//DEBUG("got %d messages\n", cans.size());
|
||||
|
||||
bool bus_empty = true;
|
||||
|
||||
// parse the messages
|
||||
for (const auto cmsg : cans) {
|
||||
if (cmsg.getSrc() != bus) {
|
||||
// DEBUG("skip %d: wrong bus\n", cmsg.getAddress());
|
||||
continue;
|
||||
}
|
||||
bus_empty = false;
|
||||
|
||||
auto state_it = message_states.find(cmsg.getAddress());
|
||||
if (state_it == message_states.end()) {
|
||||
// DEBUG("skip %d: not specified\n", cmsg.getAddress());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dat = cmsg.getDat();
|
||||
|
||||
if (dat.size() > 64) {
|
||||
DEBUG("got message longer than 64 bytes: 0x%X %zu\n", cmsg.getAddress(), dat.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: this actually triggers for some cars. fix and enable this
|
||||
//if (dat.size() != state_it->second.size) {
|
||||
// DEBUG("got message with unexpected length: expected %d, got %zu for %d", state_it->second.size, dat.size(), cmsg.getAddress());
|
||||
// continue;
|
||||
//}
|
||||
|
||||
std::vector<uint8_t> data(dat.size(), 0);
|
||||
memcpy(data.data(), dat.begin(), dat.size());
|
||||
state_it->second.parse(nanos, data);
|
||||
}
|
||||
|
||||
// update bus timeout
|
||||
if (!bus_empty) {
|
||||
last_nonempty_nanos = nanos;
|
||||
}
|
||||
bus_timeout = (nanos - last_nonempty_nanos) > bus_timeout_threshold;
|
||||
}
|
||||
#endif
|
||||
|
||||
void CANParser::UpdateCans(uint64_t nanos, const capnp::DynamicStruct::Reader& cmsg) {
|
||||
// assume message struct is `cereal::CanData` and parse
|
||||
assert(cmsg.has("address") && cmsg.has("src") && cmsg.has("dat") && cmsg.has("busTime"));
|
||||
|
||||
if (cmsg.get("src").as<uint8_t>() != bus) {
|
||||
DEBUG("skip %d: wrong bus\n", cmsg.get("address").as<uint32_t>());
|
||||
return;
|
||||
}
|
||||
|
||||
auto state_it = message_states.find(cmsg.get("address").as<uint32_t>());
|
||||
if (state_it == message_states.end()) {
|
||||
DEBUG("skip %d: not specified\n", cmsg.get("address").as<uint32_t>());
|
||||
return;
|
||||
}
|
||||
|
||||
auto dat = cmsg.get("dat").as<capnp::Data>();
|
||||
if (dat.size() > 64) return; // shouldn't ever happen
|
||||
std::vector<uint8_t> data(dat.size(), 0);
|
||||
memcpy(data.data(), dat.begin(), dat.size());
|
||||
state_it->second.parse(nanos, data);
|
||||
}
|
||||
|
||||
void CANParser::UpdateValid(uint64_t nanos) {
|
||||
const bool show_missing = (last_nanos - first_nanos) > 8e9;
|
||||
|
||||
bool _valid = true;
|
||||
bool _counters_valid = true;
|
||||
for (const auto& kv : message_states) {
|
||||
const auto& state = kv.second;
|
||||
|
||||
if (state.counter_fail >= MAX_BAD_COUNTER) {
|
||||
_counters_valid = false;
|
||||
}
|
||||
|
||||
const bool missing = state.last_seen_nanos == 0;
|
||||
const bool timed_out = (nanos - state.last_seen_nanos) > state.check_threshold;
|
||||
if (state.check_threshold > 0 && (missing || timed_out)) {
|
||||
if (show_missing && !bus_timeout) {
|
||||
if (missing) {
|
||||
LOGE("0x%X '%s' NOT SEEN", state.address, state.name.c_str());
|
||||
} else if (timed_out) {
|
||||
LOGE("0x%X '%s' TIMED OUT", state.address, state.name.c_str());
|
||||
}
|
||||
}
|
||||
_valid = false;
|
||||
}
|
||||
}
|
||||
can_invalid_cnt = _valid ? 0 : (can_invalid_cnt + 1);
|
||||
can_valid = (can_invalid_cnt < CAN_INVALID_CNT) && _counters_valid;
|
||||
}
|
||||
|
||||
void CANParser::query_latest(std::vector<SignalValue> &vals, uint64_t last_ts) {
|
||||
if (last_ts == 0) {
|
||||
last_ts = last_nanos;
|
||||
}
|
||||
for (auto& kv : message_states) {
|
||||
auto& state = kv.second;
|
||||
if (last_ts != 0 && state.last_seen_nanos < last_ts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < state.parse_sigs.size(); i++) {
|
||||
const Signal &sig = state.parse_sigs[i];
|
||||
SignalValue &v = vals.emplace_back();
|
||||
v.address = state.address;
|
||||
v.ts_nanos = state.last_seen_nanos;
|
||||
v.name = sig.name;
|
||||
v.value = state.vals[i];
|
||||
v.all_values = state.all_vals[i];
|
||||
state.all_vals[i].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
opendbc/can/parser.py
Normal file
2
opendbc/can/parser.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from opendbc.can.parser_pyx import CANParser, CANDefine # pylint: disable=no-name-in-module, import-error
|
||||
assert CANParser, CANDefine
|
||||
146
opendbc/can/parser_pyx.pyx
Normal file
146
opendbc/can/parser_pyx.pyx
Normal file
@@ -0,0 +1,146 @@
|
||||
# distutils: language = c++
|
||||
# cython: c_string_encoding=ascii, language_level=3
|
||||
|
||||
from cython.operator cimport dereference as deref, preincrement as preinc
|
||||
from libcpp.pair cimport pair
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp.unordered_set cimport unordered_set
|
||||
from libc.stdint cimport uint32_t
|
||||
|
||||
from .common cimport CANParser as cpp_CANParser
|
||||
from .common cimport dbc_lookup, SignalValue, DBC
|
||||
|
||||
import numbers
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
cdef class CANParser:
|
||||
cdef:
|
||||
cpp_CANParser *can
|
||||
const DBC *dbc
|
||||
vector[SignalValue] can_values
|
||||
|
||||
cdef readonly:
|
||||
dict vl
|
||||
dict vl_all
|
||||
dict ts_nanos
|
||||
string dbc_name
|
||||
|
||||
def __init__(self, dbc_name, messages, bus=0):
|
||||
self.dbc_name = dbc_name
|
||||
self.dbc = dbc_lookup(dbc_name)
|
||||
if not self.dbc:
|
||||
raise RuntimeError(f"Can't find DBC: {dbc_name}")
|
||||
|
||||
self.vl = {}
|
||||
self.vl_all = {}
|
||||
self.ts_nanos = {}
|
||||
msg_name_to_address = {}
|
||||
address_to_msg_name = {}
|
||||
|
||||
for i in range(self.dbc[0].msgs.size()):
|
||||
msg = self.dbc[0].msgs[i]
|
||||
name = msg.name.decode("utf8")
|
||||
|
||||
msg_name_to_address[name] = msg.address
|
||||
address_to_msg_name[msg.address] = name
|
||||
|
||||
# Convert message names into addresses and check existence in DBC
|
||||
cdef vector[pair[uint32_t, int]] message_v
|
||||
for i in range(len(messages)):
|
||||
c = messages[i]
|
||||
address = c[0] if isinstance(c[0], numbers.Number) else msg_name_to_address.get(c[0])
|
||||
if address not in address_to_msg_name:
|
||||
raise RuntimeError(f"could not find message {repr(c[0])} in DBC {self.dbc_name}")
|
||||
message_v.push_back((address, c[1]))
|
||||
|
||||
name = address_to_msg_name[address]
|
||||
self.vl[address] = {}
|
||||
self.vl[name] = self.vl[address]
|
||||
self.vl_all[address] = {}
|
||||
self.vl_all[name] = self.vl_all[address]
|
||||
self.ts_nanos[address] = {}
|
||||
self.ts_nanos[name] = self.ts_nanos[address]
|
||||
|
||||
self.can = new cpp_CANParser(bus, dbc_name, message_v)
|
||||
self.update_strings([])
|
||||
|
||||
def __dealloc__(self):
|
||||
if self.can:
|
||||
del self.can
|
||||
|
||||
def update_strings(self, strings, sendcan=False):
|
||||
for v in self.vl_all.values():
|
||||
for l in v.values(): # no-cython-lint
|
||||
l.clear()
|
||||
|
||||
cdef vector[SignalValue] new_vals
|
||||
cdef unordered_set[uint32_t] updated_addrs
|
||||
|
||||
self.can.update_strings(strings, new_vals, sendcan)
|
||||
cdef vector[SignalValue].iterator it = new_vals.begin()
|
||||
cdef SignalValue* cv
|
||||
while it != new_vals.end():
|
||||
cv = &deref(it)
|
||||
# Cast char * directly to unicode
|
||||
cv_name = <unicode>cv.name
|
||||
self.vl[cv.address][cv_name] = cv.value
|
||||
self.vl_all[cv.address][cv_name] = cv.all_values
|
||||
self.ts_nanos[cv.address][cv_name] = cv.ts_nanos
|
||||
updated_addrs.insert(cv.address)
|
||||
preinc(it)
|
||||
|
||||
return updated_addrs
|
||||
|
||||
@property
|
||||
def can_valid(self):
|
||||
return self.can.can_valid
|
||||
|
||||
@property
|
||||
def bus_timeout(self):
|
||||
return self.can.bus_timeout
|
||||
|
||||
|
||||
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)
|
||||
if not self.dbc:
|
||||
raise RuntimeError(f"Can't find DBC: '{dbc_name}'")
|
||||
|
||||
address_to_msg_name = {}
|
||||
|
||||
for i in range(self.dbc[0].msgs.size()):
|
||||
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(self.dbc[0].vals.size()):
|
||||
val = self.dbc[0].vals[i]
|
||||
|
||||
sgname = val.name.decode("utf8")
|
||||
def_val = val.def_val.decode("utf8")
|
||||
address = val.address
|
||||
msgname = address_to_msg_name[address]
|
||||
|
||||
# separate definition/value pairs
|
||||
def_val = def_val.split()
|
||||
values = [int(v) for v in def_val[::2]]
|
||||
defs = def_val[1::2]
|
||||
|
||||
# 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)
|
||||
1
opendbc/can/tests/.gitignore
vendored
Normal file
1
opendbc/can/tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.bz2
|
||||
8
opendbc/can/tests/__init__.py
Normal file
8
opendbc/can/tests/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import glob
|
||||
import os
|
||||
|
||||
from opendbc import DBC_PATH
|
||||
|
||||
ALL_DBCS = [os.path.basename(dbc).split('.')[0] for dbc in
|
||||
glob.glob(f"{DBC_PATH}/*.dbc")]
|
||||
TEST_DBC = os.path.abspath(os.path.join(os.path.dirname(__file__), "test.dbc"))
|
||||
27
opendbc/can/tests/test.dbc
Normal file
27
opendbc/can/tests/test.dbc
Normal file
@@ -0,0 +1,27 @@
|
||||
CM_ "This DBC is used for the CAN parser and packer tests.";
|
||||
|
||||
BO_ 228 STEERING_CONTROL: 5 EON
|
||||
SG_ STEER_TORQUE_REQUEST : 23|1@0+ (1,0) [0|1] "" EPS
|
||||
SG_ SET_ME_X00 : 22|7@0+ (1,0) [0|127] "" EPS
|
||||
SG_ SET_ME_X00_2 : 31|8@0+ (1,0) [0|0] "" EPS
|
||||
SG_ STEER_TORQUE : 7|16@0- (1,0) [-4096|4096] "" EPS
|
||||
SG_ STEER_DOWN_TO_ZERO : 38|1@0+ (1,0) [0|1] "" EPS
|
||||
SG_ COUNTER : 37|2@0+ (1,0) [0|3] "" EPS
|
||||
SG_ CHECKSUM : 35|4@0+ (1,0) [0|15] "" EPS
|
||||
|
||||
BO_ 316 Brake_Status: 8 XXX
|
||||
SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Signal1 : 12|46@1+ (1,0) [0|1] "" XXX
|
||||
SG_ ES_Brake : 58|1@1+ (1,0) [0|1] "" XXX
|
||||
SG_ Signal2 : 59|3@1+ (1,0) [0|1] "" XXX
|
||||
SG_ Brake : 62|1@1+ (1,0) [0|1] "" XXX
|
||||
SG_ Signal3 : 63|1@1+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 245 CAN_FD_MESSAGE: 32 XXX
|
||||
SG_ COUNTER : 7|8@0+ (1,0) [0|1] "" XXX
|
||||
SG_ SIGNED : 22|16@0- (1,0) [0|1] "" XXX
|
||||
SG_ 64_BIT_LE : 159|64@1+ (1,0) [0|1] "" XXX
|
||||
SG_ 64_BIT_BE : 80|64@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
VAL_ 80 NON_EXISTENT_ADDR 0 "test";
|
||||
42
opendbc/can/tests/test_checksums.py
Normal file
42
opendbc/can/tests/test_checksums.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.can.packer import CANPacker
|
||||
from opendbc.can.tests.test_packer_parser import can_list_to_can_capnp
|
||||
|
||||
|
||||
class TestCanChecksums(unittest.TestCase):
|
||||
|
||||
def test_honda_checksum(self):
|
||||
"""Test checksums for Honda standard and extended CAN ids"""
|
||||
dbc_file = "honda_accord_2018_can_generated"
|
||||
msgs = [("LKAS_HUD", 0), ("LKAS_HUD_A", 0)]
|
||||
parser = CANParser(dbc_file, msgs, 0)
|
||||
packer = CANPacker(dbc_file)
|
||||
|
||||
values = {
|
||||
'SET_ME_X41': 0x41,
|
||||
'STEERING_REQUIRED': 1,
|
||||
'SOLID_LANES': 1,
|
||||
'BEEP': 0,
|
||||
}
|
||||
|
||||
# known correct checksums according to the above values
|
||||
checksum_std = [11, 10, 9, 8]
|
||||
checksum_ext = [4, 3, 2, 1]
|
||||
|
||||
for std, ext in zip(checksum_std, checksum_ext):
|
||||
msgs = [
|
||||
packer.make_can_msg("LKAS_HUD", 0, values),
|
||||
packer.make_can_msg("LKAS_HUD_A", 0, values),
|
||||
]
|
||||
can_strings = [can_list_to_can_capnp(msgs), ]
|
||||
parser.update_strings(can_strings)
|
||||
|
||||
self.assertEqual(parser.vl['LKAS_HUD']['CHECKSUM'], std)
|
||||
self.assertEqual(parser.vl['LKAS_HUD_A']['CHECKSUM'], ext)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
36
opendbc/can/tests/test_dbc_exceptions.py
Normal file
36
opendbc/can/tests/test_dbc_exceptions.py
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
|
||||
from opendbc.can.parser import CANParser, CANDefine
|
||||
from opendbc.can.packer import CANPacker
|
||||
from opendbc.can.tests import TEST_DBC
|
||||
|
||||
|
||||
class TestCanParserPackerExceptions(unittest.TestCase):
|
||||
def test_civic_exceptions(self):
|
||||
dbc_file = "honda_civic_touring_2016_can_generated"
|
||||
dbc_invalid = dbc_file + "abcdef"
|
||||
msgs = [("STEERING_CONTROL", 50)]
|
||||
with self.assertRaises(RuntimeError):
|
||||
CANParser(dbc_invalid, msgs, 0)
|
||||
with self.assertRaises(RuntimeError):
|
||||
CANPacker(dbc_invalid)
|
||||
with self.assertRaises(RuntimeError):
|
||||
CANDefine(dbc_invalid)
|
||||
with self.assertRaises(KeyError):
|
||||
CANDefine(TEST_DBC)
|
||||
|
||||
parser = CANParser(dbc_file, msgs, 0)
|
||||
with self.assertRaises(RuntimeError):
|
||||
parser.update_strings([b''])
|
||||
|
||||
# Everything is supposed to work below
|
||||
CANParser(dbc_file, msgs, 0)
|
||||
CANParser(dbc_file, [], 0)
|
||||
CANPacker(dbc_file)
|
||||
CANDefine(dbc_file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
28
opendbc/can/tests/test_dbc_parser.py
Normal file
28
opendbc/can/tests/test_dbc_parser.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.can.tests import ALL_DBCS
|
||||
|
||||
|
||||
class TestDBCParser(unittest.TestCase):
|
||||
def test_enough_dbcs(self):
|
||||
# sanity check that we're running on the real DBCs
|
||||
self.assertGreater(len(ALL_DBCS), 20)
|
||||
|
||||
def test_parse_all_dbcs(self):
|
||||
"""
|
||||
Dynamic DBC parser checks:
|
||||
- Checksum and counter length, start bit, endianness
|
||||
- Duplicate message addresses and names
|
||||
- Signal out of bounds
|
||||
- All BO_, SG_, VAL_ lines for syntax errors
|
||||
"""
|
||||
|
||||
for dbc in ALL_DBCS:
|
||||
with self.subTest(dbc=dbc):
|
||||
CANParser(dbc, [], 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
35
opendbc/can/tests/test_define.py
Normal file
35
opendbc/can/tests/test_define.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
|
||||
from opendbc.can.can_define import CANDefine
|
||||
from opendbc.can.tests import ALL_DBCS
|
||||
|
||||
|
||||
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':
|
||||
{7: 'PERMANENT_FAULT',
|
||||
6: 'TMP_FAULT',
|
||||
5: 'FAULT_1',
|
||||
4: 'NO_TORQUE_ALERT_2',
|
||||
3: 'LOW_SPEED_LOCKOUT',
|
||||
2: 'NO_TORQUE_ALERT_1',
|
||||
0: 'NORMAL'}
|
||||
}
|
||||
)
|
||||
|
||||
def test_all_dbcs(self):
|
||||
# Asserts no exceptions on all DBCs
|
||||
for dbc in ALL_DBCS:
|
||||
with self.subTest(dbc=dbc):
|
||||
CANDefine(dbc)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
386
opendbc/can/tests/test_packer_parser.py
Normal file
386
opendbc/can/tests/test_packer_parser.py
Normal file
@@ -0,0 +1,386 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import random
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.can.packer import CANPacker
|
||||
from opendbc.can.tests import TEST_DBC
|
||||
|
||||
MAX_BAD_COUNTER = 5
|
||||
|
||||
|
||||
# Python implementation so we don't have to depend on boardd
|
||||
def can_list_to_can_capnp(can_msgs, msgtype='can', logMonoTime=None):
|
||||
dat = messaging.new_message(msgtype, len(can_msgs))
|
||||
|
||||
if logMonoTime is not None:
|
||||
dat.logMonoTime = logMonoTime
|
||||
|
||||
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_packer(self):
|
||||
packer = CANPacker(TEST_DBC)
|
||||
|
||||
for b in range(6):
|
||||
for i in range(256):
|
||||
values = {"COUNTER": i}
|
||||
addr, _, dat, bus = packer.make_can_msg("CAN_FD_MESSAGE", b, values)
|
||||
self.assertEqual(addr, 245)
|
||||
self.assertEqual(bus, b)
|
||||
self.assertEqual(dat[0], i)
|
||||
|
||||
def test_packer_counter(self):
|
||||
msgs = [("CAN_FD_MESSAGE", 0), ]
|
||||
packer = CANPacker(TEST_DBC)
|
||||
parser = CANParser(TEST_DBC, msgs, 0)
|
||||
|
||||
# packer should increment the counter
|
||||
for i in range(1000):
|
||||
msg = packer.make_can_msg("CAN_FD_MESSAGE", 0, {})
|
||||
dat = can_list_to_can_capnp([msg, ])
|
||||
parser.update_strings([dat])
|
||||
self.assertEqual(parser.vl["CAN_FD_MESSAGE"]["COUNTER"], i % 256)
|
||||
|
||||
# setting COUNTER should override
|
||||
for _ in range(100):
|
||||
cnt = random.randint(0, 255)
|
||||
msg = packer.make_can_msg("CAN_FD_MESSAGE", 0, {
|
||||
"COUNTER": cnt,
|
||||
})
|
||||
dat = can_list_to_can_capnp([msg, ])
|
||||
parser.update_strings([dat])
|
||||
self.assertEqual(parser.vl["CAN_FD_MESSAGE"]["COUNTER"], cnt)
|
||||
|
||||
# then, should resume counting from the override value
|
||||
cnt = parser.vl["CAN_FD_MESSAGE"]["COUNTER"]
|
||||
for i in range(100):
|
||||
msg = packer.make_can_msg("CAN_FD_MESSAGE", 0, {})
|
||||
dat = can_list_to_can_capnp([msg, ])
|
||||
parser.update_strings([dat])
|
||||
self.assertEqual(parser.vl["CAN_FD_MESSAGE"]["COUNTER"], (cnt + i) % 256)
|
||||
|
||||
def test_parser_can_valid(self):
|
||||
msgs = [("CAN_FD_MESSAGE", 10), ]
|
||||
packer = CANPacker(TEST_DBC)
|
||||
parser = CANParser(TEST_DBC, msgs, 0)
|
||||
|
||||
# shouldn't be valid initially
|
||||
self.assertFalse(parser.can_valid)
|
||||
|
||||
# not valid until the message is seen
|
||||
for _ in range(100):
|
||||
dat = can_list_to_can_capnp([])
|
||||
parser.update_strings([dat])
|
||||
self.assertFalse(parser.can_valid)
|
||||
|
||||
# valid once seen
|
||||
for i in range(1, 100):
|
||||
t = int(0.01 * i * 1e9)
|
||||
msg = packer.make_can_msg("CAN_FD_MESSAGE", 0, {})
|
||||
dat = can_list_to_can_capnp([msg, ], logMonoTime=t)
|
||||
parser.update_strings([dat])
|
||||
self.assertTrue(parser.can_valid)
|
||||
|
||||
def test_parser_counter_can_valid(self):
|
||||
"""
|
||||
Tests number of allowed bad counters + ensures CAN stays invalid
|
||||
while receiving invalid messages + that we can recover
|
||||
"""
|
||||
msgs = [
|
||||
("STEERING_CONTROL", 0),
|
||||
]
|
||||
packer = CANPacker("honda_civic_touring_2016_can_generated")
|
||||
parser = CANParser("honda_civic_touring_2016_can_generated", msgs, 0)
|
||||
|
||||
msg = packer.make_can_msg("STEERING_CONTROL", 0, {"COUNTER": 0})
|
||||
bts = can_list_to_can_capnp([msg])
|
||||
|
||||
# bad static counter, invalid once it's seen MAX_BAD_COUNTER messages
|
||||
for idx in range(0x1000):
|
||||
parser.update_strings([bts])
|
||||
self.assertEqual((idx + 1) < MAX_BAD_COUNTER, parser.can_valid)
|
||||
|
||||
# one to recover
|
||||
msg = packer.make_can_msg("STEERING_CONTROL", 0, {"COUNTER": 1})
|
||||
bts = can_list_to_can_capnp([msg])
|
||||
parser.update_strings([bts])
|
||||
self.assertTrue(parser.can_valid)
|
||||
|
||||
def test_parser_no_partial_update(self):
|
||||
"""
|
||||
Ensure that the CANParser doesn't partially update messages with invalid signals (COUNTER/CHECKSUM).
|
||||
Previously, the signal update loop would only break once it got to one of these invalid signals,
|
||||
after already updating most/all of the signals.
|
||||
"""
|
||||
msgs = [
|
||||
("STEERING_CONTROL", 0),
|
||||
]
|
||||
packer = CANPacker("honda_civic_touring_2016_can_generated")
|
||||
parser = CANParser("honda_civic_touring_2016_can_generated", msgs, 0)
|
||||
|
||||
def rx_steering_msg(values, bad_checksum=False):
|
||||
msg = packer.make_can_msg("STEERING_CONTROL", 0, values)
|
||||
if bad_checksum:
|
||||
# add 1 to checksum
|
||||
msg[2] = bytearray(msg[2])
|
||||
msg[2][4] = (msg[2][4] & 0xF0) | ((msg[2][4] & 0x0F) + 1)
|
||||
|
||||
bts = can_list_to_can_capnp([msg])
|
||||
parser.update_strings([bts])
|
||||
|
||||
rx_steering_msg({"STEER_TORQUE": 100}, bad_checksum=False)
|
||||
self.assertEqual(parser.vl["STEERING_CONTROL"]["STEER_TORQUE"], 100)
|
||||
self.assertEqual(parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"], [100])
|
||||
|
||||
for _ in range(5):
|
||||
rx_steering_msg({"STEER_TORQUE": 200}, bad_checksum=True)
|
||||
self.assertEqual(parser.vl["STEERING_CONTROL"]["STEER_TORQUE"], 100)
|
||||
self.assertEqual(parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"], [])
|
||||
|
||||
# Even if CANParser doesn't update instantaneous vl, make sure it didn't add invalid values to vl_all
|
||||
rx_steering_msg({"STEER_TORQUE": 300}, bad_checksum=False)
|
||||
self.assertEqual(parser.vl["STEERING_CONTROL"]["STEER_TORQUE"], 300)
|
||||
self.assertEqual(parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"], [300])
|
||||
|
||||
def test_packer_parser(self):
|
||||
msgs = [
|
||||
("Brake_Status", 0),
|
||||
("CAN_FD_MESSAGE", 0),
|
||||
("STEERING_CONTROL", 0),
|
||||
]
|
||||
packer = CANPacker(TEST_DBC)
|
||||
parser = CANParser(TEST_DBC, msgs, 0)
|
||||
|
||||
for steer in range(-256, 255):
|
||||
for active in (1, 0):
|
||||
values = {
|
||||
"STEERING_CONTROL": {
|
||||
"STEER_TORQUE": steer,
|
||||
"STEER_TORQUE_REQUEST": active,
|
||||
},
|
||||
"Brake_Status": {
|
||||
"Signal1": 61042322657536.0,
|
||||
},
|
||||
"CAN_FD_MESSAGE": {
|
||||
"SIGNED": steer,
|
||||
"64_BIT_LE": random.randint(0, 100),
|
||||
"64_BIT_BE": random.randint(0, 100),
|
||||
},
|
||||
}
|
||||
|
||||
msgs = [packer.make_can_msg(k, 0, v) for k, v in values.items()]
|
||||
bts = can_list_to_can_capnp(msgs)
|
||||
parser.update_strings([bts])
|
||||
|
||||
for k, v in values.items():
|
||||
for key, val in v.items():
|
||||
self.assertAlmostEqual(parser.vl[k][key], val)
|
||||
|
||||
# also check address
|
||||
for sig in ("STEER_TORQUE", "STEER_TORQUE_REQUEST", "COUNTER", "CHECKSUM"):
|
||||
self.assertEqual(parser.vl["STEERING_CONTROL"][sig], parser.vl[228][sig])
|
||||
|
||||
def test_scale_offset(self):
|
||||
"""Test that both scale and offset are correctly preserved"""
|
||||
dbc_file = "honda_civic_touring_2016_can_generated"
|
||||
msgs = [("VSA_STATUS", 50)]
|
||||
parser = CANParser(dbc_file, msgs, 0)
|
||||
packer = CANPacker(dbc_file)
|
||||
|
||||
for brake in range(100):
|
||||
values = {"USER_BRAKE": brake}
|
||||
msgs = packer.make_can_msg("VSA_STATUS", 0, values)
|
||||
bts = can_list_to_can_capnp([msgs])
|
||||
|
||||
parser.update_strings([bts])
|
||||
|
||||
self.assertAlmostEqual(parser.vl["VSA_STATUS"]["USER_BRAKE"], brake)
|
||||
|
||||
def test_subaru(self):
|
||||
# Subaru is little endian
|
||||
|
||||
dbc_file = "subaru_global_2017_generated"
|
||||
|
||||
msgs = [("ES_LKAS", 50)]
|
||||
|
||||
parser = CANParser(dbc_file, msgs, 0)
|
||||
packer = CANPacker(dbc_file)
|
||||
|
||||
idx = 0
|
||||
for steer in range(-256, 255):
|
||||
for active in [1, 0]:
|
||||
values = {
|
||||
"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_strings([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
|
||||
|
||||
def test_bus_timeout(self):
|
||||
"""Test CAN bus timeout detection"""
|
||||
dbc_file = "honda_civic_touring_2016_can_generated"
|
||||
|
||||
freq = 100
|
||||
msgs = [("VSA_STATUS", freq), ("STEER_MOTOR_TORQUE", freq/2)]
|
||||
|
||||
parser = CANParser(dbc_file, msgs, 0)
|
||||
packer = CANPacker(dbc_file)
|
||||
|
||||
i = 0
|
||||
def send_msg(blank=False):
|
||||
nonlocal i
|
||||
i += 1
|
||||
t = i*((1 / freq) * 1e9)
|
||||
|
||||
if blank:
|
||||
msgs = []
|
||||
else:
|
||||
msgs = [packer.make_can_msg("VSA_STATUS", 0, {}), ]
|
||||
|
||||
can = can_list_to_can_capnp(msgs, logMonoTime=t)
|
||||
parser.update_strings([can, ])
|
||||
|
||||
# all good, no timeout
|
||||
for _ in range(1000):
|
||||
send_msg()
|
||||
self.assertFalse(parser.bus_timeout, str(_))
|
||||
|
||||
# timeout after 10 blank msgs
|
||||
for n in range(200):
|
||||
send_msg(blank=True)
|
||||
self.assertEqual(n >= 10, parser.bus_timeout)
|
||||
|
||||
# no timeout immediately after seen again
|
||||
send_msg()
|
||||
self.assertFalse(parser.bus_timeout)
|
||||
|
||||
def test_updated(self):
|
||||
"""Test updated value dict"""
|
||||
dbc_file = "honda_civic_touring_2016_can_generated"
|
||||
msgs = [("VSA_STATUS", 50)]
|
||||
parser = CANParser(dbc_file, msgs, 0)
|
||||
packer = CANPacker(dbc_file)
|
||||
|
||||
# Make sure nothing is updated
|
||||
self.assertEqual(len(parser.vl_all["VSA_STATUS"]["USER_BRAKE"]), 0)
|
||||
|
||||
idx = 0
|
||||
for _ in range(10):
|
||||
# Ensure CANParser holds the values of any duplicate messages over multiple frames
|
||||
user_brake_vals = [random.randrange(100) for _ in range(random.randrange(5, 10))]
|
||||
half_idx = len(user_brake_vals) // 2
|
||||
can_msgs = [[], []]
|
||||
for frame, brake_vals in enumerate((user_brake_vals[:half_idx], user_brake_vals[half_idx:])):
|
||||
for user_brake in brake_vals:
|
||||
values = {"USER_BRAKE": user_brake}
|
||||
can_msgs[frame].append(packer.make_can_msg("VSA_STATUS", 0, values))
|
||||
idx += 1
|
||||
|
||||
can_strings = [can_list_to_can_capnp(msgs) for msgs in can_msgs]
|
||||
parser.update_strings(can_strings)
|
||||
vl_all = parser.vl_all["VSA_STATUS"]["USER_BRAKE"]
|
||||
|
||||
self.assertEqual(vl_all, user_brake_vals)
|
||||
if len(user_brake_vals):
|
||||
self.assertEqual(vl_all[-1], parser.vl["VSA_STATUS"]["USER_BRAKE"])
|
||||
|
||||
def test_timestamp_nanos(self):
|
||||
"""Test message timestamp dict"""
|
||||
dbc_file = "honda_civic_touring_2016_can_generated"
|
||||
|
||||
msgs = [
|
||||
("VSA_STATUS", 50),
|
||||
("POWERTRAIN_DATA", 100),
|
||||
]
|
||||
|
||||
parser = CANParser(dbc_file, msgs, 0)
|
||||
packer = CANPacker(dbc_file)
|
||||
|
||||
# Check the default timestamp is zero
|
||||
for msg in ("VSA_STATUS", "POWERTRAIN_DATA"):
|
||||
ts_nanos = parser.ts_nanos[msg].values()
|
||||
self.assertEqual(set(ts_nanos), {0})
|
||||
|
||||
# Check:
|
||||
# - timestamp is only updated for correct messages
|
||||
# - timestamp is correct for multiple runs
|
||||
# - timestamp is from the latest message if updating multiple strings
|
||||
for _ in range(10):
|
||||
can_strings = []
|
||||
log_mono_time = 0
|
||||
for i in range(10):
|
||||
log_mono_time = int(0.01 * i * 1e+9)
|
||||
can_msg = packer.make_can_msg("VSA_STATUS", 0, {})
|
||||
can_strings.append(can_list_to_can_capnp([can_msg], logMonoTime=log_mono_time))
|
||||
parser.update_strings(can_strings)
|
||||
|
||||
ts_nanos = parser.ts_nanos["VSA_STATUS"].values()
|
||||
self.assertEqual(set(ts_nanos), {log_mono_time})
|
||||
ts_nanos = parser.ts_nanos["POWERTRAIN_DATA"].values()
|
||||
self.assertEqual(set(ts_nanos), {0})
|
||||
|
||||
def test_nonexistent_messages(self):
|
||||
# Ensure we don't allow messages not in the DBC
|
||||
existing_messages = ("STEERING_CONTROL", 228, "CAN_FD_MESSAGE", 245)
|
||||
|
||||
for msg in existing_messages:
|
||||
CANParser(TEST_DBC, [(msg, 0)])
|
||||
with self.assertRaises(RuntimeError):
|
||||
new_msg = msg + "1" if isinstance(msg, str) else msg + 1
|
||||
CANParser(TEST_DBC, [(new_msg, 0)])
|
||||
|
||||
def test_track_all_signals(self):
|
||||
parser = CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 0)])
|
||||
self.assertEqual(parser.vl["ACC_CONTROL"], {
|
||||
"ACCEL_CMD": 0,
|
||||
"ALLOW_LONG_PRESS": 0,
|
||||
"ACC_MALFUNCTION": 0,
|
||||
"RADAR_DIRTY": 0,
|
||||
"DISTANCE": 0,
|
||||
"MINI_CAR": 0,
|
||||
"ACC_TYPE": 0,
|
||||
"CANCEL_REQ": 0,
|
||||
"ACC_CUT_IN": 0,
|
||||
"LEAD_VEHICLE_STOPPED": 0,
|
||||
"PERMIT_BRAKING": 0,
|
||||
"RELEASE_STANDSTILL": 0,
|
||||
"ITS_CONNECT_LEAD": 0,
|
||||
"ACCEL_CMD_ALT": 0,
|
||||
"CHECKSUM": 0,
|
||||
})
|
||||
|
||||
def test_disallow_duplicate_messages(self):
|
||||
CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 5)])
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 5), ("ACC_CONTROL", 10)])
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 10), ("ACC_CONTROL", 10)])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
55
opendbc/can/tests/test_parser_performance.py
Normal file
55
opendbc/can/tests/test_parser_performance.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.can.packer import CANPacker
|
||||
from opendbc.can.tests.test_packer_parser import can_list_to_can_capnp
|
||||
|
||||
|
||||
@unittest.skip("TODO: varies too much between machines")
|
||||
class TestParser(unittest.TestCase):
|
||||
def _benchmark(self, checks, thresholds, n):
|
||||
parser = CANParser('toyota_new_mc_pt_generated', checks, 0)
|
||||
packer = CANPacker('toyota_new_mc_pt_generated')
|
||||
|
||||
can_msgs = []
|
||||
for i in range(50000):
|
||||
values = {"ACC_CONTROL": {"ACC_TYPE": 1, "ALLOW_LONG_PRESS": 3}}
|
||||
msgs = [packer.make_can_msg(k, 0, v) for k, v in values.items()]
|
||||
bts = can_list_to_can_capnp(msgs, logMonoTime=int(0.01 * i * 1e9))
|
||||
can_msgs.append(bts)
|
||||
|
||||
ets = []
|
||||
for _ in range(25):
|
||||
if n > 1:
|
||||
strings = []
|
||||
for i in range(0, len(can_msgs), n):
|
||||
strings.append(can_msgs[i:i + n])
|
||||
t1 = time.process_time_ns()
|
||||
for m in strings:
|
||||
parser.update_strings(m)
|
||||
t2 = time.process_time_ns()
|
||||
else:
|
||||
t1 = time.process_time_ns()
|
||||
for m in can_msgs:
|
||||
parser.update_strings([m])
|
||||
t2 = time.process_time_ns()
|
||||
|
||||
ets.append(t2 - t1)
|
||||
|
||||
et = sum(ets) / len(ets)
|
||||
avg_nanos = et / len(can_msgs)
|
||||
print('%s: [%d] %.1fms to parse %s, avg: %dns' % (self._testMethodName, n, et/1e6, len(can_msgs), avg_nanos))
|
||||
|
||||
minn, maxx = thresholds
|
||||
self.assertLess(avg_nanos, maxx)
|
||||
self.assertGreater(avg_nanos, minn, "Performance seems to have improved, update test thresholds.")
|
||||
|
||||
def test_performance_all_signals(self):
|
||||
self._benchmark([('ACC_CONTROL', 10)], (10000, 19000), 1)
|
||||
self._benchmark([('ACC_CONTROL', 10)], (1300, 5000), 10)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user