Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Config/sequencerd.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ 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_SEP=5.0 # arcsec: max |telescope-target| 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)

Expand Down
96 changes: 96 additions & 0 deletions sequencerd/sequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2149,6 +2149,88 @@ namespace Sequencer {
/***** Sequencer::Sequence::camera_shutdown *********************************/


/***** Sequencer::Sequence::dothread_auto_ontarget **************************/
/**
* @brief satisfies the TCSOP wait when the TCS itself reports arrival
* @details Spawned by move_to_target when TCS_AUTO_ONTARGET is enabled and
* 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
* 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
* rare, not impossible. The operator's press works unchanged;
* whichever arrives first wins. The acknowledgment
* deliberately does not go through ontarget(): that clears
* 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 ) {
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" );
return;
}

int consecutive = 0;

while ( this->auto_ontarget_active.load() &&
!this->cancel_flag.load() && !this->is_ontarget.load() ) {

std::this_thread::sleep_for( std::chrono::seconds(1) );

// the TCS's own settled flag (0|1), returned verbatim
//
std::string reply;
if ( this->tcsd.send( TCSD_NATIVE+" ?ONTARGET", reply ) != NO_ERROR ) { consecutive=0; continue; }
bool flag_on = ( reply.rfind( "1", 0 ) == 0 );

// motion state and separation from the cached 1 Hz tcsd telemetry
//
bool fresh = false;
bool tracking = false;
double sep_as = NAN;
{
std::lock_guard<std::mutex> lock( tcsd_mtx );
fresh = this->tcsinfo.is_fresh();
if ( fresh ) {
tracking = ( this->tcsinfo.motion == TCS_MOTION_TRACKING_STR );
double dra_deg = this->tcsinfo.ra_h * 15.0 - tgt_ra_deg;
dra_deg = std::fmod( dra_deg + 540.0, 360.0 ) - 180.0; // shortest arc across RA wrap
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 );
}
}

if ( !fresh || !flag_on || !tracking ||
!( sep_as < this->tcs_auto_ontarget_sep ) ) { consecutive=0; continue; }

if ( ++consecutive < 2 ) continue; // debounce: two reads in a row

// Re-check at the last instant; do NOT clear cancel_flag (see @details).
//
if ( !this->auto_ontarget_active.load() || this->cancel_flag.load() ) return;

message.str(""); message << "TCS reports on-target (?ONTARGET=1, tracking, sep="
<< std::fixed << std::setprecision(1) << sep_as
<< " arcsec x2) -- sending ontarget";
this->broadcast.notice( function, message.str() );
this->is_ontarget.store( true );
this->cv.notify_all();
return;
}
}
/***** Sequencer::Sequence::dothread_auto_ontarget **************************/


/***** Sequencer::Sequence::move_to_target **********************************/
/**
* @brief send request to TCS to move to target coordinates
Expand Down Expand Up @@ -2297,10 +2379,24 @@ 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
// satisfy this wait through the same ontarget() the operator's press uses.
// The watcher lives exactly as long as this wait (auto_ontarget_active).
//
this->auto_ontarget_active.store( this->tcs_auto_ontarget );
if ( this->tcs_auto_ontarget ) {
// ra_out/dec_out are the SCOPE-frame coordinates this function actually
// commanded, which is the position REQPOS telemetry will report -- the
// database coordinates differ by the pointmode focal-plane offset.
std::thread( &Sequencer::Sequence::dothread_auto_ontarget, this,
ra_out, dec_out ).detach();
}

while ( !this->cancel_flag.load() && !this->is_ontarget.load() ) {
std::unique_lock<std::mutex> lock(cv_mutex);
this->cv.wait( lock, [this]() { return( this->is_ontarget.load() || this->cancel_flag.load() ); } );
}
this->auto_ontarget_active.store( false );

this->broadcast.notice( function, "received "
+(this->cancel_flag.load() ? std::string("cancel") : std::string("ontarget"))
Expand Down
8 changes: 8 additions & 0 deletions sequencerd/sequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ namespace Sequencer {
this->is_ontarget.store(false);
}

/** @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 );
std::atomic<bool> auto_ontarget_active{false}; ///< true only while move_to_target's TCSOP wait is open

/** @brief sends usercontinue signal, clears after 3 seconds */
inline void usercontinue() {
this->cancel_flag.store(false);
Expand All @@ -421,6 +427,8 @@ 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)
double tcs_auto_ontarget_sep = 5.0; ///< max |telescope-target| arcsec for auto-ontarget (config TCS_AUTO_ONTARGET_SEP)

/// std::mutex tcs_ontarget_mtx;
/// std::condition_variable tcs_ontarget_cv;
Expand Down
25 changes: 25 additions & 0 deletions sequencerd/sequencer_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,31 @@ namespace Sequencer {
applied++;
}

// TCS_AUTO_ONTARGET
if (config.param[entry] == "TCS_AUTO_ONTARGET") {
this->sequence.tcs_auto_ontarget = ( config.arg[entry] == "yes" ||
config.arg[entry] == "true" ||
config.arg[entry] == "1" );
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 {
this->sequence.tcs_auto_ontarget_sep = std::stod( config.arg[entry] );
}
catch (const std::exception &e) {
message.str(""); message << "ERROR parsing TCS_AUTO_ONTARGET_SEP: " << e.what();
logwrite( function, message.str() );
return ERROR;
}
message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry];
logwrite( function, message.str() );
applied++;
}

// OFFSET_SETTLE_SEC
if (config.param[entry] == "OFFSET_SETTLE_SEC") {
try {
Expand Down
3 changes: 3 additions & 0 deletions tcsd/tcs_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1857,6 +1857,9 @@ namespace TCS {
if ( cmd == "?NAME" ||
cmd == "LAMPS?" ||
cmd == "NPS" ||
cmd == "?ONTARGET" || // reply IS the flag (0|1), not a status code:
// 0 collides with TCS_SUCCESS ("success") and
// 1 falls through to "tcs_undefined" ERROR
cmd == "?PARALLACTIC" ||
cmd == "?WEATHER" ||
cmd == "RAWDEC" ||
Expand Down
Loading