From 004db342a825155f7bd03e7d08f3861e18b23795 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 12 Apr 2022 22:34:27 -0700 Subject: [PATCH] CANParser: add flag for bus timeout (#586) * CANParser: add flag for bus timeout * bump to 500ms * 10x most frequent msg * little test * per bus * Update can/parser.cc --- can/common.h | 3 +++ can/common.pxd | 1 + can/parser.cc | 15 +++++++++++ can/parser_pyx.pyx | 2 ++ can/tests/test_packer_parser.py | 44 ++++++++++++++++++++++++++++++++- 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/can/common.h b/can/common.h index 5041eaf..c9bd6f6 100644 --- a/can/common.h +++ b/can/common.h @@ -60,7 +60,10 @@ private: public: bool can_valid = false; + bool bus_timeout = false; uint64_t last_sec = 0; + uint64_t last_nonempty_sec = 0; + uint64_t bus_timeout_threshold = 0; CANParser(int abus, const std::string& dbc_name, const std::vector &options, diff --git a/can/common.pxd b/can/common.pxd index 107d0c2..eddc7e2 100644 --- a/can/common.pxd +++ b/can/common.pxd @@ -74,6 +74,7 @@ cdef extern from "common.h": cdef cppclass CANParser: bool can_valid + bool bus_timeout CANParser(int, string, vector[MessageParseOptions], vector[SignalParseOptions]) void update_string(string, bool) vector[SignalValue] query_latest() diff --git a/can/parser.cc b/can/parser.cc index 96b6b15..2b7f6c8 100644 --- a/can/parser.cc +++ b/can/parser.cc @@ -108,6 +108,8 @@ CANParser::CANParser(int abus, const std::string& dbc_name, assert(dbc); init_crc_lookup_tables(); + bus_timeout_threshold = std::numeric_limits::max(); + for (const auto& op : options) { MessageState &state = message_states[op.address]; state.address = op.address; @@ -116,6 +118,9 @@ CANParser::CANParser(int abus, const std::string& dbc_name, // msg is not valid if a message isn't received for 10 consecutive steps if (op.check_frequency > 0) { state.check_threshold = (1000000000ULL / op.check_frequency) * 10; + + // bus timeout threshold should be 10x the fastest msg + bus_timeout_threshold = std::min(bus_timeout_threshold, state.check_threshold); } const Msg* msg = NULL; @@ -213,6 +218,8 @@ void CANParser::update_string(const std::string &data, bool sendcan) { void CANParser::UpdateCans(uint64_t sec, const capnp::List::Reader& cans) { //DEBUG("got %d messages\n", cans.size()); + bool bus_empty = true; + // parse the messages for (int i = 0; i < cans.size(); i++) { auto cmsg = cans[i]; @@ -220,6 +227,8 @@ void CANParser::UpdateCans(uint64_t sec, const capnp::List::Rea // 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()); @@ -243,6 +252,12 @@ void CANParser::UpdateCans(uint64_t sec, const capnp::List::Rea memcpy(data.data(), dat.begin(), dat.size()); state_it->second.parse(sec, data); } + + // update bus timeout + if (!bus_empty) { + last_nonempty_sec = sec; + } + bus_timeout = (sec - last_nonempty_sec) > bus_timeout_threshold; } #endif diff --git a/can/parser_pyx.pyx b/can/parser_pyx.pyx index b5f3819..5b30cc5 100644 --- a/can/parser_pyx.pyx +++ b/can/parser_pyx.pyx @@ -30,6 +30,7 @@ cdef class CANParser: dict vl dict vl_all bool can_valid + bool bus_timeout string dbc_name int can_invalid_cnt @@ -111,6 +112,7 @@ cdef class CANParser: if self.can.can_valid: self.can_invalid_cnt = 0 self.can_valid = self.can_invalid_cnt < CAN_INVALID_CNT + self.bus_timeout = self.can.bus_timeout new_vals = self.can.query_latest() for cv in new_vals: diff --git a/can/tests/test_packer_parser.py b/can/tests/test_packer_parser.py index e7710c7..92b398c 100755 --- a/can/tests/test_packer_parser.py +++ b/can/tests/test_packer_parser.py @@ -7,10 +7,13 @@ from opendbc.can.parser import CANParser from opendbc.can.packer import CANPacker # Python implementation so we don't have to depend on boardd -def can_list_to_can_capnp(can_msgs, msgtype='can'): +def can_list_to_can_capnp(can_msgs, msgtype='can', logMonoTime=None): dat = messaging.new_message() dat.init(msgtype, len(can_msgs)) + if logMonoTime is not None: + dat.logMonoTime = logMonoTime + for i, can_msg in enumerate(can_msgs): if msgtype == 'sendcan': cc = dat.sendcan[i] @@ -149,6 +152,45 @@ class TestCanParserPacker(unittest.TestCase): idx += 1 + def test_bus_timeout(self): + """Test CAN bus timeout detection""" + dbc_file = "honda_civic_touring_2016_can_generated" + + freq = 100 + checks = [("VSA_STATUS", freq), ("STEER_MOTOR_TORQUE", freq/2)] + + parser = CANParser(dbc_file, [], checks, 0) + packer = CANPacker(dbc_file) + + i = 0 + def send_msg(blank=False): + nonlocal i + i += 1 + t = i*((1 / freq) * 1e9) + + if blank: + msgs = [] + else: + msgs = [packer.make_can_msg("VSA_STATUS", 0, {}), ] + + can = can_list_to_can_capnp(msgs, logMonoTime=t) + parser.update_strings([can, ]) + + # all good, no timeout + for _ in range(1000): + send_msg() + self.assertFalse(parser.bus_timeout, str(_)) + + # timeout after 10 blank msgs + for n in range(200): + send_msg(blank=True) + self.assertEqual(n >= 10, parser.bus_timeout) + + # no timeout immediately after seen again + send_msg() + self.assertFalse(parser.bus_timeout) + + def test_updated(self): """Test updated value dict""" dbc_file = "honda_civic_touring_2016_can_generated"