Sfoglia il codice sorgente

add candbc for use dbc decode.

yuchuli 1 giorno fa
parent
commit
3a65019825

+ 101 - 0
include/candbc.h

@@ -0,0 +1,101 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <utility>
+#include <unordered_map>
+#include <vector>
+
+#include "logger.h"
+#include "common_dbc.h"
+
+#define INFO printf
+#define WARN printf
+#define DEBUG(...)
+//#define DEBUG printf
+
+#define MAX_BAD_COUNTER 5
+#define CAN_INVALID_CNT 5
+
+// 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);
+
+struct CanFrame {
+    long src;
+    uint32_t address;
+    std::vector<uint8_t> dat;
+};
+
+struct CanData {
+    uint64_t nanos;
+    std::vector<CanFrame> frames;
+};
+
+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;
+    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);
+    void update(const std::vector<CanData> &can_data, std::vector<SignalValue> &vals);
+    void query_latest(std::vector<SignalValue> &vals, uint64_t last_ts = 0);
+
+protected:
+    void UpdateCans(const CanData &can);
+    void UpdateValid(uint64_t nanos);
+};
+
+class CANPacker {
+private:
+    const DBC *dbc = NULL;
+    std::map<std::pair<uint32_t, std::string>, Signal> signal_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);
+    const Msg* lookup_message(uint32_t address);
+};
+

+ 79 - 0
include/common_dbc.h

@@ -0,0 +1,79 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#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;
+  std::unordered_map<uint32_t, const Msg*> addr_to_msg;
+  std::unordered_map<std::string, const Msg*> name_to_msg;
+};
+
+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();

+ 27 - 0
include/logger.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#ifdef SWAGLOG
+// cppcheck-suppress preprocessorErrorDirective
+#include SWAGLOG
+#else
+
+#define CLOUDLOG_DEBUG 10
+#define CLOUDLOG_INFO 20
+#define CLOUDLOG_WARNING 30
+#define CLOUDLOG_ERROR 40
+#define CLOUDLOG_CRITICAL 50
+
+#define cloudlog(lvl, fmt, ...) printf(fmt "\n", ## __VA_ARGS__)
+#define cloudlog_rl(burst, millis, lvl, fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
+
+#define LOGD(fmt, ...) cloudlog(CLOUDLOG_DEBUG, fmt, ## __VA_ARGS__)
+#define LOG(fmt, ...) cloudlog(CLOUDLOG_INFO, fmt, ## __VA_ARGS__)
+#define LOGW(fmt, ...) cloudlog(CLOUDLOG_WARNING, fmt, ## __VA_ARGS__)
+#define LOGE(fmt, ...) cloudlog(CLOUDLOG_ERROR, fmt, ## __VA_ARGS__)
+
+#define LOGD_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_DEBUG, fmt, ## __VA_ARGS__)
+#define LOG_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_INFO, fmt, ## __VA_ARGS__)
+#define LOGW_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_WARNING, fmt, ## __VA_ARGS__)
+#define LOGE_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_ERROR, fmt, ## __VA_ARGS__)
+
+#endif

+ 74 - 0
src/common/candbc/.gitignore

@@ -0,0 +1,74 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+

+ 1 - 0
src/common/candbc/Readme.md

@@ -0,0 +1 @@
+ 代码来自opendbc项目

+ 221 - 0
src/common/candbc/candbc.cpp

