openpilot v0.9.6 release
date: 2024-01-12T10:13:37 master commit: ba792d576a49a0899b88a753fa1c52956bedf9e6
This commit is contained in:
24
opendbc/can/SConscript
Normal file
24
opendbc/can/SConscript
Normal file
@@ -0,0 +1,24 @@
|
||||
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)
|
||||
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
|
||||
49
opendbc/can/packer_pyx.pyx
Normal file
49
opendbc/can/packer_pyx.pyx
Normal file
@@ -0,0 +1,49 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
142
opendbc/can/parser_pyx.pyx
Normal file
142
opendbc/can/parser_pyx.pyx
Normal file
@@ -0,0 +1,142 @@
|
||||
# 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 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)
|
||||
Reference in New Issue
Block a user