Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replay: replace QCommandLineParser with getopt for command-line argument parsing. #33812

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 120 additions & 55 deletions tools/replay/main.cc
Original file line number Diff line number Diff line change
@@ -1,83 +1,148 @@
#include <getopt.h>

#include <QApplication>
#include <QCommandLineParser>
#include <iostream>
#include <map>
#include <string>

#include "common/prefix.h"
#include "tools/replay/consoleui.h"
#include "tools/replay/replay.h"

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
const std::string helpText =
R"(Usage: replay [options]
Options:
-a, --allow Whitelist of services to send
-b, --block Blacklist of services to send
-c, --cache Cache <n> segments in memory. Default is 5
-s, --start Start from <seconds>
-x, --playback Playback <speed>
--demo Use a demo route instead of providing your own
-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 uiDebug, userFlag
-h, --help Show this help message
)";

QCoreApplication app(argc, argv);
struct ReplayConfig {
QString route;
QStringList allow;
QStringList block;
QString data_dir;
std::string prefix;
uint32_t flags = REPLAY_FLAG_NONE;
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},
{"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},
{"help", no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0}, // Terminating entry
};

const std::tuple<QString, REPLAY_FLAGS, QString> flags[] = {
{"dcam", REPLAY_FLAG_DCAM, "load driver camera"},
{"ecam", REPLAY_FLAG_ECAM, "load wide road camera"},
{"no-loop", REPLAY_FLAG_NO_LOOP, "stop at the end of the route"},
{"no-cache", REPLAY_FLAG_NO_FILE_CACHE, "turn off local cache"},
{"qcam", REPLAY_FLAG_QCAMERA, "load qcamera"},
{"no-hw-decoder", REPLAY_FLAG_NO_HW_DECODER, "disable HW video decoding"},
{"no-vipc", REPLAY_FLAG_NO_VIPC, "do not output video"},
{"all", REPLAY_FLAG_ALL_SERVICES, "do output all messages including uiDebug, userFlag"
". this may causes issues when used along with UI"}
const std::map<std::string, REPLAY_FLAGS> 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},
};

QCommandLineParser parser;
parser.setApplicationDescription("Mock openpilot components by publishing logged messages.");
parser.addHelpOption();
parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai");
parser.addOption({{"a", "allow"}, "whitelist of services to send", "allow"});
parser.addOption({{"b", "block"}, "blacklist of services to send", "block"});
parser.addOption({{"c", "cache"}, "cache <n> segments in memory. default is 5", "n"});
parser.addOption({{"s", "start"}, "start from <seconds>", "seconds"});
parser.addOption({"x", QString("playback <speed>. between %1 - %2")
.arg(ConsoleUI::speed_array.front()).arg(ConsoleUI::speed_array.back()), "speed"});
parser.addOption({"demo", "use a demo route instead of providing your own"});
parser.addOption({"data_dir", "local directory with routes", "data_dir"});
parser.addOption({"prefix", "set OPENPILOT_PREFIX", "prefix"});
for (auto &[name, _, desc] : flags) {
parser.addOption({name, desc});
int opt, option_index = 0;
while ((opt = getopt_long(argc, argv, "a:b:c:s:x:d:p:", cli_options, &option_index)) != -1) {
switch (opt) {
case 'a': config.allow = QString(optarg).split(","); break;
case 'b': config.block = QString(optarg).split(","); 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 {
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.isEmpty() && optind < argc) {
config.route = argv[optind];
}

parser.process(app);
const QStringList args = parser.positionalArguments();
if (args.empty() && !parser.isSet("demo")) {
parser.showHelp();
if (config.route.isEmpty()) {
std::cerr << "No route provided. Use --help for usage information.\n";
return false;
}

const QString route = args.empty() ? DEMO_ROUTE : args.first();
QStringList allow = parser.value("allow").isEmpty() ? QStringList{} : parser.value("allow").split(",");
QStringList block = parser.value("block").isEmpty() ? QStringList{} : parser.value("block").split(",");
return true;
}

uint32_t replay_flags = REPLAY_FLAG_NONE;
for (const auto &[name, flag, _] : flags) {
if (parser.isSet(name)) {
replay_flags |= flag;
}
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

QCoreApplication app(argc, argv);
ReplayConfig config;

if (!parseArgs(argc, argv, config)) {
return 1;
}

std::unique_ptr<OpenpilotPrefix> op_prefix;
auto prefix = parser.value("prefix");
if (!prefix.isEmpty()) {
op_prefix.reset(new OpenpilotPrefix(prefix.toStdString()));
if (!config.prefix.empty()) {
op_prefix = std::make_unique<OpenpilotPrefix>(config.prefix);
}

Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app);
if (!parser.value("c").isEmpty()) {
replay->setSegmentCacheLimit(parser.value("c").toInt());
Replay *replay = new Replay(config.route, config.allow, config.block, nullptr, config.flags, config.data_dir, &app);
if (config.cache_segments > 0) {
replay->setSegmentCacheLimit(config.cache_segments);
}
if (!parser.value("x").isEmpty()) {
replay->setSpeed(std::clamp(parser.value("x").toFloat(),
ConsoleUI::speed_array.front(), ConsoleUI::speed_array.back()));
if (config.playback_speed > 0) {
replay->setSpeed(std::clamp(config.playback_speed, ConsoleUI::speed_array.front(), ConsoleUI::speed_array.back()));
}
if (!replay->load()) {
return 0;
return 1;
}

ConsoleUI console_ui(replay);
replay->start(parser.value("start").toInt());
replay->start(config.start_seconds);
return app.exec();
}
Loading