diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 6a5792d1..14a82f02 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -2185,6 +2185,20 @@ namespace AstroCam { logwrite(function, message.str()); #endif + // The flexure compensation can change after the exposure starts, so this + // channel's flexure positions are taken at its end, with SHUTTIME, before + // its readout. + // + { + std::lock_guard lock( server.live_telemetry_mtx ); + auto flex = server.live_telemetry.find( Topic::FLEXURED ); + if ( flex != server.live_telemetry.end() ) { + for ( const auto &k : FitsHeaderKeys::FlexureInfoKeys ) { + if ( con.channel == k.chan ) con.expinfo[expbuf].telemkeys.add_json_key( flex->second, k.jkey, k.keyword, k.comment, k.type, EXT, k.chan ); + } + } + } + // Wait for any shutter delay before reading out. // if ( cam.get_shutter_delay() > 0 ) cam.wait_shutter_delay(); @@ -2858,7 +2872,6 @@ namespace AstroCam { add( Topic::SLITD, FitsHeaderKeys::SlitInfoKeys ); // primary add( Topic::TARGETINFO, FitsHeaderKeys::TargetInfoKeys ); // primary add( Topic::TCSD, FitsHeaderKeys::TcsInfoKeys ); // primary - add( Topic::FLEXURED, FitsHeaderKeys::FlexureInfoKeys ); // extension add( Topic::FOCUSD, FitsHeaderKeys::FocusInfoKeys ); // extension add( Topic::THERMALD, FitsHeaderKeys::ThermalInfoKeys ); // extension diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 338b6077..4efbc1af 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -50,8 +50,23 @@ namespace Sequencer { std::lock_guard lock(camerad_mtx); if (jmessage.contains(Key::Camerad::READY)) { int isready = jmessage[Key::Camerad::READY].get(); + // ready again: the expose command's series has ended, re-apply no more + if ( isready && !this->can_expose.load() ) this->expose_nexp = 0; this->can_expose.store(isready); } + + // The shutter closing, other than to pause, ends an integration. The + // telescope keeps tracking, so while another sub-exposure of the expose + // command follows, re-apply the flexure compensation. + // + if (jmessage.contains(Key::Camerad::SHUTTEROPEN)) { + bool open = jmessage[Key::Camerad::SHUTTEROPEN].get(); + bool paused = jmessage.contains(Key::Camerad::PAUSED) && jmessage[Key::Camerad::PAUSED].get(); + if ( !open && !paused && this->last_shutteropen && ++this->shutter_closes < this->expose_nexp ) { + std::thread( &Sequencer::Sequence::flexure_set, this ).detach(); + } + this->last_shutteropen = open; + } this->camerad_cv.notify_all(); } /***** Sequencer::Sequence::handletopic_camerad ****************************/ @@ -742,7 +757,6 @@ namespace Sequencer { worker_threads = { { THR_MOVE_TO_TARGET, std::bind(&Sequence::move_to_target, this) }, { THR_CAMERA_SET, std::bind(&Sequence::camera_set, this) }, { THR_FOCUS_SET, std::bind(&Sequence::focus_set, this) }, - { THR_FLEXURE_SET, std::bind(&Sequence::flexure_set, this) }, { THR_CALIB_SET, std::bind(&Sequence::calib_set, this) }, // for CAL targets, slit comes from database, otherwise use VSM acquire position { THR_SLIT_SET, std::bind(&Sequence::slit_set, this, @@ -840,6 +854,15 @@ namespace Sequencer { return; } } + } + + // Set the flexure compensation for the pointing to be exposed, every time, + // however the target was acquired (automatically, by hand, or not at + // all) and after any target offset. Nothing waits for it. + // + std::thread( &Sequencer::Sequence::flexure_set, this ).detach(); + + if ( !this->target.iscal ) { // ensure slit offset is in "expose" position when needed try { error |= this->slit_set(Sequencer::VSM_EXPOSE); @@ -2426,7 +2449,9 @@ namespace Sequencer { * compensation origin (flexured adds the computed delta to defpos), * so this is the neutral state; it prevents a previous target's * compensation from being left applied to every subsequent target. - * @return NO_ERROR + * Runs in a detached thread and nothing waits for it, so it does + * not throw: a failure is reported and the sequence goes on. + * @return ERROR | NO_ERROR * */ long Sequence::flexure_set() { @@ -2452,22 +2477,28 @@ namespace Sequencer { return NO_ERROR; } + if ( this->cancel_flag.load() ) return NO_ERROR; + // build up list of activate chans std::ostringstream activechans; const std::string calname = std::string(this->target.iscal ? this->target.name : "SCIENCE"); - const auto &calinfo = this->caltarget.get_info(calname); - for (const auto &[chan,active] : calinfo.channel_active) { - if (active) activechans << " " << chan; + try { + const auto &calinfo = this->caltarget.get_info(calname); + for (const auto &[chan,active] : calinfo.channel_active) { + if (active) activechans << " " << chan; + } + } + catch ( const std::exception &e ) { + this->broadcast.error( function, "setting flexure compensator: "+std::string(e.what()) ); + return ERROR; } std::ostringstream comp; comp << FLEXURED_COMPENSATE << activechans.str(); - if ( !this->cancel_flag.load() && - this->flexured.command( comp.str() ) != NO_ERROR ) { + if ( this->flexured.command( comp.str() ) != NO_ERROR ) { this->broadcast.error( function, "setting flexure compensator" ); - this->thread_error_manager.set( THR_FLEXURE_SET ); - throw std::runtime_error("setting flexure compensator"); + return ERROR; } this->is_flexure_default.store(false); @@ -2840,6 +2871,12 @@ namespace Sequencer { return NO_ERROR; } + // Set the flexure compensation for the pointing now, once camera_set has + // seen the camera ready (the previous exposure may still have been + // integrating). Nothing waits for it. + // + std::thread( &Sequencer::Sequence::flexure_set, this ).detach(); + // Start the exposure in a thread... // auto start_exposure = std::async(std::launch::async, &Sequence::trigger_exposure, this); @@ -2890,6 +2927,9 @@ namespace Sequencer { // if ( this->tcs_preauth_time > 0 ) this->notify_tcs_next_target = true; else this->notify_tcs_next_target = false; + this->shutter_closes = 0; // counted per expose command + this->expose_nexp = this->target.nexp; + this->arm_readout_flag = true; // enables the async_listener to look for the readout and clear the EXPOSE bit this->set_imgtype(); diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 8df8f33b..f8e637b3 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -295,6 +295,8 @@ namespace Sequencer { std::atomic is_science_frame_transfer; ///< is frame transfer enabled for science cameras std::atomic notify_tcs_next_target; ///< notify TCS of next target when remaining time within TCS_PREAUTH_TIME std::atomic arm_readout_flag; ///< + std::atomic shutter_closes{0}; ///< shutter closes since the last expose command + std::atomic expose_nexp{0}; ///< sub-exposures of the last expose command std::atomic cancel_flag{false}; std::atomic is_ontarget{false}; ///< remotely set by the TCS operator to indicate that the target is ready std::atomic is_usercontinue{false}; ///< remotely set by the user to continue @@ -431,6 +433,7 @@ namespace Sequencer { std::condition_variable acam_cv; std::mutex camerad_mtx; std::condition_variable camerad_cv; + bool last_shutteropen = false; ///< SHUTTEROPEN in camerad's latest report (camerad_mtx) std::mutex slitd_mtx; std::condition_variable slitd_cv; std::mutex tcsd_mtx;