#include "fgc/Application.h" #include "fgc/Config.h" #include "fgc/Logger.h" #include "fgc/Paths.h" #include "fgc/SingleInstance.h" #include #include 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(), "path to config.ini") ("init,i", po::bool_switch(), "run the endstop-finding init sequence") ("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(), "path to the single-instance lockfile") ("log-level", po::value(), "trace|debug|info|warn|error|off") ("trace", po::value(), "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() : ""; 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; } AppConfig cfg; try { cfg = ConfigLoader::loadFromFile(*cfg_path); } 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()) { const std::string lock_path = vm.count("lock-file") ? paths::expandUser(vm["lock-file"].as()) : 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(); opts.start = vm["start"].as(); opts.demo = vm["demo"].as(); if (vm["no-mqtt"].as()) opts.use_mqtt = false; if (vm["mock-camera"].as()) opts.mock_camera = true; if (vm["mock-serial"].as()) opts.mock_serial = true; if (vm["mock-env"].as()) opts.mock_env = true; if (vm["tui"].as()) opts.use_tui = true; if (vm["no-tui"].as()) opts.use_tui = false; // --no-tui wins if (vm.count("log-level")) opts.log_level = vm["log-level"].as(); if (vm.count("trace")) opts.trace_categories = vm["trace"].as(); opts.config_path = *cfg_path; // so `gimbal calib` can offer to save back to it Application app(std::move(cfg), std::move(opts)); return app.run(); }