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
3 changes: 2 additions & 1 deletion Config/acamd.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ ACQUIRE_TIMEOUT=90 # seconds before ACAM acquisition sequence aborts
ACQUIRE_RETRYS=5 # max number of retrys before acquisition fails (optional, can be left blank to disable)
ACQUIRE_OFFSET_THRESHOLD=0.5 # computed offset below this threshold (in arcsec) defines successful acquisition
ACQUIRE_MIN_REPEAT=2 # minimum number of sequential successful acquires
ACQUIRE_TCS_MAX_OFFSET=60 # the maximum allowable offset sent to the TCS, in arcsec
ACQUIRE_TCS_MAX_OFFSET=60 # max offset (arcsec) for an ordinary guiding correction sent to the TCS
ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET=300 # max offset (arcsec) for a deliberate goal offset (put-on-slit, offset-star, end-of-fineacquire, pyGUI 'Offset') applied while guiding

# SkySimulator options:
# SKYSIM_IMAGE_SIZE=<is> where <is> is integer
Expand Down
113 changes: 63 additions & 50 deletions acamd/acam_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,27 @@ namespace Acam {
applied++;
}

if ( config.param[entry] == "ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET" ) {
double offset;
try {
offset = std::stod( config.arg[entry] );
} catch ( std::invalid_argument &e ) {
message.str(""); message << "ERROR bad ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET " << config.param[entry] << ": " << e.what();
logwrite( function, message.str() );
return(ERROR);
} catch ( std::out_of_range &e ) {
message.str(""); message << "ERROR bad ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET " << config.param[entry] << ": " << e.what();
logwrite( function, message.str() );
return(ERROR);
}
if ( this->target.set_tcs_max_putonslit_offset( offset ) != NO_ERROR ) {
message.str(""); message << "ERROR bad ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET \"" << config.param[entry] << "\" must be >= 0";
logwrite( function, message.str() );
return ERROR;
}
applied++;
}

