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.
 
 
 
 
 
 

177 lines
6.0 KiB

#include "tools/replay/route.h"
#include <QDir>
#include <QEventLoop>
#include <QJsonArray>
#include <QJsonDocument>
#include <QRegularExpression>
#include <QtConcurrent>
#include <array>
#include "selfdrive/ui/qt/api.h"
#include "system/hardware/hw.h"
#include "tools/replay/replay.h"
#include "tools/replay/util.h"
Route::Route(const QString &route, const QString &data_dir) : data_dir_(data_dir) {
route_ = parseRoute(route);
}
RouteIdentifier Route::parseRoute(const QString &str) {
RouteIdentifier identifier = {};
QRegularExpression rx(R"(^((?<dongle_id>[a-z0-9]{16})[|_/])?(?<timestamp>.{20})((?<separator>--|/)(?<range>((-?\d+(:(-?\d+)?)?)|(:-?\d+))))?$)");
if (auto match = rx.match(str); match.hasMatch()) {
identifier.dongle_id = match.captured("dongle_id");
identifier.timestamp = match.captured("timestamp");
identifier.str = identifier.dongle_id + "|" + identifier.timestamp;
auto range_str = match.captured("range");
if (auto separator = match.captured("separator"); separator == "/" && !range_str.isEmpty()) {
auto range = range_str.split(":");
identifier.begin_segment = identifier.end_segment = range[0].toInt();
if (range.size() == 2) {
identifier.end_segment = range[1].isEmpty() ? -1 : range[1].toInt();
}
} else if (separator == "--") {
identifier.begin_segment = range_str.toInt();
}
}
return identifier;
}
bool Route::load() {
if (route_.str.isEmpty() || (data_dir_.isEmpty() && route_.dongle_id.isEmpty())) {
rInfo("invalid route format");
return false;
}
date_time_ = QDateTime::fromString(route_.timestamp, "yyyy-MM-dd--HH-mm-ss");
bool ret = data_dir_.isEmpty() ? loadFromServer() : loadFromLocal();
if (ret) {
if (route_.begin_segment == -1) route_.begin_segment = segments_.rbegin()->first;
if (route_.end_segment == -1) route_.end_segment = segments_.rbegin()->first;
for (auto it = segments_.begin(); it != segments_.end(); /**/) {
if (it->first < route_.begin_segment || it->first > route_.end_segment) {
it = segments_.erase(it);
} else {
++it;
}
}
}
return !segments_.empty();
}
bool Route::loadFromServer(int retries) {
for (int i = 1; i <= retries; ++i) {
QString result;
QEventLoop loop;
HttpRequest http(nullptr, !Hardware::PC());
QObject::connect(&http, &HttpRequest::requestDone, [&loop, &result](const QString &json, bool success, QNetworkReply::NetworkError err) {
result = json;
loop.exit((int)err);
});
http.sendRequest(CommaApi::BASE_URL + "/v1/route/" + route_.str + "/files");
auto err = (QNetworkReply::NetworkError)loop.exec();
if (err == QNetworkReply::NoError) {
return loadFromJson(result);
} else if (err == QNetworkReply::ContentAccessDenied || err == QNetworkReply::AuthenticationRequiredError) {
rWarning(">> Unauthorized. Authenticate with tools/lib/auth.py <<");
return false;
}
rWarning("Retrying %d/%d", i, retries);
util::sleep_for(3000);
}
return false;
}
bool Route::loadFromJson(const QString &json) {
QRegExp rx(R"(\/(\d+)\/)");
for (const auto &value : QJsonDocument::fromJson(json.trimmed().toUtf8()).object()) {
for (const auto &url : value.toArray()) {
QString url_str = url.toString();
if (rx.indexIn(url_str) != -1) {
addFileToSegment(rx.cap(1).toInt(), url_str);
}
}
}
return !segments_.empty();
}
bool Route::loadFromLocal() {
QDirIterator it(data_dir_, {QString("%1--*").arg(route_.timestamp)}, QDir::Dirs | QDir::NoDotAndDotDot);
while (it.hasNext()) {
QString segment = it.next();
const int seg_num = segment.mid(segment.lastIndexOf("--") + 2).toInt();
QDir segment_dir(segment);
for (const auto &f : segment_dir.entryList(QDir::Files)) {
addFileToSegment(seg_num, segment_dir.absoluteFilePath(f));
}
}
return !segments_.empty();
}
void Route::addFileToSegment(int n, const QString &file) {
QString name = QUrl(file).fileName();
const int pos = name.lastIndexOf("--");
name = pos != -1 ? name.mid(pos + 2) : name;
if (name == "rlog.bz2" || name == "rlog") {
segments_[n].rlog = file;
} else if (name == "qlog.bz2" || name == "qlog") {
segments_[n].qlog = file;
} else if (name == "fcamera.hevc") {
segments_[n].road_cam = file;
} else if (name == "dcamera.hevc") {
segments_[n].driver_cam = file;
} else if (name == "ecamera.hevc") {
segments_[n].wide_road_cam = file;
} else if (name == "qcamera.ts") {
segments_[n].qcamera = file;
}
}
// class Segment
Segment::Segment(int n, const SegmentFile &files, uint32_t flags, const std::vector<bool> &filters)
: seg_num(n), flags(flags), filters_(filters) {
// [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog
const std::array file_list = {
(flags & REPLAY_FLAG_QCAMERA) || files.road_cam.isEmpty() ? files.qcamera : files.road_cam,
flags & REPLAY_FLAG_DCAM ? files.driver_cam : "",
flags & REPLAY_FLAG_ECAM ? files.wide_road_cam : "",
files.rlog.isEmpty() ? files.qlog : files.rlog,
};
for (int i = 0; i < file_list.size(); ++i) {
if (!file_list[i].isEmpty() && (!(flags & REPLAY_FLAG_NO_VIPC) || i >= MAX_CAMERAS)) {
++loading_;
synchronizer_.addFuture(QtConcurrent::run(this, &Segment::loadFile, i, file_list[i].toStdString()));
}
}
}
Segment::~Segment() {
disconnect();
abort_ = true;
synchronizer_.setCancelOnWait(true);
synchronizer_.waitForFinished();
}
void Segment::loadFile(int id, const std::string file) {
const bool local_cache = !(flags & REPLAY_FLAG_NO_FILE_CACHE);
bool success = false;
if (id < MAX_CAMERAS) {
frames[id] = std::make_unique<FrameReader>();
success = frames[id]->load((CameraType)id, file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3);
} else {
log = std::make_unique<LogReader>(filters_);
success = log->load(file, &abort_, local_cache, 0, 3);
}
if (!success) {
// abort all loading jobs.
abort_ = true;
}
if (--loading_ == 0) {
emit loadFinished(!abort_);
}
}