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
104 changes: 52 additions & 52 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,40 +3632,23 @@ 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 "
Expand Down Expand Up @@ -3685,6 +3690,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 @@ -5484,11 +5490,10 @@ logwrite( function, message.str() );
//
if ( args == "?" ) {
retstring = ACAMD_OFFSETGOAL;
retstring.append( " [ <dRA> <dDEC> [ fineguiding ]\n" );
retstring.append( " [ <dRA> <dDEC> ]\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" );
return HELP;
}

Expand All @@ -5497,17 +5502,13 @@ logwrite( function, message.str() );
double dRA=NAN, dDEC=NAN;
if (!(iss >> dRA >> dDEC) ||
(std::isnan(dRA) || std::isnan(dDEC)) ) {
logwrite( function, "ERROR expected <dRA> <dDEC> [ fineguiding ]" );
logwrite( function, "ERROR expected <dRA> <dDEC>" );
retstring="invalid_argument";
return ERROR;
}
this->target.dRA = dRA;
this->target.dDEC = dDEC;

// optional fineguiding flag used for slicecam fineacquisition mode
std::string flag;
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
// either the ACAM or slicecam GUIs and are stored in the Target class.
Expand All @@ -5521,20 +5522,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.allow_large_offset.store(true);
this->target.reset_offset_params();
Comment on lines 5535 to +5537
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 👍 / 👎.

}

this->publish_status();
Expand Down
16 changes: 13 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 Down Expand Up @@ -413,6 +414,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 +483,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 +493,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
10 changes: 2 additions & 8 deletions slicecamd/slicecam_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ namespace Slicecam {
const double cmd_dra = effective_gain * med_dra;
const double cmd_ddec = effective_gain * med_ddec;

if ( this->offset_acam_goal( { cmd_dra, cmd_ddec }, true ) != NO_ERROR ) {
if ( this->offset_acam_goal( { cmd_dra, cmd_ddec } ) != NO_ERROR ) {
logwrite( function, "ERROR failed to send offset to ACAM" );
this->is_fineacquire_running.store( false, std::memory_order_release );
this->publish_status();
Expand Down Expand Up @@ -2496,14 +2496,11 @@ namespace Slicecam {
* @return ERROR | NO_ERROR
*
*/
long Interface::offset_acam_goal(const std::pair<double, double> &offsets, std::optional<bool> fineacquire) {
long Interface::offset_acam_goal(const std::pair<double, double> &offsets) {
const char* function = "Slicecam::Interface::offset_acam_goal";

auto [ra_off, dec_off] = offsets; // local copy

bool is_fineacquire=false;
if (fineacquire) is_fineacquire = *fineacquire;

// If ACAM is guiding then slicecam must not move the telescope,
// but must allow ACAM to perform the offset.
//
Expand All @@ -2517,9 +2514,6 @@ namespace Slicecam {
std::ostringstream cmd;
cmd << ACAMD_OFFSETGOAL << " " << std::fixed << std::setprecision(6) << ra_off << " " << dec_off;

// add fineguiding arg when used for fine acquisition mode
if (is_fineacquire) cmd << " fineguiding";

if (this->acamd.command( cmd.str() ) != NO_ERROR) {
logwrite( function, "ERROR adding offset to acam goal" );
return ERROR;
Expand Down
2 changes: 1 addition & 1 deletion slicecamd/slicecam_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ namespace Slicecam {
long fan_mode( std::string args, std::string &retstring );
long gain( std::string args, std::string &retstring );

long offset_acam_goal(const std::pair<double, double> &offsets, std::optional<bool> fineacquire=std::nullopt);
long offset_acam_goal(const std::pair<double, double> &offsets);

long collect_header_info( std::unique_ptr<Andor::Interface> &slicecam );

Expand Down
Loading