From eb91f470dd33287d616a9d2b32da59c976a28c84 Mon Sep 17 00:00:00 2001 From: pgdalmeida Date: Tue, 6 Oct 2026 19:41:19 +0200 Subject: [PATCH] Cancel homing and shut down cleanly when the program is stopped during homing --- docs/configuration.md | 3 ++- docs/deployment.md | 7 ++++-- src/core/Application.cpp | 50 ++++++++++++++++++++++++++++++++++------ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a4ad64e..9d8063a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -722,7 +722,8 @@ It is modular (`test gimbal`, `test imu drift`, `test host`, …) and driven by **profiles** configured under `[Test]` / `[TestProfile.]` in `config.ini` (repetition counts, sampling windows, pass thresholds; default `standard`). While a test (or calibration, or homing) runs, interfering commands are ignored and **Esc** -cancels. Full reference: [test-command.md](test-command.md). Replaces the former +cancels. A stop is not ignored: `exit`, Ctrl-C or SIGTERM (`scripts/fgc stop`) during homing +cancels the homing, as Esc does, and shuts the program down. Full reference: [test-command.md](test-command.md). Replaces the former `gimbal diag` (now folded into `test gimbal encoder`). ## Terminal dashboard (TUI) diff --git a/docs/deployment.md b/docs/deployment.md index a993875..f86c68c 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -146,8 +146,11 @@ it is shown dimmed. `scripts/fgc stop` sends SIGTERM and waits up to 120 s; the program handles it with the same shutdown path as the `exit` command (stop capture, close the -camera, flush the image queue). Do **not** `kill -9` - an unclean exit leaves the -camera wedged for the next run. If it will not stop, attach and look at why. +camera, flush the image queue). A stop while the gimbal is homing cancels the +homing rather than waiting for it - a full home takes minutes on some towers, +longer than this wait - so the gimbal is left un-homed, and the next start homes +it again. Do **not** `kill -9` - an unclean exit leaves the camera wedged for the +next run. If it will not stop, attach and look at why. `scripts/fgc start` refuses if a run is already up, and prints the previous run's last output if it died rather than silently discarding it. diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 46eeef7..ae15d13 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -84,8 +84,18 @@ constexpr long kEncoderQuantumCounts = 13; // (StopContinuousImageAcquisition + Close). Without this, a Ctrl-C / kill / systemd // stop skips teardown and leaves the camera acquiring - which wedges it so the NEXT // run gets no frames. Only an atomic flag is set here (async-signal-safe). +// +// Installed as the very first thing run() does, so a stop that arrives while the +// camera opens or the gimbal homes is not the default action - which kills the +// process on the spot and wedges the camera. A second signal of the same kind +// goes back to that default: Ctrl-C twice still gets out of a start that hangs. std::atomic g_shutdown_requested{false}; -void handleShutdownSignal(int) { g_shutdown_requested.store(true); } +void handleShutdownSignal(int sig) { + if (g_shutdown_requested.exchange(true)) { + std::signal(sig, SIG_DFL); + std::raise(sig); + } +} long long nowEpochMs() { return std::chrono::duration_cast( @@ -1195,6 +1205,12 @@ struct Application::Impl { } } + // A stop that came in while the program was starting up: do not begin a + // home that would only be cancelled. + if (shutdownRequested()) { + LOG_INFO << "stop requested: not homing"; + return; + } motor->sendCommand("HOME"); // homes all axes; firmware runs it non-blocking auto allReady = [](const MotorTelemetry& t) { @@ -2494,12 +2510,28 @@ struct Application::Impl { Command c = parseCommand(line); if (c.verb == "cancel" || (c.verb == "mode" && c.device == "manual")) cancel_procedure_ = true; + else if (c.verb == "exit") + running = false; // not an interfering command: a stop else if (!c.empty()) LOG_WARN << "ignored '" << c.verb << "': homing in progress (Esc to cancel)"; local.pop(); } + // A stop cancels homing, as Esc does, rather than waiting for it to finish: + // a full home takes minutes on some towers - longer than `fgc stop` (120 s) + // and systemd (150 s) wait before giving up or killing the program outright, + // which wedges the camera. Dropping the stop instead, as this once did, left + // the program running with its dashboard already closed. + if (shutdownRequested() && !cancel_procedure_) { + LOG_INFO << "stop requested: cancelling homing to shut down"; + cancel_procedure_ = true; + } } + // A stop asked for in any of the ways there are: the `exit` command - which is + // also what the dashboard sends when it closes, on Ctrl-C and SIGTERM too - or + // SIGINT/SIGTERM caught by handleShutdownSignal() when there is no dashboard. + bool shutdownRequested() const { return !running || g_shutdown_requested.load(); } + // ---- `test` command ---- static std::string lowerStr(std::string s) { for (char& ch : s) ch = static_cast(std::tolower((unsigned char)ch)); @@ -2977,6 +3009,14 @@ struct Application::Impl { int run() { start_time = std::chrono::steady_clock::now(); + // Clean up on Ctrl-C / kill / systemd stop, not just on "exit"/Ctrl-D - so the + // camera is always stopped and closed (an unclean exit wedges it for next time). + // First, before anything slow: homing alone takes minutes on some towers. + // The TUI's own handler takes over while the dashboard runs (it turns the + // signal into "exit") and puts this one back when the dashboard closes. + std::signal(SIGINT, handleShutdownSignal); + std::signal(SIGTERM, handleShutdownSignal); + // Log level: config first, CLI overrides. if (!cfg.logging.level.empty() && !Logger::setLevelFromString(cfg.logging.level)) LOG_WARN << "unknown Logging.level '" << cfg.logging.level << "', keeping default"; @@ -3162,12 +3202,8 @@ struct Application::Impl { sendAnchorConfig(); applyHomedTravel(); } - if (start_in != Mode::Manual) setMode(start_in); - - // Clean up on Ctrl-C / kill / systemd stop, not just on "exit"/Ctrl-D - so the - // camera is always stopped and closed (an unclean exit wedges it for next time). - std::signal(SIGINT, handleShutdownSignal); - std::signal(SIGTERM, handleShutdownSignal); + // A stop that came in during homing: do not start sweeping on the way out. + if (start_in != Mode::Manual && !shutdownRequested()) setMode(start_in); LOG_INFO << "Entering control loop (type 'exit' to quit, or Ctrl-C)"; oneshot_since_ = std::chrono::steady_clock::now();