@@ -0,0 +1,221 @@
+#include <array>
+#include <unordered_map>
+
+#include "candbc.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;
+    }
+}
+
+// Initializes CRC lookup tables at module initialization
+struct CrcInitializer {
+    CrcInitializer() {
+        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
+    }
+};
+
+static CrcInitializer crcInitializer;
+
+static const std::unordered_map<uint32_t, std::array<uint8_t, 16>> volkswagen_mqb_crc_constants {
+    {0x40,  {0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40}},  // Airbag_01
+    {0x86,  {0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86}},  // LWI_01
+    {0x9F,  {0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5}},  // LH_EPS_03
+    {0xAD,  {0x3F, 0x69, 0x39, 0xDC, 0x94, 0xF9, 0x14, 0x64, 0xD8, 0x6A, 0x34, 0xCE, 0xA2, 0x55, 0xB5, 0x2C}},  // Getriebe_11
+    {0xFD,  {0xB4, 0xEF, 0xF8, 0x49, 0x1E, 0xE5, 0xC2, 0xC0, 0x97, 0x19, 0x3C, 0xC9, 0xF1, 0x98, 0xD6, 0x61}},  // ESP_21
+    {0x101, {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}},  // ESP_02
+    {0x106, {0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07}},  // ESP_05
+    {0x116, {0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC}},  // ESP_10
+    {0x117, {0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16}},  // ACC_10
+    {0x120, {0xC4, 0xE2, 0x4F, 0xE4, 0xF8, 0x2F, 0x56, 0x81, 0x9F, 0xE5, 0x83, 0x44, 0x05, 0x3F, 0x97, 0xDF}},  // TSK_06
+    {0x121, {0xE9, 0x65, 0xAE, 0x6B, 0x7B, 0x35, 0xE5, 0x5F, 0x4E, 0xC7, 0x86, 0xA2, 0xBB, 0xDD, 0xEB, 0xB4}},  // Motor_20
+    {0x122, {0x37, 0x7D, 0xF3, 0xA9, 0x18, 0x46, 0x6D, 0x4D, 0x3D, 0x71, 0x92, 0x9C, 0xE5, 0x32, 0x10, 0xB9}},  // ACC_06
+    {0x126, {0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA}},  // HCA_01
+    {0x12B, {0x6A, 0x38, 0xB4, 0x27, 0x22, 0xEF, 0xE1, 0xBB, 0xF8, 0x80, 0x84, 0x49, 0xC7, 0x9E, 0x1E, 0x2B}},  // GRA_ACC_01
+    {0x12E, {0xF8, 0xE5, 0x97, 0xC9, 0xD6, 0x07, 0x47, 0x21, 0x66, 0xDD, 0xCF, 0x6F, 0xA1, 0x94, 0x74, 0x63}},  // ACC_07
+    {0x187, {0x7F, 0xED, 0x17, 0xC2, 0x7C, 0xEB, 0x44, 0x21, 0x01, 0xFA, 0xDB, 0x15, 0x4A, 0x6B, 0x23, 0x05}},  // Motor_EV_01
+    {0x1AB, {0x13, 0x21, 0x9B, 0x6A, 0x9A, 0x62, 0xD4, 0x65, 0x18, 0xF1, 0xAB, 0x16, 0x32, 0x89, 0xE7, 0x26}},  // ESP_33
+    {0x30C, {0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F}},  // ACC_02
+    {0x30F, {0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C}},  // SWA_01
+    {0x324, {0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27}},  // ACC_04
+    {0x3C0, {0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3}},  // Klemmen_Status_01
+    {0x3D5, {0xC5, 0x39, 0xC7, 0xF9, 0x92, 0xD8, 0x24, 0xCE, 0xF1, 0xB5, 0x7A, 0xC4, 0xBC, 0x60, 0xE3, 0xD1}},  // Licht_Anf_01
+    {0x65D, {0xAC, 0xB3, 0xAB, 0xEB, 0x7A, 0xE1, 0x3B, 0xF7, 0x73, 0xBA, 0x7C, 0x9E, 0x06, 0x5F, 0x02, 0xD9}},  // ESP_20
+};
+
+unsigned int volkswagen_mqb_checksum(uint32_t address, const Signal &sig, const std::vector<uint8_t> &d) {
+    // This is AUTOSAR E2E Profile 2, CRC-8H2F with a "data ID" (varying by message/counter) appended to the payload
+
+    uint8_t crc = 0xFF; // CRC-8H2F initial value
+
+    // CRC over payload first, skipping the first byte where the CRC lives
+    for (int i = 1; i < d.size(); i++) {
+        crc ^= d[i];
+        crc = crc8_lut_8h2f[crc];
+    }
+
+    // Continue CRC over the "data ID"
+    uint8_t counter = d[1] & 0x0F;
+    auto crc_const = volkswagen_mqb_crc_constants.find(address);
+    if (crc_const != volkswagen_mqb_crc_constants.end()) {
+        crc ^= crc_const->second[counter];
+        crc = crc8_lut_8h2f[crc];
+    } else {
+        printf("Attempt to CRC check undefined Volkswagen message 0x%02X\n", address);
+    }
+
+    return crc ^ 0xFF; // CRC-8H2F final XOR
+}
+
+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;
+}

+ 101 - 0
src/common/candbc/candbc.h

