diff --git a/slicecamd/slicecam_interface.cpp b/slicecamd/slicecam_interface.cpp index 01604a4b..0c8b19e8 100644 --- a/slicecamd/slicecam_interface.cpp +++ b/slicecamd/slicecam_interface.cpp @@ -163,6 +163,14 @@ namespace Slicecam { // start the state machine this->fineacquire_state.reset(); + this->fineacq_total_dra = 0.0; // reset per-run ACAM->slit residual accumulators + this->fineacq_total_ddec = 0.0; + + // snapshot this run's goal (DB target) coords from the TARGETINFO published message; NAN if no target + // has been published (manual runs log nan). Frozen for the run via the is_fineacquire_running handoff. + this->fineacq_goal_ra = this->targetinfo_ra_deg.load(); + this->fineacq_goal_dec = this->targetinfo_dec_deg.load(); + this->is_fineacquire_locked.store(false, std::memory_order_release); this->is_fineacquire_running.store(true, std::memory_order_release); this->is_autoexpose_running.store(false, std::memory_order_release); // fineacquire supersedes auto-exposure @@ -461,6 +469,25 @@ namespace Slicecam { << " scatter=(" << sig_dra << "," << sig_ddec << ") arcsec)" << " goal=" << this->fineacquire_state.goal_arcsec << " arcsec"; logwrite( function, oss.str() ); + + // One structured per-run line for building an ACAM->slit geometric (flexure) model + // over time. fineacq_total_{dra,ddec} is the total correction applied this run = the + // ACAM->slit residual that acam-acquire left behind. We log it against the GOAL + // (database target) coordinates -- the INPUT to the SCOPE->ACAM transform -- plus the + // cassegrain angle. Altitude/hour-angle are derived offline from GOALRA/GOALDEC + this + // line's timestamp. We deliberately do NOT log the telescope's actual RA/DEC: that is + // the transform's OUTPUT and drifts as fine-acquire applies offsets, so it cannot be + // used to fit the geometry. + std::ostringstream acqmodel; + acqmodel << "[ACQMODEL] acam2slit dRA=" << this->fineacq_total_dra + << " dDEC=" << this->fineacq_total_ddec << " arcsec" + << " GOALRA=" << this->fineacq_goal_ra + << " GOALDEC=" << this->fineacq_goal_dec + << " CASANGLE=" << this->telem.angle_scope + << " n=" << n + << " cam=" << which; + logwrite( function, acqmodel.str() ); + this->is_fineacquire_locked.store( true, std::memory_order_release ); this->is_fineacquire_running.store( false, std::memory_order_release ); this->fineacquire_state.reset(); @@ -542,6 +569,11 @@ namespace Slicecam { return; } + // accumulate the applied correction (arcsec). Summed over the run this is the + // total ACAM->slit residual that acam-acquire left behind (the [ACQMODEL] line). + this->fineacq_total_dra += cmd_dra * 3600.0; + this->fineacq_total_ddec += cmd_ddec * 3600.0; + // reset samples and discard settle_count frames for telescope settling this->fineacquire_state.reset(); this->fineacquire_state.settle_frames = this->fineacquire_state.settle_count; @@ -960,6 +992,32 @@ namespace Slicecam { /***** Slicecam::Interface::handletopic_tcsd ********************************/ + /***** Slicecam::Interface::handletopic_targetinfo **************************/ + /** + * @brief what to do when the topic is Topic::TARGETINFO + * @details This receives target RA/DEC and stores them in the class + * as decimal degrees. + * @param[in] jmessage_in subscribed-received JSON message + * + */ + void Interface::handletopic_targetinfo( const nlohmann::json &jmessage ) { + std::string ra_hms, dec_dms; + + Common::extract_telemetry_value( jmessage, Key::TargetInfo::RA, ra_hms ); + Common::extract_telemetry_value( jmessage, Key::TargetInfo::DECL, dec_dms ); + + try { + this->targetinfo_ra_deg.store( radec_to_decimal( ra_hms ) * TO_DEGREES ); + this->targetinfo_dec_deg.store( radec_to_decimal( dec_dms ) ); + } + catch( const std::exception &e ) { + this->targetinfo_ra_deg.store(NAN); + this->targetinfo_dec_deg.store(NAN); + } + } + /***** Slicecam::Interface::handletopic_targetinfo **************************/ + + /***** Slicecam::Interface::publish_status **********************************/ /** * @brief publishes my important status on change @@ -2187,6 +2245,7 @@ namespace Slicecam { this->is_fineacquire_running.store( false, std::memory_order_release ); this->is_fineacquire_locked.store( false, std::memory_order_release ); + this->publish_status(); this->cv.notify_all(); // send notification that the loop has stopped diff --git a/slicecamd/slicecam_interface.h b/slicecamd/slicecam_interface.h index f8d3920f..0cf27085 100644 --- a/slicecamd/slicecam_interface.h +++ b/slicecamd/slicecam_interface.h @@ -143,6 +143,15 @@ namespace Slicecam { FineAcqState fineacquire_state; + // Per-run accumulators for the ACAM->slit residual, summed over a fine-acquire + // run and logged once at lock (the [ACQMODEL] line) to build a flexure model of + // the ACAM->slit pointing offset vs geometry over time. Touched only from the + // single framegrab thread, so plain doubles are sufficient. + double fineacq_total_dra = 0.0; ///< sum of applied dRA corrections this run [arcsec] + double fineacq_total_ddec = 0.0; ///< sum of applied dDEC corrections this run [arcsec] + double fineacq_goal_ra = NAN; ///< per-run snapshot of goal RA [deg], taken at fineacquire start, logged at lock + double fineacq_goal_dec = NAN; ///< per-run snapshot of goal DEC [deg], taken at fineacquire start, logged at lock + /// per-frame auto-exposure runtime (ACAM-window pre-tuning). Brightness is /// sampled over a window of frames; a high percentile (near-max) is used /// because telescope motion only smears light out (lowering brightness), @@ -184,6 +193,12 @@ namespace Slicecam { std::atomic last_acam_pubtime{0}; ///< pubtime (us) of latest received acamd status + // Latest target (goal) coords published on Topic::TARGETINFO + // NAN until a TARGETINFO arrives, so manual runs with no sequencer target log nan. + // + std::atomic targetinfo_ra_deg{NAN}; ///< latest goal RA [deg] from TARGETINFO + std::atomic targetinfo_dec_deg{NAN}; ///< latest goal DEC [deg] from TARGETINFO + /// Max acceptable age (us) for cached ACAM status used by fineacquire. static constexpr int64_t ACAM_STATUS_MAX_AGE_US = 10'000'000; @@ -258,7 +273,9 @@ namespace Slicecam { { Topic::TCSD, std::function( [this](const nlohmann::json &msg) { handletopic_tcsd(msg); } ) }, { Topic::SLITD, std::function( - [this](const nlohmann::json &msg) { handletopic_slitd(msg); } ) } + [this](const nlohmann::json &msg) { handletopic_slitd(msg); } ) }, + { Topic::TARGETINFO, std::function( + [this](const nlohmann::json &msg) { handletopic_targetinfo(msg); } ) } }; } @@ -298,6 +315,7 @@ namespace Slicecam { void handletopic_acamd( const nlohmann::json &jmessage ); void handletopic_slitd( const nlohmann::json &jmessage ); void handletopic_tcsd( const nlohmann::json &jmessage ); + void handletopic_targetinfo( const nlohmann::json &jmessage ); void publish_status(bool force=false); void publish_snapshot(); void publish_temperature(); ///< publish only the andor temperatures on Topic::SLICECAMD (periodic) diff --git a/slicecamd/slicecamd.cpp b/slicecamd/slicecamd.cpp index e4aa0006..cd507431 100644 --- a/slicecamd/slicecamd.cpp +++ b/slicecamd/slicecamd.cpp @@ -148,7 +148,8 @@ int main(int argc, char **argv) { // if ( slicecamd.interface.init_pubsub( { Topic::SLITD, Topic::ACAMD, - Topic::TCSD }) == ERROR ) { + Topic::TCSD, + Topic::TARGETINFO }) == ERROR ) { logwrite(function, "ERROR initializing publisher-subscriber handler"); slicecamd.exit_cleanly(); }