From b41774acb9667bd92cfef926057ede61d7f87d69 Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Tue, 23 Jun 2026 13:02:05 -0700 Subject: [PATCH 1/4] sequencerd: skip re-acquisition when the same target is repeated When the observer presses GO on the target already being observed (a repeat), the sequencer should NOT re-acquire -- it should proceed straight to the "continue" prompt and expose. On main it re-ran the full ACAM astrometric acquire (and fine acquire) every time. Cause: move_to_target already skips the telescope move on a repeat (matches last_ra_hms/last_dec_dms), but do_acam_acquire and do_slicecam_fineacquire are called unconditionally in the run loop -- there is no matching same-target guard on acquisition. (The vestigial `last_target` member was written but never read; the only same-target logic ever present was a commented-out *move* guard.) Fix: detect a repeat by comparing target coordinates to the last target we ACQUIRED (new last_acquire_ra_hms/last_acquire_dec_dms, set after a completed acquisition). On a repeat, skip ACAM acquire, fine acquire, and the science re-offset (target_offset), then wait for the observer to continue before exposing. Guiding state is intentionally NOT consulted -- whether ACAM is still guiding is left to the observer. `clearlasttarget` clears the new state to force a fresh acquisition on the same target. Note: the science re-offset is also skipped on a repeat. is_fineacquire_locked is telemetry-driven and would otherwise be stale-true on a repeat (move_to_target returns before do_acam_stop), so target_offset would double-apply the science offset. Skipping it on a repeat avoids that. Not compiled in this environment -- please build on the instrument. Edits mirror the existing move_to_target coordinate compare/assign, so types are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- sequencerd/sequence.cpp | 35 ++++++++++++++++++++++++++++++++--- sequencerd/sequence.h | 2 ++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 5f004244..6d856851 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -744,9 +744,20 @@ namespace Sequencer { break; } + // Detect a repeat of the same target: same coordinates as the last target we + // acquired. On a repeat the telescope was not moved (move_to_target skips it), + // acquisition was already performed, and the science offset was already applied, + // so skip ACAM acquire, fine acquire, and the science re-offset, and let the + // observer continue straight to the exposure. Guiding state is intentionally NOT + // consulted (left to the observer). Use "clearlasttarget" to force re-acquisition. + // + const bool repeat_target = ( !this->target.ra_hms.empty() && + this->target.ra_hms == this->last_acquire_ra_hms && + this->target.dec_dms == this->last_acquire_dec_dms ); + // If not a calibration target then acquire, first acam then slicecam // - if ( !this->target.iscal ) { + if ( !this->target.iscal && !repeat_target ) { // during acam acquisition, enable slicecam autoexpose to try to get the // exposure time set before fine acquisition starts. @@ -780,11 +791,27 @@ namespace Sequencer { return; } } + + // remember the target we just acquired so a repeat (GO on the same + // target) skips re-acquisition + this->last_acquire_ra_hms = this->target.ra_hms; + this->last_acquire_dec_dms = this->target.dec_dms; + } + + // Repeat of the same target: skip all re-acquisition and just wait for the + // observer to continue (or cancel) before exposing. + if ( !this->target.iscal && repeat_target ) { + this->broadcast.notice( function, "repeat of same target -- skipping re-acquisition" ); + if ( this->wait_for_user()==ABORT ) { + this->broadcast.notice( function, "cancelled" ); + return; + } } if ( !this->target.iscal ) { - // send offsets only on fineacquire, otherwise user needs to fix things - if ( this->is_fineacquire_locked.load() && + // send offsets only on fineacquire, otherwise user needs to fix things. + // skip on a repeat: the science offset was already applied last time. + if ( !repeat_target && this->is_fineacquire_locked.load() && this->target_offset() == ERROR ) { if (this->wait_for_user()==ABORT) { this->broadcast.notice( function, "cancelled" ); @@ -4788,6 +4815,8 @@ namespace Sequencer { // if ( testname == "clearlasttarget" ) { this->last_target=""; + this->last_acquire_ra_hms = mysqlx::string{}; // force a fresh acquisition on the same target + this->last_acquire_dec_dms = mysqlx::string{}; error=NO_ERROR; } else diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 6b2e7a6f..9dc9a442 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -449,6 +449,8 @@ namespace Sequencer { std::string last_target; mysqlx::string last_ra_hms; mysqlx::string last_dec_dms; + mysqlx::string last_acquire_ra_hms; ///< coords of the last target ACQUIRED (repeat-target skip) + mysqlx::string last_acquire_dec_dms; ///< coords of the last target ACQUIRED (repeat-target skip) std::string tcs_which; ///< configured TCS std::string tcs_name; ///< name of TCS set on tcs initialization and shutdown From c6d85d165a808d5f450f5d0e41bb1e8d963864d5 Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Tue, 23 Jun 2026 13:12:05 -0700 Subject: [PATCH 2/4] sequencerd: include offsets + slit angle in repeat-target key Per review feedback: the repeat-target guard keyed only on RA/DEC, so two target-list entries sharing the same acquisition coordinates but with DIFFERENT science offsets (e.g. one offset star used to reach several science targets) or a different slit angle would be wrongly treated as a repeat and skipped. Those are NOT the same target and must be fully re-acquired (ACAM acquire, move, slit off-target, fine acquire if enabled, science offset). Extend the repeat key to require equal offset_ra, offset_dec and slitangle in addition to RA/DEC; remember all five after a completed acquisition. Co-Authored-By: Claude Opus 4.8 (1M context) --- sequencerd/sequence.cpp | 33 +++++++++++++++++++++------------ sequencerd/sequence.h | 11 +++++++++-- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 6d856851..8e5cb5e7 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -744,16 +744,22 @@ namespace Sequencer { break; } - // Detect a repeat of the same target: same coordinates as the last target we - // acquired. On a repeat the telescope was not moved (move_to_target skips it), - // acquisition was already performed, and the science offset was already applied, - // so skip ACAM acquire, fine acquire, and the science re-offset, and let the - // observer continue straight to the exposure. Guiding state is intentionally NOT - // consulted (left to the observer). Use "clearlasttarget" to force re-acquisition. + // Detect a repeat of the same target: same coordinates AND same science offsets + // AND same slit angle as the last target we acquired. (Two list entries can share + // RA/DEC but differ in offset/angle -- e.g. one offset star used to reach several + // science targets -- and those are NOT the same target; they must be fully + // re-acquired.) On a true repeat the telescope was not moved (move_to_target skips + // it), acquisition was already performed, and the science offset was already + // applied, so skip ACAM acquire, fine acquire, and the science re-offset, and let + // the observer continue straight to the exposure. Guiding state is intentionally + // NOT consulted (left to the observer). Use "clearlasttarget" to force re-acquisition. // const bool repeat_target = ( !this->target.ra_hms.empty() && - this->target.ra_hms == this->last_acquire_ra_hms && - this->target.dec_dms == this->last_acquire_dec_dms ); + this->target.ra_hms == this->last_acquire_ra_hms && + this->target.dec_dms == this->last_acquire_dec_dms && + this->target.offset_ra == this->last_acquire_offset_ra && + this->target.offset_dec == this->last_acquire_offset_dec && + this->target.slitangle == this->last_acquire_slitangle ); // If not a calibration target then acquire, first acam then slicecam // @@ -792,10 +798,13 @@ namespace Sequencer { } } - // remember the target we just acquired so a repeat (GO on the same - // target) skips re-acquisition - this->last_acquire_ra_hms = this->target.ra_hms; - this->last_acquire_dec_dms = this->target.dec_dms; + // remember the target we just acquired (coords + science offsets + slit angle) + // so a repeat (GO on the same target) skips re-acquisition + this->last_acquire_ra_hms = this->target.ra_hms; + this->last_acquire_dec_dms = this->target.dec_dms; + this->last_acquire_offset_ra = this->target.offset_ra; + this->last_acquire_offset_dec = this->target.offset_dec; + this->last_acquire_slitangle = this->target.slitangle; } // Repeat of the same target: skip all re-acquisition and just wait for the diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 9dc9a442..db569422 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -449,8 +449,15 @@ namespace Sequencer { std::string last_target; mysqlx::string last_ra_hms; mysqlx::string last_dec_dms; - mysqlx::string last_acquire_ra_hms; ///< coords of the last target ACQUIRED (repeat-target skip) - mysqlx::string last_acquire_dec_dms; ///< coords of the last target ACQUIRED (repeat-target skip) + // Identity of the last target ACQUIRED, used to skip re-acquisition only on a + // true repeat. Includes the science offsets and slit angle: two list entries can + // share RA/DEC but differ in offset/angle (e.g. one offset star -> several science + // targets) -- those are NOT the same target and must be fully re-acquired. + mysqlx::string last_acquire_ra_hms; + mysqlx::string last_acquire_dec_dms; + double last_acquire_offset_ra{0.0}; + double last_acquire_offset_dec{0.0}; + double last_acquire_slitangle{0.0}; std::string tcs_which; ///< configured TCS std::string tcs_name; ///< name of TCS set on tcs initialization and shutdown From 0336594685907f78c5e64d60d3faa02164e8f4fa Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Tue, 23 Jun 2026 13:17:09 -0700 Subject: [PATCH 3/4] sequencerd: on a repeat, do not re-command the slit (stay at EXPOSE/science) On a repeat the slit must not move at all -- it should stay at the science (EXPOSE) position. The VSM_ACQUIRE worker already self-skips on a repeat (it returns early when target coords match last_ra_hms/last_dec_dms), so the slit was never moved to ACQUIRE. But the post-acquire block still called slit_set(VSM_EXPOSE) unconditionally, which re-commands the slit (and broadcasts "moving slit") even though it is already at EXPOSE. Gate the whole post-acquire block (science offset + slit_set EXPOSE) on !repeat_target so that on a repeat the slit receives no command and stays put at the science position. Co-Authored-By: Claude Opus 4.8 (1M context) --- sequencerd/sequence.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 8e5cb5e7..5efa579d 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -817,10 +817,13 @@ namespace Sequencer { } } - if ( !this->target.iscal ) { - // send offsets only on fineacquire, otherwise user needs to fix things. - // skip on a repeat: the science offset was already applied last time. - if ( !repeat_target && this->is_fineacquire_locked.load() && + // On a repeat this whole block is skipped: the science offset was already + // applied and the slit is already at the EXPOSE (science) position, so neither + // the telescope nor the slit should move at all. (The VSM_ACQUIRE worker above + // also self-skips on a repeat, so the slit was never moved to ACQUIRE.) + if ( !this->target.iscal && !repeat_target ) { + // send offsets only on fineacquire, otherwise user needs to fix things + if ( this->is_fineacquire_locked.load() && this->target_offset() == ERROR ) { if (this->wait_for_user()==ABORT) { this->broadcast.notice( function, "cancelled" ); From c27ca2e2206f0d3742dc8cb898301087a15b73c0 Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Tue, 23 Jun 2026 13:26:50 -0700 Subject: [PATCH 4/4] sequencerd: on a repeat, still apply slit WIDTH (only skip the telescope offset) Correction to the previous commit, which skipped slit_set(VSM_EXPOSE) entirely on a repeat. slit_set(VSM_EXPOSE) sets BOTH the slit width (target.slitwidth_req) and the EXPOSE offset, so skipping it would also drop a slit-width change made for the repeat exposure. The EXPOSE offset is unchanged on a repeat, so re-asserting it does not translate the slit (stays at the science position) -- only the width is (re)applied. So: keep slit_set(VSM_EXPOSE) unconditional (honors width changes, no translation) and gate only target_offset (the telescope science offset) on !repeat_target to avoid double-applying it. Co-Authored-By: Claude Opus 4.8 (1M context) --- sequencerd/sequence.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 5efa579d..26790e7c 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -817,20 +817,23 @@ namespace Sequencer { } } - // On a repeat this whole block is skipped: the science offset was already - // applied and the slit is already at the EXPOSE (science) position, so neither - // the telescope nor the slit should move at all. (The VSM_ACQUIRE worker above - // also self-skips on a repeat, so the slit was never moved to ACQUIRE.) - if ( !this->target.iscal && !repeat_target ) { - // send offsets only on fineacquire, otherwise user needs to fix things - if ( this->is_fineacquire_locked.load() && + if ( !this->target.iscal ) { + // Apply the science (telescope) offset only when NOT a repeat. On a repeat it + // was already applied last time and re-applying would double it (is_fineacquire_locked + // is telemetry-driven and can be stale-true on a repeat). + if ( !repeat_target && this->is_fineacquire_locked.load() && this->target_offset() == ERROR ) { if (this->wait_for_user()==ABORT) { this->broadcast.notice( function, "cancelled" ); return; } } - // ensure slit offset is in "expose" position when needed + // Always set the slit to EXPOSE -- this applies the slit WIDTH from the target + // entry (so a width change for a repeat exposure is honored) and re-asserts the + // EXPOSE offset. On a repeat the offset is unchanged, so the slit does NOT + // translate (it stays at the science position); only the width is (re)applied. + // The VSM_ACQUIRE worker above self-skips on a repeat, so the slit was never + // moved off the science position. try { error |= this->slit_set(Sequencer::VSM_EXPOSE); }