@@ -0,0 +1,101 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <utility>
+#include <unordered_map>
+#include <vector>
+
+#include "logger.h"
+#include "common_dbc.h"
+
+#define INFO printf
+#define WARN printf
+#define DEBUG(...)
+//#define DEBUG printf
+
+#define MAX_BAD_COUNTER 5
+#define CAN_INVALID_CNT 5
+
+// 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);
+
+struct CanFrame {
+    long src;
+    uint32_t address;
+    std::vector<uint8_t> dat;
+};
+
+struct CanData {
+    uint64_t nanos;
+    std::vector<CanFrame> frames;
+};
+
+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;
+    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);
+    void update(const std::vector<CanData> &can_data, std::vector<SignalValue> &vals);
+    void query_latest(std::vector<SignalValue> &vals, uint64_t last_ts = 0);
+
+protected:
+    void UpdateCans(const CanData &can);
+    void UpdateValid(uint64_t nanos);
+};
+
+class CANPacker {
+private:
+    const DBC *dbc = NULL;
+    std::map<std::pair<uint32_t, std::string>, Signal> signal_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);
+    const Msg* lookup_message(uint32_t address);
+};
+

+ 30 - 0
src/common/candbc/candbc.pro

@@ -0,0 +1,30 @@
+CONFIG -= qt
+
+TEMPLATE = lib
+DEFINES += CANDBC_LIBRARY
+
+CONFIG += c++17
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+    candbc.cpp \
+    dbc.cc \
+    packer.cc \
+    parser.cc
+
+HEADERS += \
+    candbc.h \
+    common_dbc.h \
+    logger.h
+
+# Default rules for deployment.
+unix {
+    target.path = /usr/lib
+}
+!isEmpty(target.path): INSTALLS += target
+
+CONFIG += plugin
+

+ 19 - 0
src/common/candbc/candbc_global.h

@@ -0,0 +1,19 @@
+#ifndef CANDBC_GLOBAL_H
+#define CANDBC_GLOBAL_H
+
+#if defined(_MSC_VER) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) \
+    || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
+#define Q_DECL_EXPORT __declspec(dllexport)
+#define Q_DECL_IMPORT __declspec(dllimport)
+#else
+#define Q_DECL_EXPORT __attribute__((visibility("default")))
+#define Q_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+#if defined(CANDBC_LIBRARY)
+#define CANDBC_EXPORT Q_DECL_EXPORT
+#else
+#define CANDBC_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // CANDBC_GLOBAL_H

+ 79 - 0
src/common/candbc/common_dbc.h

@@ -0,0 +1,79 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#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;
+  std::unordered_map<uint32_t, const Msg*> addr_to_msg;
+  std::unordered_map<std::string, const Msg*> name_to_msg;
+};
+
+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();

+ 260 - 0
src/common/candbc/dbc.cc

@@ -0,0 +1,260 @@
+#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 "candbc.h"
+#include "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];
+    dbc->addr_to_msg[m.address] = &m;
+    dbc->name_to_msg[m.name] = &m;
+  }
+  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/dbc";
+  } 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;
+}

+ 27 - 0
src/common/candbc/logger.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#ifdef SWAGLOG
+// cppcheck-suppress preprocessorErrorDirective
+#include SWAGLOG
+#else
+
+#define CLOUDLOG_DEBUG 10
+#define CLOUDLOG_INFO 20
+#define CLOUDLOG_WARNING 30
+#define CLOUDLOG_ERROR 40
+#define CLOUDLOG_CRITICAL 50
+
+#define cloudlog(lvl, fmt, ...) printf(fmt "\n", ## __VA_ARGS__)
+#define cloudlog_rl(burst, millis, lvl, fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
+
+#define LOGD(fmt, ...) cloudlog(CLOUDLOG_DEBUG, fmt, ## __VA_ARGS__)
+#define LOG(fmt, ...) cloudlog(CLOUDLOG_INFO, fmt, ## __VA_ARGS__)
+#define LOGW(fmt, ...) cloudlog(CLOUDLOG_WARNING, fmt, ## __VA_ARGS__)
+#define LOGE(fmt, ...) cloudlog(CLOUDLOG_ERROR, fmt, ## __VA_ARGS__)
+
+#define LOGD_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_DEBUG, fmt, ## __VA_ARGS__)
+#define LOG_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_INFO, fmt, ## __VA_ARGS__)
+#define LOGW_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_WARNING, fmt, ## __VA_ARGS__)
+#define LOGE_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_ERROR, fmt, ## __VA_ARGS__)
+
+#endif

