#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cereal/gen/cpp/log.capnp.h" #include "parser_common.h" #define DEBUG(...) // #define DEBUG printf #define INFO printf #define MAX_BAD_COUNTER 5 namespace { uint64_t read_u64_be(const uint8_t* v) { return (((uint64_t)v[0] << 56) | ((uint64_t)v[1] << 48) | ((uint64_t)v[2] << 40) | ((uint64_t)v[3] << 32) | ((uint64_t)v[4] << 24) | ((uint64_t)v[5] << 16) | ((uint64_t)v[6] << 8) | (uint64_t)v[7]); } std::vector g_dbc; bool honda_checksum(int address, uint64_t d, int l) { int target = (d >> l) & 0xF; DEBUG("check checksum %16lx %d", d, l); // remove checksum from calculation d &= ~(0xFLL << l); int s = 0; while (address > 0) { s += (address & 0xF); address >>= 4; } while (d > 0) { s += (d & 0xF); d >>= 4; } s = 8-s; s &= 0xF; DEBUG(" %d = %d\n", target, s); return target == s; } struct MessageState { uint32_t address; std::vector parse_sigs; std::vector vals; uint64_t seen; uint64_t check_threshold; uint8_t counter; uint8_t counter_fail; bool parse(uint64_t sec, uint64_t dat) { for (int i=0; i < parse_sigs.size(); i++) { auto& sig = parse_sigs[i]; int64_t tmp = (dat >> sig.bo) & ((1 << sig.b2)-1); if (sig.is_signed) { tmp -= (tmp >> (sig.b2-1)) ? (1< %ld\n", address, sig.name, tmp); if (sig.type == SignalType::HONDA_CHECKSUM) { if (!honda_checksum(address, dat, sig.bo)) { INFO("%X CHECKSUM FAIL\n", address); return false; } } else if (sig.type == SignalType::HONDA_COUNTER) { if (!honda_update_counter(tmp)) { return false; } } vals[i] = tmp * sig.factor + sig.offset; } seen = sec; return true; } bool honda_update_counter(int64_t v) { uint8_t old_counter = counter; counter = v; if (((old_counter+1) & 3) != v) { counter_fail += 1; if (counter_fail > 1) { INFO("%X COUNTER FAIL %d -- %d vs %d\n", address, counter_fail, old_counter, (int)v); } if (counter_fail >= MAX_BAD_COUNTER) { return false; } } else if (counter_fail > 0) { counter_fail--; } return true; } }; class CANParser { public: CANParser(int abus, const std::string& dbc_name, const std::vector &options, const std::vector &sigoptions) : bus(abus) { // connect to can on 8006 context = zmq_ctx_new(); subscriber = zmq_socket(context, ZMQ_SUB); zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, "", 0); zmq_connect(subscriber, "tcp://127.0.0.1:8006"); for (auto dbci : g_dbc) { if (dbci->name == dbc_name) { dbc = dbci; break; } } assert(dbc); for (auto &op : options) { MessageState state = { .address = op.address, // .check_frequency = op.check_frequency, }; // 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; } const Msg* msg = NULL; for (int i=0; inum_msgs; i++) { if (dbc->msgs[i].address == op.address) { msg = &dbc->msgs[i]; break; } } if (!msg) { fprintf(stderr, "CANParser: could not find message 0x%X in dnc %s\n", op.address, dbc_name.c_str()); assert(false); } // track checksums and for this message for (int i=0; inum_sigs; i++) { const Signal *sig = &msg->sigs[i]; if (sig->type == HONDA_CHECKSUM || sig->type == HONDA_COUNTER) { state.parse_sigs.push_back(*sig); state.vals.push_back(0); } } // track requested signals for this message for (auto &sigop : sigoptions) { if (sigop.address != op.address) continue; for (int i=0; inum_sigs; i++) { const Signal *sig = &msg->sigs[i]; if (strcmp(sig->name, sigop.name) == 0 && sig->type != HONDA_CHECKSUM && sig->type != HONDA_COUNTER) { state.parse_sigs.push_back(*sig); state.vals.push_back(sigop.default_value); break; } } } message_states[state.address] = state; } } void UpdateCans(uint64_t sec, const capnp::List::Reader& cans) { int msg_count = cans.size(); DEBUG("got %d messages\n", msg_count); // parse the messages for (int i = 0; i < msg_count; i++) { auto cmsg = cans[i]; if (cmsg.getSrc() != bus) { // DEBUG("skip %d: wrong bus\n", cmsg.getAddress()); continue; } auto state_it = message_states.find(cmsg.getAddress()); if (state_it == message_states.end()) { // DEBUG("skip %d: not specified\n", cmsg.getAddress()); continue; } if (cmsg.getDat().size() > 8) continue; //shouldnt ever happen uint8_t dat[8] = {0}; memcpy(dat, cmsg.getDat().begin(), cmsg.getDat().size()); uint64_t p = read_u64_be(dat); DEBUG(" proc %X: %lx\n", cmsg.getAddress(), p); state_it->second.parse(sec, p); } } void UpdateValid(uint64_t sec) { can_valid = true; for (auto &kv : message_states) { auto &state = kv.second; if (state.check_threshold > 0 && (sec - state.seen) > state.check_threshold) { if (state.seen > 0) { INFO("%X TIMEOUT\n", state.address); } can_valid = false; } } } void update(uint64_t sec, bool wait) { int err; // recv from can zmq_msg_t msg; zmq_msg_init(&msg); // multiple recv is fine bool first = wait; while (1) { if (first) { err = zmq_msg_recv(&msg, subscriber, 0); first = false; } else { err = zmq_msg_recv(&msg, subscriber, ZMQ_DONTWAIT); } if (err < 0) break; // format for board, make copy due to alignment issues, will be freed on out of scope auto amsg = kj::heapArray((zmq_msg_size(&msg) / sizeof(capnp::word)) + 1); memcpy(amsg.begin(), zmq_msg_data(&msg), zmq_msg_size(&msg)); // extract the messages capnp::FlatArrayMessageReader cmsg(amsg); cereal::Event::Reader event = cmsg.getRoot(); auto cans = event.getCan(); UpdateCans(sec, cans); } UpdateValid(sec); } std::vector query(uint64_t sec) { std::vector ret; for (auto &kv : message_states) { auto &state = kv.second; if (sec != 0 && state.seen != sec) continue; for (int i=0; i message_states; }; } void dbc_register(const DBC* dbc) { g_dbc.push_back(dbc); } extern "C" { void* can_init(int bus, const char* dbc_name, size_t num_message_options, const MessageParseOptions* message_options, size_t num_signal_options, const SignalParseOptions* signal_options) { CANParser* ret = new CANParser(bus, std::string(dbc_name), (message_options ? std::vector(message_options, message_options+num_message_options) : std::vector{}), (signal_options ? std::vector(signal_options, signal_options+num_signal_options) : std::vector{})); return (void*)ret; } void can_update(void* can, uint64_t sec, bool wait) { CANParser* cp = (CANParser*)can; cp->update(sec, wait); } size_t can_query(void* can, uint64_t sec, bool *out_can_valid, size_t out_values_size, SignalValue* out_values) { CANParser* cp = (CANParser*)can; if (out_can_valid) { *out_can_valid = cp->can_valid; } const std::vector values = cp->query(sec); if (out_values) { std::copy(values.begin(), values.begin()+std::min(out_values_size, values.size()), out_values); } return values.size(); }; } #ifdef TEST int main(int argc, char** argv) { CANParser cp(0, "honda_civic_touring_2016_can", std::vector{ // address, check_frequency {0x14a, 100}, {0x158, 100}, {0x17c, 100}, {0x191, 100}, {0x1a4, 50}, {0x326, 10}, {0x1b0, 50}, {0x1d0, 50}, {0x305, 10}, {0x324, 10}, {0x405, 3}, {0x18f, 0}, {0x130, 0}, {0x296, 0}, {0x30c, 0}, }, std::vector{ // sig_name, sig_address, default {0x158, "XMISSION_SPEED", 0}, {0x1d0, "WHEEL_SPEED_FL", 0}, {0x1d0, "WHEEL_SPEED_FR", 0}, {0x1d0, "WHEEL_SPEED_RL", 0}, {0x14a, "STEER_ANGLE", 0}, {0x18f, "STEER_TORQUE_SENSOR", 0}, {0x191, "GEAR", 0}, {0x1b0, "WHEELS_MOVING", 1}, {0x405, "DOOR_OPEN_FL", 1}, {0x405, "DOOR_OPEN_FR", 1}, {0x405, "DOOR_OPEN_RL", 1}, {0x405, "DOOR_OPEN_RR", 1}, {0x324, "CRUISE_SPEED_PCM", 0}, {0x305, "SEATBELT_DRIVER_LAMP", 1}, {0x305, "SEATBELT_DRIVER_LATCHED", 0}, {0x17c, "BRAKE_PRESSED", 0}, {0x130, "CAR_GAS", 0}, {0x296, "CRUISE_BUTTONS", 0}, {0x1a4, "ESP_DISABLED", 1}, {0x30c, "HUD_LEAD", 0}, {0x1a4, "USER_BRAKE", 0}, {0x18f, "STEER_STATUS", 5}, {0x1d0, "WHEEL_SPEED_RR", 0}, {0x1b0, "BRAKE_ERROR_1", 1}, {0x1b0, "BRAKE_ERROR_2", 1}, {0x191, "GEAR_SHIFTER", 0}, {0x326, "MAIN_ON", 0}, {0x17c, "ACC_STATUS", 0}, {0x17c, "PEDAL_GAS", 0}, {0x296, "CRUISE_SETTING", 0}, {0x326, "LEFT_BLINKER", 0}, {0x326, "RIGHT_BLINKER", 0}, {0x324, "COUNTER", 0}, {0x17c, "ENGINE_RPM", 0}, }); const std::string log_fn = "dats.bin"; int log_fd = open(log_fn.c_str(), O_RDONLY, 0); assert(log_fd >= 0); off_t log_size = lseek(log_fd, 0, SEEK_END); lseek(log_fd, 0, SEEK_SET); void* log_data = mmap(NULL, log_size, PROT_READ, MAP_PRIVATE, log_fd, 0); assert(log_data); auto words = kj::arrayPtr((const capnp::word*)log_data, log_size/sizeof(capnp::word)); while (words.size() > 0) { capnp::FlatArrayMessageReader reader(words); auto evt = reader.getRoot(); auto cans = evt.getCan(); cp.UpdateCans(0, cans); words = kj::arrayPtr(reader.getEnd(), words.end()); } munmap(log_data, log_size); close(log_fd); return 0; } #endif