From 997df1694e55b2061b1657d804300b106d04ed20 Mon Sep 17 00:00:00 2001 From: deanlee Date: Fri, 14 Mar 2025 15:13:40 +0800 Subject: [PATCH 01/10] add flag to auto load a route from the most suitable source --- tools/cabana/cabana.cc | 4 +++- tools/cabana/streams/replaystream.cc | 5 +++-- tools/cabana/streams/replaystream.h | 2 +- tools/lib/logreader.py | 20 +++++++++++++++----- tools/replay/replay.cc | 4 ++-- tools/replay/replay.h | 2 +- tools/replay/route.cc | 23 +++++++++++++++++++++-- tools/replay/route.h | 4 +++- tools/replay/seg_mgr.h | 4 ++-- 9 files changed, 51 insertions(+), 17 deletions(-) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index d140a323e1..6ccbec428e 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -23,6 +23,7 @@ int main(int argc, char *argv[]) { cmd_parser.addHelpOption(); cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); cmd_parser.addOption({"demo", "use a demo route instead of providing your own"}); + cmd_parser.addOption({"auto", "auto load a route from the most suitable source"}); cmd_parser.addOption({"qcam", "load qcamera"}); cmd_parser.addOption({"ecam", "load wide road camera"}); cmd_parser.addOption({"dcam", "load driver camera"}); @@ -69,7 +70,8 @@ int main(int argc, char *argv[]) { } if (!route.isEmpty()) { auto replay_stream = std::make_unique(&app); - if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) { + bool auto_source = cmd_parser.isSet("auto"); + if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags, auto_source)) { return 0; } stream = replay_stream.release(); diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index ebe478ded7..b8cf1be299 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -7,6 +7,7 @@ #include #include "common/timing.h" +#include "common/util.h" #include "tools/cabana/streams/routes.h" ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) { @@ -45,9 +46,9 @@ void ReplayStream::mergeSegments() { } } -bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) { +bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags, bool auto_source) { replay.reset(new Replay(route.toStdString(), {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, - {}, nullptr, replay_flags, data_dir.toStdString())); + {}, nullptr, replay_flags, data_dir.toStdString(), auto_source)); replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->installEventFilter([this](const Event *event) { return eventFilter(event); }); diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index df1f2526a5..d429ed1f95 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -18,7 +18,7 @@ class ReplayStream : public AbstractStream { public: ReplayStream(QObject *parent); void start() override { replay->start(); } - bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); + bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false); bool eventFilter(const Event *event); void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); } bool liveStreaming() const override { return false; } diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 34d3e5ea9f..e354dc8b49 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -6,12 +6,12 @@ import capnp import enum import os import pathlib -import sys import tqdm import urllib.parse import warnings import zstandard as zstd +from argparse import ArgumentParser from collections.abc import Callable, Iterable, Iterator from urllib.parse import parse_qs, urlparse @@ -333,7 +333,17 @@ if __name__ == "__main__": # capnproto <= 0.8.0 throws errors converting byte data to string # below line catches those errors and replaces the bytes with \x__ codecs.register_error("strict", codecs.backslashreplace_errors) - log_path = sys.argv[1] - lr = LogReader(log_path, sort_by_time=True) - for msg in lr: - print(msg) + + parser = ArgumentParser(description="Process a log file and print identifiers or full messages.") + parser.add_argument("log_path", help="Path to the log file") + parser.add_argument( + "--identifiers-only", action="store_true", help="Print only log identifiers instead of full messages" + ) + args = parser.parse_args() + + lr = LogReader(args.log_path, sort_by_time=True) + if args.identifiers_only: + print("\n".join(lr.logreader_identifiers)) + else: + for msg in lr: + print(msg) diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 80f586daa6..a8e5cd9d43 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -15,8 +15,8 @@ void notifyEvent(Callback &callback, Args &&...args) { } Replay::Replay(const std::string &route, std::vector allow, std::vector block, - SubMaster *sm, uint32_t flags, const std::string &data_dir) - : sm_(sm), flags_(flags), seg_mgr_(std::make_unique(route, flags, data_dir)) { + SubMaster *sm, uint32_t flags, const std::string &data_dir, bool auto_source) + : sm_(sm), flags_(flags), seg_mgr_(std::make_unique(route, flags, data_dir, auto_source)) { std::signal(SIGUSR1, interrupt_sleep_handler); if (!(flags_ & REPLAY_FLAG_ALL_SERVICES)) { diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 6a2c86ff02..5e868d2427 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -29,7 +29,7 @@ enum REPLAY_FLAGS { class Replay { public: Replay(const std::string &route, std::vector allow, std::vector block, SubMaster *sm = nullptr, - uint32_t flags = REPLAY_FLAG_NONE, const std::string &data_dir = ""); + uint32_t flags = REPLAY_FLAG_NONE, const std::string &data_dir = "", bool auto_source = false); ~Replay(); bool load(); RouteLoadError lastRouteError() const { return route().lastError(); } diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 7731d0daf4..fa359678fb 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -10,8 +10,11 @@ #include "tools/replay/replay.h" #include "tools/replay/util.h" -Route::Route(const std::string &route, const std::string &data_dir) : data_dir_(data_dir) { - route_ = parseRoute(route); +Route::Route(const std::string &route, const std::string &data_dir, bool auto_source) + : route_string_(route), data_dir_(data_dir), auto_source_(auto_source) { + if (!auto_source) { + route_ = parseRoute(route); + } } RouteIdentifier Route::parseRoute(const std::string &str) { @@ -45,6 +48,22 @@ RouteIdentifier Route::parseRoute(const std::string &str) { bool Route::load() { err_ = RouteLoadError::None; + + if (auto_source_) { + auto cmd = util::string_format("python ../lib/logreader.py \"%s\" --identifiers-only", route_string_.c_str()); + auto output = util::check_output(cmd); + auto log_files = split(output, '\n'); + for (int i = 0; i < log_files.size(); ++i) { + addFileToSegment(i, log_files[i]); + } + route_.begin_segment = 0; + route_.end_segment = log_files.size() - 1; + route_.dongle_id = route_string_; + route_.str = route_string_; + route_.timestamp = ""; + return !segments_.empty(); + } + if (route_.str.empty() || (data_dir_.empty() && route_.dongle_id.empty())) { rInfo("invalid route format"); return false; diff --git a/tools/replay/route.h b/tools/replay/route.h index 1806be5afa..898385ed42 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -40,7 +40,7 @@ struct SegmentFile { class Route { public: - Route(const std::string &route, const std::string &data_dir = {}); + Route(const std::string &route, const std::string &data_dir = {}, bool auto_source = false); bool load(); RouteLoadError lastError() const { return err_; } inline const std::string &name() const { return route_.str; } @@ -61,6 +61,8 @@ protected: std::map segments_; std::time_t date_time_; RouteLoadError err_ = RouteLoadError::None; + bool auto_source_ = false; + std::string route_string_; }; class Segment { diff --git a/tools/replay/seg_mgr.h b/tools/replay/seg_mgr.h index 9158e41618..640169749e 100644 --- a/tools/replay/seg_mgr.h +++ b/tools/replay/seg_mgr.h @@ -20,8 +20,8 @@ public: bool isSegmentLoaded(int n) const { return segments.find(n) != segments.end(); } }; - SegmentManager(const std::string &route_name, uint32_t flags, const std::string &data_dir = "") - : flags_(flags), route_(route_name, data_dir), event_data_(std::make_shared()) {} + SegmentManager(const std::string &route_name, uint32_t flags, const std::string &data_dir = "", bool auto_source = false) + : flags_(flags), route_(route_name, data_dir, auto_source), event_data_(std::make_shared()) {} ~SegmentManager(); bool load(); From 1eb4098c967beb9f91c3f2699707936f8edb76fa Mon Sep 17 00:00:00 2001 From: deanlee Date: Fri, 14 Mar 2025 21:49:32 +0800 Subject: [PATCH 02/10] split to functions --- tools/cabana/cabana.cc | 2 +- tools/replay/route.cc | 41 ++++++++++++++++++++--------------------- tools/replay/route.h | 2 ++ 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 6ccbec428e..ca8bdad990 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -23,7 +23,7 @@ int main(int argc, char *argv[]) { cmd_parser.addHelpOption(); cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); cmd_parser.addOption({"demo", "use a demo route instead of providing your own"}); - cmd_parser.addOption({"auto", "auto load a route from the most suitable source"}); + cmd_parser.addOption({"auto", "auto load the route from the most appropriate available source"}); cmd_parser.addOption({"qcam", "load qcamera"}); cmd_parser.addOption({"ecam", "load wide road camera"}); cmd_parser.addOption({"dcam", "load driver camera"}); diff --git a/tools/replay/route.cc b/tools/replay/route.cc index fa359678fb..475de1e745 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -11,11 +11,7 @@ #include "tools/replay/util.h" Route::Route(const std::string &route, const std::string &data_dir, bool auto_source) - : route_string_(route), data_dir_(data_dir), auto_source_(auto_source) { - if (!auto_source) { - route_ = parseRoute(route); - } -} + : route_string_(route), data_dir_(data_dir), auto_source_(auto_source) {} RouteIdentifier Route::parseRoute(const std::string &str) { RouteIdentifier identifier = {}; @@ -47,23 +43,11 @@ RouteIdentifier Route::parseRoute(const std::string &str) { } bool Route::load() { - err_ = RouteLoadError::None; - - if (auto_source_) { - auto cmd = util::string_format("python ../lib/logreader.py \"%s\" --identifiers-only", route_string_.c_str()); - auto output = util::check_output(cmd); - auto log_files = split(output, '\n'); - for (int i = 0; i < log_files.size(); ++i) { - addFileToSegment(i, log_files[i]); - } - route_.begin_segment = 0; - route_.end_segment = log_files.size() - 1; - route_.dongle_id = route_string_; - route_.str = route_string_; - route_.timestamp = ""; - return !segments_.empty(); - } + return auto_source_ ? loadFromAutoSource() : loadFromCommaApi(); +} +bool Route::loadFromCommaApi() { + route_ = parseRoute(route_string_); if (route_.str.empty() || (data_dir_.empty() && route_.dongle_id.empty())) { rInfo("invalid route format"); return false; @@ -88,6 +72,21 @@ bool Route::load() { return !segments_.empty(); } +bool Route::loadFromAutoSource() { + auto cmd = util::string_format("python ../lib/logreader.py \"%s\" --identifiers-only", route_string_.c_str()); + auto output = util::check_output(cmd); + auto log_files = split(output, '\n'); + for (int i = 0; i < log_files.size(); ++i) { + addFileToSegment(i, log_files[i]); + } + route_.begin_segment = 0; + route_.end_segment = log_files.size() - 1; + route_.dongle_id = route_string_; + route_.str = route_string_; + route_.timestamp = ""; + return !segments_.empty(); +} + bool Route::loadFromServer(int retries) { const std::string url = CommaApi2::BASE_URL + "/v1/route/" + route_.str + "/files"; for (int i = 1; i <= retries; ++i) { diff --git a/tools/replay/route.h b/tools/replay/route.h index 898385ed42..21090264f5 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -52,6 +52,8 @@ public: static RouteIdentifier parseRoute(const std::string &str); protected: + bool loadFromAutoSource(); + bool loadFromCommaApi(); bool loadFromLocal(); bool loadFromServer(int retries = 3); bool loadFromJson(const std::string &json); From ade89d0fcffd2098018039feba8a73d9462bacac Mon Sep 17 00:00:00 2001 From: deanlee Date: Fri, 14 Mar 2025 21:56:50 +0800 Subject: [PATCH 03/10] early return --- tools/replay/route.cc | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 475de1e745..08cf470f83 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -57,19 +57,22 @@ bool Route::loadFromCommaApi() { strptime(route_.timestamp.c_str(), "%Y-%m-%d--%H-%M-%S", &tm_time); date_time_ = mktime(&tm_time); - bool ret = data_dir_.empty() ? 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; - } + bool load_success = data_dir_.empty() ? loadFromServer() : loadFromLocal(); + if (!load_success) { + rInfo("Failed to load route from %s", data_dir_.empty() ? "server" : "local"); + return false; + } + + 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(); + return true; } bool Route::loadFromAutoSource() { From 791363ae1b270564a848f7ce60ccec70651ca6fb Mon Sep 17 00:00:00 2001 From: deanlee Date: Sat, 15 Mar 2025 01:18:32 +0800 Subject: [PATCH 04/10] add --auto to replay --- tools/replay/main.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/replay/main.cc b/tools/replay/main.cc index b33b7fa263..b3bfba9d08 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -18,6 +18,7 @@ Options: -c, --cache Cache segments in memory. Default is 5 -s, --start Start from -x, --playback Playback + --auto auto load the route from the most appropriate available source --demo Use a demo route instead of providing your own -d, --data_dir Local directory with routes -p, --prefix Set OPENPILOT_PREFIX @@ -39,6 +40,7 @@ struct ReplayConfig { std::string data_dir; std::string prefix; uint32_t flags = REPLAY_FLAG_NONE; + bool auto_source = false; int start_seconds = 0; int cache_segments = -1; float playback_speed = -1; @@ -52,6 +54,7 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { {"start", required_argument, nullptr, 's'}, {"playback", required_argument, nullptr, 'x'}, {"demo", no_argument, nullptr, 0}, + {"auto", no_argument, nullptr, 0}, {"data_dir", required_argument, nullptr, 'd'}, {"prefix", required_argument, nullptr, 'p'}, {"dcam", no_argument, nullptr, 0}, @@ -96,6 +99,8 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { std::string name = cli_options[option_index].name; if (name == "demo") { config.route = DEMO_ROUTE; + } else if (name == "auto") { + config.auto_source = true; } else { config.flags |= flag_map.at(name); } @@ -136,7 +141,7 @@ int main(int argc, char *argv[]) { op_prefix = std::make_unique(config.prefix); } - Replay replay(config.route, config.allow, config.block, nullptr, config.flags, config.data_dir); + Replay replay(config.route, config.allow, config.block, nullptr, config.flags, config.data_dir, config.auto_source); if (config.cache_segments > 0) { replay.setSegmentCacheLimit(config.cache_segments); } From 6449b1cad74d3d6d6c5a10f2273a4d98ecd98389 Mon Sep 17 00:00:00 2001 From: deanlee Date: Sat, 15 Mar 2025 01:20:08 +0800 Subject: [PATCH 05/10] README --- tools/cabana/README.md | 1 + tools/replay/README.md | 1 + tools/replay/main.cc | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/cabana/README.md b/tools/cabana/README.md index 0b7c5bf3ee..6574393ba5 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -12,6 +12,7 @@ Options: -h, --help Displays help on commandline options. --help-all Displays help including Qt specific options. --demo use a demo route instead of providing your own + --auto load the route from the most appropriate available source --qcam load qcamera --ecam load wide road camera --msgq read can messages from msgq diff --git a/tools/replay/README.md b/tools/replay/README.md index f02090c58e..eb8491465b 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -64,6 +64,7 @@ Options: -s, --start start from -x playback . between 0.2 - 3 --demo use a demo route instead of providing your own + --auto auto load the route from the most appropriate available source --data_dir local directory with routes --prefix set OPENPILOT_PREFIX --dcam load driver camera diff --git a/tools/replay/main.cc b/tools/replay/main.cc index b3bfba9d08..2346ef8faf 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -18,8 +18,8 @@ Options: -c, --cache Cache segments in memory. Default is 5 -s, --start Start from -x, --playback Playback - --auto auto load the route from the most appropriate available source --demo Use a demo route instead of providing your own + --auto auto load the route from the most appropriate available source -d, --data_dir Local directory with routes -p, --prefix Set OPENPILOT_PREFIX --dcam Load driver camera From b719daa312d4f3b4835c40b073a0602e669b0bba Mon Sep 17 00:00:00 2001 From: deanlee Date: Sat, 15 Mar 2025 03:54:19 +0800 Subject: [PATCH 06/10] cleanup --- tools/lib/logreader.py | 4 +--- tools/replay/main.cc | 10 +++------- tools/replay/route.cc | 4 +--- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index e354dc8b49..763ef35cd6 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -336,9 +336,7 @@ if __name__ == "__main__": parser = ArgumentParser(description="Process a log file and print identifiers or full messages.") parser.add_argument("log_path", help="Path to the log file") - parser.add_argument( - "--identifiers-only", action="store_true", help="Print only log identifiers instead of full messages" - ) + parser.add_argument("--identifiers-only", action="store_true", help="Print only log identifiers") args = parser.parse_args() lr = LogReader(args.log_path, sort_by_time=True) diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 2346ef8faf..3137d6d465 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -97,13 +97,9 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { case 'p': config.prefix = optarg; break; case 0: { std::string name = cli_options[option_index].name; - if (name == "demo") { - config.route = DEMO_ROUTE; - } else if (name == "auto") { - config.auto_source = true; - } else { - config.flags |= flag_map.at(name); - } + if (name == "demo") config.route = DEMO_ROUTE; + else if (name == "auto") config.auto_source = true; + else config.flags |= flag_map.at(name); break; } case 'h': std::cout << helpText; return false; diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 08cf470f83..40f4f030a2 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -77,8 +77,7 @@ bool Route::loadFromCommaApi() { bool Route::loadFromAutoSource() { auto cmd = util::string_format("python ../lib/logreader.py \"%s\" --identifiers-only", route_string_.c_str()); - auto output = util::check_output(cmd); - auto log_files = split(output, '\n'); + auto log_files = split(util::check_output(cmd), '\n'); for (int i = 0; i < log_files.size(); ++i) { addFileToSegment(i, log_files[i]); } @@ -86,7 +85,6 @@ bool Route::loadFromAutoSource() { route_.end_segment = log_files.size() - 1; route_.dongle_id = route_string_; route_.str = route_string_; - route_.timestamp = ""; return !segments_.empty(); } From 9d7b6a02f46258d0a3089efb5a69c835adcf9394 Mon Sep 17 00:00:00 2001 From: deanlee Date: Sat, 15 Mar 2025 20:42:41 +0800 Subject: [PATCH 07/10] remove prefix --- tools/replay/route.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 40f4f030a2..301f7aba8e 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -76,8 +76,16 @@ bool Route::loadFromCommaApi() { } bool Route::loadFromAutoSource() { + auto origin_prefix = getenv("OPENPILOT_PREFIX"); + if (origin_prefix) { + setenv("OPENPILOT_PREFIX", "", 1); + } auto cmd = util::string_format("python ../lib/logreader.py \"%s\" --identifiers-only", route_string_.c_str()); auto log_files = split(util::check_output(cmd), '\n'); + if (origin_prefix) { + setenv("OPENPILOT_PREFIX", origin_prefix, 1); + } + for (int i = 0; i < log_files.size(); ++i) { addFileToSegment(i, log_files[i]); } From b83c1e2d7ed246443bf6d3e967e55ff754ee2a5d Mon Sep 17 00:00:00 2001 From: deanlee Date: Sun, 16 Mar 2025 01:25:18 +0800 Subject: [PATCH 08/10] parse datetime --- tools/replay/route.cc | 26 ++++++++++++++++++++------ tools/replay/route.h | 1 + 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 301f7aba8e..c76e169e72 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -52,10 +52,7 @@ bool Route::loadFromCommaApi() { rInfo("invalid route format"); return false; } - - struct tm tm_time = {0}; - strptime(route_.timestamp.c_str(), "%Y-%m-%d--%H-%M-%S", &tm_time); - date_time_ = mktime(&tm_time); + date_time_ = strToTime(route_.timestamp); bool load_success = data_dir_.empty() ? loadFromServer() : loadFromLocal(); if (!load_success) { @@ -89,10 +86,21 @@ bool Route::loadFromAutoSource() { for (int i = 0; i < log_files.size(); ++i) { addFileToSegment(i, log_files[i]); } + static const std::regex pattern(R"(([a-z0-9]{16})\|(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2}))"); + std::smatch matches; + if (std::regex_search(route_string_, matches, pattern)) { + route_.str = matches[0]; + route_.dongle_id = matches[1]; + route_.timestamp = matches[2]; + date_time_ = strToTime(route_.timestamp); + } else { + route_.dongle_id = route_string_; + route_.timestamp = route_string_; + route_.str = route_string_; + } route_.begin_segment = 0; route_.end_segment = log_files.size() - 1; - route_.dongle_id = route_string_; - route_.str = route_string_; + return !segments_.empty(); } @@ -182,6 +190,12 @@ void Route::addFileToSegment(int n, const std::string &file) { } } +std::time_t Route::strToTime(const std::string ×tamp) { + struct tm tm_time = {0}; + strptime(timestamp.c_str(), "%Y-%m-%d--%H-%M-%S", &tm_time); + return mktime(&tm_time); +} + // class Segment Segment::Segment(int n, const SegmentFile &files, uint32_t flags, const std::vector &filters, diff --git a/tools/replay/route.h b/tools/replay/route.h index 21090264f5..b299ee9a65 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -58,6 +58,7 @@ protected: bool loadFromServer(int retries = 3); bool loadFromJson(const std::string &json); void addFileToSegment(int seg_num, const std::string &file); + std::time_t strToTime(const std::string ×tamp); RouteIdentifier route_ = {}; std::string data_dir_; std::map segments_; From eeb4368bccd8178c74825dce8fb7b91540a2e987 Mon Sep 17 00:00:00 2001 From: deanlee Date: Mon, 17 Mar 2025 00:51:02 +0800 Subject: [PATCH 09/10] cleanup --- tools/replay/route.cc | 50 ++++++++++++++++--------------------------- tools/replay/route.h | 8 +++---- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/tools/replay/route.cc b/tools/replay/route.cc index c76e169e72..47d9b66ad6 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -21,6 +21,7 @@ RouteIdentifier Route::parseRoute(const std::string &str) { if (std::regex_match(str, match, pattern)) { identifier.dongle_id = match[2].str(); identifier.timestamp = match[3].str(); + identifier.date_time = strToTime(identifier.timestamp); identifier.str = identifier.dongle_id + "|" + identifier.timestamp; const auto separator = match[5].str(); @@ -43,33 +44,35 @@ RouteIdentifier Route::parseRoute(const std::string &str) { } bool Route::load() { - return auto_source_ ? loadFromAutoSource() : loadFromCommaApi(); -} - -bool Route::loadFromCommaApi() { route_ = parseRoute(route_string_); if (route_.str.empty() || (data_dir_.empty() && route_.dongle_id.empty())) { rInfo("invalid route format"); return false; } - date_time_ = strToTime(route_.timestamp); - bool load_success = data_dir_.empty() ? loadFromServer() : loadFromLocal(); - if (!load_success) { - rInfo("Failed to load route from %s", data_dir_.empty() ? "server" : "local"); + if (!loadSegments()) { + rInfo("Failed to load segments"); return false; } - 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 true; +} + +bool Route::loadSegments() { + if (!auto_source_) { + bool ret = data_dir_.empty() ? loadFromServer() : loadFromLocal(); + if (ret) { + // Trim segments + if (route_.begin_segment > 0) { + segments_.erase(segments_.begin(), segments_.lower_bound(route_.begin_segment)); + } + if (route_.end_segment >= 0) { + segments_.erase(segments_.upper_bound(route_.end_segment), segments_.end()); + } } + return !segments_.empty(); } - return true; + return loadFromAutoSource(); } bool Route::loadFromAutoSource() { @@ -86,21 +89,6 @@ bool Route::loadFromAutoSource() { for (int i = 0; i < log_files.size(); ++i) { addFileToSegment(i, log_files[i]); } - static const std::regex pattern(R"(([a-z0-9]{16})\|(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2}))"); - std::smatch matches; - if (std::regex_search(route_string_, matches, pattern)) { - route_.str = matches[0]; - route_.dongle_id = matches[1]; - route_.timestamp = matches[2]; - date_time_ = strToTime(route_.timestamp); - } else { - route_.dongle_id = route_string_; - route_.timestamp = route_string_; - route_.str = route_string_; - } - route_.begin_segment = 0; - route_.end_segment = log_files.size() - 1; - return !segments_.empty(); } diff --git a/tools/replay/route.h b/tools/replay/route.h index b299ee9a65..6226a9d64c 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -24,6 +24,7 @@ enum class RouteLoadError { struct RouteIdentifier { std::string dongle_id; std::string timestamp; + std::time_t date_time; int begin_segment = 0; int end_segment = -1; std::string str; @@ -44,7 +45,7 @@ public: bool load(); RouteLoadError lastError() const { return err_; } inline const std::string &name() const { return route_.str; } - inline const std::time_t datetime() const { return date_time_; } + inline const std::time_t datetime() const { return route_.date_time; } inline const std::string &dir() const { return data_dir_; } inline const RouteIdentifier &identifier() const { return route_; } inline const std::map &segments() const { return segments_; } @@ -52,17 +53,16 @@ public: static RouteIdentifier parseRoute(const std::string &str); protected: + bool loadSegments(); bool loadFromAutoSource(); - bool loadFromCommaApi(); bool loadFromLocal(); bool loadFromServer(int retries = 3); bool loadFromJson(const std::string &json); void addFileToSegment(int seg_num, const std::string &file); - std::time_t strToTime(const std::string ×tamp); + static std::time_t strToTime(const std::string ×tamp); RouteIdentifier route_ = {}; std::string data_dir_; std::map segments_; - std::time_t date_time_; RouteLoadError err_ = RouteLoadError::None; bool auto_source_ = false; std::string route_string_; From 2d37a299805c8d32cec3b9258946b067067eb8e7 Mon Sep 17 00:00:00 2001 From: deanlee Date: Tue, 18 Mar 2025 09:38:34 +0800 Subject: [PATCH 10/10] improve help --- tools/cabana/README.md | 3 ++- tools/cabana/cabana.cc | 2 +- tools/replay/README.md | 3 ++- tools/replay/main.cc | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/cabana/README.md b/tools/cabana/README.md index 6574393ba5..7933098e34 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -12,7 +12,8 @@ Options: -h, --help Displays help on commandline options. --help-all Displays help including Qt specific options. --demo use a demo route instead of providing your own - --auto load the route from the most appropriate available source + --auto Auto load the route from the best available source (no video): + internal, openpilotci, comma_api, car_segments, testing_closet --qcam load qcamera --ecam load wide road camera --msgq read can messages from msgq diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index ca8bdad990..97f178b1f3 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -23,7 +23,7 @@ int main(int argc, char *argv[]) { cmd_parser.addHelpOption(); cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); cmd_parser.addOption({"demo", "use a demo route instead of providing your own"}); - cmd_parser.addOption({"auto", "auto load the route from the most appropriate available source"}); + cmd_parser.addOption({"auto", "Auto load the route from the best available source (no video): internal, openpilotci, comma_api, car_segments, testing_closet"}); cmd_parser.addOption({"qcam", "load qcamera"}); cmd_parser.addOption({"ecam", "load wide road camera"}); cmd_parser.addOption({"dcam", "load driver camera"}); diff --git a/tools/replay/README.md b/tools/replay/README.md index eb8491465b..4a09969bd3 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -64,7 +64,8 @@ Options: -s, --start start from -x playback . between 0.2 - 3 --demo use a demo route instead of providing your own - --auto auto load the route from the most appropriate available source + --auto Auto load the route from the best available source (no video): + internal, openpilotci, comma_api, car_segments, testing_closet --data_dir local directory with routes --prefix set OPENPILOT_PREFIX --dcam load driver camera diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 3137d6d465..38a1da292a 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -19,7 +19,8 @@ Options: -s, --start Start from -x, --playback Playback --demo Use a demo route instead of providing your own - --auto auto load the route from the most appropriate available source + --auto Auto load the route from the best available source (no video): + internal, openpilotci, comma_api, car_segments, testing_closet -d, --data_dir Local directory with routes -p, --prefix Set OPENPILOT_PREFIX --dcam Load driver camera