From 82715145ebe49372f8071ea338b3abf0d4b80ebd Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Thu, 29 Oct 2026 16:06:33 +0200 Subject: [PATCH] sequencerd: auto-ontarget on by default; require the cass ring at the commanded angle TCS_AUTO_ONTARGET defaults to yes. The automatic ontarget now also requires |TCS cass angle - RINGGO angle| < TCS_AUTO_ONTARGET_CASS (0.05 deg) on the same two consecutive reads: ACAM's goal is computed from the cass angle at the acquire, about 8.5" per degree of ring still to turn. When everything but the ring has held for 20 s, one warning tells the operator to press ontarget once the ring is in place. Co-Authored-By: Claude Opus 5.5 --- Config/sequencerd.cfg.in | 3 +- sequencerd/sequence.cpp | 57 +++++++++++++++++++++++++-------- sequencerd/sequence.h | 5 +-- sequencerd/sequencer_server.cpp | 15 +++++++++ 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index d8db2de4..f0a5c079 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -150,8 +150,9 @@ TCS_OFFSET_RATE_RA=45 # TCS offset rate for RA ("MRATE") in arcse TCS_OFFSET_RATE_DEC=45 # TCS offset rate for DEC ("MRATE") in arcsec per second TCS_SETTLE_TIMEOUT=20 # seconds to wait for telescope to settle (for "TRACKING_STABLY") TCS_SETTLE_STABLE=1.5 # time in fractional seconds (to 0.1) that TCS must report TRACKING before it is really stable -TCS_AUTO_ONTARGET=no # yes: TCS ?ONTARGET=1 + tracking + sep < TCS_AUTO_ONTARGET_SEP (2 consecutive 1 Hz reads) satisfies the ontarget wait; operator press always works +TCS_AUTO_ONTARGET=yes # yes: TCS ?ONTARGET=1 + tracking + sep < TCS_AUTO_ONTARGET_SEP + cass ring within TCS_AUTO_ONTARGET_CASS (2 consecutive 1 Hz reads) satisfies the ontarget wait; operator press always works TCS_AUTO_ONTARGET_SEP=5.0 # arcsec: max |telescope-target| for auto-ontarget +TCS_AUTO_ONTARGET_CASS=0.05 # deg: max |TCS cass angle - commanded ring angle| for auto-ontarget TCS_DOMEAZI_READY=10 # max degrees azimuth that dome and telescope can differ before ready to observe TCS_PREAUTH_TIME=10 # seconds before end of exposure to notify TCS of next target's coords (0 to disable) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 19bac252..aa24ba62 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -2368,11 +2368,15 @@ namespace Sequencer { * lives only while that wait is open (auto_ontarget_active). * Acknowledges when two consecutive 1 Hz reads show * ?ONTARGET==1 AND motion tracking AND |telescope-commanded| < - * tcs_auto_ontarget_sep. All three predicates are required: - * the flag alone is position-agnostic, the separation alone - * cannot see a sub-threshold hop in progress, and motion alone - * says nothing about settling. The coords are the SCOPE-frame - * position move_to_target actually commanded. A wrong + * tcs_auto_ontarget_sep AND |TCS cass angle - commanded ring + * angle| < tcs_auto_ontarget_cass. All four are required: the + * flag alone is position-agnostic, the separation alone cannot + * see a sub-threshold hop in progress, motion alone says + * nothing about settling, and none of them covers the cass + * ring, which turns on its own after RINGGO (ACAM's goal is + * computed from the cass angle at the acquire, ~8.5" per + * degree of ring still to go). The coords and ring angle are + * what move_to_target actually commanded. A wrong * acknowledgment is self-limiting -- ACAM cannot solve a field * that is not there and the acquisition retry machinery * handles the failure -- so the conditions make wrong acks @@ -2382,16 +2386,18 @@ namespace Sequencer { * cancel_flag, and a concurrent cancel must always win * against this automatic path. */ - void Sequence::dothread_auto_ontarget( double tgt_ra_deg, double tgt_dec_deg, unsigned gen ) { + void Sequence::dothread_auto_ontarget( double tgt_ra_deg, double tgt_dec_deg, double tgt_cass_deg, unsigned gen ) { const std::string function("Sequencer::Sequence::dothread_auto_ontarget"); std::stringstream message; - if ( std::isnan(tgt_ra_deg) || std::isnan(tgt_dec_deg) ) { - logwrite( function, "ERROR invalid commanded coords; auto-ontarget sits this wait out" ); + if ( std::isnan(tgt_ra_deg) || std::isnan(tgt_dec_deg) || std::isnan(tgt_cass_deg) ) { + logwrite( function, "ERROR invalid commanded coords or ring angle; auto-ontarget sits this wait out" ); return; } int consecutive = 0; + int64_t ring_wait_since_us = 0; // when everything but the ring first held + bool ring_warned = false; // gen retires this watcher the moment a newer one is launched, so one // still sleeping from an earlier move can never acknowledge a new one. @@ -2412,6 +2418,8 @@ namespace Sequencer { bool fresh = false; bool tracking = false; double sep_as = NAN; + double dcass_deg = NAN; + double cass_now = NAN; { std::lock_guard lock( tcsd_mtx ); fresh = this->tcsinfo.is_fresh(); @@ -2422,11 +2430,31 @@ namespace Sequencer { double dra_as = dra_deg * cos( tgt_dec_deg * M_PI / 180.0 ) * 3600.0; double ddec_as = ( this->tcsinfo.dec_d - tgt_dec_deg ) * 3600.0; sep_as = std::hypot( dra_as, ddec_as ); + // the cass ring against the angle RINGGO was sent, either side of the wrap + cass_now = this->tcsinfo.cassangle; + dcass_deg = std::fabs( std::remainder( cass_now - tgt_cass_deg, 360.0 ) ); + } + } + + const bool all_but_ring = fresh && flag_on && tracking && ( sep_as < this->tcs_auto_ontarget_sep ); + const bool ring_ok = ( dcass_deg < this->tcs_auto_ontarget_cass ); + + // On target with only the ring missing for 20 s (a refused RINGGO, a ring + // parked off the command, an unreadable angle): say so once, so the + // operator knows the wait needs a press. + if ( all_but_ring && !ring_ok ) { + if ( ring_wait_since_us == 0 ) ring_wait_since_us = get_time_us(); + else if ( !ring_warned && get_time_us() - ring_wait_since_us > 20000000 ) { + ring_warned = true; + message.str(""); message << "telescope on target but the cass ring reads " << cass_now + << " deg, commanded " << tgt_cass_deg + << " deg: press ontarget once the ring is in place"; + this->broadcast.warning( function, message.str() ); + } } - } + else if ( !all_but_ring ) ring_wait_since_us = 0; - if ( !fresh || !flag_on || !tracking || - !( sep_as < this->tcs_auto_ontarget_sep ) ) { consecutive=0; continue; } + if ( !all_but_ring || !ring_ok ) { consecutive=0; continue; } if ( ++consecutive < 2 ) continue; // debounce: two reads in a row @@ -2447,7 +2475,8 @@ namespace Sequencer { message.str(""); message << "TCS reports on-target (?ONTARGET=1, tracking, sep=" << std::fixed << std::setprecision(1) << sep_as - << " arcsec x2) -- sending ontarget"; + << " arcsec, ring within " << std::setprecision(3) << dcass_deg + << " deg x2) -- sending ontarget"; this->broadcast.notice( function, message.str() ); return; } @@ -2614,7 +2643,7 @@ namespace Sequencer { this->broadcast.notice( function, "waiting for TCS operator to send \"ontarget\" signal" ); - // AUTO-ONTARGET (config TCS_AUTO_ONTARGET, default no): the TCS itself can + // AUTO-ONTARGET (config TCS_AUTO_ONTARGET, default yes): the TCS itself can // satisfy this wait through the same ontarget() the operator's press uses. // The watcher lives exactly as long as this wait (auto_ontarget_active). // @@ -2625,7 +2654,7 @@ namespace Sequencer { // database coordinates differ by the pointmode focal-plane offset. // ontarget_gen was taken, retiring older watchers, before the COORDS. std::thread( &Sequencer::Sequence::dothread_auto_ontarget, this, - ra_out, dec_out, ontarget_gen ).detach(); + ra_out, dec_out, angle_out, ontarget_gen ).detach(); } while ( !this->cancel_flag.load() && !this->is_ontarget.load() ) { diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 9e6e7e13..67978046 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -402,7 +402,7 @@ namespace Sequencer { /** @brief satisfies the TCSOP wait when the TCS itself reports arrival * (config TCS_AUTO_ONTARGET); coords are the commanded SCOPE * frame position in degrees; see sequence.cpp */ - void dothread_auto_ontarget( double tgt_ra_deg, double tgt_dec_deg, unsigned gen ); + void dothread_auto_ontarget( double tgt_ra_deg, double tgt_dec_deg, double tgt_cass_deg, unsigned gen ); std::atomic auto_ontarget_active{false}; ///< true only while move_to_target's TCSOP wait is open std::atomic auto_ontarget_gen{0}; ///< launch generation; a watcher retires when a newer one is launched std::mutex ontarget_ack_mtx; ///< a watcher's acknowledgment vs a new move retiring watchers and clearing is_ontarget @@ -429,8 +429,9 @@ namespace Sequencer { double tcs_domeazi_ready; ///< max degrees azimuth that dome and telescope can differ before ready to observe double tcs_preauth_time; ///< seconds before end of exposure to notify TCS of next target's coords (0 to disable) double offset_settle_sec; ///< sec to wait after a target offset before exposing (config OFFSET_SETTLE_SEC; 0 disables) - bool tcs_auto_ontarget = false; ///< TCS ?ONTARGET may satisfy the TCSOP wait (config TCS_AUTO_ONTARGET; default no) + bool tcs_auto_ontarget = true; ///< TCS ?ONTARGET may satisfy the TCSOP wait (config TCS_AUTO_ONTARGET; default yes) double tcs_auto_ontarget_sep = 5.0; ///< max |telescope-target| arcsec for auto-ontarget (config TCS_AUTO_ONTARGET_SEP) + double tcs_auto_ontarget_cass = 0.05; ///< max |TCS cass angle - commanded ring angle| deg for auto-ontarget (config TCS_AUTO_ONTARGET_CASS) /// std::mutex tcs_ontarget_mtx; /// std::condition_variable tcs_ontarget_cv; diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index 56ea79d7..9b37a371 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -372,6 +372,21 @@ namespace Sequencer { applied++; } + // TCS_AUTO_ONTARGET_CASS + if (config.param[entry] == "TCS_AUTO_ONTARGET_CASS") { + try { + this->sequence.tcs_auto_ontarget_cass = std::stod( config.arg[entry] ); + } + catch (const std::exception &e) { + message.str(""); message << "ERROR parsing TCS_AUTO_ONTARGET_CASS: " << e.what(); + logwrite( function, message.str() ); + return ERROR; + } + message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + applied++; + } + // TCS_AUTO_ONTARGET_SEP if (config.param[entry] == "TCS_AUTO_ONTARGET_SEP") { try {