openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

111 lines
4.3 KiB

#include "tools/replay/timeline.h"
#include <array>
#include <algorithm>
#include "cereal/gen/cpp/log.capnp.h"
Timeline::~Timeline() {
should_exit_.store(true);
if (thread_.joinable()) {
thread_.join();
}
}
void Timeline::initialize(const Route &route, uint64_t route_start_ts, bool local_cache,
std::function<void(std::shared_ptr<LogReader>)> callback) {
thread_ = std::thread(&Timeline::buildTimeline, this, route, route_start_ts, local_cache, callback);
}
std::optional<uint64_t> Timeline::find(double cur_ts, FindFlag flag) const {
for (const auto &entry : *getEntries()) {
if (entry.type == TimelineType::Engaged) {
if (flag == FindFlag::nextEngagement && entry.start_time > cur_ts) {
return entry.start_time;
} else if (flag == FindFlag::nextDisEngagement && entry.end_time > cur_ts) {
return entry.end_time;
}
} else if (entry.start_time > cur_ts) {
if ((flag == FindFlag::nextUserFlag && entry.type == TimelineType::UserFlag) ||
(flag == FindFlag::nextInfo && entry.type == TimelineType::AlertInfo) ||
(flag == FindFlag::nextWarning && entry.type == TimelineType::AlertWarning) ||
(flag == FindFlag::nextCritical && entry.type == TimelineType::AlertCritical)) {
return entry.start_time;
}
}
}
return std::nullopt;
}
std::optional<Timeline::Entry> Timeline::findAlertAtTime(double target_time) const {
for (const auto &entry : *getEntries()) {
if (entry.start_time > target_time) break;
if (entry.end_time >= target_time && entry.type >= TimelineType::AlertInfo) {
return entry;
}
}
return std::nullopt;
}
void Timeline::buildTimeline(const Route &route, uint64_t route_start_ts, bool local_cache,
std::function<void(std::shared_ptr<LogReader>)> callback) {
std::optional<size_t> current_engaged_idx, current_alert_idx;
for (const auto &segment : route.segments()) {
if (should_exit_) break;
auto log = std::make_shared<LogReader>();
if (!log->load(segment.second.qlog, &should_exit_, local_cache, 0, 3) || log->events.empty()) {
continue; // Skip if log loading fails or no events
}
for (const Event &e : log->events) {
double seconds = (e.mono_time - route_start_ts) / 1e9;
if (e.which == cereal::Event::Which::SELFDRIVE_STATE) {
capnp::FlatArrayMessageReader reader(e.data);
auto cs = reader.getRoot<cereal::Event>().getSelfdriveState();
updateEngagementStatus(cs, current_engaged_idx, seconds);
updateAlertStatus(cs, current_alert_idx, seconds);
} else if (e.which == cereal::Event::Which::USER_FLAG) {
staging_entries_.emplace_back(Entry{seconds, seconds, TimelineType::UserFlag});
}
}
// Sort and finalize the timeline entries
auto entries = std::make_shared<std::vector<Entry>>(staging_entries_);
std::sort(entries->begin(), entries->end(), [](auto &a, auto &b) { return a.start_time < b.start_time; });
timeline_entries_ = entries;
callback(log); // Notify the callback once the log is processed
}
}
void Timeline::updateEngagementStatus(const cereal::SelfdriveState::Reader &cs, std::optional<size_t> &idx, double seconds) {
if (idx) staging_entries_[*idx].end_time = seconds;
if (cs.getEnabled()) {
if (!idx) {
idx = staging_entries_.size();
staging_entries_.emplace_back(Entry{seconds, seconds, TimelineType::Engaged});
}
} else {
idx.reset();
}
}
void Timeline::updateAlertStatus(const cereal::SelfdriveState::Reader &cs, std::optional<size_t> &idx, double seconds) {
static auto alert_types = std::array{TimelineType::AlertInfo, TimelineType::AlertWarning, TimelineType::AlertCritical};
Entry *entry = idx ? &staging_entries_[*idx] : nullptr;
if (entry) entry->end_time = seconds;
if (cs.getAlertSize() != cereal::SelfdriveState::AlertSize::NONE) {
auto type = alert_types[(int)cs.getAlertStatus()];
std::string text1 = cs.getAlertText1().cStr();
std::string text2 = cs.getAlertText2().cStr();
if (!entry || entry->type != type || entry->text1 != text1 || entry->text2 != text2) {
idx = staging_entries_.size();
staging_entries_.emplace_back(Entry{seconds, seconds, type, text1, text2}); // Start a new entry
}
} else {
idx.reset();
}
}