#include #include #include #include #include #include #include #include #include "common/util.h" #include "common/prefix.h" #include "third_party/json11/json11.hpp" #include "tools/replay/consoleui.h" #include "tools/replay/replay.h" #include "tools/replay/util.h" const std::string helpText = R"(Usage: replay [options] [route] Options: -a, --allow Whitelist of services to send (comma-separated) -b, --block Blacklist of services to send (comma-separated) -c, --cache Cache segments in memory. Default is 5 -s, --start Start from -x, --playback Playback --demo Use a demo route instead of providing your own --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 --ecam Load wide road camera --no-loop Stop at the end of the route --no-cache Turn off local cache --qcam Load qcamera --no-hw-decoder Disable HW video decoding --no-vipc Do not output video --all Output all messages including bookmarkButton, uiDebug, userBookmark --headless Run replay without the ncurses console UI -h, --help Show this help message )"; struct ReplayConfig { std::string route; std::vector allow; std::vector block; std::string data_dir; std::string prefix; uint32_t flags = REPLAY_FLAG_NONE; bool auto_source = false; bool headless = false; int start_seconds = 0; int cache_segments = -1; float playback_speed = -1; }; bool parseArgs(int argc, char *argv[], ReplayConfig &config) { const struct option cli_options[] = { {"allow", required_argument, nullptr, 'a'}, {"block", required_argument, nullptr, 'b'}, {"cache", required_argument, nullptr, 'c'}, {"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}, {"ecam", no_argument, nullptr, 0}, {"no-loop", no_argument, nullptr, 0}, {"no-cache", no_argument, nullptr, 0}, {"qcam", no_argument, nullptr, 0}, {"no-hw-decoder", no_argument, nullptr, 0}, {"no-vipc", no_argument, nullptr, 0}, {"all", no_argument, nullptr, 0}, {"headless", no_argument, nullptr, 0}, {"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}, // Terminating entry }; const std::map flag_map = { {"dcam", REPLAY_FLAG_DCAM}, {"ecam", REPLAY_FLAG_ECAM}, {"no-loop", REPLAY_FLAG_NO_LOOP}, {"no-cache", REPLAY_FLAG_NO_FILE_CACHE}, {"qcam", REPLAY_FLAG_QCAMERA}, {"no-hw-decoder", REPLAY_FLAG_NO_HW_DECODER}, {"no-vipc", REPLAY_FLAG_NO_VIPC}, {"all", REPLAY_FLAG_ALL_SERVICES}, }; if (argc == 1) { std::cout << helpText; return false; } int opt, option_index = 0; while ((opt = getopt_long(argc, argv, "a:b:c:s:x:d:p:h", cli_options, &option_index)) != -1) { switch (opt) { case 'a': config.allow = split(optarg, ','); break; case 'b': config.block = split(optarg, ','); break; case 'c': config.cache_segments = std::atoi(optarg); break; case 's': config.start_seconds = std::atoi(optarg); break; case 'x': config.playback_speed = std::atof(optarg); break; case 'd': config.data_dir = optarg; break; 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 if (name == "headless") config.headless = true; else config.flags |= flag_map.at(name); break; } case 'h': std::cout << helpText; return false; default: return false; } } // Check for a route name (first positional argument) if (config.route.empty() && optind < argc) { config.route = argv[optind]; } if (config.route.empty()) { std::cerr << "No route provided. Use --help for usage information.\n"; return false; } return true; } int main(int argc, char *argv[]) { #ifdef __APPLE__ // With all sockets opened, we might hit the default limit of 256 on macOS util::set_file_descriptor_limit(1024); #endif ReplayConfig config; if (!parseArgs(argc, argv, config)) { return 1; } std::unique_ptr op_prefix; if (!config.prefix.empty()) { op_prefix = std::make_unique(config.prefix); } 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); } if (config.playback_speed > 0) { replay.setSpeed(std::clamp(config.playback_speed, ConsoleUI::speed_array.front(), ConsoleUI::speed_array.back())); } if (!replay.load()) { return 1; } if (config.headless) { replay.start(config.start_seconds); std::string prefix = "default"; if (const char* env_prefix = getenv("OPENPILOT_PREFIX")) { prefix = env_prefix; } std::string state_path = "/tmp/replay_state_" + prefix + ".json"; std::string cmd_path = "/tmp/replay_cmd_" + prefix + ".json"; // Clean up any leftover command/state files from previous runs std::remove(state_path.c_str()); std::remove(cmd_path.c_str()); ExitHandler do_exit; while (!do_exit) { // 1. Check for commands from cmd_path std::ifstream cmd_file(cmd_path); if (cmd_file.good()) { std::stringstream buffer; buffer << cmd_file.rdbuf(); cmd_file.close(); std::string err; auto json = json11::Json::parse(buffer.str(), err); if (err.empty()) { if (json["play"].is_bool()) { replay.pause(!json["play"].bool_value()); } if (json["seek"].is_number()) { replay.seekTo(json["seek"].number_value(), false); } if (json["speed"].is_number()) { replay.setSpeed(json["speed"].number_value()); } } std::remove(cmd_path.c_str()); } // 2. Write current status to state_path json11::Json state = json11::Json::object { {"min_sec", replay.minSeconds()}, {"max_sec", replay.maxSeconds()}, {"cur_sec", replay.currentSeconds()}, {"paused", replay.isPaused()}, {"speed", replay.getSpeed()} }; std::ofstream state_file(state_path); if (state_file.is_open()) { state_file << state.dump(); state_file.close(); } util::sleep_for(100); } // Clean up on exit std::remove(state_path.c_str()); std::remove(cmd_path.c_str()); return 0; } ConsoleUI console_ui(&replay); replay.start(config.start_seconds); return console_ui.exec(); }