diff --git a/selfdrive/debug/can_printer.py b/selfdrive/debug/can_printer.py index 144449af96..ab09ecb1e4 100755 --- a/selfdrive/debug/can_printer.py +++ b/selfdrive/debug/can_printer.py @@ -27,7 +27,7 @@ def can_printer(bus, max_msg, addr): a = msgs[addr][-1].decode('ascii', 'backslashreplace') x = binascii.hexlify(msgs[addr][-1]).decode('ascii') if max_msg is None or addr < max_msg: - dd += "%04X(%4d)(%6d) %s \"%s\"\n" % (addr, addr, len(msgs[addr]), x, a) + dd += "%04X(%4d)(%6d) %s \"%s\"\n" % (addr, addr, len(msgs[addr]), x.ljust(20), a) print(dd) lp = sec_since_boot() diff --git a/selfdrive/ui/replay/main.cc b/selfdrive/ui/replay/main.cc index 196672d9b2..9439f2db55 100644 --- a/selfdrive/ui/replay/main.cc +++ b/selfdrive/ui/replay/main.cc @@ -83,6 +83,7 @@ int main(int argc, char *argv[]){ parser.addOption({{"b", "block"}, "blacklist of services to send", "block"}); parser.addOption({{"s", "start"}, "start from ", "seconds"}); parser.addOption({"demo", "use a demo route instead of providing your own"}); + parser.addOption({"data_dir", "local directory with routes", "data_dir"}); parser.addOption({"dcam", "load driver camera"}); parser.addOption({"ecam", "load wide road camera"}); @@ -96,9 +97,9 @@ int main(int argc, char *argv[]){ QStringList allow = parser.value("allow").isEmpty() ? QStringList{} : parser.value("allow").split(","); QStringList block = parser.value("block").isEmpty() ? QStringList{} : parser.value("block").split(","); - Replay *replay = new Replay(route, allow, block, nullptr, parser.isSet("dcam"), parser.isSet("ecam"), &app); + Replay *replay = new Replay(route, allow, block, nullptr, parser.isSet("dcam"), parser.isSet("ecam"), parser.value("data_dir"), &app); if (!replay->load()) { - return 0; + return 0; } replay->start(parser.value("start").toInt()); // start keyboard control thread diff --git a/selfdrive/ui/replay/replay.cc b/selfdrive/ui/replay/replay.cc index ce49c6ea27..59482e10ff 100644 --- a/selfdrive/ui/replay/replay.cc +++ b/selfdrive/ui/replay/replay.cc @@ -9,7 +9,7 @@ #include "selfdrive/hardware/hw.h" #include "selfdrive/ui/replay/util.h" -Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *sm_, bool dcam, bool ecam, QObject *parent) +Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *sm_, bool dcam, bool ecam, QString data_dir, QObject *parent) : sm(sm_), load_dcam(dcam), load_ecam(ecam), QObject(parent) { std::vector s; auto event_struct = capnp::Schema::from().asStruct(); @@ -27,7 +27,7 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s if (sm == nullptr) { pm = new PubMaster(s); } - route_ = std::make_unique(route); + route_ = std::make_unique(route, data_dir); events_ = new std::vector(); // doSeek & queueSegment are always executed in the same thread connect(this, &Replay::seekTo, this, &Replay::doSeek); diff --git a/selfdrive/ui/replay/replay.h b/selfdrive/ui/replay/replay.h index e6243bdf27..28fb231ec7 100644 --- a/selfdrive/ui/replay/replay.h +++ b/selfdrive/ui/replay/replay.h @@ -12,7 +12,8 @@ class Replay : public QObject { Q_OBJECT public: - Replay(QString route, QStringList allow, QStringList block, SubMaster *sm = nullptr, bool dcam = false, bool ecam = false, QObject *parent = 0); + Replay(QString route, QStringList allow, QStringList block, SubMaster *sm = nullptr, bool dcam = false, bool ecam = false, + QString data_dir="", QObject *parent = 0); ~Replay(); bool load(); void start(int seconds = 0); diff --git a/selfdrive/ui/replay/route.cc b/selfdrive/ui/replay/route.cc index f43bd47f23..8953596cdb 100644 --- a/selfdrive/ui/replay/route.cc +++ b/selfdrive/ui/replay/route.cc @@ -12,23 +12,27 @@ #include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/replay/util.h" -Route::Route(const QString &route) : route_(route) {} +Route::Route(const QString &route, const QString &data_dir) : route_(route), data_dir_(data_dir) {} bool Route::load() { - QEventLoop loop; - auto onError = [&loop](const QString &err) { loop.quit(); }; - - bool ret = false; - HttpRequest http(nullptr, !Hardware::PC()); - QObject::connect(&http, &HttpRequest::failedResponse, onError); - QObject::connect(&http, &HttpRequest::timeoutResponse, onError); - QObject::connect(&http, &HttpRequest::receivedResponse, [&](const QString json) { - ret = loadFromJson(json); - loop.quit(); - }); - http.sendRequest("https://api.commadotai.com/v1/route/" + route_ + "/files"); - loop.exec(); - return ret; + if (data_dir_.isEmpty()) { + QEventLoop loop; + auto onError = [&loop](const QString &err) { loop.quit(); }; + + bool ret = false; + HttpRequest http(nullptr, !Hardware::PC()); + QObject::connect(&http, &HttpRequest::failedResponse, onError); + QObject::connect(&http, &HttpRequest::timeoutResponse, onError); + QObject::connect(&http, &HttpRequest::receivedResponse, [&](const QString json) { + ret = loadFromJson(json); + loop.quit(); + }); + http.sendRequest("https://api.commadotai.com/v1/route/" + route_ + "/files"); + loop.exec(); + return ret; + } else { + return loadFromLocal(); + } } bool Route::loadFromJson(const QString &json) { @@ -66,6 +70,40 @@ bool Route::loadFromJson(const QString &json) { return true; } +bool Route::loadFromLocal() { + QString prefix = route_.split('|').last(); + if (prefix.isEmpty()) return false; + + QDir log_dir(data_dir_); + QStringList folders = log_dir.entryList(QDir::Dirs | QDir::NoDot | QDir::NoDotDot, QDir::NoSort); + if (folders.isEmpty()) return false; + + for (auto folder : folders) { + const int seg_num = folder.split("--")[2].toInt(); + if (segments_.size() <= seg_num) { + segments_.resize(seg_num + 1); + } + QDir segment_dir(log_dir.filePath(folder)); + for (auto f : segment_dir.entryList(QDir::Files)) { + const QString file_path = segment_dir.absoluteFilePath(f); + if (f.startsWith("rlog")) { + segments_[seg_num].rlog = file_path; + } else if (f.startsWith("qlog")) { + segments_[seg_num].qlog = file_path; + } else if (f.startsWith("fcamera")) { + segments_[seg_num].road_cam = file_path; + } else if (f.startsWith("dcamera")) { + segments_[seg_num].driver_cam = file_path; + } else if (f.startsWith("ecamera")) { + segments_[seg_num].wide_road_cam = file_path; + } else if (f.startsWith("qcamera")) { + segments_[seg_num].qcamera = file_path; + } + } + } + return true; +} + // class Segment Segment::Segment(int n, const SegmentFile &segment_files, bool load_dcam, bool load_ecam) : seg_num_(n), files_(segment_files) { @@ -154,7 +192,7 @@ void Segment::load() { } QString Segment::localPath(const QUrl &url) { - if (url.isLocalFile()) return url.toString(); + if (url.isLocalFile() || QFile(url.toString()).exists()) return url.toString(); QByteArray url_no_query = url.toString(QUrl::RemoveQuery).toUtf8(); return CACHE_DIR.filePath(QString(QCryptographicHash::hash(url_no_query, QCryptographicHash::Sha256).toHex())); diff --git a/selfdrive/ui/replay/route.h b/selfdrive/ui/replay/route.h index 2ece59c48f..754c25f380 100644 --- a/selfdrive/ui/replay/route.h +++ b/selfdrive/ui/replay/route.h @@ -23,15 +23,17 @@ struct SegmentFile { class Route { public: - Route(const QString &route); + Route(const QString &route, const QString &data_dir = {}); bool load(); inline const QString &name() const { return route_; }; inline int size() const { return segments_.size(); } inline SegmentFile &at(int n) { return segments_[n]; } protected: + bool loadFromLocal(); bool loadFromJson(const QString &json); QString route_; + QString data_dir_; std::vector segments_; };