+ 102 - 0
src/common/candbc/packer.cc

@@ -0,0 +1,102 @@
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <map>
+#include <stdexcept>
+#include <utility>
+
+#include "candbc.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) {
+    for (const auto& sig : msg.sigs) {
+      signal_lookup[std::make_pair(msg.address, sig.name)] = sig;
+    }
+  }
+}
+
+std::vector<uint8_t> CANPacker::pack(uint32_t address, const std::vector<SignalPackValue> &signals) {
+  auto msg_it = dbc->addr_to_msg.find(address);
+  if (msg_it == dbc->addr_to_msg.end()) {
+    LOGE("undefined address %d", address);
+    return {};
+  }
+
+  std::vector<uint8_t> ret(msg_it->second->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?
+      LOGE("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);
+
+    if (sigval.name == "COUNTER") {
+      counters[address] = sigval.value;
+      counter_set = true;
+    }
+  }
+
+  // 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
+const Msg* CANPacker::lookup_message(uint32_t address) {
+  return dbc->addr_to_msg.at(address);
+}

+ 265 - 0
src/common/candbc/parser.cc

@@ -0,0 +1,265 @@
+#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 "candbc.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_100("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) {
+  dbc = dbc_lookup(dbc_name);
+  assert(dbc);
+
+  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 = dbc->addr_to_msg.at(address);
+    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);
+
+  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;
+  }
+}
+
+void CANParser::update(const std::vector<CanData> &can_data, std::vector<SignalValue> &vals) {
+  uint64_t current_nanos = 0;
+  for (const auto &c : can_data) {
+    if (first_nanos == 0) {
+      first_nanos = c.nanos;
+    }
+    if (current_nanos == 0) {
+      current_nanos = c.nanos;
+    }
+    last_nanos = c.nanos;
+
+    UpdateCans(c);
+    UpdateValid(last_nanos);
+  }
+  query_latest(vals, current_nanos);
+}
+
+void CANParser::UpdateCans(const CanData &can) {
+  //DEBUG("got %zu messages\n", can.frames.size());
+
+  bool bus_empty = true;
+
+  for (const auto &frame : can.frames) {
+    if (frame.src != bus) {
+      // DEBUG("skip %d: wrong bus\n", cmsg.getAddress());
+      continue;
+    }
+    bus_empty = false;
+
+    auto state_it = message_states.find(frame.address);
+    if (state_it == message_states.end()) {
+      // DEBUG("skip %d: not specified\n", cmsg.getAddress());
+      continue;
+    }
+    if (frame.dat.size() > 64) {
+      DEBUG("got message longer than 64 bytes: 0x%X %zu\n", frame.address, frame.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;
+    //}
+
+    state_it->second.parse(can.nanos, frame.dat);
+  }
+
+  // update bus timeout
+  if (!bus_empty) {
+    last_nonempty_nanos = can.nanos;
+  }
+  bus_timeout = (can.nanos - last_nonempty_nanos) > bus_timeout_threshold;
+}
+
+void CANParser::UpdateValid(uint64_t nanos) {
+  const bool show_missing = (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_100("0x%X '%s' NOT SEEN", state.address, state.name.c_str());
+        } else if (timed_out) {
+          LOGE_100("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();
+    }
+  }
+}

+ 74 - 0
src/test/testcandbc/.gitignore

@@ -0,0 +1,74 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+

+ 11 - 0
src/test/testcandbc/main.cpp

@@ -0,0 +1,11 @@
+#include "mainwindow.h"
+
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+    QApplication a(argc, argv);
+    MainWindow w;
+    w.show();
+    return a.exec();
+}

+ 281 - 0
src/test/testcandbc/mainwindow.cpp

@@ -0,0 +1,281 @@
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include <QFileDialog>
+#include <iostream>
+
+MainWindow::MainWindow(QWidget *parent)
+    : QMainWindow(parent)
+    , ui(new Ui::MainWindow)
+{
+    CANPacker xPac("/home/yuchuli/ADCC_CH.dbc");
+    ui->setupUi(this);
+}
+
+MainWindow::~MainWindow()
+{
+    delete ui;
+}
+
+
+
+void MainWindow::on_LoadDump_clicked()
+{
+    QString str = QFileDialog::getOpenFileName(this,"Load candump file",".","*.txt");
+    if(str.isEmpty())return;
+
+    LoadDump(str);
+}
+
+
+void MainWindow::on_LoadDBC_clicked()
+{
+    QString str = QFileDialog::getOpenFileName(this,"Load dbc file",".","*.dbc");
+    if(str.isEmpty())return;
+
+    mpParser = new CANParser(0,str.toStdString(),false,false);
+    mbdbc = true;
+
+    mpPacker = new CANPacker(str.toStdString());
+}
+
+bool MainWindow::LoadDump(QString strpath)
+{
+    QFile xFile;
+    xFile.setFileName(strpath);
+    if(!xFile.open(QIODevice::ReadOnly))
+    {
+        return false;
+    }
+    mvectorFrame.clear();
+    mvectorrawframe.clear();
+    const int nlinedatamax = 1000;
+    char * pstrline = new char[nlinedatamax];
+    int64_t nread = 0;
+    nread = xFile.readLine(pstrline,nlinedatamax);
+    while(nread > 0)
+    {
+        struct CanFrame xFrame;
+        if(parsedumpline(pstrline,xFrame))
+        {
+            mvectorFrame.push_back(xFrame);
+            mvectorrawframe.push_back(QString(pstrline));
+        }
+ //       std::cout<<" line data: "<<pstrline<<std::endl;
+        nread = xFile.readLine(pstrline,nlinedatamax);
+    }
+    xFile.close();
+    ui->lineEdit_FrameCount->setText(QString::number(mvectorFrame.size()));
+
+    if(mvectorFrame.size() > 0)
+    {
+        ui->lineEdit_CurrentFrame->setText("0");
+        ui->plainTextEdit_Frame->setPlainText(mvectorrawframe[0]);
+        updateparse();
+    }
+    else
+    {
+        ui->lineEdit_CurrentFrame->setText("");
+        ui->plainTextEdit_Frame->setPlainText("");
+    }
+    return true;
+}
+
+bool MainWindow::parsedumpline(char *strline,struct CanFrame & xFrame)
+{
+    xFrame.dat.clear();
+    QString str(strline);
+    QStringList strlist = str.split(" ",Qt::SkipEmptyParts);
+
+    if(strlist.size() >= 2)
+    {
+        QString strlen = strlist[2];
+        if(strlen.length()<4)
+        {
+            return false;
+        }
+        else
+        {
+            QString strlen1 = strlen.mid(1,2);
+            int nlen = strlen1.toInt();
+            if(strlist.size() >= (nlen +3))
+            {
+                unsigned int id = strlist[1].toUInt(nullptr,16);
+
+                xFrame.address = id;
+                xFrame.src = 0;
+                int i;
+                for(i=0;i<nlen;i++)
+                {
+                    unsigned char xdata = static_cast<unsigned char>(  strlist[3+i].toUInt(nullptr,16));
+                    xFrame.dat.push_back(xdata);
+                }
+            }
+            else
+            {
+                return false;
+            }
+        }
+    }
+    else
+    {
+        return false;
+    }
+    return true;
+
+}
+
+
+void MainWindow::on_pushButton_Pre_clicked()
+{
+    int index = ui->lineEdit_CurrentFrame->text().toInt();
+    if(index == 0)
+    {
+        return;
+    }
+    index  = index -1;
+    ui->lineEdit_CurrentFrame->setText(QString::number(index));
+    if((index<static_cast<int>( mvectorrawframe.size())) && (index>= 0))
+    {
+        ui->plainTextEdit_Frame->setPlainText(mvectorrawframe[index]);
+        updateparse();
+    }
+    else
+    {
+        ui->plainTextEdit_Frame->setPlainText("");
+    }
+}
+
+
+void MainWindow::on_pushButton_Next_clicked()
+{
+    int index = ui->lineEdit_CurrentFrame->text().toInt();
+    if(index >= (static_cast<int>( mvectorrawframe.size()) -1))
+    {
+        return;
+    }
+    index  = index +1;
+    ui->lineEdit_CurrentFrame->setText(QString::number(index));
+    if((index<static_cast<int>( mvectorrawframe.size())) && (index>= 0))
+    {
+        ui->plainTextEdit_Frame->setPlainText(mvectorrawframe[index]);
+        updateparse();
+    }
+    else
+    {
+        ui->plainTextEdit_Frame->setPlainText("");
+    }
+}
+
+#ifndef byte
+#define byte unsigned char
+#endif
+
+byte CRCCheck_SAEJ1850(byte msg[], int len, byte idleCrc)
+{
+    int i = 0;
+    int j = 0;
+    byte crc8;
+    byte poly = 0x1D;
+    crc8 = idleCrc;
+    for (i = 0; i<len; i++) {
+        crc8 ^= msg[i];
+        for (j = 0; j<8; j++) {
+            if (crc8 & 0x80) {
+                crc8 = (crc8 << 1) ^ poly;
+            }
+            else {
+                crc8 <<= 1;
+            }
+        }
+    }
+    crc8 ^= 0xFF;
+    return crc8;
+}
+
+void MainWindow::on_pushButton_Goto_clicked()
+{
+
+ //    std::vector<SignalPackValue> xvals;
+ //    SignalPackValue sv;
+ // //   sv.name = "VCU_10_CRC"; sv.value = 164.000000 ;xvals.push_back(sv);
+ //    sv.name = "VCU_10_RollgCntr"; sv.value = 2.0 ;xvals.push_back(sv);
+ //    sv.name = "VCU_10_Resd"; sv.value = 4.0 ;xvals.push_back(sv);
+ //    sv.name = "VCU_TargetGear"; sv.value = 0.0;xvals.push_back(sv);
+ //    sv.name = "VCU_ActualGear"; sv.value = 1.0;xvals.push_back(sv);
+ //    sv.name = "VCU_TargetGearValidData"; sv.value = 1.0;xvals.push_back(sv);
+ //    sv.name = "VCU_ActualGearValidData"; sv.value = 1.0;xvals.push_back(sv);
+ //    sv.name = "VCU_BrakePedalStsValidData"; sv.value = 0.0;xvals.push_back(sv);
+ //    sv.name = "VCU_BrakePedalSts"; sv.value = 0.0 ;xvals.push_back(sv);
+
+ //    std::vector<uint8_t> xdata =  mpPacker->pack(56,xvals);
+
+ //    xdata[2] = 0x22;
+
+ //    byte msg[16];
+ //    int j;
+ //    for(j=0;j<16;j++)msg[j] = xdata[j];
+
+ //    byte crc = CRCCheck_SAEJ1850(msg,16,0);
+
+
+    int index = ui->lineEdit_FrameGo->text().toInt();
+
+    if((index<static_cast<int>( mvectorrawframe.size())) && (index>= 0))
+    {
+        ui->lineEdit_CurrentFrame->setText(QString::number(index));
+        ui->plainTextEdit_Frame->setPlainText(mvectorrawframe[index]);
+        updateparse();
+    }
+    else
+    {
+        ui->plainTextEdit_Frame->setPlainText("");
+    }
+}
+
+void MainWindow::updateparse()
+{
+    if(mbdbc == false)return;
+    int index = ui->lineEdit_CurrentFrame->text().toInt();
+    if((index<static_cast<int>( mvectorFrame.size())) && (index>= 0))
+    {
+        CanData xData;
+        xData.frames.push_back(mvectorFrame[index]);
+        xData.nanos = std::chrono::system_clock::now().time_since_epoch().count();
+        std::vector<CanData> xvectorcandata;
+        xvectorcandata.push_back(xData);
+        std::vector<SignalValue> xvals;
+
+
+        mpParser->update(xvectorcandata,xvals);
+        if(xvals.size()> 0)
+        {
+            char strout[10000];
+            char strline[1000];
+            snprintf(strout,10000,"");
+            unsigned int i;
+            for(i=0;i<xvals.size();i++)
+            {
+                snprintf(strline,1000,"%s:%f\n",xvals[i].name.data(),xvals[i].value);
+                strncat(strout,strline,10000);
+            }
+            ui->plainTextEdit_Value->setPlainText(strout);
+        }
+        else
+        {
+            ui->plainTextEdit_Value->setPlainText("dbc not have this id.");
+        }
+
+    }
+}
+
+void MainWindow::resizeEvent(QResizeEvent *event)
+{
+    QSize sizemain = ui->centralwidget->size();
+
+    ui->plainTextEdit_Value->setGeometry(50,180,sizemain.width()-100,sizemain.height() -230);
+
+}
+
+
+

+ 57 - 0
src/test/testcandbc/mainwindow.h

@@ -0,0 +1,57 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class MainWindow;
+}
+QT_END_NAMESPACE
+
+#include "candbc.h"
+
+class MainWindow : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    MainWindow(QWidget *parent = nullptr);
+    ~MainWindow();
+
+private slots:
+
+
+    void on_LoadDump_clicked();
+
+    void on_LoadDBC_clicked();
+
+    void on_pushButton_Pre_clicked();
+
+    void on_pushButton_Next_clicked();
+
+    void on_pushButton_Goto_clicked();
+
+public:
+    void resizeEvent(QResizeEvent *event);
+
+private:
+    Ui::MainWindow *ui;
+
+private:
+    bool LoadDump(QString strpath);
+    bool parsedumpline(char * strline,struct CanFrame & xFrame);
+
+    void updateparse();
+
+private:
+    std::vector<struct CanFrame> mvectorFrame;
+    std::vector<QString> mvectorrawframe;
+
+    CANParser * mpParser;
+
+    CANPacker * mpPacker;
+    bool mbdbc = false;
+};
+#endif // MAINWINDOW_H

