Merge cancelling homing and shutting down cleanly when the program is stopped during homing

This commit is contained in:
pgdalmeida 2026-10-06 19:49:15 +02:00
commit 19c5873df6
Signed by: pedro.almeida
GPG Key ID: D4A6C394DF13F1D7
3 changed files with 50 additions and 10 deletions

View File

@ -749,7 +749,8 @@ It is modular (`test gimbal`, `test imu drift`, `test host`, …) and driven by
**profiles** configured under `[Test]` / `[TestProfile.<name>]` 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)

View File

@ -172,8 +172,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.

View File

@ -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<bool> 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<std::chrono::milliseconds>(
@ -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,11 +2510,27 @@ 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) {
@ -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();