fwt_software/main.cpp

123 lines
5.6 KiB
C++

#include "fgc/Application.h"
#include "fgc/Config.h"
#include "fgc/Logger.h"
#include "fgc/Paths.h"
#include "fgc/SingleInstance.h"
#include <iostream>
#include <boost/program_options.hpp>
namespace po = boost::program_options;
using namespace fgc;
int main(int argc, char* argv[]) {
po::options_description desc("Fire Gimbal Control options");
desc.add_options()
("help,h", "show this help")
("config,c", po::value<std::string>(), "path to config.ini")
("hardware", po::value<std::string>(),
"path to hardware.ini (default: $FGC_HARDWARE, /etc/fire_gimbal_control/hardware.ini, "
"or one beside config.ini)")
("init,i", po::bool_switch(), "home the gimbal on startup (required: nothing is homed across a power cycle)")
("start,s", po::bool_switch(), "start capture automatically")
("demo,d", po::bool_switch(), "demo mode (copy placeholder image instead of encoding)")
("no-mqtt", po::bool_switch(), "disable MQTT (use a null control channel)")
("mock-camera", po::bool_switch(), "use a simulated camera (no hardware)")
("mock-serial", po::bool_switch(), "use a simulated motor controller (no hardware)")
("mock-env", po::bool_switch(),
"use a simulated ambient temp/humidity sensor (implies enable_env)")
("tui", po::bool_switch(), "show the full-screen terminal dashboard")
("no-tui", po::bool_switch(), "force the headless line console (overrides config)")
("no-lock", po::bool_switch(),
"skip the single-instance lock (dev only: for a second mock-only instance)")
("lock-file", po::value<std::string>(), "path to the single-instance lockfile")
("log-level", po::value<std::string>(), "trace|debug|info|warn|error|off")
("trace", po::value<std::string>(),
"verbatim wire trace, comma list: serial,mqtt,camera,control,all,none");
po::variables_map vm;
try {
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
} catch (const std::exception& e) {
std::cerr << "Argument error: " << e.what() << "\n";
return 1;
}
if (vm.count("help")) {
std::cout << desc << "\n";
return 0;
}
const std::string cli_cfg = vm.count("config") ? vm["config"].as<std::string>() : "";
auto cfg_path = paths::resolveConfigPath(cli_cfg);
if (!cfg_path) {
std::cerr << "No config.ini found. Searched:\n";
for (const auto& p : paths::configSearchPaths(cli_cfg)) std::cerr << " - " << p << "\n";
std::cerr << "Copy config/config.example.ini to one of these, pass --config, "
"or set $FGC_CONFIG.\n";
return 1;
}
// hardware.ini describes the machine itself and is optional: a tower that has
// not been migrated yet simply has none, and everything comes from config.ini
// exactly as before. An explicit --hardware that does not exist IS an error,
// though - it means someone expected an assembly description to be in force.
const std::string cli_hw = vm.count("hardware") ? vm["hardware"].as<std::string>() : "";
auto hw_path = paths::resolveHardwarePath(cli_hw, *cfg_path);
if (!cli_hw.empty() && !hw_path) {
std::cerr << "No hardware.ini at --hardware " << cli_hw << "\n";
return 1;
}
AppConfig cfg;
try {
cfg = ConfigLoader::loadFromFile(*cfg_path, hw_path.value_or(""));
} catch (const std::exception& e) {
std::cerr << "Config error (" << *cfg_path << "): " << e.what() << "\n";
return 1;
}
LOG_INFO << "Loaded config: " << *cfg_path;
// Only one instance may run: a second would fight the first over the motor's
// serial port, the camera, and the exposure store. The lock is held for the
// whole run and released when this scope ends (or the process dies).
SingleInstance lock;
if (!vm["no-lock"].as<bool>()) {
const std::string lock_path = vm.count("lock-file")
? paths::expandUser(vm["lock-file"].as<std::string>())
: SingleInstance::defaultLockPath();
lock = SingleInstance::acquire(lock_path);
if (!lock) {
std::cerr << "Cannot start: " << lock.error() << "\n";
return 2; // distinct from 1 so scripts/fgc can tell this apart
}
LOG_INFO << "Single-instance lock held: " << lock.path();
}
RuntimeOptions opts;
opts.init = vm["init"].as<bool>();
opts.start = vm["start"].as<bool>();
opts.demo = vm["demo"].as<bool>();
if (vm["no-mqtt"].as<bool>()) opts.use_mqtt = false;
if (vm["mock-camera"].as<bool>()) opts.mock_camera = true;
if (vm["mock-serial"].as<bool>()) opts.mock_serial = true;
if (vm["mock-env"].as<bool>()) opts.mock_env = true;
if (vm["tui"].as<bool>()) opts.use_tui = true;
if (vm["no-tui"].as<bool>()) opts.use_tui = false; // --no-tui wins
if (vm.count("log-level")) opts.log_level = vm["log-level"].as<std::string>();
if (vm.count("trace")) opts.trace_categories = vm["trace"].as<std::string>();
opts.config_path = *cfg_path; // so the setup commands can save back to it
Application app(std::move(cfg), std::move(opts));
try {
return app.run();
} catch (const std::exception& e) {
// Startup can still reject the configuration once it touches the
// filesystem (a missing camera-mode directory, say). Report that the way
// a bad config.ini is reported, not as an unhandled terminate.
std::cerr << "Startup failed: " << e.what() << "\n";
return 1;
}
}