Skip to content
Closed
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
44 changes: 34 additions & 10 deletions camerad/astrocam.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,10 @@ namespace AstroCam {
long Interface::do_abort() {
std::string function = "AstroCam::Interface::do_abort";
std::stringstream message;
// No sub-exposure follows an abort: clearing exposure_pending below wakes
// dothread_monitor_exposure_pending, which starts the next sub-exposure
// whenever nexp is still > 0 after its decrement.
this->nexp.store(0);
// int this_expbuf = this->get_expbuf();
for ( const auto &dev : this->active_devnums ) {
this->exposure_pending( dev, false );
Expand Down Expand Up @@ -1019,9 +1023,10 @@ namespace AstroCam {
return ERROR;
}

// Don't make any changes while frames are still being written.
// Don't make any changes while frames are still being written: give a
// write that is finishing (ms after READY) up to 2 s, then refuse.
//
if ( this->any_writes_pending() ) {
if ( !this->wait_writes_done( 2000 ) ) {
logwrite( function, "ERROR: cannot change binning while frame write is in progress" );
retstring="write_in_progress";
return ERROR;
Expand Down Expand Up @@ -2713,7 +2718,7 @@ namespace AstroCam {
if ( interface.nexp.load() > 0 ) {
message.str(""); message << "starting next exposure. " << interface.nexp << " remaining";
interface.broadcast.notice( function, message.str() );
interface.do_expose(interface.nexp);
interface.do_expose(interface.nexp, true); // continuation: nexp already holds what is left
}
else {
interface.status.can_expose.store(true);
Expand Down Expand Up @@ -2768,7 +2773,7 @@ namespace AstroCam {
* @return ERROR or NO_ERROR
*
*/
long Interface::do_expose(int nexp_in) {
long Interface::do_expose(int nexp_in, bool continuation) {
const std::string function("AstroCam::Interface::do_expose");
std::stringstream message;
std::string devstr;
Expand All @@ -2792,8 +2797,6 @@ namespace AstroCam {
}
}

this->nexp.store(nexp_in);

// Different controllers may have different rules about when to set and clear their
// exposure_pending flag (depending on whether or not they support frame transfer), but
// if an exposure is pending for any controller then do not start a new exposure.
Expand All @@ -2807,6 +2810,11 @@ namespace AstroCam {
return(ERROR);
}

// Only now, and only for a new expose command: a refused expose must not
// change how many sub-exposures the running series has left, and a
// continuation must not undo a stop that lowered nexp since it was read.
if ( !continuation ) this->nexp.store(nexp_in);

// When using the internal ARC shutter control you cannot overlap exposures
// with readout because it would require sending a command which cannot
// be handled during readout.
Expand Down Expand Up @@ -3602,9 +3610,10 @@ for ( const auto &dev : selectdev ) {
return( ERROR );
}

// Don't remap the DMA buffer while frames are still being written from it.
// Don't remap the DMA buffer while frames are still being written from it:
// give a write that is finishing (ms after READY) up to 2 s, then refuse.
//
if ( this->any_writes_pending() ) {
if ( !this->wait_writes_done( 2000 ) ) {
logwrite( function, "ERROR: cannot remap DMA buffer while frame write is in progress" );
retstring="write_in_progress";
return ERROR;
Expand Down Expand Up @@ -4427,9 +4436,18 @@ logwrite(function, message.str());
const std::string function("AstroCam::Interface::stop_exposure");
std::stringstream message;

// nothing to do if not exposing
//
// An NEXP>1 series ends after the frame in progress, whatever that frame
// is doing -- integrating, reading out, or about to start -- so no further
// sub-exposure begins. That frame is also cut short below when it is
// integrating with >= 5 s left.
const bool ends_series = ( this->nexp.load() > 1 );
if ( ends_series ) this->nexp.store(1);
if ( ! this->exposure_pending() ) {
if ( ends_series ) {
logwrite( function, "between sub-exposures: the series ends after the next frame" );
retstring="series_ends";
return NO_ERROR;
}
logwrite( function, "not exposing" );
return NOTHING;
}
Expand All @@ -4440,6 +4458,12 @@ logwrite(function, message.str());
//
long remaining_time = this->camera.shutter_timer.get_remaining();

if ( remaining_time < 5000 && ends_series ) {
logwrite( function, "too late to cut this frame short: the series ends after it" );
retstring="series_ends";
return NO_ERROR;
}

if ( remaining_time < 5000 ) {
message << "ERROR cannot stop exposure time with less than 5 s exptime remaining"
<< " (" << remaining_time << ")";
Expand Down
22 changes: 21 additions & 1 deletion camerad/astrocam.h
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,26 @@ std::vector<std::shared_ptr<Camera::Information>> fitsinfo;
/***** Interface::any_writes_pending ************************************/


/***** Interface::wait_writes_done **************************************/
/**
* @brief wait up to timeout_ms for every frame write to finish
* @return true if no writes are pending, false on timeout
* @details READY is published when the last frame has arrived, a few ms
* before its FITS write ends, so a command sent right after READY
* can find a write still in progress.
*/
inline bool wait_writes_done( int timeout_ms ) {
std::unique_lock<std::mutex> lock( this->write_lock );
return this->write_condition.wait_for( lock, std::chrono::milliseconds( timeout_ms ), [this]() {
for ( const auto &pending : this->writes_pending ) {
if ( !pending.empty() ) return false;
}
return true;
} );
}
/***** Interface::wait_writes_done **************************************/


/***** Interface::write_pending *****************************************/
/**
* @brief Set or clear the write pending state for a given exposure
Expand Down Expand Up @@ -1240,7 +1260,7 @@ std::vector<std::shared_ptr<Camera::Information>> fitsinfo;
void set_imagesize(int rowsin, int colsin, int* status);

long expose(std::string nexp_in);
long do_expose(int nexp_in);
long do_expose(int nexp_in, bool continuation=false); ///< continuation: the next sub-exposure of a running series
double get_live_airmass(); ///< latest airmass from cached tcsd telemetry, or NAN
void get_live_provenance( std::string &githash, std::string &buildtime ); ///< build provenance of all running daemons
long native(std::string cmdstr);
Expand Down
18 changes: 14 additions & 4 deletions sequencerd/sequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,7 @@ namespace Sequencer {
//
if ( this->cancel_flag.load() ) {
this->broadcast.notice( function, "exposure cancelled" );
this->arm_readout_flag = false; // the stopped frame's PIXELCOUNTs must not set READOUT later
error = this->target.update_state( Sequencer::TARGET_UNASSIGNED );
message.str(""); message << ( error==NO_ERROR ? "" : "ERROR " ) << "marking target " << this->target.name
<< " id " << this->target.obsid << " order " << this->target.obsorder
Expand Down Expand Up @@ -3209,15 +3210,24 @@ namespace Sequencer {

ScopedState thr_state( this->thread_state_manager, Sequencer::THR_STOP_EXPOSURE );

// This function is only used while exposing
// Only while an exposure command of the current target is running. For
// NEXP>1 that is the whole series: the EXPOSE bit covers only the first
// sub-exposure; after it, camerad not READY (and not the previous
// target's last readout after a pick, which has nothing left to stop)
// covers the rest. A stale READOUT bit is not evidence. camerad ends the
// series after the frame in progress, cut short if it is integrating
// with >= 5 s left. A stop never clears a cancel.
//
if ( ! this->wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) {
bool series_running = false;
{
std::lock_guard<std::mutex> lock( this->camerad_mtx );
series_running = !this->can_expose.load() && !this->readout_gate_armed;
}
if ( ! this->wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) && !series_running ) {
this->broadcast.notice(function, "not currently exposing");
return;
}

this->cancel_flag.store(false);

// Send command to the camera to stop the exposure.
//
std::string reply;
Expand Down