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
This commit is contained in:
Adeeb Shihadeh 2022-04-12 22:34:27 -07:00 committed by GitHub
parent eb56fff37a
commit 004db342a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 1 deletions

View File

@ -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<MessageParseOptions> &options,

View File

@ -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()

View File

@ -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<uint64_t>::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<cereal::CanData>::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<cereal::CanData>::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<cereal::CanData>::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

View File

@ -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:

View File

@ -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"