+ 173 - 0
src/test/testcandbc/mainwindow.ui

@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <widget class="QPushButton" name="LoadDBC">
+    <property name="geometry">
+     <rect>
+      <x>660</x>
+      <y>20</y>
+      <width>91</width>
+      <height>41</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Load DBC</string>
+    </property>
+   </widget>
+   <widget class="QPlainTextEdit" name="plainTextEdit_Value">
+    <property name="geometry">
+     <rect>
+      <x>50</x>
+      <y>180</y>
+      <width>701</width>
+      <height>351</height>
+     </rect>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="LoadDump">
+    <property name="geometry">
+     <rect>
+      <x>50</x>
+      <y>20</y>
+      <width>131</width>
+      <height>41</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Load dump</string>
+    </property>
+   </widget>
+   <widget class="QLineEdit" name="lineEdit_FrameCount">
+    <property name="geometry">
+     <rect>
+      <x>310</x>
+      <y>27</y>
+      <width>101</width>
+      <height>31</height>
+     </rect>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label">
+    <property name="geometry">
+     <rect>
+      <x>200</x>
+      <y>24</y>
+      <width>101</width>
+      <height>31</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Frame Count:</string>
+    </property>
+   </widget>
+   <widget class="QLineEdit" name="lineEdit_CurrentFrame">
+    <property name="geometry">
+     <rect>
+      <x>530</x>
+      <y>26</y>
+      <width>101</width>
+      <height>31</height>
+     </rect>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label_2">
+    <property name="geometry">
+     <rect>
+      <x>420</x>
+      <y>25</y>
+      <width>101</width>
+      <height>31</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Current Frame</string>
+    </property>
+   </widget>
+   <widget class="QPlainTextEdit" name="plainTextEdit_Frame">
+    <property name="geometry">
+     <rect>
+      <x>50</x>
+      <y>80</y>
+      <width>481</width>
+      <height>91</height>
+     </rect>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="pushButton_Pre">
+    <property name="geometry">
+     <rect>
+      <x>550</x>
+      <y>79</y>
+      <width>91</width>
+      <height>41</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Pre</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="pushButton_Next">
+    <property name="geometry">
+     <rect>
+      <x>660</x>
+      <y>78</y>
+      <width>91</width>
+      <height>41</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Next</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="pushButton_Goto">
+    <property name="geometry">
+     <rect>
+      <x>660</x>
+      <y>126</y>
+      <width>91</width>
+      <height>41</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Goto</string>
+    </property>
+   </widget>
+   <widget class="QLineEdit" name="lineEdit_FrameGo">
+    <property name="geometry">
+     <rect>
+      <x>550</x>
+      <y>130</y>
+      <width>101</width>
+      <height>31</height>
+     </rect>
+    </property>
+   </widget>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>27</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 30 - 0
src/test/testcandbc/testcandbc.pro

@@ -0,0 +1,30 @@
+QT       += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+CONFIG += c++17
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+    main.cpp \
+    mainwindow.cpp
+
+HEADERS += \
+    mainwindow.h
+
+FORMS += \
+    mainwindow.ui
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+!include(../../../include/common.pri ) {
+    error( "Couldn't find the common.pri file!" )
+}
+
+LIBS += -lcandbc