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 {