wip
This commit is contained in:
28
can/SConscript
Normal file
28
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')
|
||||||
246
can/common.cc
Normal file
246
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
can/common.h
Normal file
102
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);
|
||||||
|
};
|
||||||
77
can/common_dbc.h
Normal file
77
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
can/dbc.cc
Normal file
258
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
can/packer.cc
Normal file
98
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];
|
||||||
|
}
|
||||||
326
can/parser.cc
Normal file
326
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
can/tests/.gitignore
vendored
Normal file
1
can/tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.bz2
|
||||||
8
can/tests/__init__.py
Normal file
8
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
can/tests/test.dbc
Normal file
27
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
can/tests/test_checksums.py
Normal file
42
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
can/tests/test_dbc_exceptions.py
Normal file
36
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
can/tests/test_dbc_parser.py
Normal file
28
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
can/tests/test_define.py
Normal file
35
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
can/tests/test_packer_parser.py
Normal file
386
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
can/tests/test_parser_performance.py
Normal file
55
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()
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Reference in New Issue
Block a user