if ( config.param[entry] == "ACQUIRE_MIN_REPEAT" ) {
int repeat;
try {
Expand Down Expand Up @@ -3371,6 +3392,7 @@ logwrite( function, message.str() );
this->nacquired = 0;
this->attempts = 0;
this->sequential_failures = 0;
this->allow_large_offset.store(false); // no stale deliberate-offset allowance
this->is_acquired.store( false, std::memory_order_release );

// Start the timeout clock, initialized as the time now plus the
Expand Down Expand Up @@ -3610,45 +3632,29 @@ logwrite( function, message.str() );

message.str(""); message << "[ACQUIRE] offset=" << offset << " (arcsec)"; logwrite( function,message.str() );

// There is a maximum offset allowed to the TCS.
// This is not a TCS limit (their limit is very large).
// This is our limit so that we don't accidentally move too far off the
// slit. However, "putonslit" can include a desired offset which is
// outside this limit, so when checking the calculated offset, include a
// delta which is the change introduced by putonslit.
// There is a maximum offset we send to the TCS. This is not a TCS limit
// (theirs is very large); it is our safety limit so that a bad solution
// can't move us far off the slit. Ordinary guiding corrections use the
// normal tcs_max_offset (ACQUIRE_TCS_MAX_OFFSET). A deliberate goal offset
// applied while guiding -- via offset_goal, which covers put-on-slit,
// offset-star acquisition, the end-of-fineacquire target offset, and the
// pyGUI 'Offset' button -- is intentionally larger, so for the one
// correction that consumes it we allow up to tcs_max_putonslit_offset
// (ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET). The ACQUIRE path uses tcs_max_offset
// and is unaffected.
//
double maxoffset = this->tcs_max_offset;
if ( this->acquire_mode == Acam::TARGET_GUIDE && this->allow_large_offset.load() ) {
maxoffset = this->tcs_max_putonslit_offset;
}

// this will be the solution plus dRA, dDEC
// start by initializing with acam_ra,acam_dec
//
double acam_ra_dRA = acam_ra;
double acam_dec_dDEC = acam_dec;

// Then acam_ra_dRA, acam_dec_dDEC will be modified by applying dRA, dDEC
//
iface->fpoffsets.apply_offset( acam_ra_dRA, iface->target.dRA,
acam_dec_dDEC, iface->target.dDEC );

// the offset introduced by putonslit is therefore the separation between
// acam_ra,acam_dec and acam_ra_dRA,acam_dec_dDEC
//
this->putonslit_offset = angular_separation( acam_ra_dRA, acam_dec_dDEC, acam_ra, acam_dec );

// and the delta is the difference between this and the last time,
// which gets added to the tcs_max_offset.
//
double maxoffset = this->tcs_max_offset + std::fabs(this->putonslit_offset - this->last_putonslit_offset);

// so remember this for next time
//
this->last_putonslit_offset = this->putonslit_offset;

// Finally, check the requested offset against this putonslit-modified max allowed offset
// Check the requested offset against the applicable max allowed offset
//
if ( offset >= maxoffset ) {
message.str(""); message << "[WARNING] calculated offset " << offset << " not below max "
<< maxoffset << " and will not be sent to the TCS";
logwrite( function, message.str() );
this->allow_large_offset.store(false); // deliberate-offset allowance consumed even when rejected

// Match found but failure to send an offset is considered an attempt
// so attempts is incremented.
Expand Down Expand Up @@ -3685,6 +3691,7 @@ logwrite( function, message.str() );
if ( should_offset ) {
// send offset to TCS here (returns when offset is complete)
if ( iface->tcsd.pt_offset( ra_off*3600., dec_off*3600., OFFSETRATE )==ERROR) break;
this->allow_large_offset.store(false); // deliberate-offset allowance consumed
std::this_thread::sleep_for( std::chrono::seconds(1) );
}

Expand Down Expand Up @@ -3798,6 +3805,7 @@ logwrite( function, message.str() );
*
*/
bool Target::median_filter( double &ra_off, double &dec_off ) {
std::lock_guard<std::mutex> lock(this->offset_params_mtx); // P2: serialize vs reset_offset_params

if ( this->tcs_offset_period == 1 ) return true;

Expand Down Expand Up @@ -3833,7 +3841,10 @@ logwrite( function, message.str() );
dec_off = this->dec_offs[n/2];
}

this->reset_offset_params();
// accumulators consumed; clear inline (reset_offset_params would re-lock the non-recursive mutex)
this->ra_offs.clear();
this->dec_offs.clear();
this->time_offs.clear();

return true;
}
Expand Down Expand Up @@ -5484,11 +5495,11 @@ logwrite( function, message.str() );
//
if ( args == "?" ) {
retstring = ACAMD_OFFSETGOAL;
retstring.append( " [ <dRA> <dDEC> [ fineguiding ]\n" );
retstring.append( " [ <dRA> <dDEC> [ fineguiding ] ]\n" );
retstring.append( " Apply offsets <dRA> <dDEC> to the ACAM goal coordinates.\n" );
retstring.append( " These offsets are applied only while guiding. If omitted,\n" );
retstring.append( " the current offsets are returned. Units are in degrees.\n" );
retstring.append( " The optional 'fineguiding' is used for slicecam fine acquisition.\n" );
retstring.append( " 'fineguiding' marks a slicecam fine-guiding correction (keeps the 60\" cap).\n" );
return HELP;
}

Expand All @@ -5504,9 +5515,12 @@ logwrite( function, message.str() );
this->target.dRA = dRA;
this->target.dDEC = dDEC;

// optional fineguiding flag used for slicecam fineacquisition mode
// optional fineguiding flag: marks a slicecam fine-guiding correction, which
// must stay within the ordinary 60" guiding cap. Its absence means a
// deliberate goal offset (put-on-slit, offset-star, end-of-fineacquire,
// pyGUI 'Offset') that may legitimately be larger.
std::string flag;
bool is_fineguiding = (iss >> flag && flag == "fineguiding");
const bool is_fineguiding = (iss >> flag && flag == "fineguiding");

// Apply any dRA, dDEC goal offsets from the "put on slit" action to
// acam_ra_goal, acam_dec_goal. These dRA,dDEC offsets can come from
Expand All @@ -5521,20 +5535,19 @@ logwrite( function, message.str() );
message.str(""); message << this->target.dRA << " " << this->target.dDEC;
retstring = message.str();

// Applying an offset to the goal while guiding must never drop out of
// guiding -- there is no use case for re-acquiring on an offset. Stay in
// TARGET_GUIDE and reset the offset filter so the new goal takes effect
// quickly. This covers GUI "put on slit" and sequencer target offsets.
//
// This is a deliberate offset (put-on-slit, offset-star, end-of-fineacquire,
// or the pyGUI 'Offset' button) and may exceed the normal guiding cap, so
// allow the next correction up to ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET. The
// allowance is one-shot: do_acquire consumes it when the offset is sent.
//
if ( this->target.acquire_mode == Acam::TARGET_GUIDE ) {
// for slicecam fine aquisition/guiding, stay in TARGET_GUIDE but
// reset the filtering so the goal takes effect quickly
if ( is_fineguiding ) {
this->target.reset_offset_params();
}
else {
this->target.acquire_mode = Acam::TARGET_ACQUIRE;
this->target.nacquired = 0;
this->target.attempts = 0;
this->target.sequential_failures = 0;
this->target.timeout_time = std::chrono::steady_clock::now()
+ std::chrono::duration<double>(this->target.timeout);
}
this->target.reset_offset_params();
Comment on lines 5548 to +5549

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the enlarged offset limit until the correction is sent

When offsetperiod is configured above 1 second and a put-on-slit or target offset exceeds ACQUIRE_TCS_MAX_OFFSET, keeping TARGET_GUIDE causes the offset to never be applied. On the first frame, do_acquire() permits the large offset using the one-shot putonslit_offset - last_putonslit_offset allowance, but median_filter() returns false while filling its window; last_putonslit_offset is nevertheless updated, so every subsequent frame rejects the still-pending correction against the normal maximum at acamd/acam_interface.cpp:3640-3648. The previous transition to TARGET_ACQUIRE bypassed the median filter and sent the correction during that first allowed frame.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Synchronize the guide-filter reset with median filtering

For ordinary guided offset requests, this newly added reset can run in the server command thread while the frame-grab thread is executing do_acquire() and mutating the same ra_offs, dec_offs, and time_offs vectors in median_filter() (acamd/acam_interface.cpp:3807-3820). Clearing those vectors concurrently with a push or sort is undefined behavior and can corrupt memory or crash acamd; use a shared lock or defer the reset to the acquisition thread.

Useful? React with 👍 / 👎.

if ( !is_fineguiding ) this->target.allow_large_offset.store(true);
}

this->publish_status();
Expand Down
18 changes: 15 additions & 3 deletions acamd/acam_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ namespace Acam {
std::atomic<bool> stop_acquisition; ///< set if the acquisition sequence should stop

double tcs_max_offset;
double tcs_max_putonslit_offset{300.}; ///< max offset (arcsec) for a deliberate goal offset (put-on-slit etc.) applied while guiding; defaults 300 if ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET absent

double offset_cal_offset, offset_cal_raoff, offset_cal_decoff;

Expand All @@ -359,6 +360,7 @@ namespace Acam {

std::vector<double> ra_offs, dec_offs; ///< lists of offsets for median filtering
std::vector<std::chrono::steady_clock::time_point> time_offs;
std::mutex offset_params_mtx; ///< guards ra_offs/dec_offs/time_offs (median_filter vs reset_offset_params)
std::chrono::seconds::rep tcs_offset_period; ///< period at which to send offsets while guiding

struct coords_t {
Expand All @@ -378,6 +380,7 @@ namespace Acam {

inline std::chrono::seconds::rep get_tcs_offset_period() { return this->tcs_offset_period; }
inline void reset_offset_params(std::chrono::seconds::rep val) {
std::lock_guard<std::mutex> lock(this->offset_params_mtx); // P2: serialize vs median_filter
this->ra_offs.clear();
this->dec_offs.clear();
this->time_offs.clear();
Expand Down Expand Up @@ -413,6 +416,16 @@ namespace Acam {

inline double get_tcs_max_offset() { return this->tcs_max_offset; }

inline long set_tcs_max_putonslit_offset( const double _offset ) {
if ( std::isnan( _offset ) || _offset <= 0 ) return ERROR;
else {
this->tcs_max_putonslit_offset = _offset;
return NO_ERROR;
}
}

inline double get_tcs_max_putonslit_offset() { return this->tcs_max_putonslit_offset; }

inline void set_max_attempts( int _max ) { this->max_attempts = _max; }
inline void set_min_repeat( int _repeat ) { this->min_repeat = _repeat; }

Expand Down Expand Up @@ -472,7 +485,7 @@ namespace Acam {
double angle;
} acam_goal;

double putonslit_offset, last_putonslit_offset;
std::atomic<bool> allow_large_offset{false}; ///< one-shot: allow the next guiding correction up to tcs_max_putonslit_offset

Target() : iface(nullptr), timeout(10), max_attempts(-1), min_repeat(1),
is_acquired(false),
Expand All @@ -482,8 +495,7 @@ namespace Acam {
tcs_offset_period(1),
pointmode(Acam::POINTMODE_SLIT),
acquire_mode(Acam::TARGET_NOP),
dRA(0), dDEC(0),
putonslit_offset(0), last_putonslit_offset(0) { }
dRA(0), dDEC(0) { }
};
/***** Acam::Target *********************************************************/

Expand Down
Loading