From f671b4877dee26b9bd3ab42db79706f0fdb31b70 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 30 Mar 2026 15:27:29 -0700 Subject: [PATCH 01/35] first steps making helpers that will aid in sequencer scripting --- sequencerd/sequence.cpp | 448 ++++++++++++++++++++++++---------------- sequencerd/sequence.h | 15 +- 2 files changed, 286 insertions(+), 177 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 5db8147d..fc3de240 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -18,6 +18,199 @@ namespace Sequencer { constexpr long CAMERA_PROLOG_TIMEOUT = 6000; ///< timeout msec to send camera prolog command + /***** Sequencer::Sequence::run ********************************************/ + /** + * @brief + * @param[in] op Operation + * @param[in] function function name of operation for logging + * + */ + long Sequence::run( const Operation &op, + const std::string &function ) { + long error=NO_ERROR; + try { + error = op.func(); + + if (error != NO_ERROR) { + this->async.enqueue_and_log(function, "ERROR in "+op.name); + } + } + catch (const std::exception &e) { + logwrite(function, "ERROR in "+op.name+": "+e.what()); + } + return error; + } + /***** Sequencer::Sequence::run ********************************************/ + + + /***** Sequencer::Sequence::run_parallel ***********************************/ + /** + * @brief + * @param[in] op Operation + * @param[in] function function name of operation for logging + * + */ + long Sequence::run_parallel( const std::vector &ops, + const std::string &function ) { + + // start a thread for each operation + // + std::vector> futures; + for (const auto &op : ops) { + futures.emplace_back(std::async(std::launch::async, [this, &op, function]() { + try { + return op.func(); + } + catch (const std::exception &e) { + logwrite(function, "ERROR in "+op.name+": "+e.what()); + return ERROR; + } + })); + } + + long error=NO_ERROR; + + // wait for each thread to complete + // + for (size_t i=0; i ops; + + if (this->target.pointmode == Acam::POINTMODE_ACAM) { + this->dotype("ONE"); + ops = { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } }; + } + else { + this->target.pointmode = Acam::POINTMODE_SLIT; + + // these are the default operations prior to exposure, + // they can be done in parallel + ops = { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, + { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, + { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, + { "flexure_set", THR_FLEXURE_SET, [this]{ return flexure_set(); } }, + { "calib_set", THR_CALIB_SET, [this]{ return calib_set(); } }, + { "slit_set", THR_SLIT_SET, + [this]{ return slit_set(this->target.iscal ? VSM_DATABASE : VSM_ACQUIRE); } } }; + } + + // wait for threads + error = run_parallel(ops, caller); + + // early exit on error + if (error != NO_ERROR) return error; + + if (this->cancel_flag.load()) return ABORT; + + // ---------- POINTMODE-ACAM EXIT ---------------------- + + // If pointmode is ACAM then the user has chosen to put the star on ACAM, in + // which case the assumption is made that nothing else matters. This special + // mode of operation only points the telescope. + // + if ( this->target.pointmode == Acam::POINTMODE_ACAM ) { + this->async.enqueue_and_log(caller, "NOTICE: target list processing has stopped"); + return NO_ERROR; + } + + // if not a calibration target then acquire, first acam then slicecam + if (!this->target.iscal) { + + // ---------- TARGET ACQUISITION --------------------- + + // start ACAM acquisition. If it fails then wait for user to continue or cancel. + if ( this->do_acam_acquire() != NO_ERROR ) { + + this->async.enqueue_and_log( caller, "WARNING acam acquisition failed" ); + + if (this->wait_for_user(caller)==ABORT) { + this->async.enqueue_and_log( caller, "NOTICE: cancelled" ); + return ABORT; + } + } + else + // start SLICECAM fine acquisition + if ( this->do_slicecam_fineacquire() != NO_ERROR ) { + + this->async.enqueue_and_log( caller, "WARNING slicecam fine acquisition failed" ); + } + + // ---------- TARGET OFFSETS ------------------------- + + // send offsets. wait for user if that fails or cancelled + if (this->target_offset() == ERROR) { + + if (this->wait_for_user(caller)==ABORT) { + this->async.enqueue_and_log(caller, "NOTICE: cancelled"); + return ABORT; + } + } + + // ---------- SLIT POSITON FOR EXPOSE ---------------- + + // ensure slit offset is in "expose" position when needed + try { + error |= this->slit_set(Sequencer::VSM_EXPOSE); + } + catch (const std::exception &e) { + logwrite(caller, "ERROR slit offset exception: "+std::string(e.what())); + return ERROR; + } + } + + // ---------- EXPOSURE --------------------------------- + + logwrite(caller, "starting exposure"); + + // Start the exposure in a thread... + // set the EXPOSE bit here, outside of the trigger_exposure function, because that + // function only triggers the exposure -- it doesn't block waiting for the exposure. + // + this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); // set EXPOSE bit + auto start_exposure = std::async(std::launch::async, &Sequence::trigger_exposure, this); + try { + error = start_exposure.get(); + } + catch (const std::exception& e) { + logwrite( caller, "ERROR start_exposure exception: "+std::string(e.what()) ); + return ERROR; + } + + // wait for the exposure to end (naturally or cancelled) + // + logwrite( caller, "waiting for exposure" ); + if (error==NO_ERROR) error = this->wait_for_exposure(caller); + + // If not using frame transfer then wait for readout, too + // + if (error==NO_ERROR && !this->is_science_frame_transfer) { + logwrite( caller, "waiting for readout" ); + error = this->wait_for_readout(caller); + } + + return error; + } + /***** Sequencer::Sequence::run_default_sequence ***************************/ + + /***** Sequencer::Sequence::handletopic_snapshot ***************************/ /** * @brief publishes snapshot of my telemetry @@ -412,40 +605,88 @@ namespace Sequencer { * @brief waits for the user to click a button, or cancel * @details Use this when you just want to slow things down or get a * cup of coffee instead of observing. + * @param[in] caller reference to caller's name for logging * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_user() { - const std::string function("Sequencer::Sequence::wait_for_user"); + long Sequence::wait_for_user(const std::string &caller) { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); - this->async.enqueue_and_log( function, "NOTICE: waiting for USER to send \"continue\" signal" ); + this->async.enqueue_and_log( caller, "NOTICE: waiting for USER to send 'continue' signal" ); while ( !this->cancel_flag.load() && !this->is_usercontinue.load() ) { std::unique_lock lock(cv_mutex); this->cv.wait( lock, [this]() { return( this->is_usercontinue.load() || this->cancel_flag.load() ); } ); } - this->async.enqueue_and_log( function, "NOTICE: received " + this->async.enqueue_and_log( caller, "NOTICE: received " +(this->cancel_flag.load() ? std::string("cancel") : std::string("continue")) +" signal!" ); } // end scope for wait_state = WAIT_USER if ( this->cancel_flag.load() ) { - this->async.enqueue_and_log( function, "NOTICE: sequence cancelled" ); + this->async.enqueue_and_log( caller, "NOTICE: sequence cancelled" ); return ABORT; } this->is_usercontinue.store(false); - this->async.enqueue_and_log( function, "NOTICE: received USER continue signal!" ); - return NO_ERROR; } /***** Sequencer::Sequence::wait_for_user ***********************************/ + /***** Sequencer::Sequence::wait_for_exposure *******************************/ + /** + * @brief waits for exposure completion, or cancel + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_exposure(const std::string &caller) { + while (!this->cancel_flag.load() && + wait_state_manager.is_set(Sequencer::SEQ_WAIT_EXPOSE)) { + std::unique_lock lock(cv_mutex); + this->cv.wait( lock, [this]() { return(!wait_state_manager.is_set(SEQ_WAIT_EXPOSE) || + this->cancel_flag.load()); } ); + } + + if (this->cancel_flag.load()) { + this->async.enqueue_and_log(caller, "NOTICE: exposure cancelled"); + return ABORT; + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::wait_for_exposure *******************************/ + + + /***** Sequencer::Sequence::wait_for_readout ********************************/ + /** + * @brief waits for readout completion, or cancel + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_readout(const std::string &caller) { + while (!this->cancel_flag.load() && + wait_state_manager.is_set(Sequencer::SEQ_WAIT_READOUT)) { + std::unique_lock lock(cv_mutex); + this->cv.wait( lock, [this]() { return(!wait_state_manager.is_set(SEQ_WAIT_READOUT) || + this->cancel_flag.load()); } ); + } + + if (this->cancel_flag.load()) { + this->async.enqueue_and_log(caller, "NOTICE: wait for readout cancelled"); + return ABORT; + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::wait_for_readout ********************************/ + + /***** Sequencer::Sequence::sequence_start **********************************/ /** * @brief main sequence start thread @@ -569,174 +810,17 @@ namespace Sequencer { break; } - // get the threads going -- - // - // These things can all be done in parallel, just have to sync up at the end. - // - - // threads to start, pair their ThreadStatusBit with the function to call - std::vector>> worker_threads; - - // If pointmode is ACAM then the user has chosen to put the star on ACAM, in - // which case the assumption is made that nothing else matters. This special - // mode of operation only points the telescope. - // - if ( this->target.pointmode == Acam::POINTMODE_ACAM ) { - this->dotype( "ONE" ); - worker_threads = { { THR_MOVE_TO_TARGET, std::bind(&Sequence::move_to_target, this) } }; - - } - else { - - // For any other pointmode (SLIT, or empty, which assumes SLIT), all - // subsystems are readied. - // - // set pointmode explicitly, in case it's empty - this->target.pointmode = Acam::POINTMODE_SLIT; - - // threads to start, pair their ThreadStatusBit with the function to call - // - 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, - this->target.iscal ? Sequencer::VSM_DATABASE : Sequencer::VSM_ACQUIRE) } - }; - } - - // pair their ThreadStatusBit with their future - std::vector>> worker_futures; - - // start all of the threads - // - for ( const auto &[thr, func] : worker_threads ) { - worker_futures.emplace_back( thr, std::async(std::launch::async, func) ); - } - - // wait for the threads to complete. these can be cancelled. + // default observation sequence // - for ( auto &[thr, future] : worker_futures) { - try { - error |= future.get(); // wait for this worker to finish - logwrite( function, "NOTICE: worker "+Sequencer::thread_names.at(thr)+" completed"); - } - catch (const std::exception& e) { - logwrite( function, "ERROR: worker "+Sequencer::thread_names.at(thr)+" exception: "+std::string(e.what()) ); - return; - } - } - - logwrite(function, "DONE waiting on threads"); + error = run_default_sequence(function); - if ( this->cancel_flag.load() ) { - this->async.enqueue_and_log( function, "NOTICE: sequence cancelled" ); - return; - } - - // For pointmode ACAM, there is nothing to be done so get out - // - if ( this->target.pointmode == Acam::POINTMODE_ACAM ) { - this->async.enqueue_and_log( function, "NOTICE: target list processing has stopped" ); + if (error != NO_ERROR) { + this->thread_error_manager.set(THR_SEQUENCE_START); break; } - // If not a calibration target then acquire, first acam then slicecam - // - if ( !this->target.iscal ) { - - // start ACAM acquisition. If it fails then wait for user to continue or cancel. - if ( this->do_acam_acquire() != NO_ERROR ) { - this->async.enqueue_and_log( function, "WARNING acam acquisition failed" ); - if (this->wait_for_user()==ABORT) { - this->async.enqueue_and_log( function, "NOTICE: cancelled" ); - return; - } - } - else - // start SLICECAM fine acquisition - if ( this->do_slicecam_fineacquire() != NO_ERROR ) { - this->async.enqueue_and_log( function, "WARNING slicecam fine acquisition failed" ); - } - } - - if ( !this->target.iscal ) { - // send offsets. wait for user if that fails to continue or cancel. - if ( this->target_offset() == ERROR ) { - if (this->wait_for_user()==ABORT) { - this->async.enqueue_and_log( function, "NOTICE: cancelled" ); - return; - } - } - // ensure slit offset is in "expose" position when needed - try { - error |= this->slit_set(Sequencer::VSM_EXPOSE); - } - catch (const std::exception& e) { - logwrite( function, "ERROR slit offset exception: "+std::string(e.what()) ); - return; - } - } - - logwrite( function, "starting exposure" ); ///< TODO @todo log to telemetry! - - // Start the exposure in a thread... - // set the EXPOSE bit here, outside of the trigger_exposure function, because that - // function only triggers the exposure -- it doesn't block waiting for the exposure. - // - this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); // set EXPOSE bit - auto start_exposure = std::async(std::launch::async, &Sequence::trigger_exposure, this); - try { - error |= start_exposure.get(); - } - catch (const std::exception& e) { - logwrite( function, "ERROR repeat_exposure exception: "+std::string(e.what()) ); - return; - } - - // wait for the exposure to end (naturally or cancelled) - // - logwrite( function, "waiting for exposure" ); - while ( !this->cancel_flag.load() && wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) { - std::unique_lock lock(cv_mutex); - this->cv.wait( lock, [this]() { return( !wait_state_manager.is_set(SEQ_WAIT_EXPOSE) || this->cancel_flag.load() ); } ); - } - - // When an exposure is aborted then it will be marked as UNASSIGNED - // - if ( this->cancel_flag.load() ) { - this->async.enqueue_and_log( function, "NOTICE: exposure cancelled" ); - 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 - << " as " << Sequencer::TARGET_UNASSIGNED; - logwrite( function, message.str() ); - return; - } - - this->async.enqueue_and_log( function, "NOTICE: done waiting for expose" ); - message.str(""); message << "exposure complete for target " << this->target.name - << " id " << this->target.obsid << " order " << this->target.obsorder; - logwrite( function, message.str() ); - - // If not using frame transfer then wait for readout, too - // - if (!this->is_science_frame_transfer) { - logwrite( function, "waiting for readout" ); - while ( !this->cancel_flag.load() && wait_state_manager.is_set( Sequencer::SEQ_WAIT_READOUT ) ) { - std::unique_lock lock(cv_mutex); - this->cv.wait( lock, [this]() { return( !wait_state_manager.is_set(SEQ_WAIT_READOUT) || this->cancel_flag.load() ); } ); - } - } - - // Now that we're done waiting, check for errors or abort - // - if ( this->thread_error_manager.are_any_set() ) { - message.str(""); message << "ERROR stopping sequencer because the following thread(s) had an error: " - << this->thread_error_manager.get_set_states(); - logwrite( function, message.str() ); + if (this->cancel_flag.load()) { + this->async.enqueue_and_log(function, "NOTICE: sequence cancelled"); break; } @@ -747,14 +831,24 @@ namespace Sequencer { // Update this target's state in the database // - error = this->target.update_state( Sequencer::TARGET_COMPLETE ); // update the active target table + if (error==NO_ERROR) error = this->target.update_state( Sequencer::TARGET_COMPLETE ); + else + if (error==ABORT) error = this->target.update_state( Sequencer::TARGET_UNASSIGNED ); + if (error==NO_ERROR) error = this->target.insert_completed(); // insert into the completed table if (error!=NO_ERROR) this->thread_error_manager.set( THR_SEQUENCE_START ); // report any error // let the world know of the state change // - message.str(""); message << "TARGETSTATE:" << this->target.state << " TARGET:" << this->target.name << " OBSID:" << this->target.obsid; - this->async.enqueue( message.str() ); + std::ostringstream oss; + oss << "TARGETSTATE:" << this->target.state + << " TARGET:" << this->target.name + << " OBSID:" << this->target.obsid; + this->async.enqueue_and_log(function, oss.str()); + + // abort sequence on error + // + if ( this->thread_error_manager.are_any_set() ) break; // Check the "dotype" -- // If this was "do one" then do_once is set and get out now. @@ -767,9 +861,11 @@ namespace Sequencer { } // end while true if ( this->thread_error_manager.are_any_set() ) { - logwrite( function, "requesting stop because an error was detected" ); - if ( this->target.get_next( Sequencer::TARGET_ACTIVE, targetstatus ) == TargetInfo::TARGET_FOUND ) { // If this target was flagged as active, - this->target.update_state( Sequencer::TARGET_UNASSIGNED ); // then change it to unassigned on error. + logwrite(function, "ERROR stopping sequencer due to error in: "+ + this->thread_error_manager.get_set_states()); + // If this target was flagged as active, then change it to unassigned on error. + if ( this->target.get_next( Sequencer::TARGET_ACTIVE, targetstatus ) == TargetInfo::TARGET_FOUND ) { + this->target.update_state( Sequencer::TARGET_UNASSIGNED ); } this->thread_error_manager.clear_all(); // clear the thread error state this->do_once.store(true); diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index ab86cc45..de3975b2 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -291,6 +291,12 @@ namespace Sequencer { std::atomic is_fineacquire_locked{false}; ///< is slicecam fine acquisition locked? std::atomic is_acam_guiding{false}; ///< is acam guiding? + struct Operation { + std::string name; + ThreadStatusBits thr; + std::function func; + }; + /** @brief safely runs function in a detached thread using lambda to catch exceptions */ void safe_thread(long (Sequence::*method)(), const std::string &function) { @@ -456,6 +462,11 @@ namespace Sequencer { float slitoffsetacquire; ///< "virtual slit mode" offset for acquire float slitwidthacquire; ///< "virtual slit mode" width for acquire + // new stuff + long run(const Operation &op, const std::string &function); + long run_parallel(const std::vector &ops, const std::string &function); + long run_default_sequence(const std::string &caller); + // publish/subscribe functions // long init_pubsub(const std::initializer_list &topics={}) { @@ -560,7 +571,9 @@ namespace Sequencer { void modify_exptime( double exptime_in ); ///< modify exptime while exposure running void dothread_test(); - long wait_for_user(); ///< wait for the user or cancel + long wait_for_user(const std::string &function); ///< wait for the user or cancel + long wait_for_exposure(const std::string &function); ///< wait for exposure completion or cancel + long wait_for_readout(const std::string &function); ///< wait for readout completion or cancel void sequence_start(std::string obsid_in); ///< main sequence start thread. optional obsid_in for single target obs long calib_set(); ///< sets calib according to target entry params long camera_set(); ///< sets camera according to target entry params From 2eeb0163abe40c386d6b2a0242bb5f175230a570 Mon Sep 17 00:00:00 2001 From: David Hale Date: Tue, 31 Mar 2026 09:02:33 -0700 Subject: [PATCH 02/35] adds a sequencial operations wrapper --- sequencerd/sequence.cpp | 222 ++++++++++++++++++++++++++-------------- sequencerd/sequence.h | 1 + 2 files changed, 144 insertions(+), 79 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index fc3de240..41dd9d52 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -20,49 +20,88 @@ namespace Sequencer { /***** Sequencer::Sequence::run ********************************************/ /** - * @brief - * @param[in] op Operation - * @param[in] function function name of operation for logging + * @brief executes a single operation + * @param[in] op Operation + * @param[in] caller name of calling function for logging + * @return ERROR|NO_ERROR|ABORT * */ long Sequence::run( const Operation &op, - const std::string &function ) { + const std::string &caller ) { long error=NO_ERROR; try { error = op.func(); if (error != NO_ERROR) { - this->async.enqueue_and_log(function, "ERROR in "+op.name); + this->async.enqueue_and_log(caller, "ERROR in "+op.name); } } catch (const std::exception &e) { - logwrite(function, "ERROR in "+op.name+": "+e.what()); + logwrite(caller, "ERROR in "+op.name+": "+e.what()); } return error; } /***** Sequencer::Sequence::run ********************************************/ + /***** Sequencer::Sequence::run_sequence ***********************************/ + /** + * @brief executes operations in sequence, one at a time + * @param[in] op vector of Operations to execute + * @param[in] caller name of calling function for logging + * @return ERROR|NO_ERROR|ABORT + * + */ + long Sequence::run_sequence( const std::vector &ops, + const std::string &caller ) { + + for (const auto &op : ops) { + + if (this->cancel_flag.load()) return ABORT; + + logwrite(caller, "starting "+op.name); + + try { + long error; + if ( (error = op.func()) != NO_ERROR ) { + std::ostringstream oss; + oss << (error==ABORT ? "cancelled" : "ERROR") << " in " << op.name; + logwrite(caller, oss.str()); + return error; + } + } + catch (const std::exception &e) { + logwrite(caller, "ERROR in "+op.name+": "+std::string(e.what())); + return ERROR; + } + } + return NO_ERROR; + } + /***** Sequencer::Sequence::run_sequence ***********************************/ + + /***** Sequencer::Sequence::run_parallel ***********************************/ /** - * @brief - * @param[in] op Operation - * @param[in] function function name of operation for logging + * @brief executes operations in parallel threads + * @details This will return only when all have completed. + * @param[in] op vector of Operations to execute + * @param[in] caller name of calling function for logging + * @return ERROR|NO_ERROR|ABORT * */ long Sequence::run_parallel( const std::vector &ops, - const std::string &function ) { + const std::string &caller ) { // start a thread for each operation // std::vector> futures; for (const auto &op : ops) { - futures.emplace_back(std::async(std::launch::async, [this, &op, function]() { + futures.emplace_back(std::async(std::launch::async, [this, &op, caller]() { try { return op.func(); } catch (const std::exception &e) { - logwrite(function, "ERROR in "+op.name+": "+e.what()); + logwrite(caller, "ERROR in "+op.name+": "+e.what()); return ERROR; } })); @@ -75,10 +114,10 @@ namespace Sequencer { for (size_t i=0; i ops; + std::vector par_ops; + // If pointmode is ACAM then the user has chosen to put the star on ACAM, in + // which case the assumption is made that nothing else matters. This special + // mode of operation only points the telescope. + // if (this->target.pointmode == Acam::POINTMODE_ACAM) { this->dotype("ONE"); - ops = { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } }; + par_ops = { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } }; } else { this->target.pointmode = Acam::POINTMODE_SLIT; + // ---------- RUN THESE IN PARALLEL ------------------ + // these are the default operations prior to exposure, // they can be done in parallel - ops = { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, - { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, - { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, - { "flexure_set", THR_FLEXURE_SET, [this]{ return flexure_set(); } }, - { "calib_set", THR_CALIB_SET, [this]{ return calib_set(); } }, - { "slit_set", THR_SLIT_SET, - [this]{ return slit_set(this->target.iscal ? VSM_DATABASE : VSM_ACQUIRE); } } }; + par_ops = { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, + { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, + { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, + { "flexure_set", THR_FLEXURE_SET, [this]{ return flexure_set(); } }, + { "calib_set", THR_CALIB_SET, [this]{ return calib_set(); } }, + { "slit_set", THR_SLIT_SET, + [this]{ return slit_set(this->target.iscal ? VSM_DATABASE : VSM_ACQUIRE); } } }; } - // wait for threads - error = run_parallel(ops, caller); + // execute in parallel threads and wait for completion + error = run_parallel(par_ops, caller); // early exit on error if (error != NO_ERROR) return error; @@ -131,80 +182,89 @@ namespace Sequencer { return NO_ERROR; } + // ---------- RUN THESE IN SERIES ---------------------- + + std::vector seq_ops; + // if not a calibration target then acquire, first acam then slicecam if (!this->target.iscal) { // ---------- TARGET ACQUISITION --------------------- - // start ACAM acquisition. If it fails then wait for user to continue or cancel. - if ( this->do_acam_acquire() != NO_ERROR ) { + seq_ops.push_back( { "acam_acquire", THR_ACQUISITION, + [this,caller]() { - this->async.enqueue_and_log( caller, "WARNING acam acquisition failed" ); + // start ACAM acquisition. + if ( this->do_acam_acquire() != NO_ERROR ) { - if (this->wait_for_user(caller)==ABORT) { - this->async.enqueue_and_log( caller, "NOTICE: cancelled" ); - return ABORT; - } - } - else - // start SLICECAM fine acquisition - if ( this->do_slicecam_fineacquire() != NO_ERROR ) { + this->async.enqueue_and_log( caller, "WARNING acam acquisition failed" ); - this->async.enqueue_and_log( caller, "WARNING slicecam fine acquisition failed" ); - } + // If acquisition fails, wait for user to continue or cancel. + if (this->wait_for_user(caller)==ABORT) { + this->async.enqueue_and_log( caller, "NOTICE: cancelled" ); + return ABORT; + } + } + else + // ACAM acquire success, start SLICECAM fine acquisition + if ( this->do_slicecam_fineacquire() != NO_ERROR ) { + + // slicecam fine acquire failure is not fatal + this->async.enqueue_and_log( caller, "WARNING slicecam fine acquisition failed" ); + } + } } ); // ---------- TARGET OFFSETS ------------------------- - // send offsets. wait for user if that fails or cancelled - if (this->target_offset() == ERROR) { + seq_ops.push_back( { "target_offset", THR_MOVE_TO_TARGET, + [this,caller]() { - if (this->wait_for_user(caller)==ABORT) { - this->async.enqueue_and_log(caller, "NOTICE: cancelled"); - return ABORT; - } - } + // send offsets. wait for user if that fails or cancelled + if (this->target_offset() == ERROR) { + + if (this->wait_for_user(caller)==ABORT) { + this->async.enqueue_and_log(caller, "NOTICE: cancelled"); + return ABORT; + } + } + } } ); // ---------- SLIT POSITON FOR EXPOSE ---------------- - // ensure slit offset is in "expose" position when needed - try { - error |= this->slit_set(Sequencer::VSM_EXPOSE); - } - catch (const std::exception &e) { - logwrite(caller, "ERROR slit offset exception: "+std::string(e.what())); - return ERROR; - } + seq_ops.push_back( { "slit_expose", THR_SLIT_SET, + [this]() { + // This was moved to VSM_ACQUIRE initially, then VSM_EXPOSE after acquisition. + return this->slit_set(Sequencer::VSM_EXPOSE); + } } ); } + // end if iscal // ---------- EXPOSURE --------------------------------- - logwrite(caller, "starting exposure"); + seq_ops.push_back( { "trigger_exposure", THR_TRIGGER_EXPOSURE, + [this, caller]() { - // Start the exposure in a thread... - // set the EXPOSE bit here, outside of the trigger_exposure function, because that - // function only triggers the exposure -- it doesn't block waiting for the exposure. - // - this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); // set EXPOSE bit - auto start_exposure = std::async(std::launch::async, &Sequence::trigger_exposure, this); - try { - error = start_exposure.get(); - } - catch (const std::exception& e) { - logwrite( caller, "ERROR start_exposure exception: "+std::string(e.what()) ); - return ERROR; - } + logwrite(caller, "starting exposure"); - // wait for the exposure to end (naturally or cancelled) - // - logwrite( caller, "waiting for exposure" ); - if (error==NO_ERROR) error = this->wait_for_exposure(caller); + // Start the exposure in a thread... + // set the EXPOSE bit here, outside of the trigger_exposure function, because that + // function only triggers the exposure -- it doesn't block waiting for the exposure. + // + this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); // set EXPOSE bit + auto start_exposure = std::async(std::launch::async, &Sequence::trigger_exposure, this); + long ret = start_exposure.get(); - // If not using frame transfer then wait for readout, too - // - if (error==NO_ERROR && !this->is_science_frame_transfer) { - logwrite( caller, "waiting for readout" ); - error = this->wait_for_readout(caller); - } + if (ret==NO_ERROR) ret = this->wait_for_exposure(caller); + + if (ret==NO_ERROR && !this->is_science_frame_transfer) { + ret = this->wait_for_readout(caller); + } + return ret; + } } ); + + // ---------- RUN THE SEQUENCE NOW --------------------- + + error = run_sequence(seq_ops, caller); return error; } @@ -645,6 +705,7 @@ namespace Sequencer { * */ long Sequence::wait_for_exposure(const std::string &caller) { + logwrite(caller, "waiting for exposure"); while (!this->cancel_flag.load() && wait_state_manager.is_set(Sequencer::SEQ_WAIT_EXPOSE)) { std::unique_lock lock(cv_mutex); @@ -670,6 +731,7 @@ namespace Sequencer { * */ long Sequence::wait_for_readout(const std::string &caller) { + logwrite(caller, "waiting for readout"); while (!this->cancel_flag.load() && wait_state_manager.is_set(Sequencer::SEQ_WAIT_READOUT)) { std::unique_lock lock(cv_mutex); @@ -703,7 +765,7 @@ namespace Sequencer { */ void Sequence::sequence_start(std::string obsid_in="") { const std::string function("Sequencer::Sequence::sequence_start"); - std::stringstream message; + std::ostringstream message; std::string reply; std::string targetstatus; TargetInfo::TargetState targetstate; @@ -793,7 +855,9 @@ namespace Sequencer { // let the world know of the state change // - message.str(""); message << "TARGETSTATE:" << this->target.state << " TARGET:" << this->target.name << " OBSID:" << this->target.obsid; + message.str(""); message << "TARGETSTATE:" << this->target.state + << " TARGET:" << this->target.name + << " OBSID:" << this->target.obsid; this->async.enqueue( message.str() ); #ifdef LOGLEVEL_DEBUG logwrite( function, "[DEBUG] target found, starting threads" ); diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index de3975b2..8c954279 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -464,6 +464,7 @@ namespace Sequencer { // new stuff long run(const Operation &op, const std::string &function); + long run_sequence(const std::vector &ops, const std::string &function); long run_parallel(const std::vector &ops, const std::string &function); long run_default_sequence(const std::string &caller); From 3f699b25e83f569ecd4a97488e491b25c05217a2 Mon Sep 17 00:00:00 2001 From: David Hale Date: Tue, 31 Mar 2026 11:47:57 -0700 Subject: [PATCH 03/35] adds one more level of abstraction, Operation Blocks, to aid in scripting --- sequencerd/sequence.cpp | 230 +++++++++++++++++++++++----------------- sequencerd/sequence.h | 22 +++- 2 files changed, 149 insertions(+), 103 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 41dd9d52..9110b1d2 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -29,6 +29,8 @@ namespace Sequencer { long Sequence::run( const Operation &op, const std::string &caller ) { long error=NO_ERROR; + logwrite(caller, "starting "+op.name); + try { error = op.func(); @@ -38,6 +40,7 @@ namespace Sequencer { } catch (const std::exception &e) { logwrite(caller, "ERROR in "+op.name+": "+e.what()); + error = ERROR; } return error; } @@ -92,40 +95,83 @@ namespace Sequencer { long Sequence::run_parallel( const std::vector &ops, const std::string &caller ) { + std::vector> futures; + // start a thread for each operation // - std::vector> futures; for (const auto &op : ops) { - futures.emplace_back(std::async(std::launch::async, [this, &op, caller]() { - try { - return op.func(); - } - catch (const std::exception &e) { - logwrite(caller, "ERROR in "+op.name+": "+e.what()); - return ERROR; - } - })); + futures.emplace_back(std::async(std::launch::async, op.func)); } - long error=NO_ERROR; + long error = NO_ERROR; - // wait for each thread to complete + // wait for all threads, collect errors // - for (size_t i=0; i &blocks, + const std::string &caller, + bool continue_on_error ) { + long error = NO_ERROR; + + for (const auto &block : blocks) { + if (this->cancel_flag.load()) return ABORT; + + // PARALLEL Blocks are executed in parallel threads + // + if (block.type == OperationType::PARALLEL) { + long ret = run_parallel(block.operations, caller); + error |= ret; + + if (ret != NO_ERROR && !continue_on_error) return error; + } + // SERIAL Blocks are executed one at a time + // + else { + for (const auto &op : block.operations) { + if (this->cancel_flag.load()) return ABORT; + + long ret = run(op, caller); + error |= ret; + + if (ret != NO_ERROR && !continue_on_error) return error; + } + } + } + + return error; + } + /***** Sequencer::Sequence::run_operation_blocks ***************************/ + + /***** Sequencer::Sequence::run_default_sequence ***************************/ /** * @brief executes a default observation sequence @@ -135,9 +181,9 @@ namespace Sequencer { */ long Sequence::run_default_sequence(const std::string &caller) { - long error = NO_ERROR; + std::vector blocks; - std::vector par_ops; + // ---------- RUN THESE IN PARALLEL -------------------- // If pointmode is ACAM then the user has chosen to put the star on ACAM, in // which case the assumption is made that nothing else matters. This special @@ -145,128 +191,113 @@ namespace Sequencer { // if (this->target.pointmode == Acam::POINTMODE_ACAM) { this->dotype("ONE"); - par_ops = { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } }; + blocks.push_back( { OperationType::PARALLEL, + { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); } else { this->target.pointmode = Acam::POINTMODE_SLIT; - // ---------- RUN THESE IN PARALLEL ------------------ - // these are the default operations prior to exposure, // they can be done in parallel - par_ops = { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, - { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, - { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, - { "flexure_set", THR_FLEXURE_SET, [this]{ return flexure_set(); } }, - { "calib_set", THR_CALIB_SET, [this]{ return calib_set(); } }, - { "slit_set", THR_SLIT_SET, - [this]{ return slit_set(this->target.iscal ? VSM_DATABASE : VSM_ACQUIRE); } } }; + blocks.push_back( { OperationType::PARALLEL, + { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, + { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, + { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, + { "flexure_set", THR_FLEXURE_SET, [this]{ return flexure_set(); } }, + { "calib_set", THR_CALIB_SET, [this]{ return calib_set(); } }, + { "slit_set", THR_SLIT_SET, + [this]{ return slit_set(this->target.iscal ? VSM_DATABASE : VSM_ACQUIRE); } } } } ); } - // execute in parallel threads and wait for completion - error = run_parallel(par_ops, caller); - - // early exit on error - if (error != NO_ERROR) return error; - - if (this->cancel_flag.load()) return ABORT; - - // ---------- POINTMODE-ACAM EXIT ---------------------- - - // If pointmode is ACAM then the user has chosen to put the star on ACAM, in - // which case the assumption is made that nothing else matters. This special - // mode of operation only points the telescope. + // Early Exit for pointmode=ACAM // - if ( this->target.pointmode == Acam::POINTMODE_ACAM ) { - this->async.enqueue_and_log(caller, "NOTICE: target list processing has stopped"); - return NO_ERROR; + if (this->target.pointmode == Acam::POINTMODE_ACAM) { + return run_operation_blocks(blocks, caller); } // ---------- RUN THESE IN SERIES ---------------------- - std::vector seq_ops; - // if not a calibration target then acquire, first acam then slicecam if (!this->target.iscal) { // ---------- TARGET ACQUISITION --------------------- - seq_ops.push_back( { "acam_acquire", THR_ACQUISITION, - [this,caller]() { - - // start ACAM acquisition. - if ( this->do_acam_acquire() != NO_ERROR ) { + blocks.push_back( { OperationType::SERIAL, + { { "acam_acquire", THR_ACQUISITION, [this,caller]() { - this->async.enqueue_and_log( caller, "WARNING acam acquisition failed" ); + // if ACAM acquisition fails, wait for user to continue or cancel + if ( this->do_acam_acquire() != NO_ERROR ) { + return this->wait_for_user(caller); + } + else return NO_ERROR; + } }, - // If acquisition fails, wait for user to continue or cancel. - if (this->wait_for_user(caller)==ABORT) { - this->async.enqueue_and_log( caller, "NOTICE: cancelled" ); - return ABORT; + { "slicecam_fineacquire", THR_ACQUISITION, [this,caller]() { + if ( this->do_slicecam_fineacquire() != NO_ERROR ) { + this->async.enqueue_and_log(caller, "WARNING slicecam fine acquisition failed"); } + return NO_ERROR; // slicecam fine acquire is never fatal + } } } - else - // ACAM acquire success, start SLICECAM fine acquisition - if ( this->do_slicecam_fineacquire() != NO_ERROR ) { + } ); + } - // slicecam fine acquire failure is not fatal - this->async.enqueue_and_log( caller, "WARNING slicecam fine acquisition failed" ); - } - } } ); + if (!this->target.iscal) { // ---------- TARGET OFFSETS ------------------------- - seq_ops.push_back( { "target_offset", THR_MOVE_TO_TARGET, - [this,caller]() { + blocks.push_back( { OperationType::SERIAL, + { { "target_offset", THR_MOVE_TO_TARGET, [this,caller]() { - // send offsets. wait for user if that fails or cancelled - if (this->target_offset() == ERROR) { - - if (this->wait_for_user(caller)==ABORT) { - this->async.enqueue_and_log(caller, "NOTICE: cancelled"); - return ABORT; + // if offsets fail, wait for user to continue or cancel + if (this->target_offset() != NO_ERROR) { + return this->wait_for_user(caller); } + else return NO_ERROR; + } } } - } } ); + } ); + } + + if (!this->target.iscal) { // ---------- SLIT POSITON FOR EXPOSE ---------------- - seq_ops.push_back( { "slit_expose", THR_SLIT_SET, - [this]() { - // This was moved to VSM_ACQUIRE initially, then VSM_EXPOSE after acquisition. - return this->slit_set(Sequencer::VSM_EXPOSE); - } } ); + blocks.push_back( { OperationType::SERIAL, + { { "slit_expose", THR_SLIT_SET, [this]() { + return this->slit_set(Sequencer::VSM_EXPOSE); } } + } + } ); } - // end if iscal // ---------- EXPOSURE --------------------------------- - seq_ops.push_back( { "trigger_exposure", THR_TRIGGER_EXPOSURE, - [this, caller]() { - - logwrite(caller, "starting exposure"); + blocks.push_back( { OperationType::SERIAL, + { { "trigger_exposure", THR_EXPOSURE, [this]() { - // Start the exposure in a thread... - // set the EXPOSE bit here, outside of the trigger_exposure function, because that - // function only triggers the exposure -- it doesn't block waiting for the exposure. - // - this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); // set EXPOSE bit - auto start_exposure = std::async(std::launch::async, &Sequence::trigger_exposure, this); - long ret = start_exposure.get(); + // set the EXPOSE bit here, outside of the trigger_exposure function, because that + // function only triggers the exposure -- it doesn't block waiting for the exposure. + // + this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); // set EXPOSE bit + return trigger_exposure(); + } }, - if (ret==NO_ERROR) ret = this->wait_for_exposure(caller); + { "wait_exposure", THR_EXPOSURE, [this,caller]() { + return this->wait_for_exposure(caller); + } }, - if (ret==NO_ERROR && !this->is_science_frame_transfer) { - ret = this->wait_for_readout(caller); + { "wait_readout", THR_EXPOSURE, [this,caller]() { + if (!this->is_science_frame_transfer) { + return this->wait_for_readout(caller); + } + else return NO_ERROR; + } } } - return ret; - } } ); + } ); // ---------- RUN THE SEQUENCE NOW --------------------- - error = run_sequence(seq_ops, caller); - - return error; + return run_operation_blocks(blocks, caller); } /***** Sequencer::Sequence::run_default_sequence ***************************/ @@ -4843,7 +4874,6 @@ namespace Sequencer { return( ERROR ); } - bool ispower = false; std::string reply; // power module must be initialized before any others. If this is not diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 8c954279..c95b8dd3 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -188,6 +188,7 @@ namespace Sequencer { enum ThreadStatusBits : size_t { THR_SEQUENCER_ASYNC_LISTENER=0, THR_TRIGGER_EXPOSURE, + THR_EXPOSURE, THR_REPEAT_EXPOSURE, THR_STOP_EXPOSURE, THR_ABORT_PROCESS, @@ -232,6 +233,7 @@ namespace Sequencer { const std::map thread_names = { {THR_SEQUENCER_ASYNC_LISTENER, "async_listener"}, {THR_TRIGGER_EXPOSURE, "trigger_exposure"}, + {THR_EXPOSURE, "exposure"}, {THR_REPEAT_EXPOSURE, "repeat_exposure"}, {THR_STOP_EXPOSURE, "stop_exposure"}, {THR_ABORT_PROCESS, "abort_process"}, @@ -291,10 +293,21 @@ namespace Sequencer { std::atomic is_fineacquire_locked{false}; ///< is slicecam fine acquisition locked? std::atomic is_acam_guiding{false}; ///< is acam guiding? + enum class OperationType { + PARALLEL, + SERIAL + }; + struct Operation { - std::string name; - ThreadStatusBits thr; - std::function func; + std::string name; ///< name of this operation + ThreadStatusBits thr; ///< status bit of what is running + std::function func; ///< function that this operation calls + std::map params; ///< function parameters + }; + + struct OperationBlock { + OperationType type; + std::vector operations; }; /** @brief safely runs function in a detached thread using lambda to catch exceptions @@ -467,6 +480,9 @@ namespace Sequencer { long run_sequence(const std::vector &ops, const std::string &function); long run_parallel(const std::vector &ops, const std::string &function); long run_default_sequence(const std::string &caller); + long run_operation_blocks( const std::vector &blocks, + const std::string &caller, + bool continue_on_error=false ); // publish/subscribe functions // From 80e5a18cde70e5b9b072e8688a3bb01791e98517 Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 3 Apr 2026 16:51:38 -0700 Subject: [PATCH 04/35] encapsulates more logic into blocks to aid scripting changes std::string to string_view where string not needed --- common/common.cpp | 10 +- common/common.h | 10 +- sequencerd/sequence.cpp | 282 +++++++++++++++------------- sequencerd/sequence.h | 25 ++- sequencerd/sequence_acquisition.cpp | 50 ++++- sequencerd/sequencer_server.cpp | 2 +- utils/logentry.cpp | 7 +- utils/logentry.h | 2 +- utils/network.cpp | 8 +- utils/network.h | 2 +- 10 files changed, 231 insertions(+), 167 deletions(-) diff --git a/common/common.cpp b/common/common.cpp index 3fc176eb..196d1fb0 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -59,7 +59,7 @@ namespace Common { * @param[in] message string to write * */ - void Queue::enqueue_and_log(std::string function, std::string message) { + void Queue::enqueue_and_log(std::string_view function, std::string_view message) { std::lock_guard lock(queue_mutex); message_queue.push(message); notifier.notify_one(); @@ -77,9 +77,9 @@ namespace Common { * @param[in] message string to write * */ - void Queue::enqueue_and_log( std::string tag, std::string function, std::string message ) { + void Queue::enqueue_and_log( std::string_view tag, std::string_view function, std::string_view message ) { std::lock_guard lock(queue_mutex); - std::stringstream qmessage; + std::ostringstream qmessage; qmessage << tag << ":" << message; message_queue.push(qmessage.str()); notifier.notify_one(); @@ -98,12 +98,12 @@ namespace Common { * If the queue is empty, wait untill an element is avaiable. * */ - std::string Queue::dequeue(void) { + std::string_view Queue::dequeue(void) { std::unique_lock lock(queue_mutex); while(message_queue.empty()) { notifier.wait(lock); // release lock as long as the wait and reaquire it afterwards. } - std::string message = message_queue.front(); + std::string_view message = message_queue.front(); message_queue.pop(); return message; } diff --git a/common/common.h b/common/common.h index d540dbd3..49d4db66 100644 --- a/common/common.h +++ b/common/common.h @@ -1112,7 +1112,7 @@ namespace Common { */ class Queue { private: - std::queue message_queue; + std::queue message_queue; mutable std::mutex queue_mutex; std::condition_variable notifier; bool is_running; @@ -1123,10 +1123,10 @@ namespace Common { void service_running(bool state) { this->is_running = state; }; ///< set service running bool service_running() { return this->is_running; }; ///< is the service running? - void enqueue_and_log(std::string function, std::string message); - void enqueue_and_log(std::string tag, std::string function, std::string message); - void enqueue(std::string message); ///< push an element into the queue. - std::string dequeue(void); ///< pop an element from the queue + void enqueue_and_log(std::string_view function, std::string_view message); + void enqueue_and_log(std::string_view tag, std::string_view function, std::string_view message); + void enqueue(std::string message_view); ///< push an element into the queue. + std::string_view dequeue(void); ///< pop an element from the queue }; /**************** Common::Queue *********************************************/ diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 9110b1d2..3c2a7641 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -27,7 +27,7 @@ namespace Sequencer { * */ long Sequence::run( const Operation &op, - const std::string &caller ) { + std::string_view caller ) { long error=NO_ERROR; logwrite(caller, "starting "+op.name); @@ -56,7 +56,7 @@ namespace Sequencer { * */ long Sequence::run_sequence( const std::vector &ops, - const std::string &caller ) { + std::string_view caller ) { for (const auto &op : ops) { @@ -93,7 +93,7 @@ namespace Sequencer { * */ long Sequence::run_parallel( const std::vector &ops, - const std::string &caller ) { + std::string_view caller ) { std::vector> futures; @@ -138,7 +138,7 @@ namespace Sequencer { * */ long Sequence::run_operation_blocks( const std::vector &blocks, - const std::string &caller, + std::string_view caller, bool continue_on_error ) { long error = NO_ERROR; @@ -179,7 +179,7 @@ namespace Sequencer { * @return ERROR|NO_ERROR|ABORT * */ - long Sequence::run_default_sequence(const std::string &caller) { + long Sequence::run_default_sequence(std::string_view caller) { std::vector blocks; @@ -200,13 +200,14 @@ namespace Sequencer { // these are the default operations prior to exposure, // they can be done in parallel blocks.push_back( { OperationType::PARALLEL, - { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, - { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, - { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, - { "flexure_set", THR_FLEXURE_SET, [this]{ return flexure_set(); } }, - { "calib_set", THR_CALIB_SET, [this]{ return calib_set(); } }, - { "slit_set", THR_SLIT_SET, - [this]{ return slit_set(this->target.iscal ? VSM_DATABASE : VSM_ACQUIRE); } } } } ); + { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, + { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, + { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, + { "flexure_set", THR_FLEXURE_SET, [this]{ return flexure_set(); } }, + { "calib_set", THR_CALIB_SET, [this]{ return calib_set(); } }, + { "slit_set", THR_SLIT_SET, [this]{ return slit_set(this->target.iscal ? VSM_DATABASE + : VSM_ACQUIRE); } } + } } ); } // Early Exit for pointmode=ACAM @@ -217,58 +218,17 @@ namespace Sequencer { // ---------- RUN THESE IN SERIES ---------------------- - // if not a calibration target then acquire, first acam then slicecam - if (!this->target.iscal) { - - // ---------- TARGET ACQUISITION --------------------- - - blocks.push_back( { OperationType::SERIAL, - { { "acam_acquire", THR_ACQUISITION, [this,caller]() { - - // if ACAM acquisition fails, wait for user to continue or cancel - if ( this->do_acam_acquire() != NO_ERROR ) { - return this->wait_for_user(caller); - } - else return NO_ERROR; - } }, - - { "slicecam_fineacquire", THR_ACQUISITION, [this,caller]() { - if ( this->do_slicecam_fineacquire() != NO_ERROR ) { - this->async.enqueue_and_log(caller, "WARNING slicecam fine acquisition failed"); - } - return NO_ERROR; // slicecam fine acquire is never fatal - } } - } - } ); - } - - if (!this->target.iscal) { - - // ---------- TARGET OFFSETS ------------------------- - - blocks.push_back( { OperationType::SERIAL, - { { "target_offset", THR_MOVE_TO_TARGET, [this,caller]() { - - // if offsets fail, wait for user to continue or cancel - if (this->target_offset() != NO_ERROR) { - return this->wait_for_user(caller); - } - else return NO_ERROR; - } } - } - } ); - } - - if (!this->target.iscal) { + blocks.push_back( { OperationType::SERIAL, + { { "target_acquisition", THR_ACQUISITION, + [this,caller]() { return this->do_target_acquisition(caller); } }, - // ---------- SLIT POSITON FOR EXPOSE ---------------- + { "target_offset", THR_MOVE_TO_TARGET, + [this]() { return this->target_offset(); } }, - blocks.push_back( { OperationType::SERIAL, - { { "slit_expose", THR_SLIT_SET, [this]() { - return this->slit_set(Sequencer::VSM_EXPOSE); } } - } - } ); - } + { "slit_expose", THR_SLIT_SET, + [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } } + } + } ); // ---------- EXPOSURE --------------------------------- @@ -589,7 +549,7 @@ namespace Sequencer { * */ void Sequence::dothread_sequencer_async_listener( Sequencer::Sequence &seq, Network::UdpSocket udp ) { - const std::string function("Sequencer::Sequence::dothread_sequencer_async_listener"); + std::string_view function("Sequencer::Sequence::dothread_sequencer_async_listener"); ScopedState thr_state( seq.thread_state_manager, Sequencer::THR_SEQUENCER_ASYNC_LISTENER ); @@ -691,6 +651,40 @@ namespace Sequencer { } + /***** Sequencer::Sequence::wait_for_ontarget *******************************/ + /** + * @brief waits for the TCS Operator to click 'ontarget' + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_ontarget(std::string_view caller) { + // waiting for TCS Operator input (or cancel) + { + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCSOP ); + + this->async.enqueue_and_log(caller, "NOTICE: waiting for TCS operator to send 'ontarget' signal"); + + while ( !this->cancel_flag.load() && + !this->is_ontarget.load() ) { + + std::unique_lock lock(cv_mutex); + this->cv.wait( lock, [this]() { return( this->is_ontarget.load() || + this->cancel_flag.load() ); } ); + } + + this->async.enqueue_and_log(caller, "NOTICE: received " + +(this->cancel_flag.load() ? std::string("cancel") + : std::string("ontarget")) + +" signal!" ); + } + this->is_ontarget.store(false); + + return (this->cancel_flag.load() ? ABORT : NO_ERROR); + } + /***** Sequencer::Sequence::wait_for_ontarget *******************************/ + + /***** Sequencer::Sequence::wait_for_user ***********************************/ /** * @brief waits for the user to click a button, or cancel @@ -700,7 +694,7 @@ namespace Sequencer { * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_user(const std::string &caller) { + long Sequence::wait_for_user(std::string_view caller) { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); @@ -735,7 +729,7 @@ namespace Sequencer { * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_exposure(const std::string &caller) { + long Sequence::wait_for_exposure(std::string_view caller) { logwrite(caller, "waiting for exposure"); while (!this->cancel_flag.load() && wait_state_manager.is_set(Sequencer::SEQ_WAIT_EXPOSE)) { @@ -761,7 +755,7 @@ namespace Sequencer { * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_readout(const std::string &caller) { + long Sequence::wait_for_readout(std::string_view caller) { logwrite(caller, "waiting for readout"); while (!this->cancel_flag.load() && wait_state_manager.is_set(Sequencer::SEQ_WAIT_READOUT)) { @@ -780,6 +774,36 @@ namespace Sequencer { /***** Sequencer::Sequence::wait_for_readout ********************************/ + /***** Sequencer::Sequence::wait_for_canexpose ******************************/ + /** + * @brief waits for camera to be ready to expose, or cancel + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_canexpose(std::string_view caller) { + logwrite(caller, "waiting for can_expose"); + + while ( !this->cancel_flag.load() && + !this->can_expose.load() ) { + + this->async.enqueue_and_log(caller, "NOTICE: waiting for camera to be ready to expose"); + + std::unique_lock lock(this->camerad_mtx); + this->camerad_cv.wait( lock, [this]() { return( this->can_expose.load() || + this->cancel_flag.load() ); } ); + } + + if (this->cancel_flag.load()) { + this->async.enqueue_and_log(caller, "NOTICE: wait for can_expose cancelled"); + return ABORT; + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::wait_for_canexpose ******************************/ + + /***** Sequencer::Sequence::sequence_start **********************************/ /** * @brief main sequence start thread @@ -795,7 +819,7 @@ namespace Sequencer { * */ void Sequence::sequence_start(std::string obsid_in="") { - const std::string function("Sequencer::Sequence::sequence_start"); + std::string_view function("Sequencer::Sequence::sequence_start"); std::ostringstream message; std::string reply; std::string targetstatus; @@ -905,7 +929,7 @@ namespace Sequencer { break; } - // default observation sequence + // ---------- default observation sequence ----------- // error = run_default_sequence(function); @@ -982,27 +1006,14 @@ namespace Sequencer { * */ long Sequence::camera_set() { - const std::string function("Sequencer::Sequence::camera_set"); + std::string_view function("Sequencer::Sequence::camera_set"); std::string reply; std::stringstream camcmd; long error=NO_ERROR; // wait until camera is ready to expose // - std::unique_lock lock(this->camerad_mtx); - if (!this->can_expose.load()) { - - this->async.enqueue_and_log(function, "NOTICE: waiting for camera to be ready to expose"); - - this->camerad_cv.wait( lock, [this]() { - return( this->can_expose.load() || this->cancel_flag.load() ); - } ); - - if (this->cancel_flag.load()) { - logwrite(function, "sequence cancelled"); - return NO_ERROR; - } - } + this->wait_for_canexpose(function); logwrite( function, "setting camera parameters"); @@ -1083,7 +1094,7 @@ namespace Sequencer { * */ long Sequence::slit_set(VirtualSlitMode mode) { - const std::string function("Sequencer::Sequence::slit_set"); + std::string_view function("Sequencer::Sequence::slit_set"); std::string reply, modestr; std::stringstream slitcmd, message; @@ -1140,7 +1151,7 @@ namespace Sequencer { * */ long Sequence::power_init() { - const std::string function("Sequencer::Sequence::power_init"); + std::string_view function("Sequencer::Sequence::power_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_POWER_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_POWER ); @@ -1167,7 +1178,7 @@ namespace Sequencer { * */ long Sequence::power_shutdown() { - const std::string function("Sequencer::Sequence::power_shutdown"); + std::string_view function("Sequencer::Sequence::power_shutdown"); ScopedState thr_state( this->thread_state_manager, Sequencer::THR_POWER_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_POWER ); @@ -1190,7 +1201,7 @@ namespace Sequencer { * */ long Sequence::slit_init() { - const std::string function("Sequencer::Sequence::slit_init"); + std::string_view function("Sequencer::Sequence::slit_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_SLIT_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_SLIT ); @@ -1256,7 +1267,7 @@ namespace Sequencer { * */ long Sequence::slit_shutdown() { - const std::string function("Sequencer::Sequence::slit_shutdown"); + std::string_view function("Sequencer::Sequence::slit_shutdown"); std::stringstream message; std::string reply; long error=NO_ERROR; @@ -1324,7 +1335,7 @@ namespace Sequencer { * */ long Sequence::slicecam_init() { - const std::string function("Sequencer::Sequence::slicecam_init"); + std::string_view function("Sequencer::Sequence::slicecam_init"); this->daemon_manager.clear( Sequencer::DAEMON_SLICECAM ); // slicecamd not ready @@ -1364,7 +1375,7 @@ namespace Sequencer { * */ long Sequence::acam_init() { - const std::string function("Sequencer::Sequence::acam_init"); + std::string_view function("Sequencer::Sequence::acam_init"); this->daemon_manager.clear( Sequencer::DAEMON_ACAM ); // acamd not ready @@ -1424,7 +1435,7 @@ namespace Sequencer { * */ long Sequence::slicecam_shutdown() { - const std::string function("Sequencer::Sequence::slicecam_shutdown"); + std::string_view function("Sequencer::Sequence::slicecam_shutdown"); std::stringstream message; std::string reply; long error=NO_ERROR; @@ -1487,7 +1498,7 @@ namespace Sequencer { * */ long Sequence::acam_shutdown() { - const std::string function("Sequencer::Sequence::acam_shutdown"); + std::string_view function("Sequencer::Sequence::acam_shutdown"); std::stringstream message; std::string reply; long error=NO_ERROR; @@ -1559,7 +1570,7 @@ namespace Sequencer { * */ long Sequence::calib_init() { - const std::string function("Sequencer::Sequence::calib_init"); + std::string_view function("Sequencer::Sequence::calib_init"); this->daemon_manager.clear( Sequencer::DAEMON_CALIB ); @@ -1633,7 +1644,7 @@ namespace Sequencer { * */ long Sequence::calib_shutdown() { - const std::string function("Sequencer::Sequence::calib_shutdown"); + std::string_view function("Sequencer::Sequence::calib_shutdown"); long error=NO_ERROR; ScopedState thr_state( this->thread_state_manager, Sequencer::THR_CALIB_SHUTDOWN ); @@ -1755,7 +1766,7 @@ namespace Sequencer { * */ long Sequence::tcs_shutdown() { - const std::string function("Sequencer::Sequence::tcs_shutdown"); + std::string_view function("Sequencer::Sequence::tcs_shutdown"); std::stringstream message; ScopedState thr_state( this->thread_state_manager, Sequencer::THR_TCS_SHUTDOWN ); @@ -1797,7 +1808,7 @@ namespace Sequencer { * */ long Sequence::flexure_init() { - const std::string function("Sequencer::Sequence::flexure_init"); + std::string_view function("Sequencer::Sequence::flexure_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FLEXURE_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_FLEXURE ); @@ -1836,7 +1847,7 @@ namespace Sequencer { * */ long Sequence::flexure_shutdown() { - const std::string function("Sequencer::Sequence::flexure_shutdown"); + std::string_view function("Sequencer::Sequence::flexure_shutdown"); std::string reply; long error=NO_ERROR; @@ -1897,7 +1908,7 @@ namespace Sequencer { * */ long Sequence::focus_init() { - const std::string function("Sequencer::Sequence::focus_init"); + std::string_view function("Sequencer::Sequence::focus_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FOCUS_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_FOCUS ); @@ -1965,7 +1976,7 @@ namespace Sequencer { * */ long Sequence::focus_shutdown() { - const std::string function("Sequencer::Sequence::focus_shutdown"); + std::string_view function("Sequencer::Sequence::focus_shutdown"); std::string reply; long error=NO_ERROR; @@ -2026,7 +2037,7 @@ namespace Sequencer { * */ long Sequence::camera_init() { - const std::string function("Sequencer::Sequence::camera_init"); + std::string_view function("Sequencer::Sequence::camera_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_CAMERA_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); @@ -2080,7 +2091,7 @@ namespace Sequencer { * */ long Sequence::camera_shutdown() { - const std::string function("Sequencer::Sequence::camera_shutdown"); + std::string_view function("Sequencer::Sequence::camera_shutdown"); ScopedState thr_state( this->thread_state_manager, Sequencer::THR_CAMERA_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); @@ -2145,7 +2156,7 @@ namespace Sequencer { * */ long Sequence::move_to_target() { - const std::string function("Sequencer::Sequence::move_to_target"); + std::string_view function("Sequencer::Sequence::move_to_target"); std::stringstream message; long error=NO_ERROR; @@ -2299,7 +2310,7 @@ namespace Sequencer { * */ void Sequence::dothread_notify_tcs( Sequencer::Sequence &seq ) { - const std::string function("Sequencer::Sequence::dothread_notify_tcs"); + std::string_view function("Sequencer::Sequence::dothread_notify_tcs"); std::stringstream message; ScopedState thr_state( seq.thread_state_manager, Sequencer::THR_NOTIFY_TCS ); @@ -2371,7 +2382,7 @@ namespace Sequencer { * */ long Sequence::focus_set() { - const std::string function("Sequencer::Sequence::focus_set"); + std::string_view function("Sequencer::Sequence::focus_set"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FOCUS_SET ); @@ -2390,7 +2401,7 @@ namespace Sequencer { * */ long Sequence::flexure_set() { - const std::string function("Sequencer::Sequence::flexure_set"); + std::string_view function("Sequencer::Sequence::flexure_set"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FLEXURE_SET ); @@ -2409,7 +2420,7 @@ namespace Sequencer { * */ long Sequence::calib_set() { - const std::string function("Sequencer::Sequence::calib_set"); + std::string_view function("Sequencer::Sequence::calib_set"); std::stringstream message; ScopedState thr_state( thread_state_manager, Sequencer::THR_CALIBRATOR_SET ); @@ -2494,7 +2505,7 @@ namespace Sequencer { * */ void Sequence::abort_process() { - const std::string function("Sequencer::Sequence::abort_process"); + std::string_view function("Sequencer::Sequence::abort_process"); ScopedState thr_state( this->thread_state_manager, Sequencer::THR_ABORT_PROCESS ); @@ -2529,7 +2540,7 @@ namespace Sequencer { * */ void Sequence::stop_exposure() { - const std::string function("Sequencer::Sequence::stop_exposure"); + std::string_view function("Sequencer::Sequence::stop_exposure"); ScopedState thr_state( this->thread_state_manager, Sequencer::THR_STOP_EXPOSURE ); @@ -2577,7 +2588,7 @@ namespace Sequencer { * */ long Sequence::repeat_exposure() { - const std::string function("Sequencer::Sequence::repeat_exposure"); + std::string_view function("Sequencer::Sequence::repeat_exposure"); std::stringstream message; long error = NO_ERROR; @@ -2667,7 +2678,7 @@ namespace Sequencer { * */ long Sequence::trigger_exposure() { - const std::string function("Sequencer::Sequence::trigger_exposure"); + std::string_view function("Sequencer::Sequence::trigger_exposure"); std::stringstream message; std::string reply; long error=NO_ERROR; @@ -2721,7 +2732,7 @@ namespace Sequencer { * */ void Sequence::modify_exptime( double exptime_in ) { - const std::string function("Sequencer::Sequence::modify_exptime"); + std::string_view function("Sequencer::Sequence::modify_exptime"); std::stringstream message; std::string reply=""; long error = NO_ERROR; @@ -2775,7 +2786,7 @@ namespace Sequencer { * */ long Sequence::startup() { - const std::string function("Sequencer::Sequence::startup"); + std::string_view function("Sequencer::Sequence::startup"); std::stringstream message; long error=NO_ERROR; @@ -2984,7 +2995,7 @@ namespace Sequencer { * */ long Sequence::shutdown() { - const std::string function("Sequencer::Sequence::shutdown"); + std::string_view function("Sequencer::Sequence::shutdown"); long error=ERROR; ScopedState thr_state( this->thread_state_manager, Sequencer::THR_SHUTDOWN ); // this thread is running @@ -3075,8 +3086,8 @@ namespace Sequencer { * @return ERROR or NO_ERROR * */ - long Sequence::parse_state( std::string whoami, std::string reply, bool &state ) { - const std::string function("Sequencer::Sequence::parse_state"); + long Sequence::parse_state( std::string_view whoami, std::string reply, bool &state ) { + std::string_view function("Sequencer::Sequence::parse_state"); std::stringstream message; // Tokenize the reply -- @@ -3133,7 +3144,7 @@ namespace Sequencer { * */ long Sequence::extract_tcs_value( std::string reply, int &value ) { - const std::string function("Sequencer::Sequence::extract_tcs_value"); + std::string_view function("Sequencer::Sequence::extract_tcs_value"); std::stringstream message; std::vector tokens; long error = ERROR; @@ -3221,7 +3232,7 @@ namespace Sequencer { * */ long Sequence::parse_tcs_generic( int value ) { - const std::string function("Sequencer::Sequence::parse_tcs_generic"); + std::string_view function("Sequencer::Sequence::parse_tcs_generic"); std::stringstream message; std::string tcsreply; std::vector tokens; @@ -3270,7 +3281,7 @@ namespace Sequencer { * */ long Sequence::dotype( std::string args ) { - const std::string function("Sequencer::Sequence::dotype"); + std::string_view function("Sequencer::Sequence::dotype"); std::stringstream message; std::string dontcare; return this->dotype( args, dontcare ); @@ -3294,7 +3305,7 @@ namespace Sequencer { * */ long Sequence::dotype( std::string args, std::string &retstring ) { - const std::string function("Sequencer::Sequence::dotype"); + std::string_view function("Sequencer::Sequence::dotype"); std::stringstream message; long error = NO_ERROR; @@ -3341,7 +3352,7 @@ namespace Sequencer { return this->get_dome_position( false, domeazi, telazi ); } long Sequence::get_dome_position( bool poll, double &domeazi, double &telazi ) { - const std::string function("Sequencer::Sequence::get_dome_position"); + std::string_view function("Sequencer::Sequence::get_dome_position"); std::stringstream message; std::string tcsreply; @@ -3403,7 +3414,7 @@ namespace Sequencer { return this->get_tcs_motion( false, state_out ); } long Sequence::get_tcs_motion( bool poll, std::string &state_out ) { - const std::string function("Sequencer::Sequence::get_tcs_motion"); + std::string_view function("Sequencer::Sequence::get_tcs_motion"); std::stringstream message; std::string tcsreply; @@ -3445,7 +3456,7 @@ namespace Sequencer { return this->get_tcs_coords_type( TCSD_WEATHER_COORDS, ra_h, dec_d ); } long Sequence::get_tcs_coords_type( std::string cmd, double &ra_h, double &dec_d ) { - const std::string function("Sequencer::Sequence::get_tcs_coords"); + std::string_view function("Sequencer::Sequence::get_tcs_coords"); std::stringstream message; std::string coordstring; @@ -3498,7 +3509,7 @@ namespace Sequencer { * */ long Sequence::get_tcs_cass( double &cass ) { - const std::string function("Sequencer::Sequencer::get_tcs_cass"); + std::string_view function("Sequencer::Sequencer::get_tcs_cass"); std::stringstream message; std::string tcsreply; @@ -3549,11 +3560,14 @@ namespace Sequencer { * */ long Sequence::target_offset() { - const std::string function("Sequencer::Sequence::target_offset"); + std::string_view function("Sequencer::Sequence::target_offset"); + + bool is_ra_zero = std::abs(this->target.offset_ra) < std::numeric_limits::epsilon(); + bool is_dec_zero = std::abs(this->target.offset_dec) < std::numeric_limits::epsilon(); - // nothing to do if both ra and dec offsets are zero - if (this->target.offset_ra == 0.0 && - this->target.offset_dec == 0.0) return NO_ERROR; + // nothing to do for calibrator or if both ra and dec offsets are zero + if ( this->target.iscal || + (is_ra_zero && is_dec_zero) ) return NO_ERROR; // zero TCS offsets before applying target offset long error = this->tcsd.command( TCSD_ZERO_OFFSETS ); @@ -3661,7 +3675,7 @@ namespace Sequencer { * */ long Sequence::handle_json_message( const std::string message_in ) { - const std::string function("Sequencer::Sequence::handle_json_message"); + std::string_view function("Sequencer::Sequence::handle_json_message"); std::stringstream message; if ( message_in.empty() ) { @@ -3744,7 +3758,7 @@ namespace Sequencer { * */ void Sequence::dothread_test_fpoffset() { - const std::string function("Sequencer::Sequence::dothread_fpoffset"); + std::string_view function("Sequencer::Sequence::dothread_fpoffset"); std::stringstream message; message.str(""); message << "calling fpoffsets.compute_offset() from thread: PyGILState=" << PyGILState_Check(); @@ -3779,7 +3793,7 @@ namespace Sequencer { } long Sequence::set_power_switch( PowerState reqstate, const std::string which, std::chrono::seconds delay ) { - const std::string function("Sequencer::Sequence::set_power_switch"); + std::string_view function("Sequencer::Sequence::set_power_switch"); long error=NO_ERROR; bool need_delay=false; @@ -3885,7 +3899,7 @@ namespace Sequencer { long Sequence::open_hardware( Common::DaemonClient &daemon, const std::string opencmd, const int opentimeout, bool &was_opened, bool forceopen ) { - const std::string function("Sequencer::Sequence::open_hardware"); + std::string_view function("Sequencer::Sequence::open_hardware"); const int maxattempts=3; ///< allow retries connecting to daemon bool isopen=false; std::string reply; @@ -3945,7 +3959,7 @@ namespace Sequencer { * */ long Sequence::connect_to_daemon( Common::DaemonClient &daemon ) { - const std::string function("Sequencer::Sequence::connect_to_daemon"); + std::string_view function("Sequencer::Sequence::connect_to_daemon"); // if not connected to the daemon then connect // @@ -3973,7 +3987,7 @@ namespace Sequencer { * */ long Sequence::daemon_restart(Common::DaemonClient &daemon) { - const std::string function("Sequencer::Sequence::daemon_restart"); + std::string_view function("Sequencer::Sequence::daemon_restart"); std::string command; // the daemon control script must have been specified in the config file @@ -4028,7 +4042,7 @@ namespace Sequencer { * */ long Sequence::test( std::string args, std::string &retstring ) { - const std::string function("Sequencer::Sequence::test"); + std::string_view function("Sequencer::Sequence::test"); std::stringstream message; std::vector tokens; long error = NO_ERROR; diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index c95b8dd3..92b2baa6 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -312,7 +312,7 @@ namespace Sequencer { /** @brief safely runs function in a detached thread using lambda to catch exceptions */ - void safe_thread(long (Sequence::*method)(), const std::string &function) { + void safe_thread(long (Sequence::*method)(), std::string_view function) { std::thread([this, method, function]() { try { (this->*method)(); @@ -476,12 +476,12 @@ namespace Sequencer { float slitwidthacquire; ///< "virtual slit mode" width for acquire // new stuff - long run(const Operation &op, const std::string &function); - long run_sequence(const std::vector &ops, const std::string &function); - long run_parallel(const std::vector &ops, const std::string &function); - long run_default_sequence(const std::string &caller); + long run(const Operation &op, std::string_view function); + long run_sequence(const std::vector &ops, std::string_view function); + long run_parallel(const std::vector &ops, std::string_view function); + long run_default_sequence(std::string_view caller); long run_operation_blocks( const std::vector &blocks, - const std::string &caller, + std::string_view caller, bool continue_on_error=false ); // publish/subscribe functions @@ -543,7 +543,7 @@ namespace Sequencer { bool is_ready() { return this->ready_to_start; } ///< returns the ready_to_start state, set true only after nightly startup long parse_calibration_target(); - long parse_state( std::string whoami, std::string reply, bool &state ); ///< parse true|false state from reply string + long parse_state( std::string_view whoami, std::string reply, bool &state ); ///< parse true|false state from reply string void dothread_test_fpoffset(); ///< for testing, calls Python function from thread long test( std::string args, std::string &retstring ); ///< handles test commands long extract_tcs_value( std::string reply, int &value ); ///< extract value returned by the TCS via tcsd @@ -588,9 +588,12 @@ namespace Sequencer { void modify_exptime( double exptime_in ); ///< modify exptime while exposure running void dothread_test(); - long wait_for_user(const std::string &function); ///< wait for the user or cancel - long wait_for_exposure(const std::string &function); ///< wait for exposure completion or cancel - long wait_for_readout(const std::string &function); ///< wait for readout completion or cancel + long wait_for_ontarget(std::string_view caller); ///< wait for TCS Operator + long wait_for_user(std::string_view caller); ///< wait for the user or cancel + long wait_for_exposure(std::string_view caller); ///< wait for exposure completion or cancel + long wait_for_readout(std::string_view caller); ///< wait for readout completion or cancel + long wait_for_canexpose(std::string_view caller); ///< wait for camera can_expose + void sequence_start(std::string obsid_in); ///< main sequence start thread. optional obsid_in for single target obs long calib_set(); ///< sets calib according to target entry params long camera_set(); ///< sets camera according to target entry params @@ -605,6 +608,8 @@ namespace Sequencer { */ long do_acam_acquire(); long do_slicecam_fineacquire(); + long do_target_acquisition(std::string_view caller); + long do_target_virtualslit(VirtualSlitMode mode); long acam_init(); ///< initializes connection to acamd diff --git a/sequencerd/sequence_acquisition.cpp b/sequencerd/sequence_acquisition.cpp index 7beab093..695c0423 100644 --- a/sequencerd/sequence_acquisition.cpp +++ b/sequencerd/sequence_acquisition.cpp @@ -16,7 +16,7 @@ namespace Sequencer { * */ long Sequence::do_acam_acquire() { - const std::string function("Sequencer::Sequence::do_acam_acquire"); + std::string_view function("Sequencer::Sequence::do_acam_acquire"); std::string reply; ScopedState thr_state( thread_state_manager, Sequencer::THR_ACQUISITION ); @@ -75,7 +75,7 @@ namespace Sequencer { * */ long Sequence::do_slicecam_fineacquire() { - const std::string function("Sequencer::Sequence::do_slicecam_fineacquire"); + std::string_view function("Sequencer::Sequence::do_slicecam_fineacquire"); ScopedState wait_state(wait_state_manager, Sequencer::SEQ_WAIT_ACQUIRE); @@ -117,4 +117,50 @@ namespace Sequencer { } /***** Sequencer::Sequence::do_slicecam_fineacquire **************************/ + + /***** Sequencer::Sequence::do_target_acquisition ****************************/ + /** + * @brief performs target acquisition + * @details First acquire on ACAM, then run slicecam fineacquire + * @return NO_ERROR | ABORT + * + */ + long Sequence::do_target_acquisition(std::string_view caller) { + + if (this->target.iscal) return NO_ERROR; + + // ---------- ACAM acquire ----------------------------- + // + if ( this->do_acam_acquire() != NO_ERROR ) { + this->async.enqueue_and_log(caller, "WARNING acam acquisition failed"); + + // on ACAM acquisition failure wait for user to continue or cancel + if ( this->wait_for_user(caller) == ABORT ) return ABORT; + + return NO_ERROR; // user chose to continue + } + + // ---------- SLICECAM fineacquire --------------------- + // + if ( this->do_slicecam_fineacquire() != NO_ERROR ) { + this->async.enqueue_and_log(caller, "WARNING slicecam fine acquisition failed"); + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::do_target_acquisition ****************************/ + + + /***** Sequencer::Sequence::do_target_virtualslit ****************************/ + /** + * @brief move to virtual slit position + * @param[in] mode VirtualSlitMode + * @return NO_ERROR | NO_ERROR + * + */ + long Sequence::do_target_virtualslit(VirtualSlitMode mode) { + if (this->target.iscal) return NO_ERROR; + return this->slit_set(mode); + } + /***** Sequencer::Sequence::do_target_virtualslit ****************************/ } diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index 06514cef..75a0f436 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -1043,7 +1043,7 @@ namespace Sequencer { } while (1) { - std::string message = seq.sequence.async.dequeue(); // get the latest message from the queue (blocks) + auto message = seq.sequence.async.dequeue(); // get the latest message from the queue (blocks) retval = sock.Send(message); // transmit the message if (retval < 0) { std::stringstream errstm; diff --git a/utils/logentry.cpp b/utils/logentry.cpp index 0b95650e..6ab2ba79 100644 --- a/utils/logentry.cpp +++ b/utils/logentry.cpp @@ -175,13 +175,12 @@ void close_log() { * log filestream isn't open. * */ -void logwrite( const std::string &function, std::string message ) { - std::stringstream logmsg; - std::string timestamp = get_timestamp(); // get the current time (defined in utilities.h) +void logwrite( std::string_view function, std::string_view message ) { + std::ostringstream logmsg; std::lock_guard lock(loglock); // lock mutex to protect from multiple access - logmsg << timestamp << " (" << function << ") " << message << "\n"; + logmsg << get_timestamp() << " (" << function << ") " << message << "\n"; if (filestream.is_open()) { filestream << logmsg.str(); // send to the file stream (if open) diff --git a/utils/logentry.h b/utils/logentry.h index ddf95890..f296bcbf 100644 --- a/utils/logentry.h +++ b/utils/logentry.h @@ -21,6 +21,6 @@ extern unsigned int nextday; /// number of seconds long init_log( std::string logpath, std::string name ); /// initialize the logging system long init_log( std::string logpath, std::string name, bool stderr_in ); /// initialize the logging system void close_log(); /// close the log file stream -void logwrite(const std::string &function, std::string message); /// create a time-stamped log entry "message" from "function" +void logwrite(std::string_view function, std::string_view message); /// create a time-stamped log entry "message" from "function" #endif diff --git a/utils/network.cpp b/utils/network.cpp index 5b6a2402..caf3d36e 100644 --- a/utils/network.cpp +++ b/utils/network.cpp @@ -174,14 +174,14 @@ namespace Network { * @return 0 on success, -1 on error * */ - int UdpSocket::Send(std::string message) { - std::string function = "Network::UdpSocket::Send"; - std::stringstream errstm; + int UdpSocket::Send(std::string_view message) { + std::string_view function = "Network::UdpSocket::Send"; + std::ostringstream errstm; ssize_t nbytes; if ( !this->is_running() ) return 0; // silently do nothing if the UDP multicast socket isn't running - if ( ( nbytes = sendto( this->fd, message.c_str(), (size_t)message.length(), 0, + if ( ( nbytes = sendto( this->fd, std::string(message).c_str(), (size_t)message.length(), 0, (struct sockaddr*) &this->addr, (socklen_t)sizeof(this->addr) ) ) < 0 ) { errstm << "error " << errno << " calling sendto: " << strerror(errno); logwrite(function, errstm.str()); diff --git a/utils/network.h b/utils/network.h index f2b5637a..0fea2000 100644 --- a/utils/network.h +++ b/utils/network.h @@ -168,7 +168,7 @@ namespace Network { std::string getgroup() { return this->group; }; ///< use to get group int Create(); ///< create a UDP multi-cast socket - int Send(std::string message); ///< transmit the message to the UDP socket + int Send(std::string_view message); ///< transmit the message to the UDP socket int Close(); ///< close the UDP socket connection int Listener(); ///< creates a UDP listener, returns a file descriptor ssize_t Receive( std::string &message ); ///< receive a UDP message from the Listener fd From 2925c7f0d9d20c6f024eb6dbff34dd219486b055 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 6 Apr 2026 10:07:54 -0700 Subject: [PATCH 05/35] adds signatures for scripting functions moves wait_for_XXXX wrappers to a separate file moves sequence building tools to a separate file --- common/sequencerd_commands.h | 4 + sequencerd/CMakeLists.txt | 2 + sequencerd/sequence.cpp | 378 ++++++++++---------------------- sequencerd/sequence.h | 70 +++++- sequencerd/sequence_builder.cpp | 65 ++++++ sequencerd/sequence_wait.cpp | 168 ++++++++++++++ sequencerd/sequencer_server.cpp | 16 ++ 7 files changed, 432 insertions(+), 271 deletions(-) create mode 100644 sequencerd/sequence_builder.cpp create mode 100644 sequencerd/sequence_wait.cpp diff --git a/common/sequencerd_commands.h b/common/sequencerd_commands.h index cf38c57d..adce01ee 100644 --- a/common/sequencerd_commands.h +++ b/common/sequencerd_commands.h @@ -16,9 +16,11 @@ const std::string SEQUENCERD_GUIDE = "guide"; const std::string SEQUENCERD_MODEXPTIME = "modexptime"; const std::string SEQUENCERD_ONTARGET = "ontarget"; const std::string SEQUENCERD_USERCONTINUE = "usercontinue"; +const std::string SEQUENCERD_OP = "op"; const std::string SEQUENCERD_PAUSE = "pause"; const std::string SEQUENCERD_REPEAT = "repeat"; const std::string SEQUENCERD_RESUME = "resume"; +const std::string SEQUENCERD_SCRIPT = "script"; const std::string SEQUENCERD_SHUTDOWN = "shutdown"; const std::string SEQUENCERD_START = "start"; const std::string SEQUENCERD_STARTONE = "startone"; @@ -54,11 +56,13 @@ const std::vector SEQUENCERD_SYNTAX = { SEQUENCERD_GUIDE, SEQUENCERD_MODEXPTIME+" ", SEQUENCERD_ONTARGET, + SEQUENCERD_OP, SEQUENCERD_PAUSE, SEQUENCERD_REPEAT, SEQUENCERD_RESUME, TELEMREQUEST+" [?]", SEQUENCERD_USERCONTINUE, + SEQUENCERD_SCRIPT, SEQUENCERD_SHUTDOWN, SEQUENCERD_START, SEQUENCERD_STARTONE, diff --git a/sequencerd/CMakeLists.txt b/sequencerd/CMakeLists.txt index dda2f133..b5804c76 100644 --- a/sequencerd/CMakeLists.txt +++ b/sequencerd/CMakeLists.txt @@ -38,6 +38,8 @@ add_executable(sequencerd ${SEQUENCER_DIR}/sequencer_server.cpp ${SEQUENCER_DIR}/sequencer_interface.cpp ${SEQUENCER_DIR}/sequence_acquisition.cpp + ${SEQUENCER_DIR}/sequence_wait.cpp + ${SEQUENCER_DIR}/sequence_builder.cpp ${SEQUENCER_DIR}/sequence.cpp ${MYSQL_INCLUDES} ${PYTHON_DEV} diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 3c2a7641..598092ac 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -47,42 +47,6 @@ namespace Sequencer { /***** Sequencer::Sequence::run ********************************************/ - /***** Sequencer::Sequence::run_sequence ***********************************/ - /** - * @brief executes operations in sequence, one at a time - * @param[in] op vector of Operations to execute - * @param[in] caller name of calling function for logging - * @return ERROR|NO_ERROR|ABORT - * - */ - long Sequence::run_sequence( const std::vector &ops, - std::string_view caller ) { - - for (const auto &op : ops) { - - if (this->cancel_flag.load()) return ABORT; - - logwrite(caller, "starting "+op.name); - - try { - long error; - if ( (error = op.func()) != NO_ERROR ) { - std::ostringstream oss; - oss << (error==ABORT ? "cancelled" : "ERROR") << " in " << op.name; - logwrite(caller, oss.str()); - return error; - } - } - catch (const std::exception &e) { - logwrite(caller, "ERROR in "+op.name+": "+std::string(e.what())); - return ERROR; - } - } - return NO_ERROR; - } - /***** Sequencer::Sequence::run_sequence ***********************************/ - - /***** Sequencer::Sequence::run_parallel ***********************************/ /** * @brief executes operations in parallel threads @@ -123,40 +87,40 @@ namespace Sequencer { /***** Sequencer::Sequence::run_parallel ***********************************/ - /***** Sequencer::Sequence::run_operation_blocks ***************************/ + /***** Sequencer::Sequence::run_sequence ***********************************/ /** - * @brief executes operation blocks - * @details An operation block contains a vector of operations paired - * with a type, SERIAL|PARALLEL which specifies how that block - * is to be executed. This executes a vector of Operation Blocks. - * The optional continue_on_error allows a block to continue, or - * stop immediately when any operation within the block fails. - * @param[in] blocks vector of OperationBlocks + * @brief executes a sequence, a collection of operation groups + * @details An operation group contains a vector of operations paired + * with a type, SERIAL|PARALLEL which specifies how that group + * is to be executed. This executes a vector of Operation Groups. + * The optional continue_on_error allows a group to continue, or + * stop immediately when any operation within the group fails. + * @param[in] groups vector of OperationGroups * @param[in] caller name of calling function for logging - * @param[in] continue_on_error continue or stop block execution on error + * @param[in] continue_on_error continue or stop group execution on error * @return ERROR|NO_ERROR|ABORT * */ - long Sequence::run_operation_blocks( const std::vector &blocks, - std::string_view caller, - bool continue_on_error ) { + long Sequence::run_sequence( const std::vector &groups, + std::string_view caller, + bool continue_on_error ) { long error = NO_ERROR; - for (const auto &block : blocks) { + for (const auto &group : groups) { if (this->cancel_flag.load()) return ABORT; - // PARALLEL Blocks are executed in parallel threads + // PARALLEL Groups are executed in parallel threads // - if (block.type == OperationType::PARALLEL) { - long ret = run_parallel(block.operations, caller); + if (group.type == OperationType::PARALLEL) { + long ret = run_parallel(group.operations, caller); error |= ret; if (ret != NO_ERROR && !continue_on_error) return error; } - // SERIAL Blocks are executed one at a time + // SERIAL Groups are executed one at a time // else { - for (const auto &op : block.operations) { + for (const auto &op : group.operations) { if (this->cancel_flag.load()) return ABORT; long ret = run(op, caller); @@ -169,7 +133,7 @@ namespace Sequencer { return error; } - /***** Sequencer::Sequence::run_operation_blocks ***************************/ + /***** Sequencer::Sequence::run_sequence ***********************************/ /***** Sequencer::Sequence::run_default_sequence ***************************/ @@ -181,17 +145,18 @@ namespace Sequencer { */ long Sequence::run_default_sequence(std::string_view caller) { - std::vector blocks; + std::vector groups; // ---------- RUN THESE IN PARALLEL -------------------- // If pointmode is ACAM then the user has chosen to put the star on ACAM, in // which case the assumption is made that nothing else matters. This special - // mode of operation only points the telescope. + // mode of operation only points the telescope so this is the only operation + // added to the sequence. // if (this->target.pointmode == Acam::POINTMODE_ACAM) { this->dotype("ONE"); - blocks.push_back( { OperationType::PARALLEL, + groups.push_back( { OperationType::PARALLEL, { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); } else { @@ -199,7 +164,7 @@ namespace Sequencer { // these are the default operations prior to exposure, // they can be done in parallel - blocks.push_back( { OperationType::PARALLEL, + groups.push_back( { OperationType::PARALLEL, { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, @@ -210,56 +175,83 @@ namespace Sequencer { } } ); } - // Early Exit for pointmode=ACAM - // - if (this->target.pointmode == Acam::POINTMODE_ACAM) { - return run_operation_blocks(blocks, caller); - } - // ---------- RUN THESE IN SERIES ---------------------- - blocks.push_back( { OperationType::SERIAL, - { { "target_acquisition", THR_ACQUISITION, - [this,caller]() { return this->do_target_acquisition(caller); } }, + if (this->target.pointmode != Acam::POINTMODE_ACAM) { + groups.push_back( { OperationType::SERIAL, + { { "target_acquisition", THR_ACQUISITION, + [this,caller]() { return this->do_target_acquisition(caller); } }, - { "target_offset", THR_MOVE_TO_TARGET, - [this]() { return this->target_offset(); } }, + { "target_offset", THR_MOVE_TO_TARGET, + [this]() { return this->target_offset(); } }, - { "slit_expose", THR_SLIT_SET, - [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } } - } - } ); + { "slit_expose", THR_SLIT_SET, + [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } }, - // ---------- EXPOSURE --------------------------------- + { "science_exposure", THR_EXPOSURE, + [this,caller]() { return this->do_exposure(caller); } } + } + } ); + } - blocks.push_back( { OperationType::SERIAL, - { { "trigger_exposure", THR_EXPOSURE, [this]() { + // ---------- RUN THE SEQUENCE NOW --------------------- - // set the EXPOSE bit here, outside of the trigger_exposure function, because that - // function only triggers the exposure -- it doesn't block waiting for the exposure. - // - this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); // set EXPOSE bit - return trigger_exposure(); - } }, + return run_sequence(groups, caller); + } + /***** Sequencer::Sequence::run_default_sequence ***************************/ - { "wait_exposure", THR_EXPOSURE, [this,caller]() { - return this->wait_for_exposure(caller); - } }, - { "wait_readout", THR_EXPOSURE, [this,caller]() { - if (!this->is_science_frame_transfer) { - return this->wait_for_readout(caller); - } - else return NO_ERROR; - } } - } - } ); + /***** Sequencer::Sequence::run_script *************************************/ + /** + * @brief executes a user script + * @param[in] filename filename of script + * @return ERROR|NO_ERROR|ABORT + * + */ + long Sequence::run_script(const std::string &filename) { + return NO_ERROR; + } + /***** Sequencer::Sequence::run_script *************************************/ - // ---------- RUN THE SEQUENCE NOW --------------------- - return run_operation_blocks(blocks, caller); + /***** Sequencer::Sequence::parse_script ***********************************/ + /** + * @brief parses a user script + * @param[in] filename filename of script + * @return ERROR|NO_ERROR|ABORT + * + */ + long Sequence::parse_script(const std::string &filename, + std::vector &out) { + return NO_ERROR; } - /***** Sequencer::Sequence::run_default_sequence ***************************/ + /***** Sequencer::Sequence::parse_script ***********************************/ + + + /***** Sequencer::Sequence::validate_sequence ******************************/ + /** + * @brief + * @param[in] + * @return ERROR|NO_ERROR|ABORT + * + */ + long Sequence::validate_sequence(const std::vector &groups) { + return NO_ERROR; + } + /***** Sequencer::Sequence::validate_sequence ******************************/ + + + /***** Sequencer::Sequence::handle_cli_operation ***************************/ + /** + * @brief handle incoming operation request + * @param[in] op the name of an operation + * @return ERROR|NO_ERROR|ABORT + * + */ + long Sequence::handle_cli_operation(const std::string &op) { + return NO_ERROR; + } + /***** Sequencer::Sequence::handle_cli_operation ***************************/ /***** Sequencer::Sequence::handletopic_snapshot ***************************/ @@ -642,168 +634,6 @@ namespace Sequencer { /***** Sequencer::Sequence::dothread_sequencer_async_listener ***************/ - void Sequence::dothread_test() { - logwrite( "Sequencer::Sequence::dothread_test", "here I am" ); - std::string targetstatus; - this->target.get_specified_target( "4430", targetstatus ); - logwrite( "Sequencer::Sequence::dothread_test", targetstatus ); - return; - } - - - /***** Sequencer::Sequence::wait_for_ontarget *******************************/ - /** - * @brief waits for the TCS Operator to click 'ontarget' - * @param[in] caller reference to caller's name for logging - * @return NO_ERROR on continue | ABORT on cancel - * - */ - long Sequence::wait_for_ontarget(std::string_view caller) { - // waiting for TCS Operator input (or cancel) - { - ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCSOP ); - - this->async.enqueue_and_log(caller, "NOTICE: waiting for TCS operator to send 'ontarget' signal"); - - while ( !this->cancel_flag.load() && - !this->is_ontarget.load() ) { - - std::unique_lock lock(cv_mutex); - this->cv.wait( lock, [this]() { return( this->is_ontarget.load() || - this->cancel_flag.load() ); } ); - } - - this->async.enqueue_and_log(caller, "NOTICE: received " - +(this->cancel_flag.load() ? std::string("cancel") - : std::string("ontarget")) - +" signal!" ); - } - this->is_ontarget.store(false); - - return (this->cancel_flag.load() ? ABORT : NO_ERROR); - } - /***** Sequencer::Sequence::wait_for_ontarget *******************************/ - - - /***** Sequencer::Sequence::wait_for_user ***********************************/ - /** - * @brief waits for the user to click a button, or cancel - * @details Use this when you just want to slow things down or get a - * cup of coffee instead of observing. - * @param[in] caller reference to caller's name for logging - * @return NO_ERROR on continue | ABORT on cancel - * - */ - long Sequence::wait_for_user(std::string_view caller) { - { - ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); - - this->async.enqueue_and_log( caller, "NOTICE: waiting for USER to send 'continue' signal" ); - - while ( !this->cancel_flag.load() && !this->is_usercontinue.load() ) { - std::unique_lock lock(cv_mutex); - this->cv.wait( lock, [this]() { return( this->is_usercontinue.load() || this->cancel_flag.load() ); } ); - } - - this->async.enqueue_and_log( caller, "NOTICE: received " - +(this->cancel_flag.load() ? std::string("cancel") : std::string("continue")) - +" signal!" ); - } // end scope for wait_state = WAIT_USER - - if ( this->cancel_flag.load() ) { - this->async.enqueue_and_log( caller, "NOTICE: sequence cancelled" ); - return ABORT; - } - - this->is_usercontinue.store(false); - - return NO_ERROR; - } - /***** Sequencer::Sequence::wait_for_user ***********************************/ - - - /***** Sequencer::Sequence::wait_for_exposure *******************************/ - /** - * @brief waits for exposure completion, or cancel - * @param[in] caller reference to caller's name for logging - * @return NO_ERROR on continue | ABORT on cancel - * - */ - long Sequence::wait_for_exposure(std::string_view caller) { - logwrite(caller, "waiting for exposure"); - while (!this->cancel_flag.load() && - wait_state_manager.is_set(Sequencer::SEQ_WAIT_EXPOSE)) { - std::unique_lock lock(cv_mutex); - this->cv.wait( lock, [this]() { return(!wait_state_manager.is_set(SEQ_WAIT_EXPOSE) || - this->cancel_flag.load()); } ); - } - - if (this->cancel_flag.load()) { - this->async.enqueue_and_log(caller, "NOTICE: exposure cancelled"); - return ABORT; - } - - return NO_ERROR; - } - /***** Sequencer::Sequence::wait_for_exposure *******************************/ - - - /***** Sequencer::Sequence::wait_for_readout ********************************/ - /** - * @brief waits for readout completion, or cancel - * @param[in] caller reference to caller's name for logging - * @return NO_ERROR on continue | ABORT on cancel - * - */ - long Sequence::wait_for_readout(std::string_view caller) { - logwrite(caller, "waiting for readout"); - while (!this->cancel_flag.load() && - wait_state_manager.is_set(Sequencer::SEQ_WAIT_READOUT)) { - std::unique_lock lock(cv_mutex); - this->cv.wait( lock, [this]() { return(!wait_state_manager.is_set(SEQ_WAIT_READOUT) || - this->cancel_flag.load()); } ); - } - - if (this->cancel_flag.load()) { - this->async.enqueue_and_log(caller, "NOTICE: wait for readout cancelled"); - return ABORT; - } - - return NO_ERROR; - } - /***** Sequencer::Sequence::wait_for_readout ********************************/ - - - /***** Sequencer::Sequence::wait_for_canexpose ******************************/ - /** - * @brief waits for camera to be ready to expose, or cancel - * @param[in] caller reference to caller's name for logging - * @return NO_ERROR on continue | ABORT on cancel - * - */ - long Sequence::wait_for_canexpose(std::string_view caller) { - logwrite(caller, "waiting for can_expose"); - - while ( !this->cancel_flag.load() && - !this->can_expose.load() ) { - - this->async.enqueue_and_log(caller, "NOTICE: waiting for camera to be ready to expose"); - - std::unique_lock lock(this->camerad_mtx); - this->camerad_cv.wait( lock, [this]() { return( this->can_expose.load() || - this->cancel_flag.load() ); } ); - } - - if (this->cancel_flag.load()) { - this->async.enqueue_and_log(caller, "NOTICE: wait for can_expose cancelled"); - return ABORT; - } - - return NO_ERROR; - } - /***** Sequencer::Sequence::wait_for_canexpose ******************************/ - - /***** Sequencer::Sequence::sequence_start **********************************/ /** * @brief main sequence start thread @@ -840,6 +670,8 @@ namespace Sequencer { return; } + // ---------- SEQUENCER IS RUNNING --------------------- + // ScopedState thr_state( thread_state_manager, Sequencer::THR_SEQUENCE_START ); // this thread is running ScopedState seq_state( seq_state_manager, Sequencer::SEQ_RUNNING, true ); // state = RUNNING (only) seq_state.destruct_set( Sequencer::SEQ_READY ); // set state=READY on exit @@ -1011,8 +843,6 @@ namespace Sequencer { std::stringstream camcmd; long error=NO_ERROR; - // wait until camera is ready to expose - // this->wait_for_canexpose(function); logwrite( function, "setting camera parameters"); @@ -2683,6 +2513,8 @@ namespace Sequencer { std::string reply; long error=NO_ERROR; + this->wait_for_canexpose(function); + ScopedState thr_state( thread_state_manager, Sequencer::THR_TRIGGER_EXPOSURE ); // Check tcs_preauth_time and set notify_tcs_next_target -- @@ -2721,6 +2553,30 @@ namespace Sequencer { /***** Sequencer::Sequence::trigger_exposure ********************************/ + /***** Sequencer::Sequence::do_exposure *************************************/ + /** + * @brief wrapper for performing science exposure + * @details Triggers an exposure and waits for the exposure and readout. + * This blocks until + * @param[in] caller name of calling function + * @return ERROR|NO_ERROR + * + */ + long Sequence::do_exposure(std::string_view caller) { + + this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); + + if ( this->trigger_exposure() != NO_ERROR ) return ERROR; + + if ( this->wait_for_exposure(caller) != NO_ERROR ) return ERROR; + + if ( this->wait_for_readout(caller) != NO_ERROR ) return ERROR; + + return NO_ERROR; + } + /***** Sequencer::Sequence::do_exposure *************************************/ + + /***** Sequencer::Sequence::modify_exptime **********************************/ /** * @brief modify the exposure time while an exposure is running diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 92b2baa6..6292cd77 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -193,6 +193,7 @@ namespace Sequencer { THR_STOP_EXPOSURE, THR_ABORT_PROCESS, THR_SEQUENCE_START, + THR_RUN_SCRIPT, THR_MONITOR_READY_STATE, THR_CALIB_SET, THR_CAMERA_SET, @@ -238,6 +239,7 @@ namespace Sequencer { {THR_STOP_EXPOSURE, "stop_exposure"}, {THR_ABORT_PROCESS, "abort_process"}, {THR_SEQUENCE_START, "sequence_start"}, + {THR_RUN_SCRIPT, "run_script"}, {THR_MONITOR_READY_STATE, "monitor_ready_state"}, {THR_CALIB_SET, "calib_set"}, {THR_CAMERA_SET, "camera_set"}, @@ -293,23 +295,62 @@ namespace Sequencer { std::atomic is_fineacquire_locked{false}; ///< is slicecam fine acquisition locked? std::atomic is_acam_guiding{false}; ///< is acam guiding? + /** @brief operation type can be SERIAL or PARALLEL + */ enum class OperationType { PARALLEL, SERIAL }; + /** @brief map of parameter key=value pairs associated with operation + */ + struct OperationParams { + std::unordered_map map; + + bool has(const std::string &key) const { + return map.find(key) != map.end(); + } + + template + T get(const std::string &key, const T &default_val) const { + auto it = map.find(key); + if (it == map.end()) return default_val; + + if constexpr (std::is_same_v) { + return it->second; + } + else { + std::istringstream iss(it->second); + T val; + iss >> val; + return iss.fail() ? default_val : val; + } + } + }; + + /** @brief sequencer operation contains name, status bit, function and params + */ struct Operation { - std::string name; ///< name of this operation - ThreadStatusBits thr; ///< status bit of what is running - std::function func; ///< function that this operation calls - std::map params; ///< function parameters + std::string name; + ThreadStatusBits thr; + std::function func; + OperationParams params; }; - struct OperationBlock { + /** @brief a group of operations stored in a vector with the operation type + */ + struct OperationGroup { OperationType type; std::vector operations; }; + /** @brief associates a sequencer command with its parameters + */ + struct ParsedCommand { + std::string name; + OperationParams params; + }; + /** @brief safely runs function in a detached thread using lambda to catch exceptions */ void safe_thread(long (Sequence::*method)(), std::string_view function) { @@ -475,14 +516,22 @@ namespace Sequencer { float slitoffsetacquire; ///< "virtual slit mode" offset for acquire float slitwidthacquire; ///< "virtual slit mode" width for acquire - // new stuff + // ---------- sequencer scripting and execution tools -------------------- + // long run(const Operation &op, std::string_view function); - long run_sequence(const std::vector &ops, std::string_view function); long run_parallel(const std::vector &ops, std::string_view function); long run_default_sequence(std::string_view caller); - long run_operation_blocks( const std::vector &blocks, - std::string_view caller, - bool continue_on_error=false ); + long run_sequence( const std::vector &groups, + std::string_view caller, + bool continue_on_error=false ); + + long run_script(const std::string &filename); ///< run user script + long parse_script(const std::string &filename, + std::vector &out); ///< parse script into commands/args + long build_sequence(const std::vector &commands, + std::vector &sequence_out); ///< build sequence from parsed commands + long validate_sequence(const std::vector &groups); ///< validate sequence + long handle_cli_operation(const std::string &op); ///< handle incoming operation request // publish/subscribe functions // @@ -582,6 +631,7 @@ namespace Sequencer { // These are various jobs that are done in their own threads // long trigger_exposure(); ///< trigger and wait for exposure + long do_exposure(std::string_view caller); ///< wrapper performs and waits for science exposure void abort_process(); ///< tries to abort everything void stop_exposure(); ///< stop exposure timer in progress long repeat_exposure(); ///< repeat the last exposure diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp new file mode 100644 index 00000000..4a7489a7 --- /dev/null +++ b/sequencerd/sequence_builder.cpp @@ -0,0 +1,65 @@ +/** + * @file sequence_builder.cpp + * @brief implementation for building sequences from operations + * @author David Hale + * + */ + +#include "sequence.h" + +namespace Sequencer { + + /***** Sequencer::Sequence::build_sequence *********************************/ + /** + * @brief build a sequence from parsed commands + * @param[in] commands vector of ParsedCommands + * @param[out] sequence_out the operation group to execute + * @return ERROR|NO_ERROR|ABORT + * + */ + long Sequence::build_sequence(const std::vector &commands, + std::vector &sequence_out) { + OperationGroup group; + + group.type = OperationType::SERIAL; // default is serial + + for (const auto &command : commands) { + + if (command.name == "begin_parallel") { + if (!group.operations.empty()) sequence_out.push_back(group); + group = { OperationType::PARALLEL, {} }; + continue; + } + else + if (command.name == "end_parallel") { + sequence_out.push_back(group); + group = { OperationType::SERIAL, {} }; // back to default + continue; + } + else + if (command.name == "move_to_target") { + group.operations.emplace_back( Operation { + "move_to_target", THR_MOVE_TO_TARGET, + [this,params=command.params]() { + if (params.has("ra") && params.has("dec")) { + this->target.ra_hms = params.get(std::string("ra"),std::string("")); + this->target.dec_dms = params.get(std::string("dec"),std::string("")); + } + return move_to_target(); + }, + command.params + }); + } + + else { + this->async.enqueue_and_log("Sequencer::Sequence::build_sequence", + "ERROR unknown command '"+command.name+"'"); + } + } + if (!group.operations.empty()) sequence_out.push_back(group); + + return NO_ERROR; + } + /***** Sequencer::Sequence::build_sequence *********************************/ + +} diff --git a/sequencerd/sequence_wait.cpp b/sequencerd/sequence_wait.cpp new file mode 100644 index 00000000..078c1046 --- /dev/null +++ b/sequencerd/sequence_wait.cpp @@ -0,0 +1,168 @@ +/** + * @file sequence_wait.cpp + * @brief wait wrappers used in the Sequence class + * @author David Hale + * + */ + +#include "sequence.h" + +namespace Sequencer { + + /***** Sequencer::Sequence::wait_for_ontarget *******************************/ + /** + * @brief waits for the TCS Operator to click 'ontarget' + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_ontarget(std::string_view caller) { + // waiting for TCS Operator input (or cancel) + { + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCSOP ); + + this->async.enqueue_and_log(caller, "NOTICE: waiting for TCS operator to send 'ontarget' signal"); + + while ( !this->cancel_flag.load() && + !this->is_ontarget.load() ) { + + std::unique_lock lock(cv_mutex); + this->cv.wait( lock, [this]() { return( this->is_ontarget.load() || + this->cancel_flag.load() ); } ); + } + + this->async.enqueue_and_log(caller, "NOTICE: received " + +(this->cancel_flag.load() ? std::string("cancel") + : std::string("ontarget")) + +" signal!" ); + } + this->is_ontarget.store(false); + + return (this->cancel_flag.load() ? ABORT : NO_ERROR); + } + /***** Sequencer::Sequence::wait_for_ontarget *******************************/ + + + /***** Sequencer::Sequence::wait_for_user ***********************************/ + /** + * @brief waits for the user to click a button, or cancel + * @details Use this when you just want to slow things down or get a + * cup of coffee instead of observing. + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_user(std::string_view caller) { + { + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); + + this->async.enqueue_and_log( caller, "NOTICE: waiting for USER to send 'continue' signal" ); + + while ( !this->cancel_flag.load() && !this->is_usercontinue.load() ) { + std::unique_lock lock(cv_mutex); + this->cv.wait( lock, [this]() { return( this->is_usercontinue.load() || this->cancel_flag.load() ); } ); + } + + this->async.enqueue_and_log( caller, "NOTICE: received " + +(this->cancel_flag.load() ? std::string("cancel") : std::string("continue")) + +" signal!" ); + } // end scope for wait_state = WAIT_USER + + if ( this->cancel_flag.load() ) { + this->async.enqueue_and_log( caller, "NOTICE: sequence cancelled" ); + return ABORT; + } + + this->is_usercontinue.store(false); + + return NO_ERROR; + } + /***** Sequencer::Sequence::wait_for_user ***********************************/ + + + /***** Sequencer::Sequence::wait_for_exposure *******************************/ + /** + * @brief waits for exposure completion, or cancel + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_exposure(std::string_view caller) { + logwrite(caller, "waiting for exposure"); + while (!this->cancel_flag.load() && + wait_state_manager.is_set(Sequencer::SEQ_WAIT_EXPOSE)) { + std::unique_lock lock(cv_mutex); + this->cv.wait( lock, [this]() { return(!wait_state_manager.is_set(SEQ_WAIT_EXPOSE) || + this->cancel_flag.load()); } ); + } + + if (this->cancel_flag.load()) { + this->async.enqueue_and_log(caller, "NOTICE: exposure cancelled"); + return ABORT; + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::wait_for_exposure *******************************/ + + + /***** Sequencer::Sequence::wait_for_readout ********************************/ + /** + * @brief waits for readout completion, or cancel + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_readout(std::string_view caller) { + + // don't have to wait for readout when using frame transfer + if ( this->is_science_frame_transfer ) return NO_ERROR; + + logwrite(caller, "waiting for readout"); + + while (!this->cancel_flag.load() && + wait_state_manager.is_set(Sequencer::SEQ_WAIT_READOUT)) { + std::unique_lock lock(cv_mutex); + this->cv.wait( lock, [this]() { return(!wait_state_manager.is_set(SEQ_WAIT_READOUT) || + this->cancel_flag.load()); } ); + } + + if (this->cancel_flag.load()) { + this->async.enqueue_and_log(caller, "NOTICE: wait for readout cancelled"); + return ABORT; + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::wait_for_readout ********************************/ + + + /***** Sequencer::Sequence::wait_for_canexpose ******************************/ + /** + * @brief waits for camera to be ready to expose, or cancel + * @param[in] caller reference to caller's name for logging + * @return NO_ERROR on continue | ABORT on cancel + * + */ + long Sequence::wait_for_canexpose(std::string_view caller) { + + this->async.enqueue_and_log(caller, "NOTICE: waiting for camera to be ready to expose"); + + while ( !this->cancel_flag.load() && + !this->can_expose.load() ) { + + std::unique_lock lock(this->camerad_mtx); + this->camerad_cv.wait( lock, [this]() { return( this->can_expose.load() || + this->cancel_flag.load() ); } ); + } + + if (this->cancel_flag.load()) { + this->async.enqueue_and_log(caller, "NOTICE: wait for can_expose cancelled"); + return ABORT; + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::wait_for_canexpose ******************************/ + +} diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index 75a0f436..e0ed8961 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -1376,6 +1376,22 @@ namespace Sequencer { } else + // handle incoming CLI operation request + // + if ( cmd == SEQUENCERD_OP ) { + std::thread( &Sequencer::Sequence::handle_cli_operation, std::ref(this->sequence), args ).detach(); + ret = NO_ERROR; + } + else + + // run sequencer script + // + if ( cmd == SEQUENCERD_SCRIPT ) { + std::thread( &Sequencer::Sequence::run_script, std::ref(this->sequence), args ).detach(); + ret = NO_ERROR; + } + else + // Sequence "start" // if ( cmd == SEQUENCERD_START ) { From aaa47b29f796524507269dbd352ed491852a45a9 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 6 Apr 2026 14:41:04 -0700 Subject: [PATCH 06/35] implements the functions for script parsing and execution --- sequencerd/sequence.cpp | 165 +++++++++++++++++++++++++++----- sequencerd/sequence.h | 7 +- sequencerd/sequence_builder.cpp | 28 +++++- 3 files changed, 174 insertions(+), 26 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 598092ac..f1c719d9 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -101,12 +101,14 @@ namespace Sequencer { * @return ERROR|NO_ERROR|ABORT * */ - long Sequence::run_sequence( const std::vector &groups, + long Sequence::run_sequence( const std::vector &sequence, std::string_view caller, bool continue_on_error ) { long error = NO_ERROR; - for (const auto &group : groups) { + logwrite(caller, "starting sequence"); + + for (const auto &group : sequence) { if (this->cancel_flag.load()) return ABORT; // PARALLEL Groups are executed in parallel threads @@ -131,6 +133,8 @@ namespace Sequencer { } } + logwrite(caller, "sequence complete"); + return error; } /***** Sequencer::Sequence::run_sequence ***********************************/ @@ -145,7 +149,7 @@ namespace Sequencer { */ long Sequence::run_default_sequence(std::string_view caller) { - std::vector groups; + std::vector sequence; // ---------- RUN THESE IN PARALLEL -------------------- @@ -156,15 +160,15 @@ namespace Sequencer { // if (this->target.pointmode == Acam::POINTMODE_ACAM) { this->dotype("ONE"); - groups.push_back( { OperationType::PARALLEL, - { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); + sequence.push_back( { OperationType::PARALLEL, + { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); } else { this->target.pointmode = Acam::POINTMODE_SLIT; // these are the default operations prior to exposure, // they can be done in parallel - groups.push_back( { OperationType::PARALLEL, + sequence.push_back( { OperationType::PARALLEL, { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, @@ -178,17 +182,17 @@ namespace Sequencer { // ---------- RUN THESE IN SERIES ---------------------- if (this->target.pointmode != Acam::POINTMODE_ACAM) { - groups.push_back( { OperationType::SERIAL, - { { "target_acquisition", THR_ACQUISITION, + sequence.push_back( { OperationType::SERIAL, + { { "target_acquire", THR_ACQUISITION, [this,caller]() { return this->do_target_acquisition(caller); } }, { "target_offset", THR_MOVE_TO_TARGET, [this]() { return this->target_offset(); } }, - { "slit_expose", THR_SLIT_SET, + { "slit_set", THR_SLIT_SET, [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } }, - { "science_exposure", THR_EXPOSURE, + { "expose", THR_EXPOSURE, [this,caller]() { return this->do_exposure(caller); } } } } ); @@ -196,7 +200,7 @@ namespace Sequencer { // ---------- RUN THE SEQUENCE NOW --------------------- - return run_sequence(groups, caller); + return run_sequence(sequence, caller); } /***** Sequencer::Sequence::run_default_sequence ***************************/ @@ -209,7 +213,26 @@ namespace Sequencer { * */ long Sequence::run_script(const std::string &filename) { - return NO_ERROR; + std::string_view function("Sequencer::Sequence::run_script"); + + std::vector commands; + if ( parse_script(filename, commands) != NO_ERROR ) { + logwrite(function, "ERROR parsing '"+filename+"'"); + return ERROR; + } + + std::vector sequence; + if ( build_sequence(commands, sequence) != NO_ERROR ) { + logwrite(function, "ERROR building sequence from '"+filename+"'"); + return ERROR; + } + + if ( validate_sequence(sequence) != NO_ERROR ) { + logwrite(function, "ERROR validating sequence from '"+filename+"'"); + return ERROR; + } + + return run_sequence(sequence, function); } /***** Sequencer::Sequence::run_script *************************************/ @@ -217,25 +240,97 @@ namespace Sequencer { /***** Sequencer::Sequence::parse_script ***********************************/ /** * @brief parses a user script - * @param[in] filename filename of script - * @return ERROR|NO_ERROR|ABORT + * @details This parses a script, makes a ParsedCommand struct from each + * line, returning a vector of ParsedCommands. No validation is + * done here, only parsing. + * @param[in] filename filename of script + * @param[out] commands_out reference to vector of ParsedCommands + * @return ERROR|NO_ERROR * */ long Sequence::parse_script(const std::string &filename, - std::vector &out) { + std::vector &commands_out) { + std::ifstream file(filename); + if (!file.is_open()) { + logwrite("Sequencer::Sequence::parse_script", "ERROR opening '"+filename+"'"); + return ERROR; + } + + std::string line; + + while (std::getline(file, line)) { + + auto command = parse_command(line); + + if (command) commands_out.push_back(*command); + } + return NO_ERROR; } /***** Sequencer::Sequence::parse_script ***********************************/ + /***** Sequencer::Sequence::parse_command **********************************/ + /** + * @brief parses a single command line + * @details This parses a command and any parameters as key=val pairs + * from the supplied string and returns a ParsedCommand struct. + * @param[in] args string containing command and any optional arguments + * @return nullptr | ParsedCommand + * + */ + std::optional Sequence::parse_command(std::string &args) { + + // strip comments, everything after '#' + auto pos = args.find('#'); + if (pos != std::string::npos) args = args.substr(0, pos); + + std::istringstream iss(args); + std::string word; + + // first word is the command + if (!(iss >> word)) return std::nullopt; + + ParsedCommand command; + command.name = word; + + // any additional words are parameters, expected to be key=val pairs + while (iss >> word) { + auto eq = word.find('='); + if (eq != std::string::npos) { + std::string key = word.substr(0, eq); + std::string val = word.substr(eq+1); + command.params.map[key] = val; + } + } + + return command; + } + /***** Sequencer::Sequence::parse_command **********************************/ + + /***** Sequencer::Sequence::validate_sequence ******************************/ /** - * @brief - * @param[in] - * @return ERROR|NO_ERROR|ABORT + * @brief applies validation rules to sequence + * @param[in] sequence vector of OperationGroups + * @return ERROR|NO_ERROR * */ - long Sequence::validate_sequence(const std::vector &groups) { + long Sequence::validate_sequence(const std::vector &sequence) { + + // sequence is a vector of OperationGroups + for (const auto &group : sequence) { + + // group is a vector of Operations + for (const auto &op : group.operations) { + + if (op.name == "expose") { + } + else + if (op.name == "slit_set") { + } + } + } return NO_ERROR; } /***** Sequencer::Sequence::validate_sequence ******************************/ @@ -244,12 +339,38 @@ namespace Sequencer { /***** Sequencer::Sequence::handle_cli_operation ***************************/ /** * @brief handle incoming operation request - * @param[in] op the name of an operation + * @details This performs all the same steps for a single command as a + * sequence of one operation. This is inefficient for performing + * multiple steps. + * @param[in] args string containing command and any arguments * @return ERROR|NO_ERROR|ABORT * */ - long Sequence::handle_cli_operation(const std::string &op) { - return NO_ERROR; + long Sequence::handle_cli_operation(std::string args) { + std::string_view function("Sequencer::Sequence::handle_cli_operation"); + + if (args.empty()) return ERROR; + + // build a mini-sequence of one command in order to validate it + // + auto commands = { *parse_command(args) }; + + std::vector sequence; + + if ( build_sequence(commands, sequence) != NO_ERROR ) return ERROR; + + if ( validate_sequence(sequence) != NO_ERROR ) return ERROR; + + if ( sequence.empty() || sequence[0].operations.empty() ) { + logwrite(function, "ERROR invalid command '"+args+"'"); + return ERROR; + } + + Operation op = sequence[0].operations[0]; + + // ---------- RUN THE COMMAND -------------------------- + // + return run(op, function); } /***** Sequencer::Sequence::handle_cli_operation ***************************/ diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 6292cd77..dcac10f0 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -527,11 +527,12 @@ namespace Sequencer { long run_script(const std::string &filename); ///< run user script long parse_script(const std::string &filename, - std::vector &out); ///< parse script into commands/args + std::vector &commands_out); ///< parse script into commands/args + std::optional parse_command(std::string &args); long build_sequence(const std::vector &commands, std::vector &sequence_out); ///< build sequence from parsed commands - long validate_sequence(const std::vector &groups); ///< validate sequence - long handle_cli_operation(const std::string &op); ///< handle incoming operation request + long validate_sequence(const std::vector &sequence); ///< validate sequence + long handle_cli_operation(std::string command); ///< handle incoming operation request // publish/subscribe functions // diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp index 4a7489a7..6b2d36ba 100644 --- a/sequencerd/sequence_builder.cpp +++ b/sequencerd/sequence_builder.cpp @@ -31,12 +31,14 @@ namespace Sequencer { continue; } else + if (command.name == "end_parallel") { sequence_out.push_back(group); - group = { OperationType::SERIAL, {} }; // back to default + group = { OperationType::SERIAL, {} }; continue; } else + if (command.name == "move_to_target") { group.operations.emplace_back( Operation { "move_to_target", THR_MOVE_TO_TARGET, @@ -50,6 +52,30 @@ namespace Sequencer { command.params }); } + else + + if (command.name == "slit_set") { + group.operations.emplace_back( Operation { + "slit_set", THR_SLIT_SET, + [this,params=command.params]() { + size_t mode = params.get("mode", VSM_DATABASE); + return slit_set(static_cast(mode)); + }, + command.params + }); + } + + else + + if (command.name == "expose") { + group.operations.emplace_back( Operation { + "expose", THR_SLIT_SET, + [this]() { + return do_exposure("placeholder"); + }, + {} + }); + } else { this->async.enqueue_and_log("Sequencer::Sequence::build_sequence", From 39ab92719e38db64d263a5f6d1c7722a408ed9e5 Mon Sep 17 00:00:00 2001 From: David Hale Date: Tue, 7 Apr 2026 16:13:12 -0700 Subject: [PATCH 07/35] * updates status publishing in slitd * adds subscriber topic handlers to sequencerd * introduces a command handler header-only lib (WIP, not implemented) --- camerad/astrocam.cpp | 3 +- camerad/camerad.cpp | 2 +- common/message_keys.h | 22 +++- sequencerd/command.h | 134 ++++++++++++++++++++++ sequencerd/command_rules.h | 37 ++++++ sequencerd/sequence.cpp | 189 ++++++++----------------------- sequencerd/sequence.h | 25 +++- sequencerd/sequence_builder.cpp | 2 +- sequencerd/sequencer_interface.h | 13 +++ sequencerd/sequencer_server.h | 2 +- sequencerd/sequencerd.cpp | 2 + slitd/slit_interface.cpp | 88 ++++++-------- slitd/slit_interface.h | 18 ++- slitd/slit_server.cpp | 18 +-- slitd/slitd.cpp | 2 +- 15 files changed, 332 insertions(+), 225 deletions(-) create mode 100644 sequencerd/command.h create mode 100644 sequencerd/command_rules.h diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 23d4db37..fb2ddea2 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -29,8 +29,9 @@ namespace AstroCam { nlohmann::json jmessage_out; // build JSON message with my telemetry - jmessage_out[Key::SOURCE] = "camerad"; + jmessage_out[Key::SOURCE] = Topic::CAMERAD; jmessage_out[Key::Camerad::READY] = this->can_expose.load(); + jmessage_out[Key::Camerad::SHUTTERTIME] = this->camera.shutter.get_duration(); // publish JSON message try { diff --git a/camerad/camerad.cpp b/camerad/camerad.cpp index 2d7e0449..551a9a70 100644 --- a/camerad/camerad.cpp +++ b/camerad/camerad.cpp @@ -339,7 +339,7 @@ void async_main(Network::UdpSocket sock) { } while (1) { - std::string message = server.camera.async.dequeue(); // get the latest message from the queue (blocks) + std::string_view message = server.camera.async.dequeue(); // get the latest message from the queue (blocks) retval = sock.Send(message); // transmit the message if (retval < 0) { std::stringstream errstm; diff --git a/common/message_keys.h b/common/message_keys.h index ab7d8a46..13a4d7ff 100644 --- a/common/message_keys.h +++ b/common/message_keys.h @@ -31,7 +31,8 @@ namespace Key { } namespace Camerad { - inline const std::string READY = "ready"; + inline const std::string READY = "ready"; + inline const std::string SHUTTERTIME = "shuttime_sec"; } namespace Acamd { @@ -50,4 +51,23 @@ namespace Key { inline const std::string FINEACQUIRE_LOCKED = "fineacquire_locked"; inline const std::string FINEACQUIRE_RUNNING = "fineacquire_running"; } + + namespace Slitd { + inline const std::string SLITPOSA = "slitposa"; + inline const std::string SLITPOSB = "slitposb"; + inline const std::string SLITW = "slitw"; + inline const std::string SLITO = "slito"; + inline const std::string ISOPEN = "isopen"; + inline const std::string ISHOME = "ishome"; + } + + namespace Tcsd { + inline const std::string TELRA = "telra"; + inline const std::string TELDEC = "teldec"; + inline const std::string ALT = "alt"; + inline const std::string AZ = "az"; + inline const std::string AIRMASS = "airmass"; + inline const std::string CASANGLE = "casangle"; + } + } diff --git a/sequencerd/command.h b/sequencerd/command.h new file mode 100644 index 00000000..ebacb4bc --- /dev/null +++ b/sequencerd/command.h @@ -0,0 +1,134 @@ +/** + * @file command.h + * @brief header-only library for handling commands to daemons + * @details This provides a wrapper to form command and arg list strings. + * Also a wrapper that stores transition states, and validates that + * a command is allowed to be sent while in the current state. + * @author David Hale + * + */ + +#pragma once + +#include +#include +#include +#include + +/***** Sequencer **************************************************************/ +/** + * @brief namespace for the observation sequencer + * + */ +namespace Sequencer { + + /** + * @brief wrapper to form string from command and arglist + */ + struct Command { + std::string name; + std::vector arglist; + + std::string str() const { + std::string strung = name; + for (const auto &arg : arglist) { + strung += " " + arg; + } + return strung; + } + }; + + + /** + * @brief command specs right now just holds min/max number of allowed args + */ + struct CommandSpec { + int min_args; + int max_args; + }; + + using CommandSpecMap = std::unordered_map; + + + /** + * @brief structure contains command and states it can transition from->to + */ + template + struct Transition { + State from; + std::string command; + State to; + }; + + + /** + * @brief contains the functionality of the library + * @details This pairs specs and transitions with a command and holds + * the client object used to communiate with the daemon. + */ + template + class CommandClient { + private: + Common::DaemonClient &client; + const CommandSpecMap &specs; + State state; + const std::vector> &transitions; + + void validate_args( const Command &cmd ) const { + auto it = specs.find( cmd.name ); + if (it == specs.end()) throw std::runtime_error("unknown command: "+cmd.name); + int nargs = cmd.arglist.size(); + if (nargs < it->second.min_args || nargs > it->second.max_args) { + throw std::runtime_error("invalid arg count for "+cmd.name); + } + } + + void validate_order( const Command &cmd ) const { + for (const auto &transition : transitions) { + if (transition.from == state && transition.command == cmd.name) { + return; + } + } + throw std::runtime_error("invalid command order: "+cmd.name); + } + + void advance_state( const Command &cmd ) { + for (const auto &transition : transitions) { + if (transition.from == state && transition.command == cmd.name) { + state = transition.to; + return; + } + } + } + + public: + CommandClient( Common::DaemonClient &client, + const CommandSpecMap &specs, + State initial_state, + const std::vector> &transitions ) + : client(client), + specs(specs), + state(initial_state), + transitions(transitions) { } + + /** + * @brief primary interface to sending commands + * @details This validates the number of args, validates the + * transition state, that this command is allowed to be used + * in the current state, and sends the command. + * @param[in] cmd Command struct contains command and arglist + * @return return value from the client + * + */ + long send( const Command &cmd ) { + validate_args( cmd ); + validate_order( cmd ); + long ret = client.command( cmd.str() ); + advance_state( cmd ); + return ret; + } + + State get_state() const { return state; } + }; +} +/***** Sequencer **************************************************************/ diff --git a/sequencerd/command_rules.h b/sequencerd/command_rules.h new file mode 100644 index 00000000..5ec60136 --- /dev/null +++ b/sequencerd/command_rules.h @@ -0,0 +1,37 @@ + +#pragma once + +#include "command.h" + +#include + +namespace Sequencer { + + enum class CameraState { + IDLE, + READY, + EXPOSING, + READING + }; + + const CommandSpecMap camerad_specs = { + { CAMERAD_ACTIVATE, {0, 4} }, + { CAMERAD_DEACTIVATE, {1, 4} }, + { CAMERAD_OPEN, {0, 1} }, + { CAMERAD_CLOSE, {0, 0} }, + { CAMERAD_EXPTIME, {0, 1} }, + { CAMERAD_EXPOSE, {0, 0} }, + { CAMERAD_READOUT, {0, 2} } + }; + + const std::vector> camerad_transitions = { + { CameraState::IDLE, CAMERAD_OPEN, CameraState::READY }, + { CameraState::READY, CAMERAD_ACTIVATE, CameraState::READY }, + { CameraState::READY, CAMERAD_DEACTIVATE, CameraState::READY }, + { CameraState::READY, CAMERAD_EXPTIME, CameraState::READY }, + { CameraState::READY, CAMERAD_EXPOSE, CameraState::EXPOSING }, + { CameraState::EXPOSING, CAMERAD_READOUT, CameraState::READING }, + { CameraState::READING, CAMERAD_READOUT, CameraState::READY } + }; + +} diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index f1c719d9..564156f3 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -405,16 +405,34 @@ namespace Sequencer { * */ void Sequence::handletopic_camerad(const nlohmann::json &jmessage) { + this->target.column_from_json( DBCol::EXPTIME, Key::Camerad::SHUTTERTIME, jmessage ); if (jmessage.contains(Key::Camerad::READY)) { int isready = jmessage[Key::Camerad::READY].get(); this->can_expose.store(isready, std::memory_order_relaxed); - std::lock_guard lock(camerad_mtx); - this->camerad_cv.notify_all(); } + + std::lock_guard lock(camerad_mtx); + this->camerad_cv.notify_all(); } /***** Sequencer::Sequence::handletopic_camerad ****************************/ + /***** Sequencer::Sequence::handletopic_slitd ******************************/ + /** + * @brief handles Topic::SLITD telemetry + * @param[in] jmessage subscribed-received JSON message + * + */ + void Sequence::handletopic_slitd(const nlohmann::json &jmessage) { + this->target.column_from_json( DBCol::SLITWIDTH, Key::Slitd::SLITW, jmessage ); + this->target.column_from_json( DBCol::SLITOFFSET, Key::Slitd::SLITO, jmessage ); + + std::lock_guard lock(slitd_mtx); + this->slitd_cv.notify_all(); + } + /***** Sequencer::Sequence::handletopic_slitd ******************************/ + + /***** Sequencer::Sequence::handletopic_slicecamd **************************/ /** * @brief handles Topic::SLICECAMD telemetry @@ -426,12 +444,33 @@ namespace Sequencer { bool fineacquirelocked; Common::extract_telemetry_value( jmessage, Key::Slicecamd::FINEACQUIRE_LOCKED, fineacquirelocked ); this->is_fineacquire_locked.store(fineacquirelocked, std::memory_order_relaxed); + std::lock_guard lock(this->fineacquire_mtx); this->fineacquire_cv.notify_all(); } /***** Sequencer::Sequence::handletopic_slicecamd **************************/ + /***** Sequencer::Sequence::handletopic_tcsd *******************************/ + /** + * @brief handles Topic::TCSD telemetry + * @param[in] jmessage subscribed-received JSON message + * + */ + void Sequence::handletopic_tcsd(const nlohmann::json &jmessage) { + this->target.column_from_json( DBCol::TELRA, Key::Tcsd::TELRA, jmessage ); + this->target.column_from_json( DBCol::TELDECL, Key::Tcsd::TELDEC, jmessage ); + this->target.column_from_json( DBCol::ALT, Key::Tcsd::ALT, jmessage ); + this->target.column_from_json( DBCol::AZ, Key::Tcsd::AZ, jmessage ); + this->target.column_from_json( DBCol::AIRMASS, Key::Tcsd::AIRMASS, jmessage ); + this->target.column_from_json( DBCol::CASANGLE, Key::Tcsd::CASANGLE, jmessage ); + + std::lock_guard lock(tcsd_mtx); + this->tcsd_cv.notify_all(); + } + /***** Sequencer::Sequence::handletopic_tcsd *******************************/ + + /***** Sequencer::Sequence::handletopic_acamd ******************************/ /** * @brief handles Topic::ACAMD telemetry @@ -443,6 +482,7 @@ namespace Sequencer { bool acquired; Common::extract_telemetry_value( jmessage, Key::Acamd::IS_ACQUIRED, acquired ); this->is_acam_guiding.store(acquired, std::memory_order_relaxed); + std::lock_guard lock(this->acam_mtx); this->acam_cv.notify_all(); } @@ -769,7 +809,7 @@ namespace Sequencer { * @param[in] obsid_in optional obsid, specify for single-target observation * */ - void Sequence::sequence_start(std::string obsid_in="") { + void Sequence::sequence_start(std::string obsid_in) { std::string_view function("Sequencer::Sequence::sequence_start"); std::ostringstream message; std::string reply; @@ -896,10 +936,7 @@ namespace Sequencer { break; } - // before writing to the completed database table, get current - // telemetry from other daemons. - // - this->get_external_telemetry(); +// this->request_status(tcsd); // force tcsd to publish his status TODO WORK-IN-PROGRESS // Update this target's state in the database // @@ -989,6 +1026,7 @@ namespace Sequencer { // send two commands, one for each if (!activechans.str().empty()) { std::string cmd = CAMERAD_ACTIVATE + activechans.str(); +/*** if ( camerad_cmd.send( { CAMERAD_ACTIVATE, { activechans.str() } } ) != NO_ERROR ) { WIP ***/ if (this->camerad.send(cmd, reply)!=NO_ERROR) { this->async.enqueue_and_log(function, "ERROR sending \""+cmd+"\": "+reply); throw std::runtime_error("camera returned "+reply); @@ -3013,8 +3051,8 @@ namespace Sequencer { { THR_CAMERA_SHUTDOWN, std::bind(&Sequence::camera_shutdown, this) }, { THR_FLEXURE_SHUTDOWN, std::bind(&Sequence::flexure_shutdown, this) }, { THR_FOCUS_SHUTDOWN, std::bind(&Sequence::focus_shutdown, this) }, - { THR_SLICECAM_SHUTDOWN, std::bind(&Sequence::slit_shutdown, this) }, - { THR_SLIT_SHUTDOWN, std::bind(&Sequence::slicecam_shutdown, this) }, + { THR_SLIT_SHUTDOWN, std::bind(&Sequence::slit_shutdown, this) }, + { THR_SLICECAM_SHUTDOWN, std::bind(&Sequence::slicecam_shutdown, this) }, { THR_TCS_SHUTDOWN, std::bind(&Sequence::tcs_shutdown, this) } }; @@ -3615,120 +3653,6 @@ namespace Sequencer { /***** Sequencer::Sequence::make_telemetry_message **************************/ - /***** Sequencer::Sequence::get_external_telemetry **************************/ - /** - * @brief collect telemetry from other daemon(s) - * @details This is used for any telemetry that I need to collect from - * another daemon. Common::collect_telemetry() sends a command - * to the daemon, which will respond with a JSON message. The - * daemon(s) to contact are configured with the TELEM_PROVIDER - * key in the config file. - * - */ - void Sequence::get_external_telemetry() { - // Loop through each configured telemetry provider. This requests - // their telemetry which is returned as a serialized json string - // held in retstring. - // - // handle_json_message() will parse the serialized json string. - // - std::string retstring; - for ( const auto &provider : this->telemetry_providers ) { - Common::collect_telemetry( provider, retstring ); - handle_json_message(retstring); - } - return; - } - /***** Sequencer::Sequence::get_external_telemetry **************************/ - - - /***** Sequencer::Sequence::handle_json_message *****************************/ - /** - * @brief parses incoming telemetry messages - * @details Requesting telemetry from another daemon returns a serialized - * JSON message which needs to be passed in here to parse it. - * @param[in] message_in incoming serialized JSON message (as a string) - * @return ERROR | NO_ERROR - * - */ - long Sequence::handle_json_message( const std::string message_in ) { - std::string_view function("Sequencer::Sequence::handle_json_message"); - std::stringstream message; - - if ( message_in.empty() ) { - logwrite( function, "ERROR empty JSON message" ); - return ERROR; - } - - try { - nlohmann::json jmessage = nlohmann::json::parse( message_in ); - std::string messagetype; - - // jmessage must not contain key "error" and must contain key "messagetype" - // - if ( !jmessage.contains("error") ) { - if ( jmessage.contains("messagetype") && jmessage["messagetype"].is_string() ) { - messagetype = jmessage["messagetype"]; - } - else { - logwrite( function, "ERROR received JSON message with missing or invalid messagetype" ); - return ERROR; - } - } - else { - logwrite( function, "ERROR in JSON message" ); - return ERROR; - } - - // No errors, so disseminate the message contents based on the message type. - // - // column_from_json( colname, jkey, jmessage ) will extract the value of - // expected type with key jkey from json string jmessage, and assign it - // to this->target.external_telemetry[colname] map. It is expected that - // "colname" is the column name in the database. - // - if ( messagetype == "camerainfo" ) { - this->target.column_from_json( "EXPTIME", "SHUTTIME_SEC", jmessage ); - } - else - if ( messagetype == "slitinfo" ) { - this->target.column_from_json( "SLITWIDTH", "SLITW", jmessage ); - this->target.column_from_json( "SLITOFFSET", "SLITO", jmessage ); - } - else - if ( messagetype == "tcsinfo" ) { - this->target.column_from_json( "TELRA", "TELRA", jmessage ); - this->target.column_from_json( "TELDECL", "TELDEC", jmessage ); - this->target.column_from_json( "ALT", "ALT", jmessage ); - this->target.column_from_json( "AZ", "AZ", jmessage ); - this->target.column_from_json( "AIRMASS", "AIRMASS", jmessage ); - this->target.column_from_json( "CASANGLE", "CASANGLE", jmessage ); - } - else - if ( messagetype == "test" ) { - } - else { - message.str(""); message << "ERROR received unhandled JSON message type \"" << messagetype << "\""; - logwrite( function, message.str() ); - return ERROR; - } - } - catch ( const nlohmann::json::parse_error &e ) { - message.str(""); message << "ERROR json exception parsing message: " << e.what(); - logwrite( function, message.str() ); - return ERROR; - } - catch ( const std::exception &e ) { - message.str(""); message << "ERROR parsing message: " << e.what(); - logwrite( function, message.str() ); - return ERROR; - } - - return NO_ERROR; - } - /***** Sequencer::Sequence::handle_json_message *****************************/ - - /***** Sequencer::Sequence::dothread_test_fpoffset **************************/ /** * @brief for testing, calls a Python function from a thread @@ -4054,7 +3978,6 @@ namespace Sequencer { retstring.append( " fpoffset ? | \n" ); retstring.append( " getnext [ ? ]\n" ); retstring.append( " getobsid [ ? ]\n" ); - retstring.append( " gettelem [ ? ]\n" ); retstring.append( " isready [ ? ]\n" ); retstring.append( " moveto [ ? | ]\n" ); retstring.append( " notify [ ? ]\n" ); @@ -4437,24 +4360,6 @@ namespace Sequencer { retstring = rts.str(); } else - // ---------------------------------------------------- - // gettelem -- get external telemetry - // ---------------------------------------------------- - // - if ( testname == "gettelem" ) { - if ( tokens.size() > 1 && tokens[1] == "?" ) { - retstring = "test gettelem\n"; - retstring.append( " Get external telemetry from other daemons.\n" ); - return HELP; - } - this->get_external_telemetry(); - message.str(""); - for ( const auto &[name,data] : this->target.external_telemetry ) { - message << "name=" << name << " valid=" << (data.valid?"T":"F") << " value=" << data.value << "\n"; - } - retstring = message.str(); - } - else // ---------------------------------------------------- // addrow -- insert a (fixed, hard-coded) row into the database diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index dcac10f0..fa5e7420 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -34,6 +34,10 @@ #include "tcsd_commands.h" #include "sequencerd_commands.h" #include "message_keys.h" +/*** Work-In-Progress + * #include "command.h" + * #include "command_rules.h" + */ #include "tcs_constants.h" #include "acam_interface_shared.h" @@ -405,6 +409,10 @@ namespace Sequencer { [this](const nlohmann::json &msg) { handletopic_acamd(msg); } ) }, { Topic::SLICECAMD, std::function( [this](const nlohmann::json &msg) { handletopic_slicecamd(msg); } ) }, + { Topic::SLITD, std::function( + [this](const nlohmann::json &msg) { handletopic_slitd(msg); } ) }, + { Topic::TCSD, std::function( + [this](const nlohmann::json &msg) { handletopic_tcsd(msg); } ) }, { Topic::CAMERAD, std::function( [this](const nlohmann::json &msg) { handletopic_camerad(msg); } ) } }; @@ -456,6 +464,10 @@ namespace Sequencer { std::condition_variable acam_cv; std::mutex camerad_mtx; std::condition_variable camerad_cv; + std::mutex slitd_mtx; + std::condition_variable slitd_cv; + std::mutex tcsd_mtx; + std::condition_variable tcsd_cv; std::mutex wait_mtx; std::condition_variable cv; std::mutex cv_mutex; @@ -510,6 +522,13 @@ namespace Sequencer { Common::DaemonClient slitd { "slitd" }; Common::DaemonClient tcsd { "tcsd" }; +/**** Work-In-Progress + CommandClient camerad_cmd { camerad, + camerad_specs, + CameraState::IDLE, + camerad_transitions}; +*****/ + std::map power_switch; ///< STL map of PowerSwitch objects maps all plugnames to each subsystem float slitoffsetexpose; ///< "virtual slit mode" offset for expose @@ -546,6 +565,8 @@ namespace Sequencer { void handletopic_camerad( const nlohmann::json &jmessage ); void handletopic_acamd( const nlohmann::json &jmessage ); void handletopic_slicecamd( const nlohmann::json &jmessage ); + void handletopic_slitd( const nlohmann::json &jmessage ); + void handletopic_tcsd( const nlohmann::json &jmessage ); void publish_snapshot(); void publish_snapshot(std::string &retstring); void publish_seqstate(); @@ -616,8 +637,6 @@ namespace Sequencer { long target_offset(); void make_telemetry_message( std::string &retstring ); ///< assembles my telemetry message - void get_external_telemetry(); ///< collect telemetry from another daemon - long handle_json_message( const std::string message_in ); ///< parses incoming telemetry messages long set_power_switch( PowerState state, const std::string which, std::chrono::seconds delay ); long check_power_switch( PowerState checkstate, const std::string which, bool &is_set ); @@ -645,7 +664,7 @@ namespace Sequencer { long wait_for_readout(std::string_view caller); ///< wait for readout completion or cancel long wait_for_canexpose(std::string_view caller); ///< wait for camera can_expose - void sequence_start(std::string obsid_in); ///< main sequence start thread. optional obsid_in for single target obs + void sequence_start(std::string obsid_in=""); ///< main sequence start thread. optional obsid_in for single target obs long calib_set(); ///< sets calib according to target entry params long camera_set(); ///< sets camera according to target entry params long slit_set(VirtualSlitMode mode=VSM_DATABASE); ///< sets slit according to target entry params and mode diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp index 6b2d36ba..cf1ba269 100644 --- a/sequencerd/sequence_builder.cpp +++ b/sequencerd/sequence_builder.cpp @@ -69,7 +69,7 @@ namespace Sequencer { if (command.name == "expose") { group.operations.emplace_back( Operation { - "expose", THR_SLIT_SET, + "expose", THR_EXPOSURE, [this]() { return do_exposure("placeholder"); }, diff --git a/sequencerd/sequencer_interface.h b/sequencerd/sequencer_interface.h index f4569e7c..fd59948b 100644 --- a/sequencerd/sequencer_interface.h +++ b/sequencerd/sequencer_interface.h @@ -26,6 +26,19 @@ #define ERROR_TARGETLIST_BAD_HEADER 1001 ///< TODO change this +namespace DBCol { + inline const std::string EXPTIME = "EXPTIME"; + inline const std::string SLITWIDTH = "SLITWIDTH"; + inline const std::string SLITOFFSET = "SLITOFFSET"; + + inline const std::string TELRA = "TELRA"; + inline const std::string TELDECL = "TELDECL"; + inline const std::string ALT = "ALT"; + inline const std::string AZ = "AZ"; + inline const std::string AIRMASS = "AIRMASS"; + inline const std::string CASANGLE = "CASANGLE"; +} + /***** Sequencer **************************************************************/ /** * @namespace Sequencer diff --git a/sequencerd/sequencer_server.h b/sequencerd/sequencer_server.h index f785d42d..aa3440a8 100644 --- a/sequencerd/sequencer_server.h +++ b/sequencerd/sequencer_server.h @@ -82,7 +82,7 @@ namespace Sequencer { // are initialized here. The names are useful just for logging. // this->sequence.calibd.name = "calibd"; - this->sequence.camerad.name = "camerad"; +// this->sequence.camerad.name = "camerad"; this->sequence.filterd.name = "filterd"; this->sequence.flexured.name = "flexured"; this->sequence.focusd.name = "focusd"; diff --git a/sequencerd/sequencerd.cpp b/sequencerd/sequencerd.cpp index 3b9ba63f..26f43523 100644 --- a/sequencerd/sequencerd.cpp +++ b/sequencerd/sequencerd.cpp @@ -131,6 +131,8 @@ int main(int argc, char **argv) { // if ( sequencerd.sequence.init_pubsub( { Topic::CAMERAD, Topic::ACAMD, + Topic::TCSD, + Topic::SLITD, Topic::SLICECAMD } ) == ERROR ) { logwrite(function, "ERROR initializing publisher-subscriber handler"); sequencerd.exit_cleanly(); diff --git a/slitd/slit_interface.cpp b/slitd/slit_interface.cpp index 2569478c..96edc275 100644 --- a/slitd/slit_interface.cpp +++ b/slitd/slit_interface.cpp @@ -104,10 +104,10 @@ namespace Slit { std::string retstring; this->is_open( "", retstring ); - snapshot.isopen = ( retstring=="true" ? true : false ); - if ( snapshot.isopen ) { + status.isopen = ( retstring=="true" ? true : false ); + if ( status.isopen ) { this->is_home( "", retstring ); - snapshot.ishome = ( retstring=="true" ? true : false ); + status.ishome = ( retstring=="true" ? true : false ); } this->get( retstring ); @@ -285,14 +285,14 @@ namespace Slit { return HELP; } - if ( std::isnan(snapshot.width.arcsec()) ) { + if ( std::isnan(status.width.arcsec()) ) { logwrite( "Slit::Interface::offset", "ERROR width not previously set" ); retstring="undefined_width"; return ERROR; } std::stringstream cmd; - cmd << snapshot.width.arcsec() << " " << args; + cmd << status.width.arcsec() << " " << args; return this->set( cmd.str(), retstring ); } @@ -374,7 +374,7 @@ namespace Slit { else fval = std::round( fval * 10.0 ) / 10.0; // round to nearest tenth } reqwidth = SlitDimension( fval, unit ); - reqoffset = snapshot.offset; + reqoffset = status.offset; } if ( tokens.size() == 2 ) { if ( tokens.at(1).find("mm") != std::string::npos ) unit=Unit::MM; else unit=Unit::ARCSEC; @@ -502,30 +502,27 @@ namespace Slit { // this call reads the controller and returns the numeric values // - error = this->read_positions( poswidth, posoffset, snapshot.posA, snapshot.posB ); + error = this->read_positions( poswidth, posoffset, status.posA, status.posB ); // store the current readings in the class // - snapshot.width = SlitDimension( poswidth, Unit::MM ); - snapshot.offset = SlitDimension( posoffset, Unit::MM ); + status.width = SlitDimension( poswidth, Unit::MM ); + status.offset = SlitDimension( posoffset, Unit::MM ); // form the return value // std::stringstream s; if ( args=="mm" ) { - s << std::setprecision(2) << std::fixed << snapshot.width.mm() << " " - << std::setprecision(3) << snapshot.offset.mm() << " mm"; + s << std::setprecision(2) << std::fixed << status.width.mm() << " " + << std::setprecision(3) << status.offset.mm() << " mm"; } else { - s << std::setprecision(2) << std::fixed << snapshot.width.arcsec() << " " - << std::setprecision(3) << snapshot.offset.arcsec(); + s << std::setprecision(2) << std::fixed << status.width.arcsec() << " " + << std::setprecision(3) << status.offset.arcsec(); } retstring = s.str(); - message.str(""); message << "NOTICE:" << Slit::DAEMON_NAME << " " << retstring; - this->async.enqueue( message.str() ); - - this->publish_snapshot(); + this->publish_status(); return error; } @@ -713,55 +710,42 @@ namespace Slit { * @param[in] jmessage_in subscribed-received JSON message * */ - void Interface::handletopic_snapshot( const nlohmann::json &jmessage_in ) { - // If my name is in the jmessage then publish my snapshot - // - if ( jmessage_in.contains( Slit::DAEMON_NAME ) ) { - this->publish_snapshot(); - } - else - if ( jmessage_in.contains( "test" ) ) { - logwrite( "Slit::Interface::handletopic_snapshot", jmessage_in.dump() ); - } + void Interface::handletopic_snapshot( const nlohmann::json &jmessage ) { + if ( jmessage.contains(Topic::SLITD) ) this->publish_status(); } /***** Slit::Interface::handletopic_snapshot ********************************/ - /***** Slit::Interface::publish_snapshot ************************************/ + /***** Slit::Interface::publish_status **************************************/ /** - * @brief publishes snapshot of my telemetry - * @details This publishes a JSON message containing a snapshot of my - * telemetry. + * @brief publishes my status on change + * @param[in] force optional (default=false) force publish irrespective of change * */ - void Interface::publish_snapshot() { - std::string dontcare; - this->publish_snapshot(dontcare); - } - void Interface::publish_snapshot(std::string &retstring) { + void Interface::publish_status(bool force) { + + // unless forced, only publish if there was a change + if ( !force && this->status == this->last_published_status ) return; + nlohmann::json jmessage_out; - jmessage_out["source"] = "slitd"; - jmessage_out["ISOPEN"] = snapshot.isopen; - jmessage_out["ISHOME"] = snapshot.ishome; - jmessage_out["SLITW"] = snapshot.width.arcsec(); - jmessage_out["SLITO"] = snapshot.offset.arcsec(); - jmessage_out["SLITPOSA"] = snapshot.posA; - jmessage_out["SLITPOSB"] = snapshot.posB; - - // for backwards compatibility - jmessage_out["messagetype"] = "slitinfo"; - retstring=jmessage_out.dump(); - retstring.append(JEOF); + jmessage_out[Key::SOURCE] = Topic::SLITD; + jmessage_out[Key::Slitd::ISOPEN] = this->status.isopen; + jmessage_out[Key::Slitd::ISHOME] = this->status.ishome; + jmessage_out[Key::Slitd::SLITW] = this->status.width.arcsec(); + jmessage_out[Key::Slitd::SLITO] = this->status.offset.arcsec(); + jmessage_out[Key::Slitd::SLITPOSA] = this->status.posA; + jmessage_out[Key::Slitd::SLITPOSB] = this->status.posB; + + this->last_published_status = this->status; try { this->publisher->publish( jmessage_out ); } catch ( const std::exception &e ) { - logwrite( "Slit::Interface::publish_snapshot", - "ERROR publishing message: "+std::string(e.what()) ); - return; + logwrite( "Slit::Interface::publish_status", + "ERROR publishing status: "+std::string(e.what()) ); } } - /***** Slit::Interface::publish_snapshot ************************************/ + /***** Slit::Interface::publish_status **************************************/ } diff --git a/slitd/slit_interface.h b/slitd/slit_interface.h index 02111f81..235c2455 100644 --- a/slitd/slit_interface.h +++ b/slitd/slit_interface.h @@ -8,6 +8,7 @@ #pragma once +#include "message_keys.h" #include "network.h" #include "pi.h" #include "logentry.h" @@ -207,16 +208,24 @@ namespace Slit { SlitDimension minwidth; ///< set by config file SlitDimension center; ///< position of center in actuator units - typedef struct { + struct Status { SlitDimension width; SlitDimension offset; float posA=NAN; float posB=NAN; bool ishome=false; bool isopen=false; - } snapshot_t; - snapshot_t snapshot; + bool operator==(const Status& other) const { + return std::tie(width, offset, posA, posB, ishome, isopen) == + std::tie(other.width, other.offset, other.posA, other.posB, other.ishome, other.isopen); + } + + bool operator!=(const Status& other) const { return !(*this == other); } + }; + + Status status; + Status last_published_status; Common::Queue async; @@ -233,8 +242,7 @@ namespace Slit { void stop_subscriber_thread() { Common::PubSubHandler::stop_subscriber_thread(*this); } void handletopic_snapshot( const nlohmann::json &jmessage ); - void publish_snapshot(); - void publish_snapshot(std::string &retstring); + void publish_status(bool force=false); long initialize_class(); long open(); ///< opens the PI socket connection diff --git a/slitd/slit_server.cpp b/slitd/slit_server.cpp index 3a298599..a24960cf 100644 --- a/slitd/slit_server.cpp +++ b/slitd/slit_server.cpp @@ -376,7 +376,7 @@ namespace Slit { } while (1) { - std::string message = slit.interface.async.dequeue(); // get the latest message from the queue (blocks) + std::string_view message = slit.interface.async.dequeue(); // get the latest message from the queue (blocks) retval = sock.Send(message); // transmit the message if (retval < 0) { std::stringstream errstm; @@ -597,22 +597,6 @@ namespace Slit { if ( cmd == SLITD_NATIVE ) { ret = this->interface.send_command( args, retstring ); } - else - - // send telemetry on request - // - if ( cmd == TELEMREQUEST ) { - if ( args=="?" || args=="help" ) { - retstring=TELEMREQUEST+"\n"; - retstring.append( " Returns a serialized JSON message containing telemetry\n" ); - retstring.append( " information, terminated with \"EOF\\n\".\n" ); - ret=HELP; - } - else { - this->interface.publish_snapshot(retstring); - ret = JSON; - } - } // unknown commands generate an error // diff --git a/slitd/slitd.cpp b/slitd/slitd.cpp index 23daaa3e..4fd6a1fb 100644 --- a/slitd/slitd.cpp +++ b/slitd/slitd.cpp @@ -127,7 +127,7 @@ int main(int argc, char **argv) { } std::this_thread::sleep_for( std::chrono::milliseconds(100) ); - slitd.interface.publish_snapshot(); + slitd.interface.publish_status(true); // This will pre-thread N_THREADS threads. // The 0th thread is reserved for the blocking port, and the rest are for the non-blocking port. From e2c366fcbe2902dce7d8b6e865011ba1f55b8b6e Mon Sep 17 00:00:00 2001 From: David Hale Date: Wed, 8 Apr 2026 11:55:42 -0700 Subject: [PATCH 08/35] string/string_view cleanup --- Andor/andor.h | 2 +- Andor/andor_emulator.cpp | 4 +- Andor/andor_emulator.h | 2 +- acamd/acam_interface.h | 14 +++- camerad/camerad.cpp | 2 +- common/common.cpp | 10 +-- common/common.h | 10 +-- emulator/tcs.h | 2 +- sequencerd/command.h | 2 +- sequencerd/sequence.cpp | 114 ++++++++++++++-------------- sequencerd/sequence.h | 26 +++---- sequencerd/sequence_acquisition.cpp | 6 +- sequencerd/sequence_wait.cpp | 10 +-- slicecamd/guimanager.h | 2 +- slicecamd/slicecam_interface.h | 10 ++- slitd/slit_server.cpp | 2 +- tcsd/tcsd_client.cpp | 2 +- tcsd/tcsd_client.h | 2 +- utils/logentry.cpp | 2 +- utils/logentry.h | 2 +- utils/network.cpp | 4 +- utils/network.h | 2 +- 22 files changed, 122 insertions(+), 110 deletions(-) diff --git a/Andor/andor.h b/Andor/andor.h index 40616662..700dd4bb 100644 --- a/Andor/andor.h +++ b/Andor/andor.h @@ -517,7 +517,7 @@ namespace Andor { * @return "emulator" or "sdk" or "null" * */ - inline std::string_view get_andor_object() { + inline std::string_view get_andor_object() const { if ( this->andor == &emulator ) return ANDOR_OBJ_EMULATOR; else if ( this->andor == &sdk ) return ANDOR_OBJ_SDK; diff --git a/Andor/andor_emulator.cpp b/Andor/andor_emulator.cpp index df7a0e9d..dd41a1bb 100644 --- a/Andor/andor_emulator.cpp +++ b/Andor/andor_emulator.cpp @@ -1144,8 +1144,8 @@ namespace Andor { * @return ERROR | NO_ERROR * */ - long SkySim::generate_image( const std::string_view &headerfile, - const std::string_view &outputfile, + long SkySim::generate_image( const std::string headerfile, + const std::string outputfile, const float exptime, const bool ismex, const int simsize ) { diff --git a/Andor/andor_emulator.h b/Andor/andor_emulator.h index 09a6b594..8b8acb9a 100644 --- a/Andor/andor_emulator.h +++ b/Andor/andor_emulator.h @@ -41,7 +41,7 @@ namespace Andor { PyObject* pSkySimModule; - long generate_image( const std::string_view &headerfile, const std::string_view &outputfile, + long generate_image( const std::string headerfile, const std::string outputfile, const float exptime, const bool ismex, const int simsize ); void log_python_arguments(PyObject* pFunction, PyObject* pArgs, PyObject* pKwArgs); diff --git a/acamd/acam_interface.h b/acamd/acam_interface.h index 8c4b317e..3cb5d4f6 100644 --- a/acamd/acam_interface.h +++ b/acamd/acam_interface.h @@ -286,7 +286,7 @@ namespace Acam { * @details the script pushes the indicated file to the Guider GUI display * @param[in] filename fits file to send */ - void push_guider_image( std::string_view filename ) { + void push_guider_image( std::string filename ) { std::string function = "Acam::GuideManager::push_guider_image"; std::stringstream cmd; cmd << push_image << " " @@ -304,7 +304,7 @@ namespace Acam { * @brief calls the push_message script with the supplied message string * @param[in] message message to send */ - void push_guider_message( std::string_view message ) { + void push_guider_message( std::string message ) { std::string function = "Acam::GuideManager::push_guider_message"; std::stringstream cmd; cmd << push_message << " " @@ -613,8 +613,14 @@ namespace Acam { inline std::string get_imagename() { return this->imagename; } inline std::string get_wcsname() { return this->wcsname; } - inline void set_imagename( std::string name_in ) { this->imagename = ( name_in.empty() ? DEFAULT_IMAGENAME : name_in ); return; } - inline void set_wcsname( std::string name_in ) { this->wcsname = name_in; return; } + inline void set_imagename( std::string name_in ) { + this->imagename = ( name_in.empty() ? DEFAULT_IMAGENAME : std::move(name_in) ); + return; + } + inline void set_wcsname( std::string name_in ) { + this->wcsname = std::move(name_in); + return; + } GuideManager guide_manager; diff --git a/camerad/camerad.cpp b/camerad/camerad.cpp index 551a9a70..2d7e0449 100644 --- a/camerad/camerad.cpp +++ b/camerad/camerad.cpp @@ -339,7 +339,7 @@ void async_main(Network::UdpSocket sock) { } while (1) { - std::string_view message = server.camera.async.dequeue(); // get the latest message from the queue (blocks) + std::string message = server.camera.async.dequeue(); // get the latest message from the queue (blocks) retval = sock.Send(message); // transmit the message if (retval < 0) { std::stringstream errstm; diff --git a/common/common.cpp b/common/common.cpp index 196d1fb0..3fc176eb 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -59,7 +59,7 @@ namespace Common { * @param[in] message string to write * */ - void Queue::enqueue_and_log(std::string_view function, std::string_view message) { + void Queue::enqueue_and_log(std::string function, std::string message) { std::lock_guard lock(queue_mutex); message_queue.push(message); notifier.notify_one(); @@ -77,9 +77,9 @@ namespace Common { * @param[in] message string to write * */ - void Queue::enqueue_and_log( std::string_view tag, std::string_view function, std::string_view message ) { + void Queue::enqueue_and_log( std::string tag, std::string function, std::string message ) { std::lock_guard lock(queue_mutex); - std::ostringstream qmessage; + std::stringstream qmessage; qmessage << tag << ":" << message; message_queue.push(qmessage.str()); notifier.notify_one(); @@ -98,12 +98,12 @@ namespace Common { * If the queue is empty, wait untill an element is avaiable. * */ - std::string_view Queue::dequeue(void) { + std::string Queue::dequeue(void) { std::unique_lock lock(queue_mutex); while(message_queue.empty()) { notifier.wait(lock); // release lock as long as the wait and reaquire it afterwards. } - std::string_view message = message_queue.front(); + std::string message = message_queue.front(); message_queue.pop(); return message; } diff --git a/common/common.h b/common/common.h index 49d4db66..d540dbd3 100644 --- a/common/common.h +++ b/common/common.h @@ -1112,7 +1112,7 @@ namespace Common { */ class Queue { private: - std::queue message_queue; + std::queue message_queue; mutable std::mutex queue_mutex; std::condition_variable notifier; bool is_running; @@ -1123,10 +1123,10 @@ namespace Common { void service_running(bool state) { this->is_running = state; }; ///< set service running bool service_running() { return this->is_running; }; ///< is the service running? - void enqueue_and_log(std::string_view function, std::string_view message); - void enqueue_and_log(std::string_view tag, std::string_view function, std::string_view message); - void enqueue(std::string message_view); ///< push an element into the queue. - std::string_view dequeue(void); ///< pop an element from the queue + void enqueue_and_log(std::string function, std::string message); + void enqueue_and_log(std::string tag, std::string function, std::string message); + void enqueue(std::string message); ///< push an element into the queue. + std::string dequeue(void); ///< pop an element from the queue }; /**************** Common::Queue *********************************************/ diff --git a/emulator/tcs.h b/emulator/tcs.h index be4d43ce..99e43820 100644 --- a/emulator/tcs.h +++ b/emulator/tcs.h @@ -136,7 +136,7 @@ namespace TcsEmulator { std::map map_returnval; std::map map_motionval; - long parse_command( const std::string cmd, std::string &retstring ); ///< parse commands for the TCS + long parse_command( std::string cmd, std::string &retstring ); ///< parse commands for the TCS }; /***** TcsEmulator::Interface ***********************************************/ diff --git a/sequencerd/command.h b/sequencerd/command.h index ebacb4bc..b9a37c5d 100644 --- a/sequencerd/command.h +++ b/sequencerd/command.h @@ -112,7 +112,7 @@ namespace Sequencer { transitions(transitions) { } /** - * @brief primary interface to sending commands + * @brief primary interface to sending commands * @details This validates the number of args, validates the * transition state, that this command is allowed to be used * in the current state, and sends the command. diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 564156f3..da073068 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -27,7 +27,7 @@ namespace Sequencer { * */ long Sequence::run( const Operation &op, - std::string_view caller ) { + std::string caller ) { long error=NO_ERROR; logwrite(caller, "starting "+op.name); @@ -57,7 +57,7 @@ namespace Sequencer { * */ long Sequence::run_parallel( const std::vector &ops, - std::string_view caller ) { + std::string caller ) { std::vector> futures; @@ -102,7 +102,7 @@ namespace Sequencer { * */ long Sequence::run_sequence( const std::vector &sequence, - std::string_view caller, + std::string caller, bool continue_on_error ) { long error = NO_ERROR; @@ -147,7 +147,7 @@ namespace Sequencer { * @return ERROR|NO_ERROR|ABORT * */ - long Sequence::run_default_sequence(std::string_view caller) { + long Sequence::run_default_sequence(std::string caller) { std::vector sequence; @@ -213,7 +213,7 @@ namespace Sequencer { * */ long Sequence::run_script(const std::string &filename) { - std::string_view function("Sequencer::Sequence::run_script"); + const std::string function("Sequencer::Sequence::run_script"); std::vector commands; if ( parse_script(filename, commands) != NO_ERROR ) { @@ -347,7 +347,7 @@ namespace Sequencer { * */ long Sequence::handle_cli_operation(std::string args) { - std::string_view function("Sequencer::Sequence::handle_cli_operation"); + const std::string function("Sequencer::Sequence::handle_cli_operation"); if (args.empty()) return ERROR; @@ -702,7 +702,7 @@ namespace Sequencer { * */ void Sequence::dothread_sequencer_async_listener( Sequencer::Sequence &seq, Network::UdpSocket udp ) { - std::string_view function("Sequencer::Sequence::dothread_sequencer_async_listener"); + const std::string function("Sequencer::Sequence::dothread_sequencer_async_listener"); ScopedState thr_state( seq.thread_state_manager, Sequencer::THR_SEQUENCER_ASYNC_LISTENER ); @@ -810,7 +810,7 @@ namespace Sequencer { * */ void Sequence::sequence_start(std::string obsid_in) { - std::string_view function("Sequencer::Sequence::sequence_start"); + const std::string function("Sequencer::Sequence::sequence_start"); std::ostringstream message; std::string reply; std::string targetstatus; @@ -996,7 +996,7 @@ namespace Sequencer { * */ long Sequence::camera_set() { - std::string_view function("Sequencer::Sequence::camera_set"); + const std::string function("Sequencer::Sequence::camera_set"); std::string reply; std::stringstream camcmd; long error=NO_ERROR; @@ -1083,7 +1083,7 @@ namespace Sequencer { * */ long Sequence::slit_set(VirtualSlitMode mode) { - std::string_view function("Sequencer::Sequence::slit_set"); + const std::string function("Sequencer::Sequence::slit_set"); std::string reply, modestr; std::stringstream slitcmd, message; @@ -1140,7 +1140,7 @@ namespace Sequencer { * */ long Sequence::power_init() { - std::string_view function("Sequencer::Sequence::power_init"); + const std::string function("Sequencer::Sequence::power_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_POWER_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_POWER ); @@ -1167,7 +1167,7 @@ namespace Sequencer { * */ long Sequence::power_shutdown() { - std::string_view function("Sequencer::Sequence::power_shutdown"); + const std::string function("Sequencer::Sequence::power_shutdown"); ScopedState thr_state( this->thread_state_manager, Sequencer::THR_POWER_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_POWER ); @@ -1190,7 +1190,7 @@ namespace Sequencer { * */ long Sequence::slit_init() { - std::string_view function("Sequencer::Sequence::slit_init"); + const std::string function("Sequencer::Sequence::slit_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_SLIT_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_SLIT ); @@ -1256,7 +1256,7 @@ namespace Sequencer { * */ long Sequence::slit_shutdown() { - std::string_view function("Sequencer::Sequence::slit_shutdown"); + const std::string function("Sequencer::Sequence::slit_shutdown"); std::stringstream message; std::string reply; long error=NO_ERROR; @@ -1324,7 +1324,7 @@ namespace Sequencer { * */ long Sequence::slicecam_init() { - std::string_view function("Sequencer::Sequence::slicecam_init"); + const std::string function("Sequencer::Sequence::slicecam_init"); this->daemon_manager.clear( Sequencer::DAEMON_SLICECAM ); // slicecamd not ready @@ -1364,7 +1364,7 @@ namespace Sequencer { * */ long Sequence::acam_init() { - std::string_view function("Sequencer::Sequence::acam_init"); + const std::string function("Sequencer::Sequence::acam_init"); this->daemon_manager.clear( Sequencer::DAEMON_ACAM ); // acamd not ready @@ -1424,7 +1424,7 @@ namespace Sequencer { * */ long Sequence::slicecam_shutdown() { - std::string_view function("Sequencer::Sequence::slicecam_shutdown"); + const std::string function("Sequencer::Sequence::slicecam_shutdown"); std::stringstream message; std::string reply; long error=NO_ERROR; @@ -1487,7 +1487,7 @@ namespace Sequencer { * */ long Sequence::acam_shutdown() { - std::string_view function("Sequencer::Sequence::acam_shutdown"); + const std::string function("Sequencer::Sequence::acam_shutdown"); std::stringstream message; std::string reply; long error=NO_ERROR; @@ -1559,7 +1559,7 @@ namespace Sequencer { * */ long Sequence::calib_init() { - std::string_view function("Sequencer::Sequence::calib_init"); + const std::string function("Sequencer::Sequence::calib_init"); this->daemon_manager.clear( Sequencer::DAEMON_CALIB ); @@ -1633,7 +1633,7 @@ namespace Sequencer { * */ long Sequence::calib_shutdown() { - std::string_view function("Sequencer::Sequence::calib_shutdown"); + const std::string function("Sequencer::Sequence::calib_shutdown"); long error=NO_ERROR; ScopedState thr_state( this->thread_state_manager, Sequencer::THR_CALIB_SHUTDOWN ); @@ -1755,7 +1755,7 @@ namespace Sequencer { * */ long Sequence::tcs_shutdown() { - std::string_view function("Sequencer::Sequence::tcs_shutdown"); + const std::string function("Sequencer::Sequence::tcs_shutdown"); std::stringstream message; ScopedState thr_state( this->thread_state_manager, Sequencer::THR_TCS_SHUTDOWN ); @@ -1797,7 +1797,7 @@ namespace Sequencer { * */ long Sequence::flexure_init() { - std::string_view function("Sequencer::Sequence::flexure_init"); + const std::string function("Sequencer::Sequence::flexure_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FLEXURE_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_FLEXURE ); @@ -1836,7 +1836,7 @@ namespace Sequencer { * */ long Sequence::flexure_shutdown() { - std::string_view function("Sequencer::Sequence::flexure_shutdown"); + const std::string function("Sequencer::Sequence::flexure_shutdown"); std::string reply; long error=NO_ERROR; @@ -1897,7 +1897,7 @@ namespace Sequencer { * */ long Sequence::focus_init() { - std::string_view function("Sequencer::Sequence::focus_init"); + const std::string function("Sequencer::Sequence::focus_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FOCUS_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_FOCUS ); @@ -1965,7 +1965,7 @@ namespace Sequencer { * */ long Sequence::focus_shutdown() { - std::string_view function("Sequencer::Sequence::focus_shutdown"); + const std::string function("Sequencer::Sequence::focus_shutdown"); std::string reply; long error=NO_ERROR; @@ -2026,7 +2026,7 @@ namespace Sequencer { * */ long Sequence::camera_init() { - std::string_view function("Sequencer::Sequence::camera_init"); + const std::string function("Sequencer::Sequence::camera_init"); ScopedState thr_state( thread_state_manager, Sequencer::THR_CAMERA_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); @@ -2080,7 +2080,7 @@ namespace Sequencer { * */ long Sequence::camera_shutdown() { - std::string_view function("Sequencer::Sequence::camera_shutdown"); + const std::string function("Sequencer::Sequence::camera_shutdown"); ScopedState thr_state( this->thread_state_manager, Sequencer::THR_CAMERA_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); @@ -2145,7 +2145,7 @@ namespace Sequencer { * */ long Sequence::move_to_target() { - std::string_view function("Sequencer::Sequence::move_to_target"); + const std::string function("Sequencer::Sequence::move_to_target"); std::stringstream message; long error=NO_ERROR; @@ -2299,7 +2299,7 @@ namespace Sequencer { * */ void Sequence::dothread_notify_tcs( Sequencer::Sequence &seq ) { - std::string_view function("Sequencer::Sequence::dothread_notify_tcs"); + const std::string function("Sequencer::Sequence::dothread_notify_tcs"); std::stringstream message; ScopedState thr_state( seq.thread_state_manager, Sequencer::THR_NOTIFY_TCS ); @@ -2371,7 +2371,7 @@ namespace Sequencer { * */ long Sequence::focus_set() { - std::string_view function("Sequencer::Sequence::focus_set"); + const std::string function("Sequencer::Sequence::focus_set"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FOCUS_SET ); @@ -2390,7 +2390,7 @@ namespace Sequencer { * */ long Sequence::flexure_set() { - std::string_view function("Sequencer::Sequence::flexure_set"); + const std::string function("Sequencer::Sequence::flexure_set"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FLEXURE_SET ); @@ -2409,7 +2409,7 @@ namespace Sequencer { * */ long Sequence::calib_set() { - std::string_view function("Sequencer::Sequence::calib_set"); + const std::string function("Sequencer::Sequence::calib_set"); std::stringstream message; ScopedState thr_state( thread_state_manager, Sequencer::THR_CALIBRATOR_SET ); @@ -2494,7 +2494,7 @@ namespace Sequencer { * */ void Sequence::abort_process() { - std::string_view function("Sequencer::Sequence::abort_process"); + const std::string function("Sequencer::Sequence::abort_process"); ScopedState thr_state( this->thread_state_manager, Sequencer::THR_ABORT_PROCESS ); @@ -2529,7 +2529,7 @@ namespace Sequencer { * */ void Sequence::stop_exposure() { - std::string_view function("Sequencer::Sequence::stop_exposure"); + const std::string function("Sequencer::Sequence::stop_exposure"); ScopedState thr_state( this->thread_state_manager, Sequencer::THR_STOP_EXPOSURE ); @@ -2577,7 +2577,7 @@ namespace Sequencer { * */ long Sequence::repeat_exposure() { - std::string_view function("Sequencer::Sequence::repeat_exposure"); + const std::string function("Sequencer::Sequence::repeat_exposure"); std::stringstream message; long error = NO_ERROR; @@ -2667,7 +2667,7 @@ namespace Sequencer { * */ long Sequence::trigger_exposure() { - std::string_view function("Sequencer::Sequence::trigger_exposure"); + const std::string function("Sequencer::Sequence::trigger_exposure"); std::stringstream message; std::string reply; long error=NO_ERROR; @@ -2721,7 +2721,7 @@ namespace Sequencer { * @return ERROR|NO_ERROR * */ - long Sequence::do_exposure(std::string_view caller) { + long Sequence::do_exposure(std::string caller) { this->wait_state_manager.set( Sequencer::SEQ_WAIT_EXPOSE ); @@ -2747,7 +2747,7 @@ namespace Sequencer { * */ void Sequence::modify_exptime( double exptime_in ) { - std::string_view function("Sequencer::Sequence::modify_exptime"); + const std::string function("Sequencer::Sequence::modify_exptime"); std::stringstream message; std::string reply=""; long error = NO_ERROR; @@ -2801,7 +2801,7 @@ namespace Sequencer { * */ long Sequence::startup() { - std::string_view function("Sequencer::Sequence::startup"); + const std::string function("Sequencer::Sequence::startup"); std::stringstream message; long error=NO_ERROR; @@ -3010,7 +3010,7 @@ namespace Sequencer { * */ long Sequence::shutdown() { - std::string_view function("Sequencer::Sequence::shutdown"); + const std::string function("Sequencer::Sequence::shutdown"); long error=ERROR; ScopedState thr_state( this->thread_state_manager, Sequencer::THR_SHUTDOWN ); // this thread is running @@ -3101,8 +3101,8 @@ namespace Sequencer { * @return ERROR or NO_ERROR * */ - long Sequence::parse_state( std::string_view whoami, std::string reply, bool &state ) { - std::string_view function("Sequencer::Sequence::parse_state"); + long Sequence::parse_state( std::string whoami, std::string reply, bool &state ) { + const std::string function("Sequencer::Sequence::parse_state"); std::stringstream message; // Tokenize the reply -- @@ -3159,7 +3159,7 @@ namespace Sequencer { * */ long Sequence::extract_tcs_value( std::string reply, int &value ) { - std::string_view function("Sequencer::Sequence::extract_tcs_value"); + const std::string function("Sequencer::Sequence::extract_tcs_value"); std::stringstream message; std::vector tokens; long error = ERROR; @@ -3247,7 +3247,7 @@ namespace Sequencer { * */ long Sequence::parse_tcs_generic( int value ) { - std::string_view function("Sequencer::Sequence::parse_tcs_generic"); + const std::string function("Sequencer::Sequence::parse_tcs_generic"); std::stringstream message; std::string tcsreply; std::vector tokens; @@ -3296,7 +3296,7 @@ namespace Sequencer { * */ long Sequence::dotype( std::string args ) { - std::string_view function("Sequencer::Sequence::dotype"); + const std::string function("Sequencer::Sequence::dotype"); std::stringstream message; std::string dontcare; return this->dotype( args, dontcare ); @@ -3320,7 +3320,7 @@ namespace Sequencer { * */ long Sequence::dotype( std::string args, std::string &retstring ) { - std::string_view function("Sequencer::Sequence::dotype"); + const std::string function("Sequencer::Sequence::dotype"); std::stringstream message; long error = NO_ERROR; @@ -3367,7 +3367,7 @@ namespace Sequencer { return this->get_dome_position( false, domeazi, telazi ); } long Sequence::get_dome_position( bool poll, double &domeazi, double &telazi ) { - std::string_view function("Sequencer::Sequence::get_dome_position"); + const std::string function("Sequencer::Sequence::get_dome_position"); std::stringstream message; std::string tcsreply; @@ -3429,7 +3429,7 @@ namespace Sequencer { return this->get_tcs_motion( false, state_out ); } long Sequence::get_tcs_motion( bool poll, std::string &state_out ) { - std::string_view function("Sequencer::Sequence::get_tcs_motion"); + const std::string function("Sequencer::Sequence::get_tcs_motion"); std::stringstream message; std::string tcsreply; @@ -3471,7 +3471,7 @@ namespace Sequencer { return this->get_tcs_coords_type( TCSD_WEATHER_COORDS, ra_h, dec_d ); } long Sequence::get_tcs_coords_type( std::string cmd, double &ra_h, double &dec_d ) { - std::string_view function("Sequencer::Sequence::get_tcs_coords"); + const std::string function("Sequencer::Sequence::get_tcs_coords"); std::stringstream message; std::string coordstring; @@ -3524,7 +3524,7 @@ namespace Sequencer { * */ long Sequence::get_tcs_cass( double &cass ) { - std::string_view function("Sequencer::Sequencer::get_tcs_cass"); + const std::string function("Sequencer::Sequencer::get_tcs_cass"); std::stringstream message; std::string tcsreply; @@ -3575,7 +3575,7 @@ namespace Sequencer { * */ long Sequence::target_offset() { - std::string_view function("Sequencer::Sequence::target_offset"); + const std::string function("Sequencer::Sequence::target_offset"); bool is_ra_zero = std::abs(this->target.offset_ra) < std::numeric_limits::epsilon(); bool is_dec_zero = std::abs(this->target.offset_dec) < std::numeric_limits::epsilon(); @@ -3659,7 +3659,7 @@ namespace Sequencer { * */ void Sequence::dothread_test_fpoffset() { - std::string_view function("Sequencer::Sequence::dothread_fpoffset"); + const std::string function("Sequencer::Sequence::dothread_fpoffset"); std::stringstream message; message.str(""); message << "calling fpoffsets.compute_offset() from thread: PyGILState=" << PyGILState_Check(); @@ -3694,7 +3694,7 @@ namespace Sequencer { } long Sequence::set_power_switch( PowerState reqstate, const std::string which, std::chrono::seconds delay ) { - std::string_view function("Sequencer::Sequence::set_power_switch"); + const std::string function("Sequencer::Sequence::set_power_switch"); long error=NO_ERROR; bool need_delay=false; @@ -3800,7 +3800,7 @@ namespace Sequencer { long Sequence::open_hardware( Common::DaemonClient &daemon, const std::string opencmd, const int opentimeout, bool &was_opened, bool forceopen ) { - std::string_view function("Sequencer::Sequence::open_hardware"); + const std::string function("Sequencer::Sequence::open_hardware"); const int maxattempts=3; ///< allow retries connecting to daemon bool isopen=false; std::string reply; @@ -3860,7 +3860,7 @@ namespace Sequencer { * */ long Sequence::connect_to_daemon( Common::DaemonClient &daemon ) { - std::string_view function("Sequencer::Sequence::connect_to_daemon"); + const std::string function("Sequencer::Sequence::connect_to_daemon"); // if not connected to the daemon then connect // @@ -3888,7 +3888,7 @@ namespace Sequencer { * */ long Sequence::daemon_restart(Common::DaemonClient &daemon) { - std::string_view function("Sequencer::Sequence::daemon_restart"); + const std::string function("Sequencer::Sequence::daemon_restart"); std::string command; // the daemon control script must have been specified in the config file @@ -3943,7 +3943,7 @@ namespace Sequencer { * */ long Sequence::test( std::string args, std::string &retstring ) { - std::string_view function("Sequencer::Sequence::test"); + const std::string function("Sequencer::Sequence::test"); std::stringstream message; std::vector tokens; long error = NO_ERROR; diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index fa5e7420..6492f7b1 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -357,7 +357,7 @@ namespace Sequencer { /** @brief safely runs function in a detached thread using lambda to catch exceptions */ - void safe_thread(long (Sequence::*method)(), std::string_view function) { + void safe_thread(long (Sequence::*method)(), std::string function) { std::thread([this, method, function]() { try { (this->*method)(); @@ -537,11 +537,11 @@ namespace Sequencer { // ---------- sequencer scripting and execution tools -------------------- // - long run(const Operation &op, std::string_view function); - long run_parallel(const std::vector &ops, std::string_view function); - long run_default_sequence(std::string_view caller); + long run(const Operation &op, std::string function); + long run_parallel(const std::vector &ops, std::string function); + long run_default_sequence(std::string caller); long run_sequence( const std::vector &groups, - std::string_view caller, + std::string caller, bool continue_on_error=false ); long run_script(const std::string &filename); ///< run user script @@ -614,7 +614,7 @@ namespace Sequencer { bool is_ready() { return this->ready_to_start; } ///< returns the ready_to_start state, set true only after nightly startup long parse_calibration_target(); - long parse_state( std::string_view whoami, std::string reply, bool &state ); ///< parse true|false state from reply string + long parse_state( std::string whoami, std::string reply, bool &state ); ///< parse true|false state from reply string void dothread_test_fpoffset(); ///< for testing, calls Python function from thread long test( std::string args, std::string &retstring ); ///< handles test commands long extract_tcs_value( std::string reply, int &value ); ///< extract value returned by the TCS via tcsd @@ -651,18 +651,18 @@ namespace Sequencer { // These are various jobs that are done in their own threads // long trigger_exposure(); ///< trigger and wait for exposure - long do_exposure(std::string_view caller); ///< wrapper performs and waits for science exposure + long do_exposure(std::string caller); ///< wrapper performs and waits for science exposure void abort_process(); ///< tries to abort everything void stop_exposure(); ///< stop exposure timer in progress long repeat_exposure(); ///< repeat the last exposure void modify_exptime( double exptime_in ); ///< modify exptime while exposure running void dothread_test(); - long wait_for_ontarget(std::string_view caller); ///< wait for TCS Operator - long wait_for_user(std::string_view caller); ///< wait for the user or cancel - long wait_for_exposure(std::string_view caller); ///< wait for exposure completion or cancel - long wait_for_readout(std::string_view caller); ///< wait for readout completion or cancel - long wait_for_canexpose(std::string_view caller); ///< wait for camera can_expose + long wait_for_ontarget(std::string caller); ///< wait for TCS Operator + long wait_for_user(std::string caller); ///< wait for the user or cancel + long wait_for_exposure(std::string caller); ///< wait for exposure completion or cancel + long wait_for_readout(std::string caller); ///< wait for readout completion or cancel + long wait_for_canexpose(std::string caller); ///< wait for camera can_expose void sequence_start(std::string obsid_in=""); ///< main sequence start thread. optional obsid_in for single target obs long calib_set(); ///< sets calib according to target entry params @@ -678,7 +678,7 @@ namespace Sequencer { */ long do_acam_acquire(); long do_slicecam_fineacquire(); - long do_target_acquisition(std::string_view caller); + long do_target_acquisition(std::string caller); long do_target_virtualslit(VirtualSlitMode mode); diff --git a/sequencerd/sequence_acquisition.cpp b/sequencerd/sequence_acquisition.cpp index 695c0423..7da563c8 100644 --- a/sequencerd/sequence_acquisition.cpp +++ b/sequencerd/sequence_acquisition.cpp @@ -16,7 +16,7 @@ namespace Sequencer { * */ long Sequence::do_acam_acquire() { - std::string_view function("Sequencer::Sequence::do_acam_acquire"); + const std::string function("Sequencer::Sequence::do_acam_acquire"); std::string reply; ScopedState thr_state( thread_state_manager, Sequencer::THR_ACQUISITION ); @@ -75,7 +75,7 @@ namespace Sequencer { * */ long Sequence::do_slicecam_fineacquire() { - std::string_view function("Sequencer::Sequence::do_slicecam_fineacquire"); + const std::string function("Sequencer::Sequence::do_slicecam_fineacquire"); ScopedState wait_state(wait_state_manager, Sequencer::SEQ_WAIT_ACQUIRE); @@ -125,7 +125,7 @@ namespace Sequencer { * @return NO_ERROR | ABORT * */ - long Sequence::do_target_acquisition(std::string_view caller) { + long Sequence::do_target_acquisition(std::string caller) { if (this->target.iscal) return NO_ERROR; diff --git a/sequencerd/sequence_wait.cpp b/sequencerd/sequence_wait.cpp index 078c1046..711151fc 100644 --- a/sequencerd/sequence_wait.cpp +++ b/sequencerd/sequence_wait.cpp @@ -16,7 +16,7 @@ namespace Sequencer { * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_ontarget(std::string_view caller) { + long Sequence::wait_for_ontarget(std::string caller) { // waiting for TCS Operator input (or cancel) { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCSOP ); @@ -52,7 +52,7 @@ namespace Sequencer { * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_user(std::string_view caller) { + long Sequence::wait_for_user(std::string caller) { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); @@ -87,7 +87,7 @@ namespace Sequencer { * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_exposure(std::string_view caller) { + long Sequence::wait_for_exposure(std::string caller) { logwrite(caller, "waiting for exposure"); while (!this->cancel_flag.load() && wait_state_manager.is_set(Sequencer::SEQ_WAIT_EXPOSE)) { @@ -113,7 +113,7 @@ namespace Sequencer { * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_readout(std::string_view caller) { + long Sequence::wait_for_readout(std::string caller) { // don't have to wait for readout when using frame transfer if ( this->is_science_frame_transfer ) return NO_ERROR; @@ -144,7 +144,7 @@ namespace Sequencer { * @return NO_ERROR on continue | ABORT on cancel * */ - long Sequence::wait_for_canexpose(std::string_view caller) { + long Sequence::wait_for_canexpose(std::string caller) { this->async.enqueue_and_log(caller, "NOTICE: waiting for camera to be ready to expose"); diff --git a/slicecamd/guimanager.h b/slicecamd/guimanager.h index 2758fe41..d3856bd3 100644 --- a/slicecamd/guimanager.h +++ b/slicecamd/guimanager.h @@ -105,7 +105,7 @@ namespace Slicecam { * @details the script pushes the indicated file to the Guider GUI display * @param[in] filename fits file to send */ - void push_gui_image( std::string_view filename ) { + void push_gui_image( std::string filename ) { const std::string function("Slicecam::GUIManager::push_gui_image"); std::ostringstream cmd; cmd << push_image << " " diff --git a/slicecamd/slicecam_interface.h b/slicecamd/slicecam_interface.h index 0854d1a2..0ca7625e 100644 --- a/slicecamd/slicecam_interface.h +++ b/slicecamd/slicecam_interface.h @@ -207,8 +207,14 @@ namespace Slicecam { inline std::string get_imagename() { return this->imagename; } inline std::string get_wcsname() { return this->wcsname; } - inline void set_imagename( std::string name_in ) { this->imagename = ( name_in.empty() ? DEFAULT_IMAGENAME : name_in ); return; } - inline void set_wcsname( std::string name_in ) { this->wcsname = name_in; return; } + inline void set_imagename( std::string name_in ) { + this->imagename = ( name_in.empty() ? DEFAULT_IMAGENAME : std::move(name_in) ); + return; + } + inline void set_wcsname( std::string name_in ) { + this->wcsname = std::move(name_in); + return; + } Slicecam::FitsInfo fitsinfo; diff --git a/slitd/slit_server.cpp b/slitd/slit_server.cpp index a24960cf..1c39d8f4 100644 --- a/slitd/slit_server.cpp +++ b/slitd/slit_server.cpp @@ -376,7 +376,7 @@ namespace Slit { } while (1) { - std::string_view message = slit.interface.async.dequeue(); // get the latest message from the queue (blocks) + std::string message = slit.interface.async.dequeue(); // get the latest message from the queue (blocks) retval = sock.Send(message); // transmit the message if (retval < 0) { std::stringstream errstm; diff --git a/tcsd/tcsd_client.cpp b/tcsd/tcsd_client.cpp index 4ea8b42b..c0598402 100644 --- a/tcsd/tcsd_client.cpp +++ b/tcsd/tcsd_client.cpp @@ -25,7 +25,7 @@ * @return ERROR | NO_ERROR | HELP * */ - long TcsDaemonClient::init( std::string_view which, std::string &retstring ) { + long TcsDaemonClient::init( std::string which, std::string &retstring ) { std::string function = "TcsDaemonClient::init"; std::stringstream message; std::string reply, tcsname; diff --git a/tcsd/tcsd_client.h b/tcsd/tcsd_client.h index 29d30ed6..679bce15 100644 --- a/tcsd/tcsd_client.h +++ b/tcsd/tcsd_client.h @@ -23,7 +23,7 @@ public: Common::DaemonClient client { "tcsd", '\n', '\n' }; - long init( std::string_view which, std::string &retstring ); + long init( std::string which, std::string &retstring ); long get_name( std::string &name, bool poll ); long get_name( std::string &name ); long poll_name( std::string &name ); diff --git a/utils/logentry.cpp b/utils/logentry.cpp index 6ab2ba79..b4acb65f 100644 --- a/utils/logentry.cpp +++ b/utils/logentry.cpp @@ -175,7 +175,7 @@ void close_log() { * log filestream isn't open. * */ -void logwrite( std::string_view function, std::string_view message ) { +void logwrite( std::string function, std::string message ) { std::ostringstream logmsg; std::lock_guard lock(loglock); // lock mutex to protect from multiple access diff --git a/utils/logentry.h b/utils/logentry.h index f296bcbf..a5c464e3 100644 --- a/utils/logentry.h +++ b/utils/logentry.h @@ -21,6 +21,6 @@ extern unsigned int nextday; /// number of seconds long init_log( std::string logpath, std::string name ); /// initialize the logging system long init_log( std::string logpath, std::string name, bool stderr_in ); /// initialize the logging system void close_log(); /// close the log file stream -void logwrite(std::string_view function, std::string_view message); /// create a time-stamped log entry "message" from "function" +void logwrite(std::string function, std::string message); /// create a time-stamped log entry "message" from "function" #endif diff --git a/utils/network.cpp b/utils/network.cpp index caf3d36e..6ad3e8eb 100644 --- a/utils/network.cpp +++ b/utils/network.cpp @@ -174,8 +174,8 @@ namespace Network { * @return 0 on success, -1 on error * */ - int UdpSocket::Send(std::string_view message) { - std::string_view function = "Network::UdpSocket::Send"; + int UdpSocket::Send(std::string message) { + std::string function = "Network::UdpSocket::Send"; std::ostringstream errstm; ssize_t nbytes; diff --git a/utils/network.h b/utils/network.h index 0fea2000..f2b5637a 100644 --- a/utils/network.h +++ b/utils/network.h @@ -168,7 +168,7 @@ namespace Network { std::string getgroup() { return this->group; }; ///< use to get group int Create(); ///< create a UDP multi-cast socket - int Send(std::string_view message); ///< transmit the message to the UDP socket + int Send(std::string message); ///< transmit the message to the UDP socket int Close(); ///< close the UDP socket connection int Listener(); ///< creates a UDP listener, returns a file descriptor ssize_t Receive( std::string &message ); ///< receive a UDP message from the Listener fd From a04b36e23ef50b2a493a5c3f3a33ddb7d7ac8f52 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 9 Apr 2026 16:26:20 -0700 Subject: [PATCH 09/35] applies new Operation model to startup and shutdown sequences --- sequencerd/sequence.cpp | 195 +++++++++++--------------------- sequencerd/sequence.h | 4 +- sequencerd/sequence_builder.cpp | 6 +- 3 files changed, 70 insertions(+), 135 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index da073068..eed8cf7c 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -29,17 +29,17 @@ namespace Sequencer { long Sequence::run( const Operation &op, std::string caller ) { long error=NO_ERROR; - logwrite(caller, "starting "+op.name); + logwrite(caller, "starting "+op.name()); try { error = op.func(); if (error != NO_ERROR) { - this->async.enqueue_and_log(caller, "ERROR in "+op.name); + this->async.enqueue_and_log(caller, "ERROR in "+op.name()); } } catch (const std::exception &e) { - logwrite(caller, "ERROR in "+op.name+": "+e.what()); + logwrite(caller, "ERROR in "+op.name()+": "+e.what()); error = ERROR; } return error; @@ -74,10 +74,10 @@ namespace Sequencer { for (size_t i=0; i < futures.size(); ++i) { try { error |= futures[i].get(); - logwrite(caller, "completed "+ops[i].name); + logwrite(caller, "completed "+thread_names.at(ops[i].thr)); } catch (const std::exception &e) { - logwrite(caller, "ERROR in "+ops[i].name+": "+e.what()); + logwrite(caller, "ERROR in "+ops[i].name()+": "+e.what()); error |= ERROR; } } @@ -161,7 +161,7 @@ namespace Sequencer { if (this->target.pointmode == Acam::POINTMODE_ACAM) { this->dotype("ONE"); sequence.push_back( { OperationType::PARALLEL, - { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); + { { THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); } else { this->target.pointmode = Acam::POINTMODE_SLIT; @@ -169,12 +169,12 @@ namespace Sequencer { // these are the default operations prior to exposure, // they can be done in parallel sequence.push_back( { OperationType::PARALLEL, - { { "move_to_target", THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, - { "camera_set", THR_CAMERA_SET, [this]{ return camera_set(); } }, - { "focus_set", THR_FOCUS_SET, [this]{ return focus_set(); } }, - { "flexure_set", THR_FLEXURE_SET, [this]{ return flexure_set(); } }, - { "calib_set", THR_CALIB_SET, [this]{ return calib_set(); } }, - { "slit_set", THR_SLIT_SET, [this]{ return slit_set(this->target.iscal ? VSM_DATABASE + { { THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, + { THR_CAMERA_SET, [this]{ return camera_set(); } }, + { THR_FOCUS_SET, [this]{ return focus_set(); } }, + { THR_FLEXURE_SET, [this]{ return flexure_set(); } }, + { THR_CALIB_SET, [this]{ return calib_set(); } }, + { THR_SLIT_SET, [this]{ return slit_set(this->target.iscal ? VSM_DATABASE : VSM_ACQUIRE); } } } } ); } @@ -183,16 +183,16 @@ namespace Sequencer { if (this->target.pointmode != Acam::POINTMODE_ACAM) { sequence.push_back( { OperationType::SERIAL, - { { "target_acquire", THR_ACQUISITION, + { { THR_ACQUISITION, [this,caller]() { return this->do_target_acquisition(caller); } }, - { "target_offset", THR_MOVE_TO_TARGET, + { THR_MOVE_TO_TARGET, [this]() { return this->target_offset(); } }, - { "slit_set", THR_SLIT_SET, + { THR_SLIT_SET, [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } }, - { "expose", THR_EXPOSURE, + { THR_EXPOSURE, [this,caller]() { return this->do_exposure(caller); } } } } ); @@ -215,23 +215,31 @@ namespace Sequencer { long Sequence::run_script(const std::string &filename) { const std::string function("Sequencer::Sequence::run_script"); + // ---------- PARSE ------------------------------------------------------- + std::vector commands; if ( parse_script(filename, commands) != NO_ERROR ) { logwrite(function, "ERROR parsing '"+filename+"'"); return ERROR; } + // ---------- BUILD ------------------------------------------------------- + std::vector sequence; if ( build_sequence(commands, sequence) != NO_ERROR ) { logwrite(function, "ERROR building sequence from '"+filename+"'"); return ERROR; } + // ---------- VALIDATE ---------------------------------------------------- + if ( validate_sequence(sequence) != NO_ERROR ) { logwrite(function, "ERROR validating sequence from '"+filename+"'"); return ERROR; } + // ---------- RUN --------------------------------------------------------- + return run_sequence(sequence, function); } /***** Sequencer::Sequence::run_script *************************************/ @@ -324,10 +332,10 @@ namespace Sequencer { // group is a vector of Operations for (const auto &op : group.operations) { - if (op.name == "expose") { + if (op.name() == "expose") { } else - if (op.name == "slit_set") { + if (op.name() == "slit_set") { } } } @@ -2579,7 +2587,6 @@ namespace Sequencer { long Sequence::repeat_exposure() { const std::string function("Sequencer::Sequence::repeat_exposure"); std::stringstream message; - long error = NO_ERROR; // can only repeat when state is READY // @@ -2603,49 +2610,16 @@ namespace Sequencer { logwrite( function, targetstatus ); - // threads to start, pair their ThreadStatusBit with the function to call - std::vector>> worker_threads; - - worker_threads = { { THR_CAMERA_SET, std::bind(&Sequence::camera_set, this) }, -// { THR_SLIT_SET, std::bind(&Sequence::slit_set, this) } - }; - - // pair their ThreadStatusBit with their future - std::vector>> worker_futures; - - // start the threads - for ( const auto &[thr, func] : worker_threads ) { - worker_futures.emplace_back( thr, std::async(std::launch::async, func) ); - } - - // wait for the threads to complete. these can be cancelled. - for ( auto &[thr, future] : worker_futures) { - try { - error |= future.get(); // wait for this worker to finish - logwrite( function, "NOTICE: worker "+Sequencer::thread_names.at(thr)+" completed"); + std::vector sequence = { + { OperationType::SERIAL, { + { THR_SLIT_SET, + [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } }, + { THR_EXPOSURE, + [this,function]() { return this->do_exposure(function); } } } } - catch (const std::exception& e) { - logwrite( function, "ERROR: worker "+Sequencer::thread_names.at(thr)+" exception: "+std::string(e.what()) ); - return ERROR; - } - } - - if ( this->cancel_flag.load() ) { - this->async.enqueue_and_log( function, "NOTICE: cancelled repeat exposure" ); - return NO_ERROR; - } + }; - // Start the exposure in a thread... - // - auto start_exposure = std::async(std::launch::async, &Sequence::trigger_exposure, this); - try { - error |= start_exposure.get(); - } - catch (const std::exception& e) { - logwrite( function, "ERROR repeat_exposure exception: "+std::string(e.what()) ); - return ERROR; - } - return NO_ERROR; + return run_sequence(sequence, function); } /***** Sequencer::Sequence::repeat_exposure *********************************/ @@ -2828,48 +2802,27 @@ namespace Sequencer { // Everything (except TCS) needs the power control to be running // so initialize the power control first. // - auto start_power = std::async(std::launch::async, &Sequence::power_init, this); - error = start_power.get(); + error = run( { THR_POWER_INIT, [this]{ return power_init(); }, { } }, function ); if ( error != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR starting power control. Will try to continue (but don't hold your breath)" ); + this->async.enqueue_and_log(function, "ERROR starting power control"); + return ERROR; } - // threads to start, pair their ThreadStatusBit with the function to call - // - std::vector>> worker_threads = { - { THR_CALIB_INIT, std::bind(&Sequence::calib_init, this) }, - { THR_CAMERA_INIT, std::bind(&Sequence::camera_init, this) }, - { THR_FLEXURE_INIT, std::bind(&Sequence::flexure_init, this) }, - { THR_FOCUS_INIT, std::bind(&Sequence::focus_init, this) }, - { THR_SLIT_INIT, std::bind(&Sequence::slit_init, this) }, - { THR_TCS_INIT, std::bind(&Sequence::tcs_init, this) } - }; - - std::vector>> worker_futures; - - // launch all of the worker threads listed in the vector + // run these in parallel // - for ( const auto &[thr, func] : worker_threads ) { - worker_futures.emplace_back( thr, std::async(std::launch::async, func) ); - } + error = run_parallel( { + { THR_CALIB_INIT, [this]{ return calib_init(); }, { } }, + { THR_CAMERA_INIT, [this]{ return camera_init(); }, { } }, + { THR_FLEXURE_INIT, [this]{ return flexure_init(); }, { } }, + { THR_FOCUS_INIT, [this]{ return focus_init(); }, { } }, + { THR_SLIT_INIT, [this]{ return slit_init(); }, { } }, + { THR_TCS_INIT, [this]{ return tcs_init(); }, { } } + }, function ); - // get() will block, waiting for the threads to complete - // - for ( auto &[thr, future] : worker_futures) { - try { - // wait for this worker to finish - if ( future.get() != NO_ERROR ) { - logwrite( function, "ERROR from "+Sequencer::thread_names.at(thr)); - error = ERROR; - } - else logwrite(function, Sequencer::thread_names.at(thr)+" success"); - } - catch (const std::exception& e) { - logwrite( function, "ERROR worker "+Sequencer::thread_names.at(thr)+" exception: "+std::string(e.what()) ); - error = ERROR; - break; - } + if ( error != NO_ERROR ) { + this->async.enqueue_and_log(function, "ERROR starting something"); // TODO need granularity here + return ERROR; } // Now the Andor cameras must be done individually, first slicecam, @@ -3037,48 +2990,28 @@ namespace Sequencer { // Everything (except TCS) needs the power control to be running // so make sure power control is initialized before continuing. // - auto start_power = std::async(std::launch::async, &Sequence::power_init, this); - if ( start_power.get() != NO_ERROR ) { + error = run( { THR_POWER_INIT, [this]{ return power_init(); }, { } }, function ); + + if ( error != NO_ERROR ) { this->async.enqueue_and_log( function, "ERROR from power control. Will try to continue (but don't hold your breath)" ); } // container of shutdown threads to launch, // pair their ThreadStatusBit with the function to call // - std::vector>> worker_threads = { - { THR_ACAM_SHUTDOWN, std::bind(&Sequence::acam_shutdown, this) }, - { THR_CALIB_SHUTDOWN, std::bind(&Sequence::calib_shutdown, this) }, - { THR_CAMERA_SHUTDOWN, std::bind(&Sequence::camera_shutdown, this) }, - { THR_FLEXURE_SHUTDOWN, std::bind(&Sequence::flexure_shutdown, this) }, - { THR_FOCUS_SHUTDOWN, std::bind(&Sequence::focus_shutdown, this) }, - { THR_SLIT_SHUTDOWN, std::bind(&Sequence::slit_shutdown, this) }, - { THR_SLICECAM_SHUTDOWN, std::bind(&Sequence::slicecam_shutdown, this) }, - { THR_TCS_SHUTDOWN, std::bind(&Sequence::tcs_shutdown, this) } - }; - - std::vector>> worker_futures; - - // launch the shutdown threads - // - for ( const auto &[thr, func] : worker_threads ) { - worker_futures.emplace_back( thr, std::async(std::launch::async, func) ); - } + error = run_parallel( { + { THR_ACAM_SHUTDOWN, [this]{ return acam_shutdown(); }, { } }, + { THR_CALIB_SHUTDOWN, [this]{ return calib_shutdown(); }, { } }, + { THR_CAMERA_SHUTDOWN, [this]{ return camera_shutdown(); }, { } }, + { THR_FLEXURE_SHUTDOWN, [this]{ return flexure_shutdown(); }, { } }, + { THR_FOCUS_SHUTDOWN, [this]{ return focus_shutdown(); }, { } }, + { THR_SLIT_SHUTDOWN, [this]{ return slit_shutdown(); }, { } }, + { THR_SLICECAM_SHUTDOWN, [this]{ return slicecam_shutdown(); }, { } }, + { THR_TCS_SHUTDOWN, [this]{ return tcs_shutdown(); }, { } } + }, function ); - // wait for the threads to complete - // - for ( auto &[thr, future] : worker_futures) { - try { - error=future.get(); // wait for this worker to finish - logwrite( function, "NOTICE: worker "+Sequencer::thread_names.at(thr)+" completed"); - } - catch (const std::exception& e) { - logwrite( function, "ERROR: worker "+Sequencer::thread_names.at(thr)+" exception: "+std::string(e.what()) ); - error=ERROR; - } - } - - std::stringstream message; - if (error==NO_ERROR) { + std::ostringstream message; + if (error==NO_ERROR) { // TODO need granularity here message << "NOTICE: instrument is shut down"; } else { diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 6492f7b1..8fc3e929 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -335,10 +335,12 @@ namespace Sequencer { /** @brief sequencer operation contains name, status bit, function and params */ struct Operation { - std::string name; ThreadStatusBits thr; std::function func; OperationParams params; + std::string name() const { + return (thread_names.find(thr)==thread_names.end()?"":thread_names.at(thr)); + } }; /** @brief a group of operations stored in a vector with the operation type diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp index cf1ba269..2eddc960 100644 --- a/sequencerd/sequence_builder.cpp +++ b/sequencerd/sequence_builder.cpp @@ -41,7 +41,7 @@ namespace Sequencer { if (command.name == "move_to_target") { group.operations.emplace_back( Operation { - "move_to_target", THR_MOVE_TO_TARGET, + THR_MOVE_TO_TARGET, [this,params=command.params]() { if (params.has("ra") && params.has("dec")) { this->target.ra_hms = params.get(std::string("ra"),std::string("")); @@ -56,7 +56,7 @@ namespace Sequencer { if (command.name == "slit_set") { group.operations.emplace_back( Operation { - "slit_set", THR_SLIT_SET, + THR_SLIT_SET, [this,params=command.params]() { size_t mode = params.get("mode", VSM_DATABASE); return slit_set(static_cast(mode)); @@ -69,7 +69,7 @@ namespace Sequencer { if (command.name == "expose") { group.operations.emplace_back( Operation { - "expose", THR_EXPOSURE, + THR_EXPOSURE, [this]() { return do_exposure("placeholder"); }, From 379a6a5998f8c995e37e9c6d8267c90f694cde45 Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 10 Apr 2026 08:46:05 -0700 Subject: [PATCH 10/35] cleans up cancel_flag a bit --- sequencerd/sequence.cpp | 41 ++++++++++++++++++++--------------------- sequencerd/sequence.h | 16 ++++++++++++---- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index eed8cf7c..08f1a847 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -109,7 +109,7 @@ namespace Sequencer { logwrite(caller, "starting sequence"); for (const auto &group : sequence) { - if (this->cancel_flag.load()) return ABORT; + if (is_cancelled()) return ABORT; // PARALLEL Groups are executed in parallel threads // @@ -123,7 +123,7 @@ namespace Sequencer { // else { for (const auto &op : group.operations) { - if (this->cancel_flag.load()) return ABORT; + if (is_cancelled()) return ABORT; long ret = run(op, caller); error |= ret; @@ -855,7 +855,7 @@ namespace Sequencer { // clear stop flags // - this->cancel_flag.store(false); + clear_cancel_flag(); this->is_ontarget.store(false); this->is_usercontinue.store(false); @@ -939,7 +939,7 @@ namespace Sequencer { break; } - if (this->cancel_flag.load()) { + if (is_cancelled()) { this->async.enqueue_and_log(function, "NOTICE: sequence cancelled"); break; } @@ -2269,19 +2269,19 @@ namespace Sequencer { this->async.enqueue_and_log( function, "NOTICE: waiting for TCS operator to send \"ontarget\" signal" ); - while ( !this->cancel_flag.load() && !this->is_ontarget.load() ) { + while ( !is_cancelled() && !this->is_ontarget.load() ) { std::unique_lock lock(cv_mutex); - this->cv.wait( lock, [this]() { return( this->is_ontarget.load() || this->cancel_flag.load() ); } ); + this->cv.wait( lock, [this]() { return( this->is_ontarget.load() || is_cancelled() ); } ); } this->async.enqueue_and_log( function, "NOTICE: received " - +(this->cancel_flag.load() ? std::string("cancel") : std::string("ontarget")) + +(is_cancelled() ? std::string("cancel") : std::string("ontarget")) +" signal!" ); } // If waiting for TCS operator was cancelled then don't continue // - if ( this->cancel_flag.load() ) return NO_ERROR; + if ( is_cancelled() ) return NO_ERROR; this->is_ontarget.store(false); @@ -2442,7 +2442,7 @@ namespace Sequencer { << " cover=" << ( calinfo.calcover ? "open" : "close" ); logwrite( function, "calib: "+cmd.str() ); - if ( !this->cancel_flag.load() && + if ( !is_cancelled() && this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { this->async.enqueue_and_log( function, "ERROR moving calib door and/or cover" ); throw std::runtime_error("moving calib door and/or cover"); @@ -2451,7 +2451,7 @@ namespace Sequencer { // set the internal calibration lamps // for ( const auto &[lamp,state] : calinfo.lamp ) { - if ( this->cancel_flag.load() ) break; + if ( is_cancelled() ) break; cmd.str(""); cmd << lamp << " " << (state?"on":"off"); message.str(""); message << "power " << cmd.str(); logwrite( function, message.str() ); @@ -2467,7 +2467,7 @@ namespace Sequencer { // // set the dome lamps // // // for ( const auto &[lamp,state] : calinfo.domelamp ) { -// if ( this->cancel_flag.load() ) break; +// if ( is_cancelled() ) break; // cmd.str(""); cmd << TCSD_NATIVE << " NPS " << lamp << " " << (state?1:0); // if ( this->tcsd.command( cmd.str() ) != NO_ERROR ) { // this->async.enqueue_and_log( function, "ERROR "+cmd.str() ); @@ -2478,7 +2478,7 @@ namespace Sequencer { // set the lamp modulators // for ( const auto &[mod,state] : calinfo.lampmod ) { - if ( this->cancel_flag.load() ) break; + if ( is_cancelled() ) break; cmd.str(""); cmd << CALIBD_LAMPMOD << " " << mod << " " << (state?1:0) << " 1000"; if ( this->calibd.command( cmd.str() ) != NO_ERROR ) { this->async.enqueue_and_log( function, "ERROR "+cmd.str() ); @@ -2486,7 +2486,7 @@ namespace Sequencer { } } - if ( this->cancel_flag.load() ) { + if ( is_cancelled() ) { this->async.enqueue_and_log( function, "NOTICE: abort may have left calib system partially set" ); } @@ -2506,7 +2506,7 @@ namespace Sequencer { ScopedState thr_state( this->thread_state_manager, Sequencer::THR_ABORT_PROCESS ); - this->cancel_flag.store(false); + clear_cancel_flag(); // stop any exposure that may be in progress // @@ -2520,8 +2520,7 @@ namespace Sequencer { // set the cancel flag to stop any cancel-able tasks // - this->cancel_flag.store(true); - this->cv.notify_all(); + set_cancel_flag(); // drop into do-one to prevent auto increment to next target // @@ -2548,7 +2547,7 @@ namespace Sequencer { return; } - this->cancel_flag.store(false); + clear_cancel_flag(); // Send command to the camera to stop the exposure. // @@ -2601,7 +2600,7 @@ namespace Sequencer { // clear stop flags // - this->cancel_flag.store(false); + clear_cancel_flag(); this->is_ontarget.store(false); this->is_usercontinue.store(false); @@ -2795,7 +2794,7 @@ namespace Sequencer { // clear stop flags // - this->cancel_flag.store(false); + clear_cancel_flag(); this->is_ontarget.store(false); this->is_usercontinue.store(false); @@ -2979,7 +2978,7 @@ namespace Sequencer { // clear stop flags // - this->cancel_flag.store(false); + clear_cancel_flag(); this->is_ontarget.store(false); this->is_usercontinue.store(false); @@ -4495,7 +4494,7 @@ namespace Sequencer { // clear stop flags // - this->cancel_flag.store(false); + clear_cancel_flag(); this->is_ontarget.store(false); this->is_usercontinue.store(false); diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 8fc3e929..fb513657 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -332,14 +332,16 @@ namespace Sequencer { } }; - /** @brief sequencer operation contains name, status bit, function and params + /** @brief sequencer operation contains status bit, function and params */ struct Operation { ThreadStatusBits thr; std::function func; OperationParams params; - std::string name() const { - return (thread_names.find(thr)==thread_names.end()?"":thread_names.at(thr)); + const std::string &name() const { + static const std::string empty; + auto it = thread_names.find(thr); + return(it==thread_names.end() ? empty : it->second); } }; @@ -444,7 +446,13 @@ namespace Sequencer { this->is_usercontinue.store(false); } - inline void reset_cancel_flag() { this->cancel_flag.store(false); } + inline bool is_cancelled() const { return this->cancel_flag.load(std::memory_order_acquire); } + + inline void clear_cancel_flag() { this->cancel_flag.store(false, std::memory_order_release); } + inline void set_cancel_flag() { + this->cancel_flag.store(true, std::memory_order_release); + this->cv.notify_all(); + } std::map telemetry_providers; ///< map of port[daemon_name] for external telemetry providers From f10060d8bfd5edd9577695437d50230349f27614 Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 10 Apr 2026 13:56:42 -0700 Subject: [PATCH 11/35] adds optional retry function to Operation struct and updates Sequencer::Sequence::run() to use this for retry on error, and uses this to tidy the startup() sequence --- sequencerd/CMakeLists.txt | 1 + sequencerd/sequence.cpp | 299 +++++++++++++---------------- sequencerd/sequence.h | 36 +++- sequencerd/sequence_operations.cpp | 166 ++++++++++++++++ 4 files changed, 331 insertions(+), 171 deletions(-) create mode 100644 sequencerd/sequence_operations.cpp diff --git a/sequencerd/CMakeLists.txt b/sequencerd/CMakeLists.txt index b5804c76..a365d772 100644 --- a/sequencerd/CMakeLists.txt +++ b/sequencerd/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(sequencerd ${SEQUENCER_DIR}/sequence_acquisition.cpp ${SEQUENCER_DIR}/sequence_wait.cpp ${SEQUENCER_DIR}/sequence_builder.cpp + ${SEQUENCER_DIR}/sequence_operations.cpp ${SEQUENCER_DIR}/sequence.cpp ${MYSQL_INCLUDES} ${PYTHON_DEV} diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 08f1a847..23a3516a 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -18,6 +18,42 @@ namespace Sequencer { constexpr long CAMERA_PROLOG_TIMEOUT = 6000; ///< timeout msec to send camera prolog command + /***** Sequencer::Sequence::operation_sleep ********************************/ + /** + * @brief interruptable sleep for operations + * @details Use this if an Operation needs to sleep. Can be cancelled. + * @param[in] delay_ms delay in milliseconds + * + */ + void Sequence::operation_sleep(int delay_ms) { + std::unique_lock lock(cv_mutex); + cv.wait_for(lock, + std::chrono::milliseconds(delay_ms), + [this]() { return is_cancelled(); }); + } + /***** Sequencer::Sequence::operation_sleep ********************************/ + + + /***** Sequencer::Sequence::handle_operation_exception *********************/ + /** + * @brief logs exceptions thrown by operations + * @param[in] eptr exception pointer + * @param[in] name name of operation + * @param[in] caller calling function + * + */ + void Sequence::handle_operation_exception( std::exception_ptr eptr, + std::string name, std::string caller ) { + try { + if (eptr) std::rethrow_exception(eptr); + } + catch (const std::exception &e) { + logwrite(caller, "ERROR in "+name+": "+std::string(e.what())); + } + } + /***** Sequencer::Sequence::handle_operation_exception *********************/ + + /***** Sequencer::Sequence::run ********************************************/ /** * @brief executes a single operation @@ -29,20 +65,40 @@ namespace Sequencer { long Sequence::run( const Operation &op, std::string caller ) { long error=NO_ERROR; + int attempt=1; + logwrite(caller, "starting "+op.name()); - try { - error = op.func(); + while (!is_cancelled()) { - if (error != NO_ERROR) { - this->async.enqueue_and_log(caller, "ERROR in "+op.name()); + try { + error = op.func(); + if (error==NO_ERROR) return NO_ERROR; + if (error==ABORT || + is_cancelled()) return ABORT; + } + catch (...) { + error=ERROR; + handle_operation_exception( std::current_exception(), op.name(), caller ); + } + + if (attempt >= op.max_attempts) { + logwrite(caller, "ERROR "+op.name()+ + " failed after "+std::to_string(attempt)+ + " attempt(s)"); + return ERROR; + } + + ++attempt; + + if (op.on_retry) { + logwrite(caller, "retrying operation "+op.name()+ + " attempt "+std::to_string(attempt)); + if (!is_cancelled()) op.on_retry(); + if (!is_cancelled()) operation_sleep(op.retry_delay); } } - catch (const std::exception &e) { - logwrite(caller, "ERROR in "+op.name()+": "+e.what()); - error = ERROR; - } - return error; + return ( is_cancelled() ? ABORT : error ); } /***** Sequencer::Sequence::run ********************************************/ @@ -64,25 +120,43 @@ namespace Sequencer { // start a thread for each operation // for (const auto &op : ops) { - futures.emplace_back(std::async(std::launch::async, op.func)); + futures.emplace_back( std::async( std::launch::async, [this, op, &caller]() { + if (is_cancelled()) return ABORT; + return run(op, caller); + } ) ); } - long error = NO_ERROR; + bool is_error=false; + bool is_abort=false; - // wait for all threads, collect errors + // wait for all threads to complete before returning, + // logging each status as they arrive // for (size_t i=0; i < futures.size(); ++i) { + std::ostringstream oss; + long ret; try { - error |= futures[i].get(); - logwrite(caller, "completed "+thread_names.at(ops[i].thr)); + ret = futures[i].get(); + oss << ops[i].name(); + if (ret==ABORT) { is_abort=true; oss << " cancelled"; } + else + oss << " completed" << ( (ret==NO_ERROR) ? "" : " with error"); } catch (const std::exception &e) { - logwrite(caller, "ERROR in "+ops[i].name()+": "+e.what()); - error |= ERROR; + oss << " received exception: " << e.what(); + is_error=true; + } + catch (...) { + oss << " received unknown exception"; + is_error=true; } + logwrite(caller, oss.str()); } - return error; + if (is_abort) return ABORT; + if (is_error) return ERROR; + + return NO_ERROR; } /***** Sequencer::Sequence::run_parallel ***********************************/ @@ -102,8 +176,7 @@ namespace Sequencer { * */ long Sequence::run_sequence( const std::vector &sequence, - std::string caller, - bool continue_on_error ) { + std::string caller ) { long error = NO_ERROR; logwrite(caller, "starting sequence"); @@ -117,7 +190,8 @@ namespace Sequencer { long ret = run_parallel(group.operations, caller); error |= ret; - if (ret != NO_ERROR && !continue_on_error) return error; + if (ret != NO_ERROR && + group.on_error == OnError::STOP) return error; } // SERIAL Groups are executed one at a time // @@ -128,7 +202,8 @@ namespace Sequencer { long ret = run(op, caller); error |= ret; - if (ret != NO_ERROR && !continue_on_error) return error; + if (ret != NO_ERROR && + group.on_error == OnError::STOP) return error; } } } @@ -160,7 +235,7 @@ namespace Sequencer { // if (this->target.pointmode == Acam::POINTMODE_ACAM) { this->dotype("ONE"); - sequence.push_back( { OperationType::PARALLEL, + sequence.push_back( { OperationType::PARALLEL, OnError::STOP, { { THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); } else { @@ -168,7 +243,7 @@ namespace Sequencer { // these are the default operations prior to exposure, // they can be done in parallel - sequence.push_back( { OperationType::PARALLEL, + sequence.push_back( { OperationType::PARALLEL, OnError::STOP, { { THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, { THR_CAMERA_SET, [this]{ return camera_set(); } }, { THR_FOCUS_SET, [this]{ return focus_set(); } }, @@ -182,7 +257,7 @@ namespace Sequencer { // ---------- RUN THESE IN SERIES ---------------------- if (this->target.pointmode != Acam::POINTMODE_ACAM) { - sequence.push_back( { OperationType::SERIAL, + sequence.push_back( { OperationType::SERIAL, OnError::STOP, { { THR_ACQUISITION, [this,caller]() { return this->do_target_acquisition(caller); } }, @@ -359,9 +434,14 @@ namespace Sequencer { if (args.empty()) return ERROR; - // build a mini-sequence of one command in order to validate it - // - auto commands = { *parse_command(args) }; + // ----- build a mini-sequence of one command in order to validate it ----- + + auto parsed = parse_command(args); + if (!parsed) { + logwrite(function, "ERROR parsing '"+args+"'"); + return ERROR; + } + auto commands = { *parsed }; std::vector sequence; @@ -376,8 +456,8 @@ namespace Sequencer { Operation op = sequence[0].operations[0]; - // ---------- RUN THE COMMAND -------------------------- - // + // ---------- RUN THE COMMAND --------------------------------------------- + return run(op, function); } /***** Sequencer::Sequence::handle_cli_operation ***************************/ @@ -2610,7 +2690,7 @@ namespace Sequencer { logwrite( function, targetstatus ); std::vector sequence = { - { OperationType::SERIAL, { + { OperationType::SERIAL, OnError::STOP, { { THR_SLIT_SET, [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } }, { THR_EXPOSURE, @@ -2776,7 +2856,6 @@ namespace Sequencer { long Sequence::startup() { const std::string function("Sequencer::Sequence::startup"); std::stringstream message; - long error=NO_ERROR; if ( ! seq_state_manager.are_any_set( Sequencer::SEQ_READY, Sequencer::SEQ_NOTREADY ) ) { message << "ERROR cannot perform system startup while " @@ -2798,147 +2877,33 @@ namespace Sequencer { this->is_ontarget.store(false); this->is_usercontinue.store(false); - // Everything (except TCS) needs the power control to be running - // so initialize the power control first. - // - error = run( { THR_POWER_INIT, [this]{ return power_init(); }, { } }, function ); - - if ( error != NO_ERROR ) { - this->async.enqueue_and_log(function, "ERROR starting power control"); - return ERROR; - } - - // run these in parallel - // - error = run_parallel( { - { THR_CALIB_INIT, [this]{ return calib_init(); }, { } }, - { THR_CAMERA_INIT, [this]{ return camera_init(); }, { } }, - { THR_FLEXURE_INIT, [this]{ return flexure_init(); }, { } }, - { THR_FOCUS_INIT, [this]{ return focus_init(); }, { } }, - { THR_SLIT_INIT, [this]{ return slit_init(); }, { } }, - { THR_TCS_INIT, [this]{ return tcs_init(); }, { } } - }, function ); + Ops ops(this); - if ( error != NO_ERROR ) { - this->async.enqueue_and_log(function, "ERROR starting something"); // TODO need granularity here - return ERROR; - } + // ---------- DEFINE STARTUP SEQUENCE ------------------------------------- - // Now the Andor cameras must be done individually, first slicecam, - // then the acam. - // Sometimes the Andors lose connection with the driver and the only - // recovery seems to be power-cycling the Andor and restarting the - // daemon. Try up to maxattempts times if necessary. - // - const int maxattempts=3; - - // slicecam_init - { - long __error=NO_ERROR; // keep track of the error just for this scope - int attempt=1; - while (attempt <= maxattempts) { - try { - // launch slicecam_init async task and wait for result - std::async(std::launch::async, &Sequence::slicecam_init, this).get(); - logwrite(function, Sequencer::thread_names.at(THR_SLICECAM_INIT)+" success"); - break; + std::vector sequence = { + // stop on error in power_init because everything else needs that + { OperationType::SERIAL, OnError::STOP, + { ops.power_init() } + }, + // everything else can continue on error + { OperationType::PARALLEL, OnError::CONTINUE, + { ops.calib_init(), + ops.camera_init(), + ops.flexure_init(), + ops.focus_init(), + ops.slit_init(), + ops.tcs_init() } + }, + { OperationType::SERIAL, OnError::CONTINUE, + { ops.acam_init(), + ops.slicecam_init() } } - catch (const SlicecamException &e) { - logwrite( function, "ERROR slicecam_init exception: "+std::string(e.what()) ); - - // If there was an error with the SLICECAM cameras, turn them off, - // restart the slicecam daemon, then loop to try again. - if (attempt < maxattempts) { - if ( set_power_switch(OFF, POWER_SLICECAM, std::chrono::seconds(5)) != NO_ERROR ) { - async.enqueue_and_log( function, "ERROR switching off slicecams" ); - __error=ERROR; - break; - } - logwrite(function, "slicecams powered off"); + }; - // restart slicecamd - __error=this->daemon_restart(this->slicecamd); + // ---------- RUN THE SEQUENCE -------------------------------------------- - logwrite(function, "retrying slicecam_init"); - ++attempt; - continue; - } - else { - async.enqueue_and_log( function, "ERROR exceeded max attempts starting slicecam" ); - __error=ERROR; - } - } - catch (const std::exception &e) { - logwrite( function, "ERROR slicecam_init exception: "+std::string(e.what()) ); - __error=ERROR; - break; - } - catch (...) { - logwrite(function, "ERROR unknown slicecam_init exception"); - __error=ERROR; - break; - } - } // end while - if (__error == ERROR) { - async.enqueue_and_log( function, "ERROR slicecam not initialized" ); - error=ERROR; - } - } - - // acam_init - { - long __error=NO_ERROR; // keep track of the error just for this scope - int attempt=1; - while (attempt <= maxattempts) { - try { - // launch acam_init async task and wait for result - std::async(std::launch::async, &Sequence::acam_init, this).get(); - logwrite(function, Sequencer::thread_names.at(THR_ACAM_INIT)+" success"); - break; - } - catch (const AcamException &e) { - logwrite( function, "ERROR acam_init exception: "+std::string(e.what()) ); - - // If there was an error with the ACAM camera, turn it off, - // restart the acam daemon, then loop to try again. - if (e.code == ErrorCode::ERROR_ACAM_CAMERA) { - if (attempt < maxattempts) { - if ( set_power_switch(OFF, POWER_ACAM_CAM, std::chrono::seconds(5)) != NO_ERROR ) { - async.enqueue_and_log( function, "ERROR switching off acam camera" ); - __error=ERROR; - } - logwrite(function, "acam camera powered off"); - - // restart acamd - __error=this->daemon_restart(this->acamd); - - logwrite(function, "retrying acam_init"); - attempt++; - continue; - } - else { - async.enqueue_and_log( function, "ERROR exceeded max attempts starting acam" ); - __error=ERROR; - } - } - break; - } - catch (const std::exception &e) { - logwrite( function, "ERROR acam_init exception: "+std::string(e.what()) ); - __error=ERROR; - break; - } - catch (...) { - logwrite(function, "ERROR unknown acam_init exception"); - __error=ERROR; - break; - } - } // end while - if (__error == ERROR) { - async.enqueue_and_log( function, "ERROR acam not initialized" ); - error=ERROR; - } - } + long error = run_sequence(sequence, function); // change state to READY if all daemons ready w/o error if ( error==NO_ERROR && daemon_manager.are_all_set() ) { diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index fb513657..869727c8 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -343,12 +343,23 @@ namespace Sequencer { auto it = thread_names.find(thr); return(it==thread_names.end() ? empty : it->second); } + int max_attempts=1; + int retry_delay=0; + std::function on_retry; + }; + + /** @brief what to do with an OperationGroup on error + */ + enum class OnError { + CONTINUE, + STOP }; /** @brief a group of operations stored in a vector with the operation type */ struct OperationGroup { OperationType type; + OnError on_error; std::vector operations; }; @@ -359,6 +370,23 @@ namespace Sequencer { OperationParams params; }; + class Ops { + private: + Sequence* seq; + public: + explicit Ops(Sequence* seq); + + Operation acam_init(); + Operation calib_init(); + Operation camera_init(); + Operation flexure_init(); + Operation focus_init(); + Operation power_init(); + Operation slicecam_init(); + Operation slit_init(); + Operation tcs_init(); + }; + /** @brief safely runs function in a detached thread using lambda to catch exceptions */ void safe_thread(long (Sequence::*method)(), std::string function) { @@ -547,12 +575,13 @@ namespace Sequencer { // ---------- sequencer scripting and execution tools -------------------- // - long run(const Operation &op, std::string function); + void operation_sleep(int delay_ms); + void handle_operation_exception(std::exception_ptr eptr, std::string name, std::string caller); + long run(const Operation &op, std::string caller); long run_parallel(const std::vector &ops, std::string function); long run_default_sequence(std::string caller); long run_sequence( const std::vector &groups, - std::string caller, - bool continue_on_error=false ); + std::string caller ); long run_script(const std::string &filename); ///< run user script long parse_script(const std::string &filename, @@ -691,7 +720,6 @@ namespace Sequencer { long do_target_acquisition(std::string caller); long do_target_virtualslit(VirtualSlitMode mode); - long acam_init(); ///< initializes connection to acamd long calib_init(); ///< initializes connection to calibd long camera_init(); ///< initializes connection to camerad diff --git a/sequencerd/sequence_operations.cpp b/sequencerd/sequence_operations.cpp new file mode 100644 index 00000000..a43608d7 --- /dev/null +++ b/sequencerd/sequence_operations.cpp @@ -0,0 +1,166 @@ +/** + * @file sequence_operations.cpp + * @brief implementation of operations + * @author David Hale + * + */ + +#include "sequence.h" + +namespace Sequencer { + + Sequence::Ops::Ops(Sequence* seq) : seq(seq) { } + + /***** Sequencer::Sequence::Ops::acam_init *********************************/ + /** + * @brief defines the acam_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::acam_init() { + Sequence::Operation op; + op.thr = THR_ACAM_INIT; + op.func = [this]() { return seq->acam_init(); }; + op.params = { }; + op.max_attempts = 3; + op.retry_delay = 2000; + op.on_retry = [this]() { + long error=NO_ERROR; + error |= seq->set_power_switch(OFF, POWER_ACAM_CAM, std::chrono::seconds(5)); + error |= seq->daemon_restart(seq->acamd); + return error; + }; + return op; + } + /***** Sequencer::Sequence::Ops::acam_init *********************************/ + + + /***** Sequencer::Sequence::Ops::calib_init ********************************/ + /** + * @brief defines the calib_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::calib_init() { + Sequence::Operation op; + op.thr = THR_CALIB_INIT; + op.func = [this]() { return seq->calib_init(); }; + return op; + } + /***** Sequencer::Sequence::Ops::calib_init ********************************/ + + + /***** Sequencer::Sequence::Ops::camera_init *******************************/ + /** + * @brief defines the camera_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::camera_init() { + Sequence::Operation op; + op.thr = THR_CAMERA_INIT; + op.func = [this]() { return seq->camera_init(); }; + return op; + } + /***** Sequencer::Sequence::Ops::camera_init *******************************/ + + + /***** Sequencer::Sequence::Ops::flexure_init ******************************/ + /** + * @brief defines the flexure_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::flexure_init() { + Sequence::Operation op; + op.thr = THR_FLEXURE_INIT; + op.func = [this]() { return seq->flexure_init(); }; + return op; + } + /***** Sequencer::Sequence::Ops::flexure_init ******************************/ + + + /***** Sequencer::Sequence::Ops::focus_init ********************************/ + /** + * @brief defines the focus_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::focus_init() { + Sequence::Operation op; + op.thr = THR_FOCUS_INIT; + op.func = [this]() { return seq->focus_init(); }; + return op; + } + /***** Sequencer::Sequence::Ops::focus_init ********************************/ + + + /***** Sequencer::Sequence::Ops::power_init ********************************/ + /** + * @brief defines the power_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::power_init() { + Sequence::Operation op; + op.thr = THR_POWER_INIT; + op.func = [this]() { return seq->power_init(); }; + return op; + } + /***** Sequencer::Sequence::Ops::power_init ********************************/ + + + /***** Sequencer::Sequence::Ops::slicecam_init *****************************/ + /** + * @brief defines the slicecam_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::slicecam_init() { + Sequence::Operation op; + op.thr = THR_ACAM_INIT; + op.func = [this]() { return seq->slicecam_init(); }; + op.params = { }; + op.max_attempts = 3; + op.retry_delay = 2000; + op.on_retry = [this]() { + long error=NO_ERROR; + error |= seq->set_power_switch(OFF, POWER_SLICECAM, std::chrono::seconds(5)); + error |= seq->daemon_restart(seq->slicecamd); + return error; + }; + return op; + } + /***** Sequencer::Sequence::Ops::slicecam_init *****************************/ + + + /***** Sequencer::Sequence::Ops::slit_init *********************************/ + /** + * @brief defines the slit_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::slit_init() { + Sequence::Operation op; + op.thr = THR_SLIT_INIT; + op.func = [this]() { return seq->slit_init(); }; + return op; + } + /***** Sequencer::Sequence::Ops::slit_init *********************************/ + + + /***** Sequencer::Sequence::Ops::tcs_init **********************************/ + /** + * @brief defines the tcs_init operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::tcs_init() { + Sequence::Operation op; + op.thr = THR_TCS_INIT; + op.func = [this]() { return seq->tcs_init(); }; + return op; + } + /***** Sequencer::Sequence::Ops::tcs_init **********************************/ + +} From edc934c05f53bdeb07cef7ba93090b951ea4e7ee Mon Sep 17 00:00:00 2001 From: David Hale Date: Sun, 12 Apr 2026 23:51:52 -0700 Subject: [PATCH 12/35] * adds some functionality to Sequence::parse_script() * implements a generic ThreadPool class so that the sequencer isn't continuously creating new threads (something I should have done a long time ago) -- not tested yet --- sequencerd/sequence.cpp | 41 ++++++++++++---- sequencerd/sequence.h | 7 +++ utils/thread_pool.h | 102 ++++++++++++++++++++++++++++++++++++++++ utils/utilities.h | 4 ++ 4 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 utils/thread_pool.h diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 23a3516a..5e7d4dc9 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -116,11 +116,13 @@ namespace Sequencer { std::string caller ) { std::vector> futures; + futures.reserve(ops.size()); - // start a thread for each operation + // start each operation in a thread from the pool // for (const auto &op : ops) { - futures.emplace_back( std::async( std::launch::async, [this, op, &caller]() { + if (is_cancelled()) return ABORT; + futures.emplace_back( pool.enqueue( [this, op, caller]() { if (is_cancelled()) return ABORT; return run(op, caller); } ) ); @@ -333,19 +335,46 @@ namespace Sequencer { */ long Sequence::parse_script(const std::string &filename, std::vector &commands_out) { + const std::string function("Sequencer::Sequence::parse_script"); + std::ifstream file(filename); if (!file.is_open()) { - logwrite("Sequencer::Sequence::parse_script", "ERROR opening '"+filename+"'"); + logwrite(function, "ERROR opening '"+filename+"'"); return ERROR; } + commands_out.clear(); std::string line; + int linenum=0; while (std::getline(file, line)) { + ++linenum; + + // trim off leading and trailing spaces + lrtrim(line); + + // skip empty lines + if (line.empty()) continue; + + // skip comment lines + if (line[0] == '#') continue; + + // strip inline comments, everything after '#' + auto pos = line.find('#'); + if (pos != std::string::npos) line = line.substr(0, pos); auto command = parse_command(line); - if (command) commands_out.push_back(*command); + if (!command) continue; + + command->linenum = linenum; + + commands_out.push_back(std::move(command.value())); + } + + if (commands_out.empty()) { + logwrite(function, "ERROR empty script"); + return ERROR; } return NO_ERROR; @@ -364,10 +393,6 @@ namespace Sequencer { */ std::optional Sequence::parse_command(std::string &args) { - // strip comments, everything after '#' - auto pos = args.find('#'); - if (pos != std::string::npos) args = args.substr(0, pos); - std::istringstream iss(args); std::string word; diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 869727c8..319641a3 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -34,6 +34,7 @@ #include "tcsd_commands.h" #include "sequencerd_commands.h" #include "message_keys.h" +#include "thread_pool.h" /*** Work-In-Progress * #include "command.h" * #include "command_rules.h" @@ -50,6 +51,8 @@ */ namespace Sequencer { + constexpr size_t NTHREADS = 10; ///< number of simultaneous operation threads + /** * @enum ErrorCodes * @brief @@ -299,6 +302,8 @@ namespace Sequencer { std::atomic is_fineacquire_locked{false}; ///< is slicecam fine acquisition locked? std::atomic is_acam_guiding{false}; ///< is acam guiding? + ThreadPool pool; + /** @brief operation type can be SERIAL or PARALLEL */ enum class OperationType { @@ -368,6 +373,7 @@ namespace Sequencer { struct ParsedCommand { std::string name; OperationParams params; + int linenum{0}; }; class Ops { @@ -411,6 +417,7 @@ namespace Sequencer { is_science_frame_transfer(false), notify_tcs_next_target(false), arm_readout_flag(false), + pool(NTHREADS), acquisition_timeout(0), acquisition_max_retrys(-1), tcs_offsetrate_ra(45), diff --git a/utils/thread_pool.h b/utils/thread_pool.h new file mode 100644 index 00000000..e18eb89c --- /dev/null +++ b/utils/thread_pool.h @@ -0,0 +1,102 @@ +/** + * @file thread_pool.h + * @brief general purpose thread pool + * @author David Hale + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ThreadPool { + private: + std::mutex mtx; + std::condition_variable cv_worker; + std::condition_variable cv_backlog; + std::vector workers; + std::queue> tasks; + size_t max_queue_size; + std::atomic stop; + + void worker_loop() { + while (true) { + std::function task; + { + std::unique_lock lock(mtx); + // blocks until work to do, or stop + cv_worker.wait( lock, [this]() { return stop || !tasks.empty(); } ); + if (stop && tasks.empty()) return; + + task = std::move(tasks.front()); + tasks.pop(); + } + // notify backlogged tasks waiting for a free slot + cv_backlog.notify_one(); + // execute the task outside the lock + task(); + } + } + + public: + explicit ThreadPool(size_t nthreads, size_t max_tasks=100) + : max_queue_size(max_tasks), + stop(false) { + if (nthreads==0) throw std::invalid_argument("ThreadPool requires at least one thread"); + if (max_queue_size==0) throw std::invalid_argument("max_queue_size must be at least 1"); + workers.reserve(nthreads); + for (size_t n=0; n < nthreads; ++n) { + workers.emplace_back( [this]() { this->worker_loop(); } ); + } + } + + ~ThreadPool() { + { + std::unique_lock lock(mtx); + // signal workers to drain and exit + stop=true; + } + // wake-up all blocked workers + cv_worker.notify_all(); + cv_backlog.notify_all(); + + // wait for clean shutdown + for (auto &thr : workers) { if (thr.joinable()) thr.join(); } + } + + // not copyable + ThreadPool(const ThreadPool&) = delete; + ThreadPool& operator=(const ThreadPool&) = delete; + + // accepts any callable + arguments, returns future + template + auto enqueue(F&& f, Args&&... args) -> std::future> { + using return_type = std::invoke_result_t; + // wrap in a packaged task + auto task = std::make_shared>( + [f = std::forward(f), + args = std::make_tuple(std::forward(args)...)]() mutable { + return std::apply(std::move(f), std::move(args)); + }); + std::future ret = task->get_future(); + { + std::unique_lock lock(mtx); + // block until the queue has space + cv_backlog.wait( lock, [this]() { return stop || tasks.size() < max_queue_size; } ); + if (stop) throw std::runtime_error("job added to stopped ThreadPool"); + tasks.emplace( [task]() { (*task)(); } ); + } + cv_worker.notify_one(); + return ret; + } +}; diff --git a/utils/utilities.h b/utils/utilities.h index 7203c578..f53f10f9 100644 --- a/utils/utilities.h +++ b/utils/utilities.h @@ -144,6 +144,10 @@ double angular_separation( double ra1, double dec1, double ra2, double dec2 ); static inline void rtrim(std::string &s) { /// trim off trailing whitespace from a string s.erase( std::find_if( s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); } ).base(), s.end() ); } +static inline void ltrim(std::string &s) { /// trim off leading white space + s.erase(std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); } ) ); +} +static inline void lrtrim(std::string &s) { rtrim(s); ltrim(s); } inline bool caseCompareChar( char a, char b ) { return ( std::toupper(a) == std::toupper(b) ); } From cbd901312722fc25bdf14349d6b009f11193e2c4 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 13 Apr 2026 11:36:15 -0700 Subject: [PATCH 13/35] adds unit test for ThreadPool to Sequencer::Sequence::test --- sequencerd/sequence.cpp | 42 +++++++++++++++++++++++++++++++++++++++++ utils/thread_pool.h | 30 +++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 5e7d4dc9..cf6b39ed 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -3917,6 +3917,7 @@ namespace Sequencer { retstring.append( " tablenames [ ? ]\n" ); retstring.append( " threadoffset [ ? ]\n" ); retstring.append( " update ? | { pending | complete | unassigned }\n" ); + retstring.append( " threadpool [ ? ]\n" ); return HELP; } else @@ -4774,6 +4775,47 @@ namespace Sequencer { logwrite( function, message.str() ); } } + else + + // --------------------------------------------------------- + // threadpool -- unit test for thread pool + // --------------------------------------------------------- + // + if ( testname == "threadpool" ) { + if ( tokens.size() > 1 && tokens[1] == "?" ) { + retstring = SEQUENCERD_TEST; + retstring.append( " threadpool\n" ); + retstring.append( " unit test ThreadPool object\n" ); + return HELP; + } + std::vector> futures; + futures.reserve(10); + + // add 15 2+ sec timers to the pool + for (int i=0; i<15; i++) { + std::ostringstream oss; + oss << "pooling timer " << i << ". tasks=" << pool.get_active() << " backlog=" << pool.get_backlog(); + logwrite(function, oss.str()); + futures.emplace_back( pool.enqueue( [this,i]() { + PreciseTimer timer; + timer.delay( 2000+(i*200) ); + } ) ); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + + // wait for timer completion + for (size_t i=0; i < futures.size(); ++i) { + try { + futures[i].get(); + std::ostringstream oss; + oss << "timer " << i << " complete. tasks=" << pool.get_active() << " backlog=" << pool.get_backlog(); + logwrite(function, oss.str()); + } + catch (const std::exception &e) { + logwrite(function, "received exception: "+std::string(e.what())); + } + } + } else { // ---------------------------------------------------- diff --git a/utils/thread_pool.h b/utils/thread_pool.h index e18eb89c..e01c0892 100644 --- a/utils/thread_pool.h +++ b/utils/thread_pool.h @@ -28,6 +28,14 @@ class ThreadPool { std::queue> tasks; size_t max_queue_size; std::atomic stop; + std::atomic active_tasks; + + void log_exception(std::exception_ptr eptr) { + try { if (eptr) std::rethrow_exception(eptr); } + catch (const std::exception &e) { + std::cerr << get_timestamp() << "(ThreadPool::worker_loop) exception: " << e.what(); + } + } void worker_loop() { while (true) { @@ -40,18 +48,25 @@ class ThreadPool { task = std::move(tasks.front()); tasks.pop(); + active_tasks++; } // notify backlogged tasks waiting for a free slot cv_backlog.notify_one(); // execute the task outside the lock - task(); + try { task(); } + catch (...) { + log_exception(std::current_exception()); + } + active_tasks--; } } public: explicit ThreadPool(size_t nthreads, size_t max_tasks=100) : max_queue_size(max_tasks), - stop(false) { + stop(false), + active_tasks(0) + { if (nthreads==0) throw std::invalid_argument("ThreadPool requires at least one thread"); if (max_queue_size==0) throw std::invalid_argument("max_queue_size must be at least 1"); workers.reserve(nthreads); @@ -78,6 +93,17 @@ class ThreadPool { ThreadPool(const ThreadPool&) = delete; ThreadPool& operator=(const ThreadPool&) = delete; + // return number of active tasks + size_t get_active() const { return active_tasks.load(std::memory_order_relaxed); } + + // return number of backlogged tasks + size_t get_backlog() { + std::lock_guard lock(mtx); + size_t backlog = tasks.size(); + size_t avail = workers.size() - active_tasks; + return (backlog > avail) ? (backlog-avail) : 0; + } + // accepts any callable + arguments, returns future template auto enqueue(F&& f, Args&&... args) -> std::future> { From 054930dddf1c588dddfe149f2027a73f0abec292 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 13 Apr 2026 16:04:57 -0700 Subject: [PATCH 14/35] adds functionality to sequence_builder --- sequencerd/sequence.cpp | 10 +-- sequencerd/sequence_builder.cpp | 114 ++++++++++++++++++++------------ 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index cf6b39ed..67617b69 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -190,10 +190,10 @@ namespace Sequencer { // if (group.type == OperationType::PARALLEL) { long ret = run_parallel(group.operations, caller); - error |= ret; + error |= ret; // accumulate any errors if (ret != NO_ERROR && - group.on_error == OnError::STOP) return error; + group.on_error == OnError::STOP) return ret; } // SERIAL Groups are executed one at a time // @@ -202,17 +202,17 @@ namespace Sequencer { if (is_cancelled()) return ABORT; long ret = run(op, caller); - error |= ret; + error |= ret; // accumulate any errors if (ret != NO_ERROR && - group.on_error == OnError::STOP) return error; + group.on_error == OnError::STOP) return ret; } } } logwrite(caller, "sequence complete"); - return error; + return (error==NO_ERROR) ? NO_ERROR : ERROR; } /***** Sequencer::Sequence::run_sequence ***********************************/ diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp index 2eddc960..7eeab2a2 100644 --- a/sequencerd/sequence_builder.cpp +++ b/sequencerd/sequence_builder.cpp @@ -19,70 +19,102 @@ namespace Sequencer { */ long Sequence::build_sequence(const std::vector &commands, std::vector &sequence_out) { - OperationGroup group; + const std::string function("Sequencer::Sequence::build_sequence"); - group.type = OperationType::SERIAL; // default is serial + // local failure-reporting wrapper + auto fail = [&](const ParsedCommand &cmd, const std::string &msg) { + logwrite(function, "ERROR line "+std::to_string(cmd.linenum)+": "+msg); + return ERROR; + }; + + // group of operations currently being processed + std::optional current_group; + + sequence_out.clear(); for (const auto &command : commands) { - if (command.name == "begin_parallel") { - if (!group.operations.empty()) sequence_out.push_back(group); - group = { OperationType::PARALLEL, {} }; + const std::string &name = command.name; + + if (name == "on_error") { + if (!current_group) return fail(command, "'on_error' out of a group"); + if (!command.params.has("action")) return fail(command, "on_error requires action=stop|continue"); + + const std::string val = command.params.get("action",""); + + if (val == "stop") current_group->on_error = OnError::STOP; + else + if (val == "continue") current_group->on_error = OnError::CONTINUE; + else { + return fail(command, "on_error requires action=stop|continue"); + } + } + + if (name == "parallel:" || name == "serial:") { + + if (current_group) return fail(command, "group already open"); + + // create a new group + OperationGroup new_group; + new_group.on_error = OnError::STOP; + new_group.type = (name=="parallel:") ? OperationType::PARALLEL + : OperationType::SERIAL; + current_group = std::move(new_group); continue; } - else - if (command.name == "end_parallel") { - sequence_out.push_back(group); - group = { OperationType::SERIAL, {} }; + if (name == "end") { + if (!current_group) return fail(command, "unexpected 'end' outside group"); + + // end of group, add it to the sequence now + sequence_out.push_back(std::move(*current_group)); + + current_group.reset(); continue; } - else - if (command.name == "move_to_target") { - group.operations.emplace_back( Operation { - THR_MOVE_TO_TARGET, - [this,params=command.params]() { - if (params.has("ra") && params.has("dec")) { - this->target.ra_hms = params.get(std::string("ra"),std::string("")); - this->target.dec_dms = params.get(std::string("dec"),std::string("")); - } - return move_to_target(); - }, - command.params - }); + if (!current_group) return fail(command, "command outside group"); + + Operation &op = current_group->operations.emplace_back(); + + if (name == "move_to_target") { + op.thr = THR_MOVE_TO_TARGET; + op.func = [this,params=command.params]() { + if (params.has("ra") && params.has("dec")) { + this->target.ra_hms = params.get(std::string("ra"),std::string("")); + this->target.dec_dms = params.get(std::string("dec"),std::string("")); + } + return move_to_target(); + }; + op.params = command.params; } else - if (command.name == "slit_set") { - group.operations.emplace_back( Operation { - THR_SLIT_SET, - [this,params=command.params]() { - size_t mode = params.get("mode", VSM_DATABASE); - return slit_set(static_cast(mode)); - }, - command.params - }); + if (name == "slit_set") { + op.thr = THR_SLIT_SET; + op.func = [this,params=command.params]() { + size_t mode = params.get("mode", VSM_DATABASE); + return slit_set(static_cast(mode)); + }; + op.params = command.params; } - else - if (command.name == "expose") { - group.operations.emplace_back( Operation { - THR_EXPOSURE, - [this]() { - return do_exposure("placeholder"); - }, - {} - }); + if (name == "expose") { + op.thr = THR_EXPOSURE; + op.func = [this]() { + return do_exposure("placeholder"); + }; + op.params = command.params; } else { this->async.enqueue_and_log("Sequencer::Sequence::build_sequence", "ERROR unknown command '"+command.name+"'"); + continue; } + current_group->operations.emplace_back(std::move(op)); } - if (!group.operations.empty()) sequence_out.push_back(group); return NO_ERROR; } From cf6ae4b7df6bc5f35ea4736afa5536e1c088e72f Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 17 Apr 2026 18:26:51 -0700 Subject: [PATCH 15/35] replaces if/else in build_sequence with an operation builder map (just a start, needs to be completed) --- sequencerd/CMakeLists.txt | 1 + sequencerd/operation_builders.cpp | 79 +++++++++++++++++++++++++++++++ sequencerd/sequence.h | 6 +++ sequencerd/sequence_builder.cpp | 72 +++++++++++++++++++++------- 4 files changed, 141 insertions(+), 17 deletions(-) create mode 100644 sequencerd/operation_builders.cpp diff --git a/sequencerd/CMakeLists.txt b/sequencerd/CMakeLists.txt index a365d772..7e50812c 100644 --- a/sequencerd/CMakeLists.txt +++ b/sequencerd/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(sequencerd ${SEQUENCER_DIR}/sequence_acquisition.cpp ${SEQUENCER_DIR}/sequence_wait.cpp ${SEQUENCER_DIR}/sequence_builder.cpp + ${SEQUENCER_DIR}/operation_builders.cpp ${SEQUENCER_DIR}/sequence_operations.cpp ${SEQUENCER_DIR}/sequence.cpp ${MYSQL_INCLUDES} diff --git a/sequencerd/operation_builders.cpp b/sequencerd/operation_builders.cpp new file mode 100644 index 00000000..d5c09880 --- /dev/null +++ b/sequencerd/operation_builders.cpp @@ -0,0 +1,79 @@ +/** + * @file operation_builders.cpp + * @brief implementation for building operations from command names + * @author David Hale + * + */ + +#include "sequence.h" + +namespace Sequencer { + + /***** Sequencer::Sequence::init_operation_builders ************************/ + /** + * @brief initializes registration between name and operation + * @details Maps command names to function that populate an Operation + * from a ParsedCommand. + * + */ + void Sequence::init_operation_builders() { + + // ---------- FOCUS_SET -------------------------------------------------- + + op_builders["focus_set"] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_FOCUS_SET; + op.func = [this]() { + return focus_set(); + }; + op.params = cmd.params; + }; + + // ---------- MOVE_TO_TARGET --------------------------------------------- + + op_builders["move_to_target"] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_MOVE_TO_TARGET; + op.func = [this,params=cmd.params]() { + if (params.has("ra") && params.has("dec")) { + this->target.ra_hms = params.get(std::string("ra"),std::string("")); + this->target.dec_dms = params.get(std::string("dec"),std::string("")); + } + return move_to_target(); + }; + op.params = cmd.params; + }; + + // ---------- SLIT_SET --------------------------------------------------- + + op_builders["slit_set"] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_SLIT_SET; + op.func = [this,params=cmd.params]() { + size_t mode = params.get("mode", VSM_DATABASE); + return slit_set(static_cast(mode)); + }; + op.params = cmd.params; + }; + + // ---------- EXPOSE ----------------------------------------------------- + + op_builders["expose"] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_EXPOSURE; + op.func = [this]() { + return do_exposure("operation_builder"); + }; + op.params = cmd.params; + }; + + // ---------- FOCUS_SET -------------------------------------------------- + + op_builders["focus_set"] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_FOCUS_SET; + op.func = [this]() { + return focus_set(); + }; + op.params = cmd.params; + }; + + } + /***** Sequencer::Sequence::init_operation_builders ************************/ + +} diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 319641a3..b25608f5 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -393,6 +393,12 @@ namespace Sequencer { Operation tcs_init(); }; + using OperationBuilder = std::function; + + std::unordered_map op_builders; + + void init_operation_builders(); + /** @brief safely runs function in a detached thread using lambda to catch exceptions */ void safe_thread(long (Sequence::*method)(), std::string function) { diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp index 7eeab2a2..1d4b143c 100644 --- a/sequencerd/sequence_builder.cpp +++ b/sequencerd/sequence_builder.cpp @@ -12,8 +12,15 @@ namespace Sequencer { /***** Sequencer::Sequence::build_sequence *********************************/ /** * @brief build a sequence from parsed commands + * @details This accepts a vector of ParsedCommands and returns by reference + * a sequence, which is one or more OperationGroups. Accepts special + * ".dsl" commands for the Sequencer Domain Specific Language. + * Start a group with "parallel:" or "serial:", finish group + * with "end". Include "on_error stop|continue" to set behavior + * of that group when an error occurs in an operation in that + * group. * @param[in] commands vector of ParsedCommands - * @param[out] sequence_out the operation group to execute + * @param[out] sequence_out vector of Operation Groups (sequence) to execute * @return ERROR|NO_ERROR|ABORT * */ @@ -27,29 +34,22 @@ namespace Sequencer { return ERROR; }; - // group of operations currently being processed + // create temporay group of operations currently being processed std::optional current_group; + // empty the sequence sequence_out.clear(); + // loop through vector of commands + // for (const auto &command : commands) { const std::string &name = command.name; - if (name == "on_error") { - if (!current_group) return fail(command, "'on_error' out of a group"); - if (!command.params.has("action")) return fail(command, "on_error requires action=stop|continue"); - - const std::string val = command.params.get("action",""); - - if (val == "stop") current_group->on_error = OnError::STOP; - else - if (val == "continue") current_group->on_error = OnError::CONTINUE; - else { - return fail(command, "on_error requires action=stop|continue"); - } - } + // ---------- SPECIAL COMMAND { parallel: | serial: } ------------------ + // start a group + // if (name == "parallel:" || name == "serial:") { if (current_group) return fail(command, "group already open"); @@ -63,18 +63,47 @@ namespace Sequencer { continue; } + // ---------- SPECIAL COMMAND { end } ---------------------------------- + + // finish a group + // if (name == "end") { if (!current_group) return fail(command, "unexpected 'end' outside group"); - // end of group, add it to the sequence now + // add it to the sequence now sequence_out.push_back(std::move(*current_group)); current_group.reset(); continue; } + // ---------- SPECIAL COMMAND { on_error } ----------------------------- + + // set how does the group behave on error + // + if (name == "on_error") { + // must be in a group for on_error to mean anything + if (!current_group) return fail(command, "'on_error' out of a group"); + if (!command.params.has("action")) return fail(command, "on_error requires action=stop|continue"); + + const std::string val = command.params.get("action",""); + + if (val == "stop") current_group->on_error = OnError::STOP; + else + if (val == "continue") current_group->on_error = OnError::CONTINUE; + else { + return fail(command, "on_error requires action=stop|continue"); + } + continue; + } + + // anything other than { parallel | serial | end | on_error } is a + // command to parse, so a group must have been started first. + // if (!current_group) return fail(command, "command outside group"); + // ---------- MAKE OPERATION FOR GIVEN COMMAND ------------------------- + Operation &op = current_group->operations.emplace_back(); if (name == "move_to_target") { @@ -102,8 +131,17 @@ namespace Sequencer { if (name == "expose") { op.thr = THR_EXPOSURE; + op.func = [this,function]() { + return do_exposure(function); + }; + op.params = command.params; + } + else + + if (name == "focus_set") { + op.thr = THR_FOCUS_SET; op.func = [this]() { - return do_exposure("placeholder"); + return focus_set(); }; op.params = command.params; } From 866c324333fb6db2cf8bb8f24606abe1f17f47cd Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 20 Apr 2026 15:35:51 -0700 Subject: [PATCH 16/35] expand sequencerd DSL ops and validation * adds subsystem command coverage in sequencerd_command and operation_builders * write Ops factories and sequence build/parse flow for daemon-backed operations * preserve positional args for passthrough operations * adds functionality to validate_sequence with safety checks for parallel/serial * adds engineering mode to relax checks --- common/sequencerd_commands.h | 8 + sequencerd/command_rules.h | 466 +++++++++++++++++++++++++++-- sequencerd/operation_builders.cpp | 270 ++++++++++++++--- sequencerd/sequence.cpp | 377 ++++++++++++++++++++++- sequencerd/sequence.h | 27 +- sequencerd/sequence_builder.cpp | 55 +--- sequencerd/sequence_operations.cpp | 194 +++++++++++- sequencerd/sequencer_server.cpp | 11 + 8 files changed, 1293 insertions(+), 115 deletions(-) diff --git a/common/sequencerd_commands.h b/common/sequencerd_commands.h index adce01ee..3a6504e2 100644 --- a/common/sequencerd_commands.h +++ b/common/sequencerd_commands.h @@ -10,6 +10,7 @@ const std::string SEQUENCERD_ABORT = "abort"; const std::string SEQUENCERD_CONFIG = "config"; const std::string SEQUENCERD_DOTYPE = "do"; +const std::string SEQUENCERD_ENGINEERING = "engineering"; const std::string SEQUENCERD_EXIT = "exit"; const std::string SEQUENCERD_GETONETARGET = "getone"; const std::string SEQUENCERD_GUIDE = "guide"; @@ -36,7 +37,10 @@ const std::string SEQUENCERD_ACAM = "acam"; const std::string SEQUENCERD_CALIB = "calib"; const std::string SEQUENCERD_CAMERA = "camera"; const std::string SEQUENCERD_FILTER = "filter"; +const std::string SEQUENCERD_FLEXURE = "flexure"; +const std::string SEQUENCERD_FOCUS = "focus"; const std::string SEQUENCERD_POWER = "power"; +const std::string SEQUENCERD_SLICECAM = "slicecam"; const std::string SEQUENCERD_SLIT = "slit"; const std::string SEQUENCERD_TCS = "tcs"; const std::vector SEQUENCERD_SYNTAX = { @@ -44,13 +48,17 @@ const std::vector SEQUENCERD_SYNTAX = { SEQUENCERD_CALIB+" ...", SEQUENCERD_CAMERA+" ...", SEQUENCERD_FILTER+" ...", + SEQUENCERD_FLEXURE+" ...", + SEQUENCERD_FOCUS+" ...", SEQUENCERD_POWER+" ...", + SEQUENCERD_SLICECAM+" ...", SEQUENCERD_SLIT+" ...", SEQUENCERD_TCS+" ...", "", SEQUENCERD_ABORT, SEQUENCERD_CONFIG, SEQUENCERD_DOTYPE+" [ one | all ]", + SEQUENCERD_ENGINEERING+" [ true | false ]", SEQUENCERD_EXIT, SEQUENCERD_GETONETARGET, SEQUENCERD_GUIDE, diff --git a/sequencerd/command_rules.h b/sequencerd/command_rules.h index 5ec60136..2cb086a1 100644 --- a/sequencerd/command_rules.h +++ b/sequencerd/command_rules.h @@ -1,37 +1,461 @@ +/** + * @file command_rules.h + * @brief rules for validating command parameters and transitioning states + * @details Each daemon has an enum of states, min/max number of parameters + * and a list of from-to states for each command. The daemon must + * be in the "from" state to accept the command, which then takes it + * to the "to" state. + * @author David Hale + * + */ #pragma once - #include "command.h" - #include namespace Sequencer { + // ---------- CAMERAD -------------------------------------------------------- + enum class CameraState { - IDLE, - READY, - EXPOSING, - READING - }; + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + EXPOSING, ///< exposure in progress + READING, ///< readout in progress + PAUSED ///< exposure paused + }; const CommandSpecMap camerad_specs = { - { CAMERAD_ACTIVATE, {0, 4} }, - { CAMERAD_DEACTIVATE, {1, 4} }, - { CAMERAD_OPEN, {0, 1} }, - { CAMERAD_CLOSE, {0, 0} }, - { CAMERAD_EXPTIME, {0, 1} }, - { CAMERAD_EXPOSE, {0, 0} }, - { CAMERAD_READOUT, {0, 2} } + { CAMERAD_ABORT, {0, 0} }, + { CAMERAD_ACTIVATE, {0, 4} }, ///< 0 or more of: [ { U G R I } ] + { CAMERAD_BIN, {1, 2} }, ///< [ ] + { CAMERAD_BOI, {1,21} }, ///< | [full| [ [...]]]] + { CAMERAD_CLOSE, {0, 0} }, + { CAMERAD_DEACTIVATE, {1, 4} }, ///< 1 or more of: { U G R I } + { CAMERAD_EXPTIME, {0, 1} }, ///< [ ] + { CAMERAD_EXPOSE, {0, 0} }, + { CAMERAD_FITSNAME, {0, 1} }, + { CAMERAD_FITSNAMING, {0, 1} }, ///< [ time | number] + { CAMERAD_FRAMETRANSFER, {1, 2} }, ///< | | all [ yes | no ] + { CAMERAD_GEOMETRY, {1, 3} }, ///< | [ | ] + { CAMERAD_IMDIR, {0, 1} }, ///< [ ] + { CAMERAD_IMNUM, {0, 1} }, ///< [ ] + { CAMERAD_IMSIZE, {1, 5} }, ///< | [ ] + { CAMERAD_KEY, {1, 2} }, ///< list | KEYWORD=VALUE//COMMENT + { CAMERAD_LOAD, {0, 2} }, ///< [ ] + { CAMERAD_MODEXPTIME, {1, 1} }, ///< + { CAMERAD_OPEN, {0, 4} }, ///< 0 or more of: [ { 0 1 2 3 } ] + { CAMERAD_PAUSE, {0, 0} }, + { CAMERAD_READOUT, {0, 2} }, + { CAMERAD_RESUME, {0, 0} }, + { CAMERAD_SHUTTER, {0, 1} }, ///< [ enable | 1 | disable | 0 ] + { CAMERAD_STOP, {0, 0} } }; const std::vector> camerad_transitions = { - { CameraState::IDLE, CAMERAD_OPEN, CameraState::READY }, - { CameraState::READY, CAMERAD_ACTIVATE, CameraState::READY }, - { CameraState::READY, CAMERAD_DEACTIVATE, CameraState::READY }, - { CameraState::READY, CAMERAD_EXPTIME, CameraState::READY }, - { CameraState::READY, CAMERAD_EXPOSE, CameraState::EXPOSING }, - { CameraState::EXPOSING, CAMERAD_READOUT, CameraState::READING }, - { CameraState::READING, CAMERAD_READOUT, CameraState::READY } + { CameraState::EXPOSING, CAMERAD_ABORT, CameraState::READY }, + { CameraState::READING, CAMERAD_ABORT, CameraState::READY }, + { CameraState::PAUSED, CAMERAD_ABORT, CameraState::READY }, + { CameraState::READY, CAMERAD_ACTIVATE, CameraState::READY }, + { CameraState::READY, CAMERAD_BIN, CameraState::READY }, + { CameraState::READY, CAMERAD_BOI, CameraState::READY }, + { CameraState::READY, CAMERAD_CLOSE, CameraState::IDLE }, + { CameraState::READY, CAMERAD_DEACTIVATE, CameraState::READY }, + { CameraState::READY, CAMERAD_EXPTIME, CameraState::READY }, + { CameraState::READY, CAMERAD_EXPOSE, CameraState::EXPOSING }, + { CameraState::READY, CAMERAD_FITSNAME, CameraState::READY }, + { CameraState::READY, CAMERAD_FITSNAMING, CameraState::READY }, + { CameraState::READY, CAMERAD_FRAMETRANSFER, CameraState::READY }, + { CameraState::READY, CAMERAD_GEOMETRY, CameraState::READY }, + { CameraState::READY, CAMERAD_IMDIR, CameraState::READY }, + { CameraState::READY, CAMERAD_IMNUM, CameraState::READY }, + { CameraState::READY, CAMERAD_IMSIZE, CameraState::READY }, + { CameraState::READY, CAMERAD_KEY, CameraState::READY }, + { CameraState::READY, CAMERAD_LOAD, CameraState::READY }, + { CameraState::EXPOSING, CAMERAD_MODEXPTIME, CameraState::EXPOSING }, + { CameraState::IDLE, CAMERAD_OPEN, CameraState::READY }, + { CameraState::EXPOSING, CAMERAD_PAUSE, CameraState::PAUSED }, + { CameraState::EXPOSING, CAMERAD_READOUT, CameraState::READING }, + { CameraState::READING, CAMERAD_READOUT, CameraState::READY }, + { CameraState::PAUSED, CAMERAD_RESUME, CameraState::EXPOSING }, + { CameraState::READY, CAMERAD_SHUTTER, CameraState::READY }, + { CameraState::EXPOSING, CAMERAD_STOP, CameraState::READY } + }; + + + // ---------- ACAMD ---------------------------------------------------------- + + enum class AcamState { + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + ACQUIRING, ///< target acquisition in progress + GUIDING ///< guiding in progress + }; + + const CommandSpecMap acamd_specs = { + { ACAMD_OPEN, {0, 2} }, ///< [ motion ] [ camera [] ] + { ACAMD_CLOSE, {0, 0} }, + { ACAMD_INIT, {0, 0} }, + { ACAMD_ACQUIRE, {0, 4} }, ///< [ | target | guide | stop ] + { ACAMD_COORDS, {0, 3} }, ///< [ ] + { ACAMD_EXPTIME, {0, 1} }, ///< [ ] + { ACAMD_BIN, {0, 2} }, ///< [ ] + { ACAMD_GAIN, {0, 1} }, ///< [ ] + { ACAMD_TEMP, {0, 1} }, ///< [ ] + { ACAMD_FRAMEGRAB, {1, 3} }, ///< start | stop | one [ ] | status + { ACAMD_HOME, {0, 1} }, ///< [ ] + { ACAMD_FILTER, {0, 1} }, ///< [ | home | ishome ] + { ACAMD_COVER, {0, 1} }, ///< [ open | close | home | ishome ] + { ACAMD_ISOPEN, {0, 1} }, ///< [ motion | camera ] + { ACAMD_ISHOME, {0, 1} }, ///< [ ] + { ACAMD_ISACQUIRED, {0, 0} }, + { ACAMD_PUTONSLIT, {0, 2} }, ///< [ ] + { ACAMD_SOLVE, {0, 1} }, ///< [ ] [ = ... ] -- simplified + { ACAMD_SHUTDOWN, {0, 0} }, + { ACAMD_TCSINIT, {0, 1} }, ///< [ tcs | sim ] + { ACAMD_OFFSETGOAL, {0, 2} }, ///< [ ] + { ACAMD_OFFSETPERIOD,{0, 1} } ///< [ ] + }; + + const std::vector> acamd_transitions = { + { AcamState::IDLE, ACAMD_OPEN, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_CLOSE, AcamState::IDLE }, // TODO: verify + { AcamState::READY, ACAMD_INIT, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_ACQUIRE, AcamState::ACQUIRING }, // TODO: verify + { AcamState::READY, ACAMD_COORDS, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_EXPTIME, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_BIN, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_GAIN, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_TEMP, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_FRAMEGRAB, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_HOME, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_FILTER, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_COVER, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_PUTONSLIT, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_SOLVE, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_OFFSETGOAL, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_OFFSETPERIOD,AcamState::READY }, // TODO: verify + { AcamState::ACQUIRING, ACAMD_ACQUIRE, AcamState::READY }, // TODO: verify (stop) + { AcamState::ACQUIRING, ACAMD_ISACQUIRED, AcamState::ACQUIRING }, // TODO: verify + { AcamState::READY, ACAMD_TCSINIT, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_SHUTDOWN, AcamState::IDLE }, // TODO: verify + { AcamState::READY, ACAMD_ISOPEN, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_ISHOME, AcamState::READY }, // TODO: verify + { AcamState::READY, ACAMD_ISACQUIRED, AcamState::READY } // TODO: verify + }; + + + // ---------- CALIBD --------------------------------------------------------- + + enum class CalibState { + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + MOVING ///< actuator motion in progress + }; + + const CommandSpecMap calibd_specs = { + { CALIBD_OPEN, {0, 1} }, ///< [ motion | lampmod ] + { CALIBD_CLOSE, {0, 1} }, ///< [ motion | lampmod ] + { CALIBD_ISOPEN, {0, 1} }, ///< [ motion | lampmod ] + { CALIBD_GET, {0, 1} }, ///< [ ] + { CALIBD_HOME, {0, 0} }, + { CALIBD_ISHOME, {0, 0} }, + { CALIBD_SET, {1, 8} }, ///< =open|close ... (variable) + { CALIBD_LAMPMOD, {1, 4} }, ///< open | close | reconnect | default | ... + { CALIBD_NATIVE, {2, 2} } ///< + }; + + const std::vector> calibd_transitions = { + { CalibState::IDLE, CALIBD_OPEN, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_CLOSE, CalibState::IDLE }, // TODO: verify + { CalibState::READY, CALIBD_ISOPEN, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_GET, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_HOME, CalibState::MOVING }, // TODO: verify + { CalibState::MOVING, CALIBD_ISHOME, CalibState::MOVING }, // TODO: verify + { CalibState::READY, CALIBD_ISHOME, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_SET, CalibState::MOVING }, // TODO: verify + { CalibState::MOVING, CALIBD_SET, CalibState::READY }, // TODO: verify (completion) + { CalibState::READY, CALIBD_LAMPMOD, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_NATIVE, CalibState::READY } // TODO: verify + }; + + + // ---------- SLITD ---------------------------------------------------------- + + enum class SlitState { + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + MOVING, ///< slit motion in progress + HOMED ///< slit has been homed + }; + + const CommandSpecMap slitd_specs = { + { SLITD_OPEN, {0, 0} }, + { SLITD_CLOSE, {0, 0} }, + { SLITD_ISOPEN, {0, 0} }, + { SLITD_GET, {0, 1} }, ///< [ mm ] + { SLITD_HOME, {0, 0} }, + { SLITD_ISHOME, {0, 0} }, + { SLITD_OFFSET, {1, 1} }, ///< + { SLITD_SET, {1, 2} }, ///< [ ] + { SLITD_NATIVE, {2, 4} } ///< [ ] + }; + + const std::vector> slitd_transitions = { + { SlitState::IDLE, SLITD_OPEN, SlitState::READY }, // TODO: verify + { SlitState::READY, SLITD_CLOSE, SlitState::IDLE }, // TODO: verify + { SlitState::HOMED, SLITD_CLOSE, SlitState::IDLE }, // TODO: verify + { SlitState::READY, SLITD_ISOPEN, SlitState::READY }, // TODO: verify + { SlitState::HOMED, SLITD_ISOPEN, SlitState::HOMED }, // TODO: verify + { SlitState::READY, SLITD_GET, SlitState::READY }, // TODO: verify + { SlitState::HOMED, SLITD_GET, SlitState::HOMED }, // TODO: verify + { SlitState::READY, SLITD_HOME, SlitState::MOVING }, // TODO: verify + { SlitState::MOVING, SLITD_ISHOME, SlitState::MOVING }, // TODO: verify (poll) + { SlitState::MOVING, SLITD_HOME, SlitState::HOMED }, // TODO: verify (completion) + { SlitState::READY, SLITD_ISHOME, SlitState::READY }, // TODO: verify + { SlitState::HOMED, SLITD_ISHOME, SlitState::HOMED }, // TODO: verify + { SlitState::HOMED, SLITD_SET, SlitState::MOVING }, // TODO: verify + { SlitState::MOVING, SLITD_SET, SlitState::HOMED }, // TODO: verify (completion) + { SlitState::HOMED, SLITD_OFFSET, SlitState::MOVING }, // TODO: verify + { SlitState::MOVING, SLITD_OFFSET, SlitState::HOMED }, // TODO: verify (completion) + { SlitState::HOMED, SLITD_NATIVE, SlitState::HOMED }, // TODO: verify + { SlitState::READY, SLITD_NATIVE, SlitState::READY } // TODO: verify + }; + + + // ---------- TCSD ----------------------------------------------------------- + + enum class TcsState { + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + SLEWING, ///< telescope slew in progress + TRACKING, ///< telescope tracking on target + OFFSETTING ///< telescope offset in progress + }; + + const CommandSpecMap tcsd_specs = { + { TCSD_OPEN, {1, 1} }, ///< + { TCSD_CLOSE, {0, 0} }, + { TCSD_ISOPEN, {0, 0} }, + { TCSD_COORDS, {5, 7} }, ///< [] [""] + { TCSD_GET_COORDS, {0, 0} }, + { TCSD_GET_CASS, {0, 0} }, + { TCSD_GET_DOME, {0, 0} }, + { TCSD_GET_FOCUS, {0, 0} }, + { TCSD_GET_MOTION, {0, 0} }, + { TCSD_GET_OFFSETS, {0, 0} }, + { TCSD_GET_PA, {0, 0} }, + { TCSD_GET_NAME, {0, 0} }, + { TCSD_RINGGO, {1, 1} }, ///< + { TCSD_PTOFFSET, {2, 2} }, ///< + { TCSD_RETOFFSETS, {0, 0} }, + { TCSD_ZERO_OFFSETS, {0, 0} }, + { TCSD_OFFSETRATE, {0, 2} }, ///< [ ] + { TCSD_SET_FOCUS, {1, 1} }, ///< + { TCSD_NATIVE, {1, 1} }, ///< + { TCSD_LIST, {0, 0} }, + { TCSD_LLIST, {0, 0} }, + { TCSD_WEATHER_COORDS, {0, 0} } + }; + + const std::vector> tcsd_transitions = { + { TcsState::IDLE, TCSD_OPEN, TcsState::READY }, // TODO: verify + { TcsState::READY, TCSD_CLOSE, TcsState::IDLE }, // TODO: verify + { TcsState::TRACKING, TCSD_CLOSE, TcsState::IDLE }, // TODO: verify + { TcsState::READY, TCSD_ISOPEN, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_ISOPEN, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_COORDS, TcsState::SLEWING }, // TODO: verify + { TcsState::TRACKING, TCSD_COORDS, TcsState::SLEWING }, // TODO: verify + { TcsState::SLEWING, TCSD_GET_MOTION, TcsState::SLEWING }, // TODO: verify (poll) + { TcsState::SLEWING, TCSD_GET_COORDS, TcsState::TRACKING }, // TODO: verify (completion) + { TcsState::READY, TCSD_GET_COORDS, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_GET_COORDS, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_GET_CASS, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_GET_CASS, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_GET_DOME, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_GET_DOME, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_GET_FOCUS, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_GET_FOCUS, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_GET_MOTION, TcsState::READY }, // TODO: verify + { TcsState::READY, TCSD_GET_OFFSETS, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_GET_OFFSETS, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_GET_PA, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_GET_PA, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_GET_NAME, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_GET_NAME, TcsState::TRACKING }, // TODO: verify + { TcsState::TRACKING, TCSD_RINGGO, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_RINGGO, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_PTOFFSET, TcsState::OFFSETTING }, // TODO: verify + { TcsState::OFFSETTING, TCSD_GET_MOTION, TcsState::OFFSETTING }, // TODO: verify (poll) + { TcsState::OFFSETTING, TCSD_RETOFFSETS, TcsState::TRACKING }, // TODO: verify (completion) + { TcsState::TRACKING, TCSD_RETOFFSETS, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_ZERO_OFFSETS, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_ZERO_OFFSETS, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_OFFSETRATE, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_OFFSETRATE, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_SET_FOCUS, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_SET_FOCUS, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_NATIVE, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_NATIVE, TcsState::TRACKING }, // TODO: verify + { TcsState::READY, TCSD_LIST, TcsState::READY }, // TODO: verify + { TcsState::READY, TCSD_LLIST, TcsState::READY }, // TODO: verify + { TcsState::READY, TCSD_WEATHER_COORDS, TcsState::READY }, // TODO: verify + { TcsState::TRACKING, TCSD_WEATHER_COORDS, TcsState::TRACKING } // TODO: verify + }; + + + // ---------- FOCUSD --------------------------------------------------------- + + enum class FocusState { + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + MOVING ///< focus motion in progress + }; + + const CommandSpecMap focusd_specs = { + { FOCUSD_OPEN, {0, 0} }, + { FOCUSD_CLOSE, {0, 0} }, + { FOCUSD_ISOPEN, {0, 0} }, + { FOCUSD_GET, {1, 1} }, ///< + { FOCUSD_HOME, {0, 1} }, ///< [ ] + { FOCUSD_ISHOME, {0, 0} }, + { FOCUSD_SET, {2, 2} }, ///< { | nominal } + { FOCUSD_DEFAULTPOS, {0, 0} }, + { FOCUSD_NATIVE, {2, 2} }, ///< + { FOCUSD_TEST, {1, 4} } ///< ... + }; + + const std::vector> focusd_transitions = { + { FocusState::IDLE, FOCUSD_OPEN, FocusState::READY }, // TODO: verify + { FocusState::READY, FOCUSD_CLOSE, FocusState::IDLE }, // TODO: verify + { FocusState::READY, FOCUSD_ISOPEN, FocusState::READY }, // TODO: verify + { FocusState::READY, FOCUSD_GET, FocusState::READY }, // TODO: verify + { FocusState::READY, FOCUSD_HOME, FocusState::MOVING }, // TODO: verify + { FocusState::MOVING, FOCUSD_HOME, FocusState::READY }, // TODO: verify (completion) + { FocusState::READY, FOCUSD_ISHOME, FocusState::READY }, // TODO: verify + { FocusState::MOVING, FOCUSD_ISHOME, FocusState::MOVING }, // TODO: verify (poll) + { FocusState::READY, FOCUSD_SET, FocusState::MOVING }, // TODO: verify + { FocusState::MOVING, FOCUSD_SET, FocusState::READY }, // TODO: verify (completion) + { FocusState::READY, FOCUSD_DEFAULTPOS, FocusState::MOVING }, // TODO: verify + { FocusState::MOVING, FOCUSD_DEFAULTPOS, FocusState::READY }, // TODO: verify (completion) + { FocusState::READY, FOCUSD_NATIVE, FocusState::READY }, // TODO: verify + { FocusState::READY, FOCUSD_TEST, FocusState::READY } // TODO: verify + }; + + + // ---------- FLEXURED ------------------------------------------------------- + + enum class FlexureState { + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + MOVING ///< flexure compensation motion in progress + }; + + const CommandSpecMap flexured_specs = { + { FLEXURED_OPEN, {0, 0} }, + { FLEXURED_CLOSE, {0, 0} }, + { FLEXURED_ISOPEN, {0, 0} }, + { FLEXURED_GET, {2, 2} }, ///< + { FLEXURED_SET, {3, 3} }, ///< + { FLEXURED_COMPENSATE, {0, 1} }, ///< [ dryrun ] + { FLEXURED_DEFAULTPOS, {0, 0} }, + { FLEXURED_NATIVE, {2, 2} }, ///< + { FLEXURED_TEST, {1, 4} } ///< ... + }; + + const std::vector> flexured_transitions = { + { FlexureState::IDLE, FLEXURED_OPEN, FlexureState::READY }, // TODO: verify + { FlexureState::READY, FLEXURED_CLOSE, FlexureState::IDLE }, // TODO: verify + { FlexureState::READY, FLEXURED_ISOPEN, FlexureState::READY }, // TODO: verify + { FlexureState::READY, FLEXURED_GET, FlexureState::READY }, // TODO: verify + { FlexureState::READY, FLEXURED_SET, FlexureState::MOVING }, // TODO: verify + { FlexureState::MOVING, FLEXURED_SET, FlexureState::READY }, // TODO: verify (completion) + { FlexureState::READY, FLEXURED_COMPENSATE, FlexureState::MOVING }, // TODO: verify + { FlexureState::MOVING, FLEXURED_COMPENSATE, FlexureState::READY }, // TODO: verify (completion) + { FlexureState::READY, FLEXURED_DEFAULTPOS, FlexureState::MOVING }, // TODO: verify + { FlexureState::MOVING, FLEXURED_DEFAULTPOS, FlexureState::READY }, // TODO: verify (completion) + { FlexureState::READY, FLEXURED_NATIVE, FlexureState::READY }, // TODO: verify + { FlexureState::READY, FLEXURED_TEST, FlexureState::READY } // TODO: verify + }; + + + // ---------- POWERD --------------------------------------------------------- + + enum class PowerState { + IDLE, ///< not yet opened + READY ///< open and ready to accept commands + }; + + const CommandSpecMap powerd_specs = { + { POWERD_OPEN, {0, 0} }, + { POWERD_CLOSE, {0, 0} }, + { POWERD_ISOPEN, {0, 0} }, + { POWERD_STATUS, {0, 1} }, ///< [ ? ] + { POWERD_REOPEN, {0, 0} }, + { POWERD_LIST, {0, 0} } + // NOTE: plug switching commands ( ON|OFF|BOOT or ON|OFF|BOOT) + // do not have a fixed first-word command name and so cannot be registered here by name. + // TODO: decide how plug switching is exposed via SEQUENCERD_OP. + }; + + const std::vector> powerd_transitions = { + { PowerState::IDLE, POWERD_OPEN, PowerState::READY }, // TODO: verify + { PowerState::READY, POWERD_CLOSE, PowerState::IDLE }, // TODO: verify + { PowerState::READY, POWERD_ISOPEN, PowerState::READY }, // TODO: verify + { PowerState::READY, POWERD_STATUS, PowerState::READY }, // TODO: verify + { PowerState::READY, POWERD_REOPEN, PowerState::READY }, // TODO: verify + { PowerState::READY, POWERD_LIST, PowerState::READY } // TODO: verify + }; + + + // ---------- SLICECAMD ------------------------------------------------------ + + enum class SlicecamState { + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + ACQUIRING, ///< fine acquisition in progress + GUIDING ///< guiding in progress + }; + + const CommandSpecMap slicecamd_specs = { + { SLICECAMD_OPEN, {0, 2} }, ///< [ motion ] [ camera [] ] + { SLICECAMD_CLOSE, {0, 0} }, + { SLICECAMD_ISOPEN, {0, 1} }, ///< [ motion | camera ] + { SLICECAMD_INIT, {0, 0} }, + { SLICECAMD_EXPTIME, {0, 1} }, ///< [ ] + { SLICECAMD_BIN, {0, 3} }, ///< [ ] + { SLICECAMD_GAIN, {0, 1} }, ///< [ ] + { SLICECAMD_TEMP, {0, 1} }, ///< [ ] + { SLICECAMD_FRAMEGRAB, {1, 3} }, ///< start | stop | one [ ] | status + { SLICECAMD_FINEACQUIRE, {1, 2} }, ///< status | stop | start { L | R } + { SLICECAMD_PUTONSLIT, {0, 4} }, ///< [ ] + { SLICECAMD_SAVEFRAMES, {0, 2} }, ///< [ ] + { SLICECAMD_SHUTTER, {1, 1} }, ///< open | close | auto + { SLICECAMD_SHUTDOWN, {0, 0} }, + { SLICECAMD_TCSINIT, {0, 1} }, ///< [ tcs | sim ] + { SLICECAMD_IMFLIP, {0, 3} }, ///< [ ] + { SLICECAMD_IMROT, {0, 2} } ///< [ ] + }; + + const std::vector> slicecamd_transitions = { + { SlicecamState::IDLE, SLICECAMD_OPEN, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_CLOSE, SlicecamState::IDLE }, // TODO: verify + { SlicecamState::READY, SLICECAMD_ISOPEN, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_INIT, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_EXPTIME, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_BIN, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_GAIN, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_TEMP, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_FRAMEGRAB, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_FINEACQUIRE, SlicecamState::ACQUIRING }, // TODO: verify + { SlicecamState::ACQUIRING, SLICECAMD_FINEACQUIRE, SlicecamState::READY }, // TODO: verify (stop) + { SlicecamState::READY, SLICECAMD_PUTONSLIT, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_SAVEFRAMES, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_SHUTTER, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_SHUTDOWN, SlicecamState::IDLE }, // TODO: verify + { SlicecamState::READY, SLICECAMD_TCSINIT, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_IMFLIP, SlicecamState::READY }, // TODO: verify + { SlicecamState::READY, SLICECAMD_IMROT, SlicecamState::READY } // TODO: verify }; } diff --git a/sequencerd/operation_builders.cpp b/sequencerd/operation_builders.cpp index d5c09880..38d32f1a 100644 --- a/sequencerd/operation_builders.cpp +++ b/sequencerd/operation_builders.cpp @@ -3,76 +3,274 @@ * @brief implementation for building operations from command names * @author David Hale * + * This file registers the mapping from a parsed sequencer command name to an + * OperationBuilder lambda. Builders fall into two categories: + * + * 1. Internal : sequencer-level orchestration (database-driven, may span + * multiple daemons). These delegate to Ops:: factory methods + * which wrap Sequence:: member functions. Parameters are + * carried as key=value pairs on cmd.params and are exposed to + * the member function via Sequence::current_op_params (set by + * Sequence::run() immediately before op.func() is invoked). + * + * 2. Passthrough : per-daemon validated passthrough (e.g. "camera exptime 30000" + * or "slit set 2.0 0.0"). These are built inline here (no Ops:: + * method) and forward the positional args (cmd.args) to the + * target daemon. The first arg is the daemon subcommand; the + * remainder are forwarded as-is in daemon native syntax. + * + * NOTE: Passthrough commands currently forward via Common::DaemonClient::send() as a + * PLACEHOLDER. This will be replaced by CommandClient::send() + * later, at which point per-command state-machine validation and + * arg-count checks will be performed at execution time. + * */ #include "sequence.h" +#include "sequencerd_commands.h" namespace Sequencer { + namespace { + + /***** Sequencer::daemon_passthrough *************************************/ + /** + * @brief TEMPORARY daemon passthrough helper + * @param[in] function calling function name for logging + * @param[in] daemon DaemonClient to forward to + * @param[in] args positional args from ParsedCommand (first is subcommand) + * @return ERROR|NO_ERROR + * @details Builds a daemon-native command string from args[0..N-1] and + * forwards via Common::DaemonClient::send(). Self-identifies in + * log as a placeholder so the pending replacement is unambiguous. + * + * TEMPORARY -- REMOVE ONCE CommandClient IS WIRED IN (Steps 2+3). + * Each Passthrough op_builder lambda will then call + * CommandClient::send() directly, performing per-command + * state-machine validation and arg-count checks at execution + * time, and this helper becomes unnecessary. + * + */ + long daemon_passthrough( const std::string &function, + Common::DaemonClient &daemon, + const std::vector &args ) { + if (args.empty()) { + logwrite(function, "ERROR no subcommand provided to "+daemon.name); + return ERROR; + } + std::string cmd_str = args[0]; + for (size_t i=1; i "+daemon.name+ + "): "+cmd_str); + long error = daemon.send(cmd_str, reply); + if (error != NO_ERROR) { + logwrite(function, "ERROR forwarding \""+cmd_str+"\" to "+daemon.name+": "+reply); + } + return error; + } + /***** Sequencer::daemon_passthrough *************************************/ + + } // anonymous namespace + + /***** Sequencer::Sequence::init_operation_builders ************************/ /** * @brief initializes registration between name and operation - * @details Maps command names to function that populate an Operation + * @details Maps command names to functions that populate an Operation * from a ParsedCommand. * */ void Sequence::init_operation_builders() { - // ---------- FOCUS_SET -------------------------------------------------- + // ---------- INTERNAL ------------------------------------------------------ - op_builders["focus_set"] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_FOCUS_SET; - op.func = [this]() { - return focus_set(); - }; + // sequencer-level orchestration (Ops factory methods) + // Command names come from the THR_ -> thread_names map in sequence.h. + // I probably should rename these from "thread" to something else (operation?) + + op_builders[thread_names.at(THR_ACAM_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.acam_init(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_CALIB_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.calib_init(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_CALIB_SET)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.calib_set(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_CAMERA_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.camera_init(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_CAMERA_SET)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.camera_set(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_EXPOSURE)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.do_expose(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_FLEXURE_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.flexure_init(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_FLEXURE_SET)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.flexure_set(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_FOCUS_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.focus_init(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_FOCUS_SET)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.focus_set(); + op.params = cmd.params; + }; + + op_builders[thread_names.at(THR_MOVE_TO_TARGET)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.move_to_target(); + op.params = cmd.params; // may carry optional ra= / dec= overrides + }; + + op_builders[thread_names.at(THR_POWER_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.power_init(); op.params = cmd.params; }; - // ---------- MOVE_TO_TARGET --------------------------------------------- + op_builders[thread_names.at(THR_REPEAT_EXPOSURE)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.repeat_exposure(); + op.params = cmd.params; + }; - op_builders["move_to_target"] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_MOVE_TO_TARGET; - op.func = [this,params=cmd.params]() { - if (params.has("ra") && params.has("dec")) { - this->target.ra_hms = params.get(std::string("ra"),std::string("")); - this->target.dec_dms = params.get(std::string("dec"),std::string("")); - } - return move_to_target(); - }; + op_builders[thread_names.at(THR_SHUTDOWN)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.do_shutdown(); op.params = cmd.params; }; - // ---------- SLIT_SET --------------------------------------------------- + op_builders[thread_names.at(THR_SLICECAM_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.slicecam_init(); + op.params = cmd.params; + }; - op_builders["slit_set"] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_SLIT_SET; - op.func = [this,params=cmd.params]() { - size_t mode = params.get("mode", VSM_DATABASE); - return slit_set(static_cast(mode)); - }; + op_builders[thread_names.at(THR_SLIT_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.slit_init(); op.params = cmd.params; }; - // ---------- EXPOSE ----------------------------------------------------- + op_builders[thread_names.at(THR_SLIT_SET)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.slit_set(); + op.params = cmd.params; // may carry optional mode= override + }; - op_builders["expose"] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_EXPOSURE; - op.func = [this]() { - return do_exposure("operation_builder"); - }; + op_builders[thread_names.at(THR_STARTUP)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.do_startup(); op.params = cmd.params; }; - // ---------- FOCUS_SET -------------------------------------------------- + op_builders[thread_names.at(THR_TCS_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.tcs_init(); + op.params = cmd.params; + }; - op_builders["focus_set"] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_FOCUS_SET; - op.func = [this]() { - return focus_set(); - }; + // target_offset shares THR_MOVE_TO_TARGET with move_to_target, so it needs a + // distinct CLI/DSL name. Use its Sequence:: member-function name "target_offset" + // (there is no separate entry in thread_names for it). + op_builders["target_offset"] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.target_offset(); op.params = cmd.params; }; + // ---------- PASSTHROUGH --------------------------------------------------- + + // per-daemon validated passthrough (inline, no Ops method). + // Command names come from SEQUENCERD_* constants in common/sequencerd_commands.h. + // PLACEHOLDER: forwards via Common::DaemonClient::send() until Steps 2+3 + // replace with CommandClient::send() (state-machine validated). + + op_builders[SEQUENCERD_ACAM] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_ACAM_INIT; // passthrough uses nearest daemon THR_ identity + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_ACAM+"]"); + return daemon_passthrough(function, this->acamd, args); + }; + }; + + op_builders[SEQUENCERD_CALIB] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_CALIB_INIT; + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_CALIB+"]"); + return daemon_passthrough(function, this->calibd, args); + }; + }; + + op_builders[SEQUENCERD_CAMERA] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_CAMERA_INIT; + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_CAMERA+"]"); + return daemon_passthrough(function, this->camerad, args); + }; + }; + + op_builders[SEQUENCERD_FLEXURE] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_FLEXURE_INIT; + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_FLEXURE+"]"); + return daemon_passthrough(function, this->flexured, args); + }; + }; + + op_builders[SEQUENCERD_FOCUS] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_FOCUS_INIT; + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_FOCUS+"]"); + return daemon_passthrough(function, this->focusd, args); + }; + }; + + op_builders[SEQUENCERD_POWER] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_POWER_INIT; + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_POWER+"]"); + return daemon_passthrough(function, this->powerd, args); + }; + }; + + op_builders[SEQUENCERD_SLICECAM] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_SLICECAM_INIT; + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_SLICECAM+"]"); + return daemon_passthrough(function, this->slicecamd, args); + }; + }; + + op_builders[SEQUENCERD_SLIT] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_SLIT_INIT; + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_SLIT+"]"); + return daemon_passthrough(function, this->slitd, args); + }; + }; + + op_builders[SEQUENCERD_TCS] = [this](Operation &op, const ParsedCommand &cmd) { + op.thr = THR_TCS_INIT; + op.func = [this, args=cmd.args]() { + const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_TCS+"]"); + return daemon_passthrough(function, this->tcsd, args); + }; + }; + } /***** Sequencer::Sequence::init_operation_builders ************************/ diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 67617b69..d854ad9d 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -18,6 +18,97 @@ namespace Sequencer { constexpr long CAMERA_PROLOG_TIMEOUT = 6000; ///< timeout msec to send camera prolog command + namespace { + // ------------------------------------------------------------------------ + // Maps each Operation::thr to the DaemonBit it depends on. Operations + // that manage daemon state themselves (startup, shutdown, power_init, + // power_shutdown) are intentionally absent from this map. + // Used exclusively by validate_sequence(). + // ------------------------------------------------------------------------ + const std::unordered_map thr_to_daemon = { + { THR_EXPOSURE, DAEMON_CAMERA }, + { THR_CAMERA_SET, DAEMON_CAMERA }, + { THR_CAMERA_INIT, DAEMON_CAMERA }, + { THR_CAMERA_SHUTDOWN, DAEMON_CAMERA }, + { THR_SLIT_SET, DAEMON_SLIT }, + { THR_SLIT_INIT, DAEMON_SLIT }, + { THR_SLIT_SHUTDOWN, DAEMON_SLIT }, + { THR_MOVE_TO_TARGET, DAEMON_TCS }, + { THR_TCS_INIT, DAEMON_TCS }, + { THR_TCS_SHUTDOWN, DAEMON_TCS }, + { THR_FLEXURE_SET, DAEMON_FLEXURE }, + { THR_FLEXURE_INIT, DAEMON_FLEXURE }, + { THR_FLEXURE_SHUTDOWN, DAEMON_FLEXURE }, + { THR_FOCUS_SET, DAEMON_FOCUS }, + { THR_FOCUS_INIT, DAEMON_FOCUS }, + { THR_FOCUS_SHUTDOWN, DAEMON_FOCUS }, + { THR_CALIB_SET, DAEMON_CALIB }, + { THR_CALIBRATOR_SET, DAEMON_CALIB }, + { THR_CALIB_INIT, DAEMON_CALIB }, + { THR_CALIB_SHUTDOWN, DAEMON_CALIB }, + { THR_ACAM_INIT, DAEMON_ACAM }, + { THR_ACAM_SHUTDOWN, DAEMON_ACAM }, + { THR_SLICECAM_INIT, DAEMON_SLICECAM }, + { THR_SLICECAM_SHUTDOWN,DAEMON_SLICECAM } + }; + + // ------------------------------------------------------------------------ + // Returns true if the two Operation::thr values are unsafe to run in the + // same PARALLEL group. Serial ordering of any such pair is fine. + // ------------------------------------------------------------------------ + inline bool is_parallel_unsafe_pair(ThreadStatusBits a, ThreadStatusBits b) { + // normalize ordering so each pair is checked once + if (a > b) std::swap(a, b); + + // global sequence ops conflict with anything big + if (a == THR_STARTUP && b == THR_SHUTDOWN) return true; + if (a == THR_STARTUP && b == THR_EXPOSURE) return true; + + // startup runs all inits internally; parallelling any init with startup is a double-init + auto is_init = [](ThreadStatusBits t) { + return t == THR_ACAM_INIT || t == THR_CALIB_INIT || + t == THR_CAMERA_INIT || t == THR_FLEXURE_INIT|| + t == THR_FOCUS_INIT || t == THR_POWER_INIT || + t == THR_SLICECAM_INIT || t == THR_SLIT_INIT || + t == THR_TCS_INIT; + }; + auto is_shutdown = [](ThreadStatusBits t) { + return t == THR_ACAM_SHUTDOWN || t == THR_CALIB_SHUTDOWN || + t == THR_CAMERA_SHUTDOWN || t == THR_FLEXURE_SHUTDOWN|| + t == THR_FOCUS_SHUTDOWN || t == THR_POWER_SHUTDOWN || + t == THR_SLICECAM_SHUTDOWN || t == THR_SLIT_SHUTDOWN || + t == THR_TCS_SHUTDOWN; + }; + + if (a == THR_STARTUP && is_init(b)) return true; + if (a == THR_SHUTDOWN && is_init(b)) return true; + + // power_init vs power_shutdown + if (a == THR_POWER_INIT && b == THR_POWER_SHUTDOWN) return true; + + // matching init/shutdown pair for the same subsystem + if (is_init(a) && is_shutdown(b)) { + auto subsystem_of = [](ThreadStatusBits t) -> int { + switch (t) { + case THR_ACAM_INIT: case THR_ACAM_SHUTDOWN: return 1; + case THR_CALIB_INIT: case THR_CALIB_SHUTDOWN: return 2; + case THR_CAMERA_INIT: case THR_CAMERA_SHUTDOWN: return 3; + case THR_FLEXURE_INIT: case THR_FLEXURE_SHUTDOWN: return 4; + case THR_FOCUS_INIT: case THR_FOCUS_SHUTDOWN: return 5; + case THR_POWER_INIT: case THR_POWER_SHUTDOWN: return 6; + case THR_SLICECAM_INIT: case THR_SLICECAM_SHUTDOWN: return 7; + case THR_SLIT_INIT: case THR_SLIT_SHUTDOWN: return 8; + case THR_TCS_INIT: case THR_TCS_SHUTDOWN: return 9; + default: return 0; + } + }; + if (subsystem_of(a) == subsystem_of(b)) return true; + } + + return false; + } + } // anonymous namespace + /***** Sequencer::Sequence::operation_sleep ********************************/ /** * @brief interruptable sleep for operations @@ -72,6 +163,7 @@ namespace Sequencer { while (!is_cancelled()) { try { + this->current_op_params = op.params; // expose params to op member functions error = op.func(); if (error==NO_ERROR) return NO_ERROR; if (error==ABORT || @@ -402,14 +494,21 @@ namespace Sequencer { ParsedCommand command; command.name = word; - // any additional words are parameters, expected to be key=val pairs + // Additional words are either key=val params (INTERNAL) or positional + // args (PASSTHROUGH). Both collections are populated; each op_builder + // chooses which to consume. while (iss >> word) { + // key=value parameters auto eq = word.find('='); if (eq != std::string::npos) { std::string key = word.substr(0, eq); std::string val = word.substr(eq+1); command.params.map[key] = val; } + // positional arguments + else { + command.args.push_back(word); + } } return command; @@ -425,21 +524,204 @@ namespace Sequencer { * */ long Sequence::validate_sequence(const std::vector &sequence) { + const std::string function("Sequencer::Sequence::validate_sequence"); + long error = NO_ERROR; + + // ---------- RULE ------------------------------------------------------- + // Sequencer must not already be running, stopping, starting, or paused. + // Only READY or NOTREADY states allow a new sequence. + + if ( seq_state_manager.is_set(Sequencer::SEQ_RUNNING) ) { + this->async.enqueue_and_log(function, "ERROR sequence rejected: sequencer already running"); + return ERROR; + } + + if ( seq_state_manager.is_set(Sequencer::SEQ_STOPPING) ) { + this->async.enqueue_and_log(function, "ERROR sequence rejected: sequencer is stopping"); + return ERROR; + } + + if ( seq_state_manager.is_set(Sequencer::SEQ_STARTING) ) { + this->async.enqueue_and_log(function, "ERROR sequence rejected: sequencer is starting"); + return ERROR; + } + + if ( seq_state_manager.is_set(Sequencer::SEQ_PAUSED) ) { + this->async.enqueue_and_log(function, "ERROR sequence rejected: sequencer is paused"); + return ERROR; + } + + // ---------- RULE ------------------------------------------------------- + // Sequence must not be empty. + + if ( sequence.empty() ) { + this->async.enqueue_and_log(function, "ERROR sequence is empty"); + return ERROR; + } + + // ---------- RULE ------------------------------------------------------- + // Every group must contain at least one operation. - // sequence is a vector of OperationGroups for (const auto &group : sequence) { + if ( group.operations.empty() ) { + this->async.enqueue_and_log(function, "ERROR sequence contains an empty operation group"); + return ERROR; + } + } - // group is a vector of Operations + // ---------- RULE ------------------------------------------------------- + // Collect presence flags + counts by Operation::thr identifier. + // Used by subsequent rules. + + bool has_expose = false; + bool has_camera_set = false; + bool has_slit_set = false; + int expose_count = 0; + + for (const auto &group : sequence) { for (const auto &op : group.operations) { + switch (op.thr) { + case THR_EXPOSURE: has_expose = true; ++expose_count; break; + case THR_CAMERA_SET: has_camera_set = true; break; + case THR_SLIT_SET: has_slit_set = true; break; + default: break; + } + } + } + + // ---------- RULE ------------------------------------------------------- + // If expose is present, camera_set must also be present. + + if ( has_expose && !has_camera_set ) { + this->async.enqueue_and_log(function, + "ERROR sequence contains 'expose' without 'camera_set'"); + error = ERROR; + } + + // ---------- RULE ------------------------------------------------------- + // Daemon readiness. + // Science mode (engineering_mode == false): + // If the sequence contains any op that depends on a daemon, SEQ_READY + // must be set (meaning all daemons are up, established by startup()). + // Engineering mode (engineering_mode == true): + // Only the specific daemons needed by the operations in the sequence + // are required. All failures are reported together. - if (op.name() == "expose") { + { + // collect the set of daemons this sequence depends on + std::bitset required; + for (const auto &group : sequence) { + for (const auto &op : group.operations) { + auto it = thr_to_daemon.find(op.thr); + if ( it != thr_to_daemon.end() ) required.set(it->second); + } + } + + if ( required.any() ) { + // for engineering mode it is sufficient if only the required subsystem + // is ready. + if ( this->engineering_mode.load() ) { + for (std::size_t bit = 0; bit < NUM_DAEMONS; ++bit) { + if ( required.test(bit) && + !daemon_manager.is_set(static_cast(bit)) ) { + std::ostringstream oss; + oss << "ERROR sequence requires daemon '" + << daemon_name.at(static_cast(bit)) + << "' but it is not ready"; + this->async.enqueue_and_log(function, oss.str()); + error = ERROR; + } } - else - if (op.name() == "slit_set") { + } + else + // for science mode (non-engineering) all subsystems must be ready + if ( !seq_state_manager.is_set(Sequencer::SEQ_READY) ) { + this->async.enqueue_and_log(function, + "ERROR sequence requires SEQ_READY (all daemons ready) in science mode"); + error = ERROR; + } + } + } + + // ---------- RULE ------------------------------------------------------- + // Parallel-unsafe pairs. + // Always (either mode): + // No PARALLEL group may contain a parallel-unsafe pair of operations. + // Science mode only: + // A parallel-unsafe pair is also rejected if it appears anywhere in + // the sequence (across groups), because scripted science ops should + // not mix startup/shutdown/expose even in serial form. + // Engineering mode: + // SERIAL placement of any parallel-unsafe pair is allowed; ordering + // is explicit and on_error STOP handles failures. + + { + // intra-group check: no PARALLEL group may contain a parallel-unsafe pair + for (const auto &group : sequence) { + if ( group.type != OperationType::PARALLEL ) continue; + for (std::size_t i = 0; i < group.operations.size(); ++i) { + for (std::size_t j = i+1; j < group.operations.size(); ++j) { + if ( is_parallel_unsafe_pair(group.operations[i].thr, + group.operations[j].thr) ) { + std::ostringstream oss; + oss << "ERROR parallel group contains unsafe pair: '" + << thread_names.at(group.operations[i].thr) << "' and '" + << thread_names.at(group.operations[j].thr) << "'"; + this->async.enqueue_and_log(function, oss.str()); + error = ERROR; + } } } } - return NO_ERROR; + + // science-mode cross-group check: reject parallel-unsafe pairs anywhere + if ( !this->engineering_mode.load() ) { + std::vector all_thrs; + all_thrs.reserve(NUM_THREAD_STATES); + for (const auto &group : sequence) { + for (const auto &op : group.operations) all_thrs.push_back(op.thr); + } + for (std::size_t i = 0; i < all_thrs.size(); ++i) { + for (std::size_t j = i+1; j < all_thrs.size(); ++j) { + if ( is_parallel_unsafe_pair(all_thrs[i], all_thrs[j]) ) { + std::ostringstream oss; + oss << "ERROR sequence contains unsafe pair in science mode: '" + << thread_names.at(all_thrs[i]) << "' and '" + << thread_names.at(all_thrs[j]) + << "' (use 'engineering true' to allow in SERIAL groups)"; + this->async.enqueue_and_log(function, oss.str()); + error = ERROR; + } + } + } + } + } + + // ---------- RULE ------------------------------------------------------- + // TBD should I allow expose to not appear more than once across all groups? + + if ( expose_count > 1 ) { + this->async.enqueue_and_log(function, + "NOTICE sequence contains multiple 'expose' operations -- placeholder check"); + // not a hard error yet; pending clarification of multi-expose DSL usage + } + + // ---------- RULE ------------------------------------------------------- + // PLACEHOLDER: parallel contention check. + // Parallel groups must not contain operations that share a subsystem wait + // state, since they would contend on the same daemon. + // + // When Operation gains a wait_bit field (WaitStateBits), check here that + // no two operations in the same PARALLEL group share the same wait_bit. + // For now this is a no-op stub that compiles and runs cleanly. + + if (false) { + // stub -- replace with real contention check + this->async.enqueue_and_log(function, + "PLACEHOLDER parallel contention check not yet implemented"); + } + + return error; } /***** Sequencer::Sequence::validate_sequence ******************************/ @@ -2265,6 +2547,38 @@ namespace Sequencer { ScopedState thr_state( thread_state_manager, Sequencer::THR_MOVE_TO_TARGET ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCS ); + // Apply optional CLI/DSL coordinate overrides carried in current_op_params + // (e.g. "move_to_target ra=01:23:45 dec=+45:00:00"). Overrides are written + // into this->target for the duration of this call and restored on exit so + // the database-backed target fields are not permanently mutated. + // + struct TargetCoordGuard { + TargetInfo &t; + mysqlx::string saved_ra; + mysqlx::string saved_dec; + bool ra_overridden; + bool dec_overridden; + TargetCoordGuard(TargetInfo &t_, bool ra_ov, bool dec_ov) + : t(t_), + saved_ra(t_.ra_hms), + saved_dec(t_.dec_dms), + ra_overridden(ra_ov), + dec_overridden(dec_ov) {} + ~TargetCoordGuard() { + if (ra_overridden) t.ra_hms = saved_ra; + if (dec_overridden) t.dec_dms = saved_dec; + } + }; + const bool ra_override = this->current_op_params.has("ra"); + const bool dec_override = this->current_op_params.has("dec"); + TargetCoordGuard coord_guard( this->target, ra_override, dec_override ); + if (ra_override) { + this->target.ra_hms = this->current_op_params.get("ra", std::string(this->target.ra_hms)); + } + if (dec_override) { + this->target.dec_dms = this->current_op_params.get("dec", std::string(this->target.dec_dms)); + } + // If RA and DEC fields are both empty then no telescope move // if ( this->target.ra_hms.empty() && this->target.dec_dms.empty() ) { @@ -3274,6 +3588,55 @@ namespace Sequencer { /***** Sequencer::Sequence::dotype ******************************************/ + /***** Sequencer::Sequence::engineering *************************************/ + /** + * @brief set or get the engineering-mode flag + * @param[in] args empty (query) or { "true"|"false"|"1"|"0"|"enable"|"disable" } + * @param[out] retstring returns "true" or "false" + * @return NO_ERROR | ERROR + * + * When engineering_mode is true, validate_sequence() applies per-daemon + * readiness checks instead of requiring SEQ_READY, and it allows + * parallel-unsafe operation pairs when they are in SERIAL groups. + * + * Intended for single-subsystem / bench / engineering operation. Should not + * be used for science operations. + * + */ + long Sequence::engineering( std::string args, std::string &retstring ) { + const std::string function("Sequencer::Sequence::engineering"); + std::stringstream message; + long error = NO_ERROR; + + if ( not args.empty() ) { + std::transform( args.begin(), args.end(), args.begin(), ::tolower ); + + if ( args == "true" || args == "1" || args == "enable" ) { + this->engineering_mode.store( true ); + logwrite( function, "WARNING engineering mode ENABLED: validate_sequence will bypass full-instrument checks" ); + } + else + if ( args == "false" || args == "0" || args == "disable" ) { + this->engineering_mode.store( false ); + logwrite( function, "engineering mode DISABLED: validate_sequence will require SEQ_READY for full-instrument operations" ); + } + else { + message.str(""); message << "ERROR unrecognized argument " << args << ": expected {true|false|1|0|enable|disable}"; + logwrite( function, message.str() ); + error = ERROR; + } + } + + retstring = ( this->engineering_mode.load() ? "true" : "false" ); + + message.str(""); message << "ENGINEERING: " << retstring; + this->async.enqueue( message.str() ); + + return( error ); + } + /***** Sequencer::Sequence::engineering *************************************/ + + /***** Sequencer::Sequence::get_dome_position *******************************/ /** * @brief read the dome and telescope positions diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index b25608f5..a89c7793 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -289,6 +289,7 @@ namespace Sequencer { * @brief the Sequence class defines the "sequences", i.e. functions of the sequencer */ class Sequence { + friend struct SequenceTestAccess; ///< test-only friend (see sequencerd/test_sequence.cpp) private: zmqpp::context context; bool ready_to_start; ///< set on nightly startup success, used to return seqstate to READY after an abort @@ -301,6 +302,7 @@ namespace Sequencer { std::atomic is_usercontinue{false}; ///< remotely set by the user to continue std::atomic is_fineacquire_locked{false}; ///< is slicecam fine acquisition locked? std::atomic is_acam_guiding{false}; ///< is acam guiding? + std::atomic engineering_mode{false}; ///< when true, validate_sequence applies per-daemon checks instead of SEQ_READY ThreadPool pool; @@ -372,10 +374,18 @@ namespace Sequencer { */ struct ParsedCommand { std::string name; - OperationParams params; + OperationParams params; ///< key=value pairs (for Internal Operations) + std::vector args; ///< positional args (for Passthrough Operations) int linenum{0}; }; + /** @brief key=value params of the currently executing operation + * @details set by run() just before op.func() is invoked and consumed + * by Sequence:: member functions that accept optional overrides + * (e.g. move_to_target, slit_set) + */ + OperationParams current_op_params; + class Ops { private: Sequence* seq; @@ -384,12 +394,23 @@ namespace Sequencer { Operation acam_init(); Operation calib_init(); + Operation calib_set(); Operation camera_init(); + Operation camera_set(); + Operation do_expose(); + Operation do_shutdown(); + Operation do_startup(); Operation flexure_init(); + Operation flexure_set(); Operation focus_init(); + Operation focus_set(); + Operation move_to_target(); Operation power_init(); + Operation repeat_exposure(); Operation slicecam_init(); Operation slit_init(); + Operation slit_set(); + Operation target_offset(); Operation tcs_init(); }; @@ -397,6 +418,8 @@ namespace Sequencer { std::unordered_map op_builders; + Ops ops{ this }; ///< factory used by op_builders to build Internal Operations + void init_operation_builders(); /** @brief safely runs function in a detached thread using lambda to catch exceptions @@ -442,6 +465,7 @@ namespace Sequencer { is_subscriber_thread_running(false), should_subscriber_thread_run(false) { + init_operation_builders(); seq_state_manager.set_callback([this](const std::bitset& states) { broadcast_seqstate(); }); wait_state_manager.set_callback([this](const std::bitset& states) { broadcast_waitstate(); }); thread_state_manager.set_callback([this](const std::bitset& states) { publish_threadstate(); }); @@ -674,6 +698,7 @@ namespace Sequencer { std::string seqstate_string( uint32_t state ); ///< returns string form of states set in state word long dotype( std::string args ); ///< set do type (one/all) long dotype( std::string args, std::string &retstring ); ///< set or get do type (one/all) + long engineering( std::string args, std::string &retstring ); ///< set or get engineering mode (true/false) long poll_dome_position( double &domeazi, double &telazi ); long get_dome_position( double &domeazi, double &telazi ); long get_dome_position( bool poll, double &domeazi, double &telazi ); diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp index 1d4b143c..8706c0bb 100644 --- a/sequencerd/sequence_builder.cpp +++ b/sequencerd/sequence_builder.cpp @@ -97,61 +97,22 @@ namespace Sequencer { continue; } + // ---------- MAKE OPERATION FOR GIVEN COMMAND ------------------------- + // anything other than { parallel | serial | end | on_error } is a // command to parse, so a group must have been started first. // if (!current_group) return fail(command, "command outside group"); - // ---------- MAKE OPERATION FOR GIVEN COMMAND ------------------------- + // find the command in the operation builders map + // + auto it = op_builders.find(name); - Operation &op = current_group->operations.emplace_back(); + if (it == op_builders.end()) return fail(command, "unrecognized command"); - if (name == "move_to_target") { - op.thr = THR_MOVE_TO_TARGET; - op.func = [this,params=command.params]() { - if (params.has("ra") && params.has("dec")) { - this->target.ra_hms = params.get(std::string("ra"),std::string("")); - this->target.dec_dms = params.get(std::string("dec"),std::string("")); - } - return move_to_target(); - }; - op.params = command.params; - } - else - - if (name == "slit_set") { - op.thr = THR_SLIT_SET; - op.func = [this,params=command.params]() { - size_t mode = params.get("mode", VSM_DATABASE); - return slit_set(static_cast(mode)); - }; - op.params = command.params; - } - else - - if (name == "expose") { - op.thr = THR_EXPOSURE; - op.func = [this,function]() { - return do_exposure(function); - }; - op.params = command.params; - } - else - - if (name == "focus_set") { - op.thr = THR_FOCUS_SET; - op.func = [this]() { - return focus_set(); - }; - op.params = command.params; - } + Operation &op = current_group->operations.emplace_back(); - else { - this->async.enqueue_and_log("Sequencer::Sequence::build_sequence", - "ERROR unknown command '"+command.name+"'"); - continue; - } - current_group->operations.emplace_back(std::move(op)); + it->second(op, command); } return NO_ERROR; diff --git a/sequencerd/sequence_operations.cpp b/sequencerd/sequence_operations.cpp index a43608d7..832fa646 100644 --- a/sequencerd/sequence_operations.cpp +++ b/sequencerd/sequence_operations.cpp @@ -3,6 +3,13 @@ * @brief implementation of operations * @author David Hale * + * Each Ops factory method here returns a populated Sequence::Operation that + * wraps a Sequence:: member function and identifies itself via its THR_ bit. + * These are the "Internal" sequencer-level operations (database-driven, + * may coordinate multiple daemons). Per-daemon "Passthrough" operations + * (e.g. "camera exptime 30000") are built inline by init_operation_builders() + * and therefore do not appear here. + * */ #include "sequence.h" @@ -27,7 +34,7 @@ namespace Sequencer { op.on_retry = [this]() { long error=NO_ERROR; error |= seq->set_power_switch(OFF, POWER_ACAM_CAM, std::chrono::seconds(5)); - error |= seq->daemon_restart(seq->acamd); + error |= seq->daemon_restart(seq->acamd); return error; }; return op; @@ -50,6 +57,21 @@ namespace Sequencer { /***** Sequencer::Sequence::Ops::calib_init ********************************/ + /***** Sequencer::Sequence::Ops::calib_set *********************************/ + /** + * @brief defines the calib_set operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::calib_set() { + Sequence::Operation op; + op.thr = THR_CALIB_SET; + op.func = [this]() { return seq->calib_set(); }; + return op; + } + /***** Sequencer::Sequence::Ops::calib_set *********************************/ + + /***** Sequencer::Sequence::Ops::camera_init *******************************/ /** * @brief defines the camera_init operation @@ -65,6 +87,69 @@ namespace Sequencer { /***** Sequencer::Sequence::Ops::camera_init *******************************/ + /***** Sequencer::Sequence::Ops::camera_set ********************************/ + /** + * @brief defines the camera_set operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::camera_set() { + Sequence::Operation op; + op.thr = THR_CAMERA_SET; + op.func = [this]() { return seq->camera_set(); }; + return op; + } + /***** Sequencer::Sequence::Ops::camera_set ********************************/ + + + /***** Sequencer::Sequence::Ops::do_expose *********************************/ + /** + * @brief defines the expose operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::do_expose() { + Sequence::Operation op; + op.thr = THR_EXPOSURE; + op.func = [this]() { + const std::string caller("Sequencer::Sequence::Ops::do_expose"); + return seq->do_exposure(caller); + }; + return op; + } + /***** Sequencer::Sequence::Ops::do_expose *********************************/ + + + /***** Sequencer::Sequence::Ops::do_shutdown *******************************/ + /** + * @brief defines the shutdown operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::do_shutdown() { + Sequence::Operation op; + op.thr = THR_SHUTDOWN; + op.func = [this]() { return seq->shutdown(); }; + return op; + } + /***** Sequencer::Sequence::Ops::do_shutdown *******************************/ + + + /***** Sequencer::Sequence::Ops::do_startup ********************************/ + /** + * @brief defines the startup operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::do_startup() { + Sequence::Operation op; + op.thr = THR_STARTUP; + op.func = [this]() { return seq->startup(); }; + return op; + } + /***** Sequencer::Sequence::Ops::do_startup ********************************/ + + /***** Sequencer::Sequence::Ops::flexure_init ******************************/ /** * @brief defines the flexure_init operation @@ -80,6 +165,21 @@ namespace Sequencer { /***** Sequencer::Sequence::Ops::flexure_init ******************************/ + /***** Sequencer::Sequence::Ops::flexure_set *******************************/ + /** + * @brief defines the flexure_set operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::flexure_set() { + Sequence::Operation op; + op.thr = THR_FLEXURE_SET; + op.func = [this]() { return seq->flexure_set(); }; + return op; + } + /***** Sequencer::Sequence::Ops::flexure_set *******************************/ + + /***** Sequencer::Sequence::Ops::focus_init ********************************/ /** * @brief defines the focus_init operation @@ -95,6 +195,42 @@ namespace Sequencer { /***** Sequencer::Sequence::Ops::focus_init ********************************/ + /***** Sequencer::Sequence::Ops::focus_set *********************************/ + /** + * @brief defines the focus_set operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::focus_set() { + Sequence::Operation op; + op.thr = THR_FOCUS_SET; + op.func = [this]() { return seq->focus_set(); }; + return op; + } + /***** Sequencer::Sequence::Ops::focus_set *********************************/ + + + /***** Sequencer::Sequence::Ops::move_to_target ****************************/ + /** + * @brief defines the move_to_target operation + * @details Optional ra= and dec= params on the ParsedCommand override the + * target coordinates for this invocation only; the overrides are + * read from seq->current_op_params inside Sequence::move_to_target() + * (populated by Sequence::run() just before op.func() is called) + * and restored on return so the database-backed target is not + * permanently mutated. + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::move_to_target() { + Sequence::Operation op; + op.thr = THR_MOVE_TO_TARGET; + op.func = [this]() { return seq->move_to_target(); }; + return op; + } + /***** Sequencer::Sequence::Ops::move_to_target ****************************/ + + /***** Sequencer::Sequence::Ops::power_init ********************************/ /** * @brief defines the power_init operation @@ -110,6 +246,21 @@ namespace Sequencer { /***** Sequencer::Sequence::Ops::power_init ********************************/ + /***** Sequencer::Sequence::Ops::repeat_exposure ***************************/ + /** + * @brief defines the repeat_exposure operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::repeat_exposure() { + Sequence::Operation op; + op.thr = THR_REPEAT_EXPOSURE; + op.func = [this]() { return seq->repeat_exposure(); }; + return op; + } + /***** Sequencer::Sequence::Ops::repeat_exposure ***************************/ + + /***** Sequencer::Sequence::Ops::slicecam_init *****************************/ /** * @brief defines the slicecam_init operation @@ -118,7 +269,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::slicecam_init() { Sequence::Operation op; - op.thr = THR_ACAM_INIT; + op.thr = THR_SLICECAM_INIT; op.func = [this]() { return seq->slicecam_init(); }; op.params = { }; op.max_attempts = 3; @@ -126,7 +277,7 @@ namespace Sequencer { op.on_retry = [this]() { long error=NO_ERROR; error |= seq->set_power_switch(OFF, POWER_SLICECAM, std::chrono::seconds(5)); - error |= seq->daemon_restart(seq->slicecamd); + error |= seq->daemon_restart(seq->slicecamd); return error; }; return op; @@ -149,6 +300,43 @@ namespace Sequencer { /***** Sequencer::Sequence::Ops::slit_init *********************************/ + /***** Sequencer::Sequence::Ops::slit_set **********************************/ + /** + * @brief defines the slit_set operation + * @details Reads optional mode= param from seq->current_op_params. When + * absent the default VSM_DATABASE mode is used (drives slit from + * database target entry). + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::slit_set() { + Sequence::Operation op; + op.thr = THR_SLIT_SET; + op.func = [this]() { + const size_t mode = seq->current_op_params.get("mode", + static_cast(VSM_DATABASE)); + return seq->slit_set(static_cast(mode)); + }; + return op; + } + /***** Sequencer::Sequence::Ops::slit_set **********************************/ + + + /***** Sequencer::Sequence::Ops::target_offset *****************************/ + /** + * @brief defines the target_offset operation + * @return Operation + * + */ + Sequence::Operation Sequence::Ops::target_offset() { + Sequence::Operation op; + op.thr = THR_MOVE_TO_TARGET; // no dedicated THR_ for target_offset; reuse move_to_target identity + op.func = [this]() { return seq->target_offset(); }; + return op; + } + /***** Sequencer::Sequence::Ops::target_offset *****************************/ + + /***** Sequencer::Sequence::Ops::tcs_init **********************************/ /** * @brief defines the tcs_init operation diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index e0ed8961..7206e859 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -1484,6 +1484,17 @@ namespace Sequencer { } else + // Set/Get Engineering mode. + // When enabled, validate_sequence allows single-subsystem operation + // (per-daemon checks instead of SEQ_READY) and permits parallel-unsafe + // operation pairs in SERIAL groups. + // + if ( cmd == SEQUENCERD_ENGINEERING ) { + ret = this->sequence.engineering( args, retstring ); + retstring.append( " " ); + } + else + // Report the Sequencer State, // which will be returned, logged, and written to the async message port. // From 188300b297c4a7e59e7e12ca0134f648d96e28bf Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 24 Apr 2026 15:01:48 -0700 Subject: [PATCH 17/35] * starts migrating sequencer off UDP broadcast, replaced with ZMQ * splits ACQUIRE wait state into MOVETO, ACAM_ACQUIRE, SLICECAM_FINEACQUIRE * fixes minor logging typos in focus_server and tcs_server --- common/message_keys.h | 40 +++- sequencerd/sequence.cpp | 277 ++++++++++++++++------------ sequencerd/sequence.h | 45 +++-- sequencerd/sequence_acquisition.cpp | 22 +-- sequencerd/sequence_wait.cpp | 18 +- 5 files changed, 248 insertions(+), 154 deletions(-) diff --git a/common/message_keys.h b/common/message_keys.h index 13a4d7ff..12ef41c7 100644 --- a/common/message_keys.h +++ b/common/message_keys.h @@ -8,13 +8,39 @@ #include +namespace Daemon { + inline const std::string ACAMD = "acamd"; + inline const std::string CALIBD = "calibd"; + inline const std::string CAMERAD = "camerad"; + inline const std::string FLEXURED = "flexured"; + inline const std::string FOCUSD = "focusd"; + inline const std::string POWERD = "powerd"; + inline const std::string SEQUENCER = "sequencerd"; + inline const std::string SLICECAMD = "slicecamd"; + inline const std::string SLITD = "slitd"; + inline const std::string TCSD = "tcsd"; + inline const std::string THERMALD = "thermald"; +} + +namespace Severity { + inline const std::string NOTICE = "NOTICE"; + inline const std::string WARNING = "WARNING"; + inline const std::string ERROR = "ERROR"; +} + namespace Topic { inline const std::string SNAPSHOT = "_snapshot"; inline const std::string TARGETINFO = "targetinfo"; + inline const std::string BROADCAST = "broadcast"; inline const std::string TCSD = "tcsd"; inline const std::string SLITD = "slitd"; inline const std::string CAMERAD = "camerad"; inline const std::string ACAMD = "acamd"; + inline const std::string CALIBD = "calibd"; + inline const std::string FLEXURED = "flexured"; + inline const std::string FOCUSD = "focusd"; + inline const std::string POWERD = "powerd"; + inline const std::string THERMALD = "thermald"; inline const std::string SEQ_DAEMONSTATE = "seq_daemonstate"; inline const std::string SEQ_SEQSTATE = "seq_seqstate"; inline const std::string SEQ_THREADSTATE = "seq_threadstate"; @@ -26,13 +52,23 @@ namespace Key { inline const std::string SOURCE = "source"; + namespace Broadcast { + inline const std::string SEVERITY = "severity"; + inline const std::string MESSAGE = "message"; + } + namespace Sequencer { inline const std::string SEQSTATE = "seqstate"; } namespace Camerad { - inline const std::string READY = "ready"; - inline const std::string SHUTTERTIME = "shuttime_sec"; + inline const std::string READY = "ready"; + inline const std::string SHUTTERTIME = "shuttime_sec"; + inline const std::string EXPTIME = "exptime"; + inline const std::string IMNUM = "imnum"; + inline const std::string IMNAME = "imname"; + inline const std::string FRAMECOUNT = "framecount"; + inline const std::string FRAMETRANSFER = "frametransfer"; } namespace Acamd { diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index d854ad9d..35599995 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -532,22 +532,22 @@ namespace Sequencer { // Only READY or NOTREADY states allow a new sequence. if ( seq_state_manager.is_set(Sequencer::SEQ_RUNNING) ) { - this->async.enqueue_and_log(function, "ERROR sequence rejected: sequencer already running"); + this->broadcast( function, Severity::ERROR, "sequence rejected: sequencer already running" ); return ERROR; } if ( seq_state_manager.is_set(Sequencer::SEQ_STOPPING) ) { - this->async.enqueue_and_log(function, "ERROR sequence rejected: sequencer is stopping"); + this->broadcast( function, Severity::ERROR, "sequence rejected: sequencer is stopping" ); return ERROR; } if ( seq_state_manager.is_set(Sequencer::SEQ_STARTING) ) { - this->async.enqueue_and_log(function, "ERROR sequence rejected: sequencer is starting"); + this->broadcast( function, Severity::ERROR, "sequence rejected: sequencer is starting" ); return ERROR; } if ( seq_state_manager.is_set(Sequencer::SEQ_PAUSED) ) { - this->async.enqueue_and_log(function, "ERROR sequence rejected: sequencer is paused"); + this->broadcast( function, Severity::ERROR, "sequence rejected: sequencer is paused" ); return ERROR; } @@ -555,7 +555,7 @@ namespace Sequencer { // Sequence must not be empty. if ( sequence.empty() ) { - this->async.enqueue_and_log(function, "ERROR sequence is empty"); + this->broadcast( function, Severity::ERROR, "sequence is empty" ); return ERROR; } @@ -564,7 +564,7 @@ namespace Sequencer { for (const auto &group : sequence) { if ( group.operations.empty() ) { - this->async.enqueue_and_log(function, "ERROR sequence contains an empty operation group"); + this->broadcast( function, Severity::ERROR, "sequence contains an empty operation group" ); return ERROR; } } @@ -593,8 +593,7 @@ namespace Sequencer { // If expose is present, camera_set must also be present. if ( has_expose && !has_camera_set ) { - this->async.enqueue_and_log(function, - "ERROR sequence contains 'expose' without 'camera_set'"); + this->broadcast( function, Severity::ERROR, "sequence contains 'expose' without 'camera_set'" ); error = ERROR; } @@ -625,10 +624,10 @@ namespace Sequencer { if ( required.test(bit) && !daemon_manager.is_set(static_cast(bit)) ) { std::ostringstream oss; - oss << "ERROR sequence requires daemon '" + oss << "sequence requires daemon '" << daemon_name.at(static_cast(bit)) << "' but it is not ready"; - this->async.enqueue_and_log(function, oss.str()); + this->broadcast(function, Severity::ERROR, oss.str()); error = ERROR; } } @@ -636,8 +635,7 @@ namespace Sequencer { else // for science mode (non-engineering) all subsystems must be ready if ( !seq_state_manager.is_set(Sequencer::SEQ_READY) ) { - this->async.enqueue_and_log(function, - "ERROR sequence requires SEQ_READY (all daemons ready) in science mode"); + this->broadcast( function, Severity::ERROR, "sequence requires SEQ_READY (all daemons ready) in science mode" ); error = ERROR; } } @@ -664,10 +662,10 @@ namespace Sequencer { if ( is_parallel_unsafe_pair(group.operations[i].thr, group.operations[j].thr) ) { std::ostringstream oss; - oss << "ERROR parallel group contains unsafe pair: '" + oss << "parallel group contains unsafe pair: '" << thread_names.at(group.operations[i].thr) << "' and '" << thread_names.at(group.operations[j].thr) << "'"; - this->async.enqueue_and_log(function, oss.str()); + this->broadcast(function, Severity::ERROR, oss.str()); error = ERROR; } } @@ -685,11 +683,11 @@ namespace Sequencer { for (std::size_t j = i+1; j < all_thrs.size(); ++j) { if ( is_parallel_unsafe_pair(all_thrs[i], all_thrs[j]) ) { std::ostringstream oss; - oss << "ERROR sequence contains unsafe pair in science mode: '" + oss << "sequence contains unsafe pair in science mode: '" << thread_names.at(all_thrs[i]) << "' and '" << thread_names.at(all_thrs[j]) << "' (use 'engineering true' to allow in SERIAL groups)"; - this->async.enqueue_and_log(function, oss.str()); + this->broadcast(function, Severity::ERROR, oss.str()); error = ERROR; } } @@ -701,8 +699,7 @@ namespace Sequencer { // TBD should I allow expose to not appear more than once across all groups? if ( expose_count > 1 ) { - this->async.enqueue_and_log(function, - "NOTICE sequence contains multiple 'expose' operations -- placeholder check"); + this->broadcast( function, Severity::NOTICE, "sequence contains multiple 'expose' operations -- placeholder check" ); // not a hard error yet; pending clarification of multi-expose DSL usage } @@ -717,7 +714,7 @@ namespace Sequencer { if (false) { // stub -- replace with real contention check - this->async.enqueue_and_log(function, + this->broadcast(function, Severity::NOTICE, "PLACEHOLDER parallel contention check not yet implemented"); } @@ -1049,31 +1046,58 @@ namespace Sequencer { /***** Sequencer::Sequence::broadcast_seqstate ******************************/ /** - * @brief writes string of seq_state to the async port - * @details This broadcasts the seqstate as a string with the "SEQSTATE:" - * message tag. + * @brief publishes seq_state via PUB-SUB + * @details The legacy "SEQSTATE:" UDP async string has been removed; + * seqstate is published exclusively on the PUB-SUB topic now. * */ void Sequence::broadcast_seqstate() { this->publish_seqstate(); - this->async.enqueue_and_log( "Sequencer::Sequence::broadcast_seqstate", - "SEQSTATE: "+seq_state_manager.get_set_states() ); this->cv.notify_all(); } /***** Sequencer::Sequence::broadcast_seqstate ******************************/ + /***** Sequencer::Sequence::broadcast ***************************************/ + /** + * @brief logs a narrative message and publishes it on Topic::BROADCAST + * @param[in] function name of caller function (for log) + * @param[in] severity one of Severity::NOTICE, Severity::WARNING, Severity::ERROR + * @param[in] message the operator-facing narrative text + * @details This replaces the legacy pattern of enqueuing narrative + * strings onto the UDP async queue. Messages are routed through + * the PUB-SUB broadcast topic instead, where the GUI message + * area subscribes. Logging behavior is preserved. + */ + void Sequence::broadcast( const std::string &function, + const std::string &severity, + const std::string &message ) { + logwrite( function, severity+": "+message ); + if ( this->publisher ) { + nlohmann::json jmessage; + jmessage[Key::Broadcast::SEVERITY] = severity; + jmessage[Key::Broadcast::MESSAGE] = message; + jmessage[Key::SOURCE] = Daemon::SEQUENCER; + try { + this->publisher->publish( jmessage, Topic::BROADCAST ); + } + catch ( const std::exception &e ) { + logwrite( function, "ERROR publishing broadcast: "+std::string(e.what()) ); + } + } + } + /***** Sequencer::Sequence::broadcast ***************************************/ + + /***** Sequencer::Sequence::broadcast_waitstate *****************************/ /** - * @brief writes string of all set wait_state bits to the asyn port - * @details This broadcasts the seqstate as a string with the "WAITSTATE:" - * message tag. + * @brief publishes wait_state via PUB-SUB + * @details The legacy "WAITSTATE:" UDP async string has been removed; + * waitstate is published exclusively on the PUB-SUB topic now. * */ void Sequence::broadcast_waitstate() { this->publish_waitstate(); - this->async.enqueue_and_log( "Sequencer::Sequence::broadcast_waitstate", - "WAITSTATE: "+wait_state_manager.get_set_states() ); this->cv.notify_all(); } /***** Sequencer::Sequence::broadcast_waitstate *****************************/ @@ -1215,14 +1239,14 @@ namespace Sequencer { // The Sequencer can only be started once // if ( thread_state_manager.is_set( Sequencer::THR_SEQUENCE_START ) ) { - this->async.enqueue_and_log( function, "ERROR sequencer already running" ); + this->broadcast( function, Severity::ERROR, "sequencer already running" ); return; } // The Sequencer can only be started when state is READY // if ( ! seq_state_manager.is_set( Sequencer::SEQ_READY ) ) { - this->async.enqueue_and_log( function, "ERROR cannot start: system not ready" ); + this->broadcast( function, Severity::ERROR, "cannot start: system not ready" ); return; } @@ -1313,7 +1337,7 @@ namespace Sequencer { } else if ( targetstate == TargetInfo::TARGET_ERROR ) { // request stop on error - this->async.enqueue_and_log( function, "ERROR getting next target. stopping" ); + this->broadcast( function, Severity::ERROR, "getting next target. stopping" ); break; } @@ -1327,7 +1351,7 @@ namespace Sequencer { } if (is_cancelled()) { - this->async.enqueue_and_log(function, "NOTICE: sequence cancelled"); + this->broadcast( function, Severity::NOTICE, "sequence cancelled" ); break; } @@ -1423,14 +1447,14 @@ namespace Sequencer { std::string cmd = CAMERAD_ACTIVATE + activechans.str(); /*** if ( camerad_cmd.send( { CAMERAD_ACTIVATE, { activechans.str() } } ) != NO_ERROR ) { WIP ***/ if (this->camerad.send(cmd, reply)!=NO_ERROR) { - this->async.enqueue_and_log(function, "ERROR sending \""+cmd+"\": "+reply); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\": "+reply ); throw std::runtime_error("camera returned "+reply); } } if (!deactivechans.str().empty()) { std::string cmd = CAMERAD_DEACTIVATE + deactivechans.str(); if (this->camerad.send(cmd, reply)!=NO_ERROR) { - this->async.enqueue_and_log(function, "ERROR sending \""+cmd+"\": "+reply); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\": "+reply ); throw std::runtime_error("camera returned "+reply); } } @@ -1443,7 +1467,7 @@ namespace Sequencer { long exptime_msec = (long)( this->target.exptime_req * 1000 ); camcmd.str(""); camcmd << CAMERAD_EXPTIME << " " << exptime_msec; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->async.enqueue_and_log( function, "ERROR sending \""+camcmd.str()+"\": "+reply ); + this->broadcast( function, Severity::ERROR, "sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } @@ -1451,12 +1475,12 @@ namespace Sequencer { // camcmd.str(""); camcmd << CAMERAD_BIN << " spat " << this->target.binspat; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->async.enqueue_and_log( function, "ERROR sending \""+camcmd.str()+"\": "+reply ); + this->broadcast( function, Severity::ERROR, "sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } camcmd.str(""); camcmd << CAMERAD_BIN << " spec " << this->target.binspect; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->async.enqueue_and_log( function, "ERROR sending \""+camcmd.str()+"\": "+reply ); + this->broadcast( function, Severity::ERROR, "sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } @@ -1517,7 +1541,7 @@ namespace Sequencer { logwrite( function, "moving slit to "+slitcmd.str()+" for "+modestr+"position" ); if ( this->slitd.command_timeout( slitcmd.str(), reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR setting slit" ); + this->broadcast( function, Severity::ERROR, "setting slit" ); this->thread_error_manager.set( THR_SLIT_SET ); throw std::runtime_error("slit returned: "+reply); } @@ -1543,7 +1567,7 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_POWER ); // powerd not ready if ( this->reopen_hardware(this->powerd, POWERD_REOPEN, 10000 ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR initializing power control" ); + this->broadcast( function, Severity::ERROR, "initializing power control" ); throw std::runtime_error("could not initialize power control"); } @@ -1595,13 +1619,13 @@ namespace Sequencer { this->thread_error_manager.set( THR_SLIT_INIT ); // assume the worst, clear on success if ( this->set_power_switch(ON, POWER_SLIT, std::chrono::seconds(5)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR powering slit hardware" ); + this->broadcast( function, Severity::ERROR, "powering slit hardware" ); throw std::runtime_error("could not power slit hardware"); } bool was_opened=false; if ( this->open_hardware(this->slitd, was_opened) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR connecting to slit" ); + this->broadcast( function, Severity::ERROR, "connecting to slit" ); throw std::runtime_error("could not open connection to slit hardware"); } @@ -1610,7 +1634,7 @@ namespace Sequencer { bool ishomed=false; std::string reply; if ( this->slitd.command( SLITD_ISHOME, reply ) ) { - this->async.enqueue_and_log( function, "ERROR communicating with slit hardware" ); + this->broadcast( function, Severity::ERROR, "communicating with slit hardware" ); throw std::runtime_error("could not communicate with slit hardware: "+reply); } this->parse_state( function, reply, ishomed ); @@ -1620,7 +1644,7 @@ namespace Sequencer { if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->slitd.command_timeout( SLITD_HOME, reply, SLITD_HOME_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR communicating with slit hardware" ); + this->broadcast( function, Severity::ERROR, "communicating with slit hardware" ); throw std::runtime_error("could not home slit hardware: "+reply); } } @@ -1630,7 +1654,7 @@ namespace Sequencer { if ( was_opened && !this->config_init["SLIT"].empty() ) { std::string cmd = SLITD_SET+" "+this->config_init["SLIT"]; if ( this->slitd.command_timeout( cmd, reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending \""+cmd+"\" to slit" ); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to slit" ); throw std::runtime_error("slit "+cmd+" returned: "+reply); } } @@ -1684,7 +1708,7 @@ namespace Sequencer { if (error==NO_ERROR && !this->config_shutdown["SLIT"].empty() ) { std::string cmd = SLITD_SET+" "+this->config_shutdown["SLIT"]; if ( this->slitd.command_timeout( cmd, reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending \""+cmd+"\" to slit" ); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to slit" ); throw std::runtime_error(cmd+" returned: "+reply); } } @@ -1694,7 +1718,7 @@ namespace Sequencer { logwrite( function, "closing slit hardware" ); error = this->slitd.command( SLITD_CLOSE, reply ); if ( error != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR closing connection to slit hardware" ); + this->broadcast( function, Severity::ERROR, "closing connection to slit hardware" ); throw std::runtime_error("closing slit connection returned: "+reply); } @@ -1731,14 +1755,14 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_SLICECAM, std::chrono::seconds(10)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR initializing slicecam control" ); + this->broadcast( function, Severity::ERROR, "initializing slicecam control" ); throw std::runtime_error("could not power slicecam hardware"); } // open connection is all that is needed, slicecamd takes care of everything // if ( this->open_hardware(this->slicecamd, SLICECAMD_OPEN, SLICECAMD_OPEN_TIMEOUT) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR starting slicecam" ); + this->broadcast( function, Severity::ERROR, "starting slicecam" ); throw SlicecamException("could not start slicecam"); } @@ -1771,7 +1795,7 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_ACAM, std::chrono::seconds(10)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR powering acam hardware" ); + this->broadcast( function, Severity::ERROR, "powering acam hardware" ); throw std::runtime_error("could not power acam hardware"); } @@ -1779,7 +1803,7 @@ namespace Sequencer { // bool was_opened=false; if ( this->open_hardware(this->acamd, ACAMD_OPEN, ACAMD_OPEN_TIMEOUT, was_opened) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR opening acam camera" ); + this->broadcast( function, Severity::ERROR, "opening acam camera" ); throw AcamException(ErrorCode::ERROR_ACAM_CAMERA, "could not open acam camera"); } @@ -1790,14 +1814,14 @@ namespace Sequencer { if ( ! this->config_init["ACAM_FILTER"].empty() ) { cmd = ACAMD_FILTER+" "+this->config_init["ACAM_FILTER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending \""+cmd+"\" to acamd: "+reply ); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } if ( ! this->config_init["ACAM_COVER"].empty() ) { cmd = ACAMD_COVER+" "+this->config_init["ACAM_COVER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending \""+cmd+"\" to acamd: "+reply ); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } @@ -1846,14 +1870,14 @@ namespace Sequencer { } if ( (error=this->connect_to_daemon(this->slicecamd)) != NO_ERROR ) { - this->async.enqueue_and_log(function, "ERROR connecting to slicecamd"); + this->broadcast( function, Severity::ERROR, "connecting to slicecamd" ); } // close connections between slicecamd and the hardware with which it communicates // logwrite( function, "closing slicecam hardware" ); if ( (error=this->slicecamd.command_timeout( SLICECAMD_SHUTDOWN, reply, SLICECAMD_SHUTDOWN_TIMEOUT )) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR closing connection to slicecam hardware" ); + this->broadcast( function, Severity::ERROR, "closing connection to slicecam hardware" ); } // disconnect me from slicecamd, irrespective of any previous error @@ -1864,7 +1888,7 @@ namespace Sequencer { // Turn off power to slicecam hardware. // if ( this->set_power_switch(OFF, POWER_SLICECAM, std::chrono::seconds(0)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR switching off slicecam" ); + this->broadcast( function, Severity::ERROR, "switching off slicecam" ); throw std::runtime_error("could not power off slicecam hardware"); } @@ -1904,14 +1928,14 @@ namespace Sequencer { if ( ! this->config_shutdown["ACAM_FILTER"].empty() ) { cmd = ACAMD_FILTER+" "+this->config_shutdown["ACAM_FILTER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending \""+cmd+"\" to acamd: "+reply ); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } if ( ! this->config_shutdown["ACAM_COVER"].empty() ) { cmd = ACAMD_COVER+" "+this->config_shutdown["ACAM_COVER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending \""+cmd+"\" to acamd: "+reply ); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } @@ -1924,7 +1948,7 @@ namespace Sequencer { if ( error==NO_ERROR ) { logwrite( function, "closing acam hardware" ); error = this->acamd.command_timeout( ACAMD_SHUTDOWN, ACAMD_SHUTDOWN_TIMEOUT ); - if ( error != NO_ERROR ) this->async.enqueue_and_log( function, "ERROR shutting down acam" ); + if ( error != NO_ERROR ) this->broadcast( function, Severity::ERROR, "shutting down acam" ); } // disconnect me from acamd, irrespective of any previous error @@ -1935,7 +1959,7 @@ namespace Sequencer { // Turn off power to acam hardware. // if ( this->set_power_switch(OFF, POWER_ACAM, std::chrono::seconds(0)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR switching off acam" ); + this->broadcast( function, Severity::ERROR, "switching off acam" ); throw std::runtime_error("could not switch off acam"); } @@ -1965,14 +1989,14 @@ namespace Sequencer { // make sure calib hardware is powered if ( this->set_power_switch(ON, POWER_CALIB, std::chrono::seconds(5)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR powering focus control" ); + this->broadcast( function, Severity::ERROR, "powering focus control" ); throw std::runtime_error("could not power focus control"); } // connect to calibd bool was_opened=false; if ( this->open_hardware(this->calibd, was_opened) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR initializing calib control" ); + this->broadcast( function, Severity::ERROR, "initializing calib control" ); throw std::runtime_error("could not power calib control"); } @@ -1984,14 +2008,14 @@ namespace Sequencer { std::string reply; long error = this->calibd.command( CALIBD_ISHOME, reply ); if ( error!=NO_ERROR || this->parse_state( function, reply, ishomed ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR communicating with calib hardware" ); + this->broadcast( function, Severity::ERROR, "communicating with calib hardware" ); throw std::runtime_error("could not communicate with calib hardware: "+reply); } // home calib actuators if not already homed if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->calibd.command_timeout( CALIBD_HOME, reply, CALIBD_HOME_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR communicating with calib hardware" ); + this->broadcast( function, Severity::ERROR, "communicating with calib hardware" ); throw std::runtime_error("could not communicate with calib hardware: "+reply); } } @@ -2004,7 +2028,7 @@ namespace Sequencer { if ( !this->config_init["CALIB_DOOR"].empty() ) cmd << " door=" << this->config_init["CALIB_DOOR"]; logwrite( function, "calib default: "+cmd.str() ); if ( this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR moving calib door and/or cover" ); + this->broadcast( function, Severity::ERROR, "moving calib door and/or cover" ); throw std::runtime_error("could not move calib door and/or cover"); } } @@ -2041,7 +2065,7 @@ namespace Sequencer { // bool poweron=false; if ( check_power_switch(ON, POWER_CALIB, poweron ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR checking calib power switch" ); + this->broadcast( function, Severity::ERROR, "checking calib power switch" ); throw std::runtime_error("checking calib power switch"); } @@ -2059,7 +2083,7 @@ namespace Sequencer { if ( !this->config_shutdown["CALIB_DOOR"].empty() ) cmd << " door=" << this->config_shutdown["CALIB_DOOR"]; logwrite( function, "calib default: "+cmd.str() ); if ( this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR moving calib door and/or cover" ); + this->broadcast( function, Severity::ERROR, "moving calib door and/or cover" ); throw std::runtime_error("moving calib door and/or cover"); } } @@ -2083,14 +2107,14 @@ namespace Sequencer { // Turn off power to calib hardware. // if ( this->set_power_switch(OFF, POWER_CALIB, std::chrono::seconds(0)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR switching off calib hardware" ); + this->broadcast( function, Severity::ERROR, "switching off calib hardware" ); error=ERROR; } // always turn off power to lamps // if ( this->set_power_switch(OFF, POWER_LAMP, std::chrono::seconds(5)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR powering off lamps" ); + this->broadcast( function, Severity::ERROR, "powering off lamps" ); error=ERROR; } @@ -2124,7 +2148,7 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_TCS ); // tcsd not ready if ( this->open_hardware(this->tcsd) != NO_ERROR ) { - this->async.enqueue_and_log( "Sequencer::Sequence::tcs_init", "ERROR initializing TCS" ); + this->broadcast( "Sequencer::Sequence::tcs_init", Severity::ERROR, "initializing TCS" ); this->thread_error_manager.set( THR_TCS_INIT ); throw std::runtime_error("could not initialize TCS"); } @@ -2168,7 +2192,7 @@ namespace Sequencer { std::string reply; error = this->tcsd.send( TCSD_CLOSE, reply ); if ( error != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR: closing connection to TCS" ); + this->broadcast( function, Severity::ERROR, "closing connection to TCS" ); throw std::runtime_error("closing TCS connection: "+reply); } } @@ -2202,13 +2226,13 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_FLEXURE, std::chrono::seconds(21)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR powering flexure control" ); + this->broadcast( function, Severity::ERROR, "powering flexure control" ); this->thread_error_manager.set( THR_FLEXURE_INIT ); throw std::runtime_error("could not power flexure control"); } if ( this->open_hardware(this->flexured) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR initializing flexure control" ); + this->broadcast( function, Severity::ERROR, "initializing flexure control" ); this->thread_error_manager.set( THR_FLEXURE_INIT ); throw std::runtime_error("could not initialize flexure control"); } @@ -2255,7 +2279,7 @@ namespace Sequencer { } if ( this->connect_to_daemon(this->flexured) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR connecting to flexure hardware" ); + this->broadcast( function, Severity::ERROR, "connecting to flexure hardware" ); error=ERROR; } @@ -2264,7 +2288,7 @@ namespace Sequencer { // logwrite( function, "closing flexure hardware" ); if (error==NO_ERROR && (error=this->flexured.command( FLEXURED_CLOSE, reply )) != NO_ERROR) { - this->async.enqueue_and_log( function, "ERROR closing connection to flexure hardware" ); + this->broadcast( function, Severity::ERROR, "closing connection to flexure hardware" ); } // disconnect me from flexured, irrespective of any previous error @@ -2275,7 +2299,7 @@ namespace Sequencer { // Turn off power to flexure hardware. // if ( this->set_power_switch(OFF, POWER_FLEXURE, std::chrono::seconds(0)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR switching off flexure" ); + this->broadcast( function, Severity::ERROR, "switching off flexure" ); throw std::runtime_error("switching off flexure hardware"); } @@ -2302,14 +2326,14 @@ namespace Sequencer { this->thread_error_manager.set( THR_FOCUS_INIT ); // assume failure, clear on success if ( this->set_power_switch(ON, POWER_FOCUS, std::chrono::seconds(5)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR powering focus control" ); + this->broadcast( function, Severity::ERROR, "powering focus control" ); throw std::runtime_error("could not power focus control"); } // connect to focusd bool was_opened=false; if ( this->open_hardware(this->focusd, was_opened) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR initializing focus control" ); + this->broadcast( function, Severity::ERROR, "initializing focus control" ); throw std::runtime_error("could not open focus hardware"); } @@ -2321,14 +2345,14 @@ namespace Sequencer { std::string reply; long error = this->focusd.command( FOCUSD_ISHOME, reply ); if ( error!=NO_ERROR || this->parse_state( function, reply, ishomed ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR communicating with focus hardware" ); + this->broadcast( function, Severity::ERROR, "communicating with focus hardware" ); throw std::runtime_error("focus "+FOCUSD_ISHOME+" returned: "+reply); } // home focus actuators if not already homed if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->focusd.command_timeout( FOCUSD_HOME, reply, FOCUSD_HOME_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR communicating with focus hardware" ); + this->broadcast( function, Severity::ERROR, "communicating with focus hardware" ); throw std::runtime_error("focus "+FOCUSD_HOME+" returned: "+reply); } } @@ -2338,7 +2362,7 @@ namespace Sequencer { for ( const auto &chan : chans ) { std::string command = "set " + chan + " nominal"; if ( this->focusd.command_timeout( command, reply, FOCUSD_SET_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR setting focus "+chan ); + this->broadcast( function, Severity::ERROR, "setting focus "+chan ); throw std::runtime_error("focus "+command+" returned: "+reply); } } @@ -2384,7 +2408,7 @@ namespace Sequencer { } if ( this->connect_to_daemon(this->focusd) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR connecting to focus hardware" ); + this->broadcast( function, Severity::ERROR, "connecting to focus hardware" ); error=ERROR; } @@ -2393,7 +2417,7 @@ namespace Sequencer { // logwrite( function, "closing focus hardware" ); if (error==NO_ERROR && (error=this->focusd.command( FOCUSD_CLOSE, reply )) != NO_ERROR) { - this->async.enqueue_and_log( function, "ERROR closing connection to focus hardware" ); + this->broadcast( function, Severity::ERROR, "closing connection to focus hardware" ); } // disconnect me from focusd, irrespective of any previous error @@ -2404,7 +2428,7 @@ namespace Sequencer { // Turn off power to focus hardware. // if ( this->set_power_switch(OFF, POWER_FOCUS, std::chrono::seconds(0)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR switching off focus" ); + this->broadcast( function, Severity::ERROR, "switching off focus" ); throw std::runtime_error("switching off focus hardware"); } @@ -2432,13 +2456,13 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_CAMERA, std::chrono::seconds(5)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR powering camera" ); + this->broadcast( function, Severity::ERROR, "powering camera" ); throw std::runtime_error("switching on camera"); } bool was_opened=false; if ( this->open_hardware(this->camerad, "open", 12000, was_opened) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR initializing camera" ); + this->broadcast( function, Severity::ERROR, "initializing camera" ); throw std::runtime_error("initializing camera"); } @@ -2448,7 +2472,7 @@ namespace Sequencer { if ( was_opened) { for ( const auto &cmd : this->camera_prologue ) { if ( this->camerad.command_timeout( cmd, reply, CAMERA_PROLOG_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending \""+cmd+"\" to camera" ); + this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to camera" ); throw std::runtime_error("sending \""+cmd+"\" to camera"); } } @@ -2519,7 +2543,7 @@ namespace Sequencer { // turn off power to camera hardware // if ( this->set_power_switch(OFF, POWER_CAMERA, std::chrono::seconds(5)) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR powering off camera" ); + this->broadcast( function, Severity::ERROR, "powering off camera" ); throw std::runtime_error("switching off camera"); } @@ -2546,6 +2570,7 @@ namespace Sequencer { ScopedState thr_state( thread_state_manager, Sequencer::THR_MOVE_TO_TARGET ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCS ); + ScopedState wait_moveto( wait_state_manager, Sequencer::SEQ_WAIT_MOVETO ); // Apply optional CLI/DSL coordinate overrides carried in current_op_params // (e.g. "move_to_target ra=01:23:45 dec=+45:00:00"). Overrides are written @@ -2590,7 +2615,7 @@ namespace Sequencer { // if ( this->target.ra_hms == this->last_ra_hms && this->target.dec_dms == this->last_dec_dms ) { - this->async.enqueue_and_log( function, "NOTICE: no move required for repeat target" ); + this->broadcast( function, Severity::NOTICE, "no move required for repeat target" ); return NO_ERROR; } @@ -2614,7 +2639,7 @@ namespace Sequencer { if ( ra_isnan ) { message << " RA=\"" << this->target.ra_hms << "\""; } if ( dec_isnan ) { message << " DEC=\"" << this->target.dec_dms << "\""; } message << " to decimal"; - this->async.enqueue_and_log( function, "ERROR "+message.str() ); + this->broadcast( function, Severity::ERROR, ""+message.str() ); this->thread_error_manager.set( THR_MOVE_TO_TARGET ); throw std::runtime_error(message.str()); } @@ -2686,14 +2711,14 @@ namespace Sequencer { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCSOP ); - this->async.enqueue_and_log( function, "NOTICE: waiting for TCS operator to send \"ontarget\" signal" ); + this->broadcast( function, Severity::NOTICE, "waiting for TCS operator to send \"ontarget\" signal" ); while ( !is_cancelled() && !this->is_ontarget.load() ) { std::unique_lock lock(cv_mutex); this->cv.wait( lock, [this]() { return( this->is_ontarget.load() || is_cancelled() ); } ); } - this->async.enqueue_and_log( function, "NOTICE: received " + this->broadcast( function, Severity::NOTICE, "received " +(is_cancelled() ? std::string("cancel") : std::string("ontarget")) +" signal!" ); } @@ -2765,7 +2790,7 @@ namespace Sequencer { // if ( std::isnan( radec_to_decimal( seq.target.ra_hms, ra_hms ) ) || std::isnan( radec_to_decimal( seq.target.dec_dms, dec_dms ) ) ) { - seq.async.enqueue_and_log( function, "ERROR: can't handle NaN value for RA, DEC" ); + seq.broadcast( function, Severity::ERROR, "can't handle NaN value for RA, DEC" ); seq.thread_error_manager.set( THR_NOTIFY_TCS ); return; } @@ -2851,7 +2876,7 @@ namespace Sequencer { // const auto &calinfo = this->caltarget.get_info(calname); - this->async.enqueue_and_log(function, "NOTICE: configuring calibrator for "+calname); + this->broadcast( function, Severity::NOTICE, "configuring calibrator for "+calname ); // set the calib door and cover // @@ -2863,7 +2888,7 @@ namespace Sequencer { logwrite( function, "calib: "+cmd.str() ); if ( !is_cancelled() && this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR moving calib door and/or cover" ); + this->broadcast( function, Severity::ERROR, "moving calib door and/or cover" ); throw std::runtime_error("moving calib door and/or cover"); } @@ -2876,7 +2901,7 @@ namespace Sequencer { logwrite( function, message.str() ); std::string reply; if ( this->powerd.send( cmd.str(), reply ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR "+message.str() ); + this->broadcast( function, Severity::ERROR, ""+message.str() ); throw std::runtime_error("setting lamp "+message.str()); } } @@ -2900,13 +2925,13 @@ namespace Sequencer { if ( is_cancelled() ) break; cmd.str(""); cmd << CALIBD_LAMPMOD << " " << mod << " " << (state?1:0) << " 1000"; if ( this->calibd.command( cmd.str() ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR "+cmd.str() ); + this->broadcast( function, Severity::ERROR, ""+cmd.str() ); throw std::runtime_error("setting lamp modulator "+cmd.str()); } } if ( is_cancelled() ) { - this->async.enqueue_and_log( function, "NOTICE: abort may have left calib system partially set" ); + this->broadcast( function, Severity::NOTICE, "abort may have left calib system partially set" ); } this->thread_error_manager.clear( THR_CALIBRATOR_SET ); // success @@ -2925,6 +2950,24 @@ namespace Sequencer { ScopedState thr_state( this->thread_state_manager, Sequencer::THR_ABORT_PROCESS ); + // Determine post-abort seqstate based on current seqstate, before we + // enter SEQ_ABORTING. Aborting during RUNNING or PAUSED returns to READY; + // aborting during STARTING or STOPPING is unsafe and transitions to FAILED. + // Otherwise, leave seqstate unchanged (abort bit only; no destruct bit). + const bool abort_during_run = seq_state_manager.are_any_set( Sequencer::SEQ_RUNNING, + Sequencer::SEQ_PAUSED ); + const bool abort_during_lifecycle = seq_state_manager.are_any_set( Sequencer::SEQ_STARTING, + Sequencer::SEQ_STOPPING ); + + // Scoped SEQ_ABORTING bit: set on entry, cleared on exit (RAII). + ScopedState seq_state( this->seq_state_manager, Sequencer::SEQ_ABORTING ); + if ( abort_during_run ) { + seq_state.destruct_set( Sequencer::SEQ_READY ); + } + else if ( abort_during_lifecycle ) { + seq_state.destruct_set( Sequencer::SEQ_FAILED ); + } + clear_cancel_flag(); // stop any exposure that may be in progress @@ -2945,7 +2988,7 @@ namespace Sequencer { // this->do_once.store(true); - this->async.enqueue_and_log( function, "NOTICE: cancel signal sent" ); + this->broadcast( function, Severity::NOTICE, "cancel signal sent" ); } /***** Sequencer::Sequence::stop_exposure *********************************/ @@ -2962,7 +3005,7 @@ namespace Sequencer { // This function is only used while exposing // if ( ! this->wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) { - this->async.enqueue_and_log( function, "NOTICE: not currently exposing" ); + this->broadcast( function, Severity::NOTICE, "not currently exposing" ); return; } @@ -2979,12 +3022,12 @@ namespace Sequencer { else if ( error == NOTHING ) { // if not exposing, this is a way to ensure WAIT_EXPOSE bit can be cleared - this->async.enqueue_and_log( function, "NOTICE: not exposing" ); + this->broadcast( function, Severity::NOTICE, "not exposing" ); this->wait_state_manager.clear( Sequencer::SEQ_WAIT_EXPOSE ); } else if ( error == BUSY ) { - this->async.enqueue_and_log( function, "NOTICE: too late to stop exposure" ); + this->broadcast( function, Severity::NOTICE, "too late to stop exposure" ); // can't stop in the last 5 sec so wait that long and it should stop on its own std::this_thread::sleep_for(std::chrono::seconds(5)); } @@ -3009,7 +3052,7 @@ namespace Sequencer { // can only repeat when state is READY // if ( ! seq_state_manager.is_set( Sequencer::SEQ_READY ) ) { - this->async.enqueue_and_log( function, "ERROR cannot repeat: system not ready" ); + this->broadcast( function, Severity::ERROR, "cannot repeat: system not ready" ); return ERROR; } @@ -3088,7 +3131,7 @@ namespace Sequencer { // if ( this->camerad.async( message.str() ) != NO_ERROR ) { // if ( this->camerad.send( message.str(), reply ) != NO_ERROR ) { if ( this->camerad.command_timeout( message.str(), reply, 30000 ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending camera "+message.str() ); + this->broadcast( function, Severity::ERROR, "sending camera "+message.str() ); this->thread_error_manager.set( THR_TRIGGER_EXPOSURE ); // tell the world this thread had an error this->target.update_state( Sequencer::TARGET_PENDING ); // return the target state to pending this->wait_state_manager.clear( Sequencer::SEQ_WAIT_EXPOSE ); // clear EXPOSE bit @@ -3150,7 +3193,7 @@ namespace Sequencer { // This function is only used while exposing // if ( ! this->wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) { - this->async.enqueue_and_log( function, "ERROR cannot update exposure time when not currently exposing" ); + this->broadcast( function, Severity::ERROR, "cannot update exposure time when not currently exposing" ); error = ERROR; } @@ -3196,7 +3239,9 @@ namespace Sequencer { const std::string function("Sequencer::Sequence::startup"); std::stringstream message; - if ( ! seq_state_manager.are_any_set( Sequencer::SEQ_READY, Sequencer::SEQ_NOTREADY ) ) { + if ( ! seq_state_manager.are_any_set( Sequencer::SEQ_READY, + Sequencer::SEQ_NOTREADY, + Sequencer::SEQ_FAILED ) ) { message << "ERROR cannot perform system startup while " << seq_state_manager.get_set_states(); this->async.enqueue_and_log( function, message.str() ); @@ -3244,12 +3289,12 @@ namespace Sequencer { long error = run_sequence(sequence, function); - // change state to READY if all daemons ready w/o error + // change state to READY if all daemons ready w/o error; otherwise FAILED if ( error==NO_ERROR && daemon_manager.are_all_set() ) { seq_state_manager.set_only( {Sequencer::SEQ_READY} ); } else { - seq_state_manager.set_only( {Sequencer::SEQ_NOTREADY} ); + seq_state_manager.set_only( {Sequencer::SEQ_FAILED} ); } return error; @@ -3271,11 +3316,9 @@ namespace Sequencer { ScopedState thr_state( this->thread_state_manager, Sequencer::THR_SHUTDOWN ); // this thread is running - // set only STOPPING (and clear everything else) + // set only STOPPING (and clear everything else, including any prior FAILED) ScopedState seq_state( seq_state_manager, Sequencer::SEQ_STOPPING, true ); // state=STOPPING (only) - seq_state.destruct_set( Sequencer::SEQ_NOTREADY ); // set state=NOTREADY on exit - // stop everything // this->abort_process(); @@ -3296,7 +3339,7 @@ namespace Sequencer { error = run( { THR_POWER_INIT, [this]{ return power_init(); }, { } }, function ); if ( error != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR from power control. Will try to continue (but don't hold your breath)" ); + this->broadcast( function, Severity::ERROR, "from power control. Will try to continue (but don't hold your breath)" ); } // container of shutdown threads to launch, @@ -3316,9 +3359,11 @@ namespace Sequencer { std::ostringstream message; if (error==NO_ERROR) { // TODO need granularity here message << "NOTICE: instrument is shut down"; + seq_state.destruct_set( Sequencer::SEQ_NOTREADY ); // clean shutdown -> NOTREADY } else { message << "ERROR occurred during shutdown and may not have completed"; + seq_state.destruct_set( Sequencer::SEQ_FAILED ); // bad shutdown -> FAILED } this->async.enqueue_and_log( function, message.str() ); @@ -4104,7 +4149,7 @@ namespace Sequencer { } // connection failed too many times if (attempt > maxattempts) { - async.enqueue_and_log(function, "ERROR exceeded max attempts connecting to " + daemon.name); + broadcast( function, Severity::ERROR, "exceeded max attempts connecting to " + daemon.name ); return ERROR; } @@ -4113,7 +4158,7 @@ namespace Sequencer { error |= daemon.send( "isopen", reply ); error |= this->parse_state( function, reply, isopen ); if ( error != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR opening "+daemon.name+" hardware" ); + this->broadcast( function, Severity::ERROR, "opening "+daemon.name+" hardware" ); return ERROR; } @@ -4123,7 +4168,7 @@ namespace Sequencer { logwrite( function, "opening "+daemon.name+" hardware connections with " +std::to_string(opentimeout)+" ms timeout" ); if ( daemon.command_timeout( opencmd, reply, opentimeout ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR opening connection to "+daemon.name+" hardware" ); + this->broadcast( function, Severity::ERROR, "opening connection to "+daemon.name+" hardware" ); return ERROR; } was_opened=true; @@ -4152,7 +4197,7 @@ namespace Sequencer { if ( !daemon.socket.isconnected() ) { logwrite( function, "connecting to "+daemon.name+" daemon" ); if ( daemon.connect() != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR connecting to "+daemon.name ); + this->broadcast( function, Severity::ERROR, "connecting to "+daemon.name ); return ERROR; } } diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index a89c7793..656f8861 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -132,6 +132,8 @@ namespace Sequencer { SEQ_STOPPING, ///< set when sequencer is shutting down SEQ_PAUSED, ///< set when sequencer is paused SEQ_STARTING, ///< set when sequencer is starting up + SEQ_FAILED, ///< set when sequencer has failed in an unsafe/indeterminate way + SEQ_ABORTING, ///< transitory state while aborting; cleared via RAII NUM_SEQ_STATES }; @@ -141,7 +143,9 @@ namespace Sequencer { {SEQ_RUNNING, "RUNNING"}, {SEQ_STOPPING, "STOPPING"}, {SEQ_PAUSED, "PAUSED"}, - {SEQ_STARTING, "STARTING"} + {SEQ_STARTING, "STARTING"}, + {SEQ_FAILED, "FAILED"}, + {SEQ_ABORTING, "ABORTING"} }; /** @@ -160,7 +164,9 @@ namespace Sequencer { SEQ_WAIT_SLIT, ///< set when waiting for slit SEQ_WAIT_TCS, ///< set when waiting for tcs // states - SEQ_WAIT_ACQUIRE, ///< set when waiting for acquire + SEQ_WAIT_MOVETO, ///< set when waiting for move-to-target + SEQ_WAIT_ACAM_ACQUIRE, ///< set when waiting for ACAM acquire + SEQ_WAIT_FINEACQUIRE, ///< set when waiting for slicecam fineacquire SEQ_WAIT_EXPOSE, ///< set when waiting for camera exposure SEQ_WAIT_READOUT, ///< set when waiting for camera readout SEQ_WAIT_TCSOP, ///< set when waiting specifically for tcs operator @@ -170,21 +176,23 @@ namespace Sequencer { const std::map wait_state_names = { // daemons - {SEQ_WAIT_ACAM, "ACAM"}, - {SEQ_WAIT_CALIB, "CALIB"}, - {SEQ_WAIT_CAMERA, "CAMERA"}, - {SEQ_WAIT_FLEXURE, "FLEXURE"}, - {SEQ_WAIT_FOCUS, "FOCUS"}, - {SEQ_WAIT_POWER, "POWER"}, - {SEQ_WAIT_SLICECAM, "SLICECAM"}, - {SEQ_WAIT_SLIT, "SLIT"}, - {SEQ_WAIT_TCS, "TCS"}, + {SEQ_WAIT_ACAM, "ACAM"}, + {SEQ_WAIT_CALIB, "CALIB"}, + {SEQ_WAIT_CAMERA, "CAMERA"}, + {SEQ_WAIT_FLEXURE, "FLEXURE"}, + {SEQ_WAIT_FOCUS, "FOCUS"}, + {SEQ_WAIT_POWER, "POWER"}, + {SEQ_WAIT_SLICECAM, "SLICECAM"}, + {SEQ_WAIT_SLIT, "SLIT"}, + {SEQ_WAIT_TCS, "TCS"}, // states - {SEQ_WAIT_ACQUIRE, "ACQUIRE"}, - {SEQ_WAIT_EXPOSE, "EXPOSE"}, - {SEQ_WAIT_READOUT, "READOUT"}, - {SEQ_WAIT_TCSOP, "TCSOP"}, - {SEQ_WAIT_USER, "USER"} + {SEQ_WAIT_MOVETO, "MOVETO"}, + {SEQ_WAIT_ACAM_ACQUIRE, "ACAM_ACQUIRE"}, + {SEQ_WAIT_FINEACQUIRE, "FINEACQUIRE"}, + {SEQ_WAIT_EXPOSE, "EXPOSE"}, + {SEQ_WAIT_READOUT, "READOUT"}, + {SEQ_WAIT_TCSOP, "TCSOP"}, + {SEQ_WAIT_USER, "USER"} }; /** @@ -650,6 +658,11 @@ namespace Sequencer { void publish_daemonstate(); void publish_threadstate(); + /** @brief publishes a narrative operator message on Topic::BROADCAST and logs it */ + void broadcast( const std::string &function, + const std::string &severity, + const std::string &message ); + std::unique_ptr publisher; ///< publisher object std::string publisher_address; ///< publish socket endpoint std::string publisher_topic; ///< my default topic for publishing diff --git a/sequencerd/sequence_acquisition.cpp b/sequencerd/sequence_acquisition.cpp index 7da563c8..e794bd77 100644 --- a/sequencerd/sequence_acquisition.cpp +++ b/sequencerd/sequence_acquisition.cpp @@ -20,7 +20,7 @@ namespace Sequencer { std::string reply; ScopedState thr_state( thread_state_manager, Sequencer::THR_ACQUISITION ); - ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_ACQUIRE ); + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_ACAM_ACQUIRE ); // form and send the ACQUIRE command to ACAM // @@ -29,17 +29,17 @@ namespace Sequencer { double angle_in = this->target.slitangle; if ( std::isnan(ra_in) || std::isnan(dec_in) ) { - this->async.enqueue_and_log( function, "ERROR converting target coordinates to decimal" ); + this->broadcast( function, Severity::ERROR, "converting target coordinates to decimal" ); return ERROR; } std::ostringstream cmd; cmd << ACAMD_ACQUIRE << " " << ra_in << " " << dec_in << " " << angle_in; - this->async.enqueue_and_log( function, "NOTICE: starting ACAM acquisition" ); + this->broadcast( function, Severity::NOTICE, "starting ACAM acquisition" ); if ( this->acamd.command( cmd.str(), reply ) != NO_ERROR ) { - this->async.enqueue_and_log( function, "ERROR sending acquire command to acamd" ); + this->broadcast( function, Severity::ERROR, "sending acquire command to acamd" ); return ERROR; } @@ -58,7 +58,7 @@ namespace Sequencer { if (this->cancel_flag.load()) return ABORT; if (use_timeout && !this->is_acam_guiding.load()) { - this->async.enqueue_and_log(function, "ERROR ACAM acquisition timed out!"); + this->broadcast( function, Severity::ERROR, "ACAM acquisition timed out!" ); return TIMEOUT; } @@ -77,12 +77,12 @@ namespace Sequencer { long Sequence::do_slicecam_fineacquire() { const std::string function("Sequencer::Sequence::do_slicecam_fineacquire"); - ScopedState wait_state(wait_state_manager, Sequencer::SEQ_WAIT_ACQUIRE); + ScopedState wait_state(wait_state_manager, Sequencer::SEQ_WAIT_FINEACQUIRE); // TODO don't hard-code the arguments here: std::string reply; if (this->slicecamd.command( SLICECAMD_FINEACQUIRE+" start L", reply ) != NO_ERROR) { - this->async.enqueue_and_log(function, "ERROR starting slicecam fine acquisition"); + this->broadcast( function, Severity::ERROR, "starting slicecam fine acquisition" ); return ERROR; } @@ -91,7 +91,7 @@ namespace Sequencer { return ERROR; } - this->async.enqueue_and_log(function, "NOTICE: slicecam fine acquisition started"); + this->broadcast( function, Severity::NOTICE, "slicecam fine acquisition started" ); const bool use_timeout = ( this->acquisition_timeout > 0 ); const auto timeout_time = std::chrono::steady_clock::now() @@ -108,7 +108,7 @@ namespace Sequencer { if (this->cancel_flag.load()) return ABORT; if (use_timeout && !this->is_fineacquire_locked.load()) { - this->async.enqueue_and_log(function, "ERROR slicecam fine acquisition timed out!"); + this->broadcast( function, Severity::ERROR, "slicecam fine acquisition timed out!" ); return TIMEOUT; } @@ -132,7 +132,7 @@ namespace Sequencer { // ---------- ACAM acquire ----------------------------- // if ( this->do_acam_acquire() != NO_ERROR ) { - this->async.enqueue_and_log(caller, "WARNING acam acquisition failed"); + this->broadcast( caller, Severity::WARNING, "acam acquisition failed" ); // on ACAM acquisition failure wait for user to continue or cancel if ( this->wait_for_user(caller) == ABORT ) return ABORT; @@ -143,7 +143,7 @@ namespace Sequencer { // ---------- SLICECAM fineacquire --------------------- // if ( this->do_slicecam_fineacquire() != NO_ERROR ) { - this->async.enqueue_and_log(caller, "WARNING slicecam fine acquisition failed"); + this->broadcast( caller, Severity::WARNING, "slicecam fine acquisition failed" ); } return NO_ERROR; diff --git a/sequencerd/sequence_wait.cpp b/sequencerd/sequence_wait.cpp index 711151fc..a9f0a459 100644 --- a/sequencerd/sequence_wait.cpp +++ b/sequencerd/sequence_wait.cpp @@ -21,7 +21,7 @@ namespace Sequencer { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCSOP ); - this->async.enqueue_and_log(caller, "NOTICE: waiting for TCS operator to send 'ontarget' signal"); + this->broadcast( caller, Severity::NOTICE, "waiting for TCS operator to send 'ontarget' signal" ); while ( !this->cancel_flag.load() && !this->is_ontarget.load() ) { @@ -31,7 +31,7 @@ namespace Sequencer { this->cancel_flag.load() ); } ); } - this->async.enqueue_and_log(caller, "NOTICE: received " + this->broadcast( caller, Severity::NOTICE, "received " +(this->cancel_flag.load() ? std::string("cancel") : std::string("ontarget")) +" signal!" ); @@ -56,20 +56,20 @@ namespace Sequencer { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); - this->async.enqueue_and_log( caller, "NOTICE: waiting for USER to send 'continue' signal" ); + this->broadcast( caller, Severity::NOTICE, "waiting for USER to send 'continue' signal" ); while ( !this->cancel_flag.load() && !this->is_usercontinue.load() ) { std::unique_lock lock(cv_mutex); this->cv.wait( lock, [this]() { return( this->is_usercontinue.load() || this->cancel_flag.load() ); } ); } - this->async.enqueue_and_log( caller, "NOTICE: received " + this->broadcast( caller, Severity::NOTICE, "received " +(this->cancel_flag.load() ? std::string("cancel") : std::string("continue")) +" signal!" ); } // end scope for wait_state = WAIT_USER if ( this->cancel_flag.load() ) { - this->async.enqueue_and_log( caller, "NOTICE: sequence cancelled" ); + this->broadcast( caller, Severity::NOTICE, "sequence cancelled" ); return ABORT; } @@ -97,7 +97,7 @@ namespace Sequencer { } if (this->cancel_flag.load()) { - this->async.enqueue_and_log(caller, "NOTICE: exposure cancelled"); + this->broadcast( caller, Severity::NOTICE, "exposure cancelled" ); return ABORT; } @@ -128,7 +128,7 @@ namespace Sequencer { } if (this->cancel_flag.load()) { - this->async.enqueue_and_log(caller, "NOTICE: wait for readout cancelled"); + this->broadcast( caller, Severity::NOTICE, "wait for readout cancelled" ); return ABORT; } @@ -146,7 +146,7 @@ namespace Sequencer { */ long Sequence::wait_for_canexpose(std::string caller) { - this->async.enqueue_and_log(caller, "NOTICE: waiting for camera to be ready to expose"); + this->broadcast( caller, Severity::NOTICE, "waiting for camera to be ready to expose" ); while ( !this->cancel_flag.load() && !this->can_expose.load() ) { @@ -157,7 +157,7 @@ namespace Sequencer { } if (this->cancel_flag.load()) { - this->async.enqueue_and_log(caller, "NOTICE: wait for can_expose cancelled"); + this->broadcast( caller, Severity::NOTICE, "wait for can_expose cancelled" ); return ABORT; } From 1750162994de782de54eb311d93fad72876b025a Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 9 Apr 2026 09:18:55 -0700 Subject: [PATCH 18/35] fixes typo in slicecam for fine acquisition (#405) --- slicecamd/slicecam_interface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slicecamd/slicecam_interface.cpp b/slicecamd/slicecam_interface.cpp index fca6d53f..7f26c795 100644 --- a/slicecamd/slicecam_interface.cpp +++ b/slicecamd/slicecam_interface.cpp @@ -754,7 +754,7 @@ namespace Slicecam { applied++; } else - if ( config.param[entry] == "FINE_ACQUIRE_AIMPOINT=" ) { + if ( config.param[entry] == "FINE_ACQUIRE_AIMPOINT" ) { std::string which; double x,y; std::istringstream iss(config.arg[entry]); @@ -766,7 +766,7 @@ namespace Slicecam { this->fineacquire_state.aimpoint = { x, y }; } else - if ( config.param[entry] == "FINE_ACQUIRE_BACKGROUND=" ) { + if ( config.param[entry] == "FINE_ACQUIRE_BACKGROUND" ) { long x1, x2, y1, y2; std::istringstream iss(config.arg[entry]); if (!(iss >> x1 >> x2 >> y1 >> y2)) { From 10c5f28cbbb63acc79e7577c6e09743d2d607e12 Mon Sep 17 00:00:00 2001 From: David Hale Date: Wed, 29 Apr 2026 19:15:53 -0700 Subject: [PATCH 19/35] Integrates new pubsub model taking place in parallel, which replaces UDP broadcast with a ZMQ publisher and uses PUB-SUB to send/get status automatically instead of having to ask for it. These changes are not pertinent to this feature branch but are being included so that this branch keeps up with other progress. --- Config/sequencerd.cfg.in | 10 -- acamd/acam_interface.cpp | 24 +-- common/common.cpp | 31 ++++ common/common.h | 51 +++++++ common/message_keys.h | 12 +- flexured/flexure_interface.cpp | 4 +- sequencerd/sequence.cpp | 221 ++++++++++++---------------- sequencerd/sequence.h | 8 +- sequencerd/sequence_acquisition.cpp | 18 +-- sequencerd/sequence_wait.cpp | 18 +-- sequencerd/sequencer_server.cpp | 25 ---- slicecamd/slicecam_interface.cpp | 10 +- tcsd/tcs_interface.cpp | 81 ++++++---- tcsd/tcs_interface.h | 7 +- tcsd/tcsd.cpp | 4 + 15 files changed, 285 insertions(+), 239 deletions(-) diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index 61d81707..636b86d1 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -190,16 +190,6 @@ CAL_TARGET=(SCIENCE close open on on on on off off off off off off # UPDATE_TIME_REMAINING=10 # block updates to active target with less than this exptime (sec) remaining -# ----------------------------------------------------------------------------- -# TELEM_PROVIDER=( ) -# -# This is a list of telemetry providers where is the daemon name, -# and is the port on which to send the telemetry request. -# Provide one per line. -# -TELEM_PROVIDER=(slitd @SLITD_NB_PORT@) -TELEM_PROVIDER=(tcsd @TCSD_NB_PORT@) -TELEM_PROVIDER=(camerad @CAMERAD_NB_PORT@) # SUBSCRIBE_TO=(slitd "tcp://127.0.0.1:@SLITD_PUB_PORT@") SUBSCRIBE_TO=(tcsd "tcp://127.0.0.1:@TCSD_PUB_PORT@") diff --git a/acamd/acam_interface.cpp b/acamd/acam_interface.cpp index f19fbfcf..86df30be 100644 --- a/acamd/acam_interface.cpp +++ b/acamd/acam_interface.cpp @@ -1605,18 +1605,18 @@ namespace Acam { } // extract and store values in the class // - Common::extract_telemetry_value( jmessage, "TCSNAME", telem.tcsname ); - Common::extract_telemetry_value( jmessage, "ISOPEN", telem.is_tcs_open ); - Common::extract_telemetry_value( jmessage, "CASANGLE", telem.angle_scope ); - Common::extract_telemetry_value( jmessage, "TELRA", telem.ra_scope_hms ); - Common::extract_telemetry_value( jmessage, "TELDEC", telem.dec_scope_dms ); - Common::extract_telemetry_value( jmessage, "RA", telem.ra_scope_h ); - Common::extract_telemetry_value( jmessage, "DEC", telem.dec_scope_d ); - Common::extract_telemetry_value( jmessage, "RAOFFSET", telem.offsetra ); - Common::extract_telemetry_value( jmessage, "DECLOFFS", telem.offsetdec ); - Common::extract_telemetry_value( jmessage, "AZ", telem.az ); - Common::extract_telemetry_value( jmessage, "TELFOCUS", telem.telfocus ); - Common::extract_telemetry_value( jmessage, "AIRMASS", telem.airmass ); + Common::extract_telemetry_value( jmessage, "TCSNAME", telem.tcsname ); + Common::extract_telemetry_value( jmessage, "ISOPEN", telem.is_tcs_open ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::CASANGLE, telem.angle_scope ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::TELRA, telem.ra_scope_hms ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::TELDEC, telem.dec_scope_dms ); + Common::extract_telemetry_value( jmessage, "RA", telem.ra_scope_h ); + Common::extract_telemetry_value( jmessage, "DEC", telem.dec_scope_d ); + Common::extract_telemetry_value( jmessage, "RAOFFSET", telem.offsetra ); + Common::extract_telemetry_value( jmessage, "DECLOFFS", telem.offsetdec ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::AZ, telem.az ); + Common::extract_telemetry_value( jmessage, "TELFOCUS", telem.telfocus ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::AIRMASS, telem.airmass ); // save them to the database // diff --git a/common/common.cpp b/common/common.cpp index 3fc176eb..464fd76d 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -9,6 +9,37 @@ namespace Common { + /***** Common::Broadcaster::emit ********************************************/ + /** + * @brief logs a narrative message and publishes it on Topic::BROADCAST + * @param[in] function name of caller (used for log line) + * @param[in] severity one of Severity::NOTICE, Severity::WARNING, Severity::ERROR + * @param[in] message operator-facing narrative text + * @details Logs message via logwrite, then publishes a JSON payload + * on Topic::BROADCAST if the publisher has been initialized. + * + */ + void Broadcaster::emit( const std::string &function, + const std::string &severity, + const std::string &message ) { + logwrite( function, severity+": "+message ); + + if ( ! this->publisher ) return; + nlohmann::json jmessage; + jmessage[Key::SOURCE] = this->source; + jmessage[Key::Broadcast::SEVERITY] = severity; + jmessage[Key::Broadcast::MESSAGE] = message; + + try { + this->publisher->publish( jmessage, Topic::BROADCAST ); + } + catch ( const std::exception &e ) { + logwrite( function, "ERROR publishing broadcast: "+std::string(e.what()) ); + } + } + /***** Common::Broadcaster::emit ********************************************/ + + /***** Common::collect_telemetry ********************************************/ /** * @brief send the TELEMREQUEST command to daemon to get telemetry diff --git a/common/common.h b/common/common.h index d540dbd3..b8c768f9 100644 --- a/common/common.h +++ b/common/common.h @@ -22,6 +22,7 @@ #include "logentry.h" #include "network.h" +#include "message_keys.h" const long NOTHING = -1; const long NO_ERROR = 0; @@ -369,6 +370,56 @@ namespace Common { }; + /**************** Common::Broadcaster ***************************************/ + /** + * @class Broadcaster + * @brief logs a narrative message and publishes it on Topic::BROADCAST + * @details Captures a reference to a publisher and the source daemon name + * at construction time so that call sites need only supply the + * caller function name and message (and severity, for emit). + * The publisher reference is to the daemon's Common::PubSub, which + * may be null at the time this Broadcaster is constructed and gets + * populated later by init_pubsub. + * + */ + class Broadcaster { + private: + const std::unique_ptr &publisher; ///< reference to owner's publisher + std::string source; ///< source daemon name + + public: + Broadcaster( const std::unique_ptr &publisher, + std::string source ) + : publisher(publisher), source(std::move(source)) { } + + /** + * @brief publish a NOTICE severity broadcast + */ + inline void notice( const std::string &function, const std::string &message ) { + this->emit( function, Severity::NOTICE, message ); + } + + /** + * @brief publish a WARNING severity broadcast + */ + inline void warning( const std::string &function, const std::string &message ) { + this->emit( function, Severity::WARNING, message ); + } + + /** + * @brief publish an ERROR severity broadcast + */ + inline void error( const std::string &function, const std::string &message ) { + this->emit( function, Severity::ERROR, message ); + } + + void emit( const std::string &function, + const std::string &severity, + const std::string &message ); + }; + /**************** Common::Broadcaster ***************************************/ + + void collect_telemetry(const std::pair &provider, std::string &retstring); /***** Common::extract_telemetry_value **************************************/ diff --git a/common/message_keys.h b/common/message_keys.h index 12ef41c7..ef738a7e 100644 --- a/common/message_keys.h +++ b/common/message_keys.h @@ -98,12 +98,12 @@ namespace Key { } namespace Tcsd { - inline const std::string TELRA = "telra"; - inline const std::string TELDEC = "teldec"; - inline const std::string ALT = "alt"; - inline const std::string AZ = "az"; - inline const std::string AIRMASS = "airmass"; - inline const std::string CASANGLE = "casangle"; + inline const std::string TELRA = "TELRA"; + inline const std::string TELDEC = "TELDEC"; + inline const std::string ALT = "ALT"; + inline const std::string AZ = "AZ"; + inline const std::string AIRMASS = "AIRMASS"; + inline const std::string CASANGLE = "CASANGLE"; } } diff --git a/flexured/flexure_interface.cpp b/flexured/flexure_interface.cpp index 55288f71..626eaa9c 100644 --- a/flexured/flexure_interface.cpp +++ b/flexured/flexure_interface.cpp @@ -520,8 +520,8 @@ namespace Flexure { else if ( messagetype == "tcsinfo" ) { double casangle=NAN, alt=NAN; - Common::extract_telemetry_value( message_in, "CASANGLE", casangle ); - Common::extract_telemetry_value( message_in, "ALT", alt ); + Common::extract_telemetry_value( message_in, Key::Tcsd::CASANGLE, casangle ); + Common::extract_telemetry_value( message_in, Key::Tcsd::ALT, alt ); message.str(""); message << "casangle=" << casangle << " alt=" << alt; logwrite( function, message.str() ); } diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 35599995..d0b164fd 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -532,22 +532,22 @@ namespace Sequencer { // Only READY or NOTREADY states allow a new sequence. if ( seq_state_manager.is_set(Sequencer::SEQ_RUNNING) ) { - this->broadcast( function, Severity::ERROR, "sequence rejected: sequencer already running" ); + this->broadcast.error( function, "sequence rejected: sequencer already running" ); return ERROR; } if ( seq_state_manager.is_set(Sequencer::SEQ_STOPPING) ) { - this->broadcast( function, Severity::ERROR, "sequence rejected: sequencer is stopping" ); + this->broadcast.error( function, "sequence rejected: sequencer is stopping" ); return ERROR; } if ( seq_state_manager.is_set(Sequencer::SEQ_STARTING) ) { - this->broadcast( function, Severity::ERROR, "sequence rejected: sequencer is starting" ); + this->broadcast.error( function, "sequence rejected: sequencer is starting" ); return ERROR; } if ( seq_state_manager.is_set(Sequencer::SEQ_PAUSED) ) { - this->broadcast( function, Severity::ERROR, "sequence rejected: sequencer is paused" ); + this->broadcast.error( function, "sequence rejected: sequencer is paused" ); return ERROR; } @@ -555,7 +555,7 @@ namespace Sequencer { // Sequence must not be empty. if ( sequence.empty() ) { - this->broadcast( function, Severity::ERROR, "sequence is empty" ); + this->broadcast.error( function, "sequence is empty" ); return ERROR; } @@ -564,7 +564,7 @@ namespace Sequencer { for (const auto &group : sequence) { if ( group.operations.empty() ) { - this->broadcast( function, Severity::ERROR, "sequence contains an empty operation group" ); + this->broadcast.error( function, "sequence contains an empty operation group" ); return ERROR; } } @@ -593,7 +593,7 @@ namespace Sequencer { // If expose is present, camera_set must also be present. if ( has_expose && !has_camera_set ) { - this->broadcast( function, Severity::ERROR, "sequence contains 'expose' without 'camera_set'" ); + this->broadcast.error( function, "sequence contains 'expose' without 'camera_set'" ); error = ERROR; } @@ -627,7 +627,7 @@ namespace Sequencer { oss << "sequence requires daemon '" << daemon_name.at(static_cast(bit)) << "' but it is not ready"; - this->broadcast(function, Severity::ERROR, oss.str()); + this->broadcast.error(function, oss.str()); error = ERROR; } } @@ -635,7 +635,7 @@ namespace Sequencer { else // for science mode (non-engineering) all subsystems must be ready if ( !seq_state_manager.is_set(Sequencer::SEQ_READY) ) { - this->broadcast( function, Severity::ERROR, "sequence requires SEQ_READY (all daemons ready) in science mode" ); + this->broadcast.error( function, "sequence requires SEQ_READY (all daemons ready) in science mode" ); error = ERROR; } } @@ -665,7 +665,7 @@ namespace Sequencer { oss << "parallel group contains unsafe pair: '" << thread_names.at(group.operations[i].thr) << "' and '" << thread_names.at(group.operations[j].thr) << "'"; - this->broadcast(function, Severity::ERROR, oss.str()); + this->broadcast.error(function, oss.str()); error = ERROR; } } @@ -687,7 +687,7 @@ namespace Sequencer { << thread_names.at(all_thrs[i]) << "' and '" << thread_names.at(all_thrs[j]) << "' (use 'engineering true' to allow in SERIAL groups)"; - this->broadcast(function, Severity::ERROR, oss.str()); + this->broadcast.error(function, oss.str()); error = ERROR; } } @@ -699,7 +699,7 @@ namespace Sequencer { // TBD should I allow expose to not appear more than once across all groups? if ( expose_count > 1 ) { - this->broadcast( function, Severity::NOTICE, "sequence contains multiple 'expose' operations -- placeholder check" ); + this->broadcast.notice( function, "sequence contains multiple 'expose' operations -- placeholder check" ); // not a hard error yet; pending clarification of multi-expose DSL usage } @@ -714,7 +714,7 @@ namespace Sequencer { if (false) { // stub -- replace with real contention check - this->broadcast(function, Severity::NOTICE, + this->broadcast.notice(function, "PLACEHOLDER parallel contention check not yet implemented"); } @@ -1058,37 +1058,6 @@ namespace Sequencer { /***** Sequencer::Sequence::broadcast_seqstate ******************************/ - /***** Sequencer::Sequence::broadcast ***************************************/ - /** - * @brief logs a narrative message and publishes it on Topic::BROADCAST - * @param[in] function name of caller function (for log) - * @param[in] severity one of Severity::NOTICE, Severity::WARNING, Severity::ERROR - * @param[in] message the operator-facing narrative text - * @details This replaces the legacy pattern of enqueuing narrative - * strings onto the UDP async queue. Messages are routed through - * the PUB-SUB broadcast topic instead, where the GUI message - * area subscribes. Logging behavior is preserved. - */ - void Sequence::broadcast( const std::string &function, - const std::string &severity, - const std::string &message ) { - logwrite( function, severity+": "+message ); - if ( this->publisher ) { - nlohmann::json jmessage; - jmessage[Key::Broadcast::SEVERITY] = severity; - jmessage[Key::Broadcast::MESSAGE] = message; - jmessage[Key::SOURCE] = Daemon::SEQUENCER; - try { - this->publisher->publish( jmessage, Topic::BROADCAST ); - } - catch ( const std::exception &e ) { - logwrite( function, "ERROR publishing broadcast: "+std::string(e.what()) ); - } - } - } - /***** Sequencer::Sequence::broadcast ***************************************/ - - /***** Sequencer::Sequence::broadcast_waitstate *****************************/ /** * @brief publishes wait_state via PUB-SUB @@ -1239,14 +1208,14 @@ namespace Sequencer { // The Sequencer can only be started once // if ( thread_state_manager.is_set( Sequencer::THR_SEQUENCE_START ) ) { - this->broadcast( function, Severity::ERROR, "sequencer already running" ); + this->broadcast.error( function, "sequencer already running" ); return; } // The Sequencer can only be started when state is READY // if ( ! seq_state_manager.is_set( Sequencer::SEQ_READY ) ) { - this->broadcast( function, Severity::ERROR, "cannot start: system not ready" ); + this->broadcast.error( function, "cannot start: system not ready" ); return; } @@ -1337,7 +1306,7 @@ namespace Sequencer { } else if ( targetstate == TargetInfo::TARGET_ERROR ) { // request stop on error - this->broadcast( function, Severity::ERROR, "getting next target. stopping" ); + this->broadcast.error( function, "getting next target. stopping" ); break; } @@ -1351,12 +1320,10 @@ namespace Sequencer { } if (is_cancelled()) { - this->broadcast( function, Severity::NOTICE, "sequence cancelled" ); + this->broadcast.notice( function, "sequence cancelled" ); break; } -// this->request_status(tcsd); // force tcsd to publish his status TODO WORK-IN-PROGRESS - // Update this target's state in the database // if (error==NO_ERROR) error = this->target.update_state( Sequencer::TARGET_COMPLETE ); @@ -1447,14 +1414,14 @@ namespace Sequencer { std::string cmd = CAMERAD_ACTIVATE + activechans.str(); /*** if ( camerad_cmd.send( { CAMERAD_ACTIVATE, { activechans.str() } } ) != NO_ERROR ) { WIP ***/ if (this->camerad.send(cmd, reply)!=NO_ERROR) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\": "+reply ); + this->broadcast.error( function, "sending \""+cmd+"\": "+reply ); throw std::runtime_error("camera returned "+reply); } } if (!deactivechans.str().empty()) { std::string cmd = CAMERAD_DEACTIVATE + deactivechans.str(); if (this->camerad.send(cmd, reply)!=NO_ERROR) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\": "+reply ); + this->broadcast.error( function, "sending \""+cmd+"\": "+reply ); throw std::runtime_error("camera returned "+reply); } } @@ -1467,7 +1434,7 @@ namespace Sequencer { long exptime_msec = (long)( this->target.exptime_req * 1000 ); camcmd.str(""); camcmd << CAMERAD_EXPTIME << " " << exptime_msec; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->broadcast( function, Severity::ERROR, "sending \""+camcmd.str()+"\": "+reply ); + this->broadcast.error( function, "sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } @@ -1475,12 +1442,12 @@ namespace Sequencer { // camcmd.str(""); camcmd << CAMERAD_BIN << " spat " << this->target.binspat; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->broadcast( function, Severity::ERROR, "sending \""+camcmd.str()+"\": "+reply ); + this->broadcast.error( function, "sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } camcmd.str(""); camcmd << CAMERAD_BIN << " spec " << this->target.binspect; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->broadcast( function, Severity::ERROR, "sending \""+camcmd.str()+"\": "+reply ); + this->broadcast.error( function, "sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } @@ -1541,7 +1508,7 @@ namespace Sequencer { logwrite( function, "moving slit to "+slitcmd.str()+" for "+modestr+"position" ); if ( this->slitd.command_timeout( slitcmd.str(), reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "setting slit" ); + this->broadcast.error( function, "setting slit" ); this->thread_error_manager.set( THR_SLIT_SET ); throw std::runtime_error("slit returned: "+reply); } @@ -1567,7 +1534,7 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_POWER ); // powerd not ready if ( this->reopen_hardware(this->powerd, POWERD_REOPEN, 10000 ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "initializing power control" ); + this->broadcast.error( function, "initializing power control" ); throw std::runtime_error("could not initialize power control"); } @@ -1619,13 +1586,13 @@ namespace Sequencer { this->thread_error_manager.set( THR_SLIT_INIT ); // assume the worst, clear on success if ( this->set_power_switch(ON, POWER_SLIT, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "powering slit hardware" ); + this->broadcast.error( function, "powering slit hardware" ); throw std::runtime_error("could not power slit hardware"); } bool was_opened=false; if ( this->open_hardware(this->slitd, was_opened) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "connecting to slit" ); + this->broadcast.error( function, "connecting to slit" ); throw std::runtime_error("could not open connection to slit hardware"); } @@ -1634,7 +1601,7 @@ namespace Sequencer { bool ishomed=false; std::string reply; if ( this->slitd.command( SLITD_ISHOME, reply ) ) { - this->broadcast( function, Severity::ERROR, "communicating with slit hardware" ); + this->broadcast.error( function, "communicating with slit hardware" ); throw std::runtime_error("could not communicate with slit hardware: "+reply); } this->parse_state( function, reply, ishomed ); @@ -1644,7 +1611,7 @@ namespace Sequencer { if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->slitd.command_timeout( SLITD_HOME, reply, SLITD_HOME_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "communicating with slit hardware" ); + this->broadcast.error( function, "communicating with slit hardware" ); throw std::runtime_error("could not home slit hardware: "+reply); } } @@ -1654,7 +1621,7 @@ namespace Sequencer { if ( was_opened && !this->config_init["SLIT"].empty() ) { std::string cmd = SLITD_SET+" "+this->config_init["SLIT"]; if ( this->slitd.command_timeout( cmd, reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to slit" ); + this->broadcast.error( function, "sending \""+cmd+"\" to slit" ); throw std::runtime_error("slit "+cmd+" returned: "+reply); } } @@ -1708,7 +1675,7 @@ namespace Sequencer { if (error==NO_ERROR && !this->config_shutdown["SLIT"].empty() ) { std::string cmd = SLITD_SET+" "+this->config_shutdown["SLIT"]; if ( this->slitd.command_timeout( cmd, reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to slit" ); + this->broadcast.error( function, "sending \""+cmd+"\" to slit" ); throw std::runtime_error(cmd+" returned: "+reply); } } @@ -1718,7 +1685,7 @@ namespace Sequencer { logwrite( function, "closing slit hardware" ); error = this->slitd.command( SLITD_CLOSE, reply ); if ( error != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "closing connection to slit hardware" ); + this->broadcast.error( function, "closing connection to slit hardware" ); throw std::runtime_error("closing slit connection returned: "+reply); } @@ -1755,14 +1722,14 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_SLICECAM, std::chrono::seconds(10)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "initializing slicecam control" ); + this->broadcast.error( function, "initializing slicecam control" ); throw std::runtime_error("could not power slicecam hardware"); } // open connection is all that is needed, slicecamd takes care of everything // if ( this->open_hardware(this->slicecamd, SLICECAMD_OPEN, SLICECAMD_OPEN_TIMEOUT) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "starting slicecam" ); + this->broadcast.error( function, "starting slicecam" ); throw SlicecamException("could not start slicecam"); } @@ -1795,7 +1762,7 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_ACAM, std::chrono::seconds(10)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "powering acam hardware" ); + this->broadcast.error( function, "powering acam hardware" ); throw std::runtime_error("could not power acam hardware"); } @@ -1803,7 +1770,7 @@ namespace Sequencer { // bool was_opened=false; if ( this->open_hardware(this->acamd, ACAMD_OPEN, ACAMD_OPEN_TIMEOUT, was_opened) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "opening acam camera" ); + this->broadcast.error( function, "opening acam camera" ); throw AcamException(ErrorCode::ERROR_ACAM_CAMERA, "could not open acam camera"); } @@ -1814,14 +1781,14 @@ namespace Sequencer { if ( ! this->config_init["ACAM_FILTER"].empty() ) { cmd = ACAMD_FILTER+" "+this->config_init["ACAM_FILTER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to acamd: "+reply ); + this->broadcast.error( function, "sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } if ( ! this->config_init["ACAM_COVER"].empty() ) { cmd = ACAMD_COVER+" "+this->config_init["ACAM_COVER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to acamd: "+reply ); + this->broadcast.error( function, "sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } @@ -1870,14 +1837,14 @@ namespace Sequencer { } if ( (error=this->connect_to_daemon(this->slicecamd)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "connecting to slicecamd" ); + this->broadcast.error( function, "connecting to slicecamd" ); } // close connections between slicecamd and the hardware with which it communicates // logwrite( function, "closing slicecam hardware" ); if ( (error=this->slicecamd.command_timeout( SLICECAMD_SHUTDOWN, reply, SLICECAMD_SHUTDOWN_TIMEOUT )) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "closing connection to slicecam hardware" ); + this->broadcast.error( function, "closing connection to slicecam hardware" ); } // disconnect me from slicecamd, irrespective of any previous error @@ -1888,7 +1855,7 @@ namespace Sequencer { // Turn off power to slicecam hardware. // if ( this->set_power_switch(OFF, POWER_SLICECAM, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "switching off slicecam" ); + this->broadcast.error( function, "switching off slicecam" ); throw std::runtime_error("could not power off slicecam hardware"); } @@ -1928,14 +1895,14 @@ namespace Sequencer { if ( ! this->config_shutdown["ACAM_FILTER"].empty() ) { cmd = ACAMD_FILTER+" "+this->config_shutdown["ACAM_FILTER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to acamd: "+reply ); + this->broadcast.error( function, "sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } if ( ! this->config_shutdown["ACAM_COVER"].empty() ) { cmd = ACAMD_COVER+" "+this->config_shutdown["ACAM_COVER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to acamd: "+reply ); + this->broadcast.error( function, "sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } @@ -1948,7 +1915,7 @@ namespace Sequencer { if ( error==NO_ERROR ) { logwrite( function, "closing acam hardware" ); error = this->acamd.command_timeout( ACAMD_SHUTDOWN, ACAMD_SHUTDOWN_TIMEOUT ); - if ( error != NO_ERROR ) this->broadcast( function, Severity::ERROR, "shutting down acam" ); + if ( error != NO_ERROR ) this->broadcast.error( function, "shutting down acam" ); } // disconnect me from acamd, irrespective of any previous error @@ -1959,7 +1926,7 @@ namespace Sequencer { // Turn off power to acam hardware. // if ( this->set_power_switch(OFF, POWER_ACAM, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "switching off acam" ); + this->broadcast.error( function, "switching off acam" ); throw std::runtime_error("could not switch off acam"); } @@ -1989,14 +1956,14 @@ namespace Sequencer { // make sure calib hardware is powered if ( this->set_power_switch(ON, POWER_CALIB, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "powering focus control" ); + this->broadcast.error( function, "powering focus control" ); throw std::runtime_error("could not power focus control"); } // connect to calibd bool was_opened=false; if ( this->open_hardware(this->calibd, was_opened) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "initializing calib control" ); + this->broadcast.error( function, "initializing calib control" ); throw std::runtime_error("could not power calib control"); } @@ -2008,14 +1975,14 @@ namespace Sequencer { std::string reply; long error = this->calibd.command( CALIBD_ISHOME, reply ); if ( error!=NO_ERROR || this->parse_state( function, reply, ishomed ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "communicating with calib hardware" ); + this->broadcast.error( function, "communicating with calib hardware" ); throw std::runtime_error("could not communicate with calib hardware: "+reply); } // home calib actuators if not already homed if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->calibd.command_timeout( CALIBD_HOME, reply, CALIBD_HOME_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "communicating with calib hardware" ); + this->broadcast.error( function, "communicating with calib hardware" ); throw std::runtime_error("could not communicate with calib hardware: "+reply); } } @@ -2028,7 +1995,7 @@ namespace Sequencer { if ( !this->config_init["CALIB_DOOR"].empty() ) cmd << " door=" << this->config_init["CALIB_DOOR"]; logwrite( function, "calib default: "+cmd.str() ); if ( this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "moving calib door and/or cover" ); + this->broadcast.error( function, "moving calib door and/or cover" ); throw std::runtime_error("could not move calib door and/or cover"); } } @@ -2065,7 +2032,7 @@ namespace Sequencer { // bool poweron=false; if ( check_power_switch(ON, POWER_CALIB, poweron ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "checking calib power switch" ); + this->broadcast.error( function, "checking calib power switch" ); throw std::runtime_error("checking calib power switch"); } @@ -2083,7 +2050,7 @@ namespace Sequencer { if ( !this->config_shutdown["CALIB_DOOR"].empty() ) cmd << " door=" << this->config_shutdown["CALIB_DOOR"]; logwrite( function, "calib default: "+cmd.str() ); if ( this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "moving calib door and/or cover" ); + this->broadcast.error( function, "moving calib door and/or cover" ); throw std::runtime_error("moving calib door and/or cover"); } } @@ -2107,14 +2074,14 @@ namespace Sequencer { // Turn off power to calib hardware. // if ( this->set_power_switch(OFF, POWER_CALIB, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "switching off calib hardware" ); + this->broadcast.error( function, "switching off calib hardware" ); error=ERROR; } // always turn off power to lamps // if ( this->set_power_switch(OFF, POWER_LAMP, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "powering off lamps" ); + this->broadcast.error( function, "powering off lamps" ); error=ERROR; } @@ -2148,7 +2115,7 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_TCS ); // tcsd not ready if ( this->open_hardware(this->tcsd) != NO_ERROR ) { - this->broadcast( "Sequencer::Sequence::tcs_init", Severity::ERROR, "initializing TCS" ); + this->broadcast.error( "Sequencer::Sequence::tcs_init", "initializing TCS" ); this->thread_error_manager.set( THR_TCS_INIT ); throw std::runtime_error("could not initialize TCS"); } @@ -2192,7 +2159,7 @@ namespace Sequencer { std::string reply; error = this->tcsd.send( TCSD_CLOSE, reply ); if ( error != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "closing connection to TCS" ); + this->broadcast.error( function, "closing connection to TCS" ); throw std::runtime_error("closing TCS connection: "+reply); } } @@ -2226,13 +2193,13 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_FLEXURE, std::chrono::seconds(21)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "powering flexure control" ); + this->broadcast.error( function, "powering flexure control" ); this->thread_error_manager.set( THR_FLEXURE_INIT ); throw std::runtime_error("could not power flexure control"); } if ( this->open_hardware(this->flexured) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "initializing flexure control" ); + this->broadcast.error( function, "initializing flexure control" ); this->thread_error_manager.set( THR_FLEXURE_INIT ); throw std::runtime_error("could not initialize flexure control"); } @@ -2279,7 +2246,7 @@ namespace Sequencer { } if ( this->connect_to_daemon(this->flexured) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "connecting to flexure hardware" ); + this->broadcast.error( function, "connecting to flexure hardware" ); error=ERROR; } @@ -2288,7 +2255,7 @@ namespace Sequencer { // logwrite( function, "closing flexure hardware" ); if (error==NO_ERROR && (error=this->flexured.command( FLEXURED_CLOSE, reply )) != NO_ERROR) { - this->broadcast( function, Severity::ERROR, "closing connection to flexure hardware" ); + this->broadcast.error( function, "closing connection to flexure hardware" ); } // disconnect me from flexured, irrespective of any previous error @@ -2299,7 +2266,7 @@ namespace Sequencer { // Turn off power to flexure hardware. // if ( this->set_power_switch(OFF, POWER_FLEXURE, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "switching off flexure" ); + this->broadcast.error( function, "switching off flexure" ); throw std::runtime_error("switching off flexure hardware"); } @@ -2326,14 +2293,14 @@ namespace Sequencer { this->thread_error_manager.set( THR_FOCUS_INIT ); // assume failure, clear on success if ( this->set_power_switch(ON, POWER_FOCUS, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "powering focus control" ); + this->broadcast.error( function, "powering focus control" ); throw std::runtime_error("could not power focus control"); } // connect to focusd bool was_opened=false; if ( this->open_hardware(this->focusd, was_opened) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "initializing focus control" ); + this->broadcast.error( function, "initializing focus control" ); throw std::runtime_error("could not open focus hardware"); } @@ -2345,14 +2312,14 @@ namespace Sequencer { std::string reply; long error = this->focusd.command( FOCUSD_ISHOME, reply ); if ( error!=NO_ERROR || this->parse_state( function, reply, ishomed ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "communicating with focus hardware" ); + this->broadcast.error( function, "communicating with focus hardware" ); throw std::runtime_error("focus "+FOCUSD_ISHOME+" returned: "+reply); } // home focus actuators if not already homed if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->focusd.command_timeout( FOCUSD_HOME, reply, FOCUSD_HOME_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "communicating with focus hardware" ); + this->broadcast.error( function, "communicating with focus hardware" ); throw std::runtime_error("focus "+FOCUSD_HOME+" returned: "+reply); } } @@ -2362,7 +2329,7 @@ namespace Sequencer { for ( const auto &chan : chans ) { std::string command = "set " + chan + " nominal"; if ( this->focusd.command_timeout( command, reply, FOCUSD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "setting focus "+chan ); + this->broadcast.error( function, "setting focus "+chan ); throw std::runtime_error("focus "+command+" returned: "+reply); } } @@ -2408,7 +2375,7 @@ namespace Sequencer { } if ( this->connect_to_daemon(this->focusd) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "connecting to focus hardware" ); + this->broadcast.error( function, "connecting to focus hardware" ); error=ERROR; } @@ -2417,7 +2384,7 @@ namespace Sequencer { // logwrite( function, "closing focus hardware" ); if (error==NO_ERROR && (error=this->focusd.command( FOCUSD_CLOSE, reply )) != NO_ERROR) { - this->broadcast( function, Severity::ERROR, "closing connection to focus hardware" ); + this->broadcast.error( function, "closing connection to focus hardware" ); } // disconnect me from focusd, irrespective of any previous error @@ -2428,7 +2395,7 @@ namespace Sequencer { // Turn off power to focus hardware. // if ( this->set_power_switch(OFF, POWER_FOCUS, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "switching off focus" ); + this->broadcast.error( function, "switching off focus" ); throw std::runtime_error("switching off focus hardware"); } @@ -2456,13 +2423,13 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_CAMERA, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "powering camera" ); + this->broadcast.error( function, "powering camera" ); throw std::runtime_error("switching on camera"); } bool was_opened=false; if ( this->open_hardware(this->camerad, "open", 12000, was_opened) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "initializing camera" ); + this->broadcast.error( function, "initializing camera" ); throw std::runtime_error("initializing camera"); } @@ -2472,7 +2439,7 @@ namespace Sequencer { if ( was_opened) { for ( const auto &cmd : this->camera_prologue ) { if ( this->camerad.command_timeout( cmd, reply, CAMERA_PROLOG_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending \""+cmd+"\" to camera" ); + this->broadcast.error( function, "sending \""+cmd+"\" to camera" ); throw std::runtime_error("sending \""+cmd+"\" to camera"); } } @@ -2543,7 +2510,7 @@ namespace Sequencer { // turn off power to camera hardware // if ( this->set_power_switch(OFF, POWER_CAMERA, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "powering off camera" ); + this->broadcast.error( function, "powering off camera" ); throw std::runtime_error("switching off camera"); } @@ -2615,7 +2582,7 @@ namespace Sequencer { // if ( this->target.ra_hms == this->last_ra_hms && this->target.dec_dms == this->last_dec_dms ) { - this->broadcast( function, Severity::NOTICE, "no move required for repeat target" ); + this->broadcast.notice( function, "no move required for repeat target" ); return NO_ERROR; } @@ -2639,7 +2606,7 @@ namespace Sequencer { if ( ra_isnan ) { message << " RA=\"" << this->target.ra_hms << "\""; } if ( dec_isnan ) { message << " DEC=\"" << this->target.dec_dms << "\""; } message << " to decimal"; - this->broadcast( function, Severity::ERROR, ""+message.str() ); + this->broadcast.error( function, ""+message.str() ); this->thread_error_manager.set( THR_MOVE_TO_TARGET ); throw std::runtime_error(message.str()); } @@ -2711,14 +2678,14 @@ namespace Sequencer { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCSOP ); - this->broadcast( function, Severity::NOTICE, "waiting for TCS operator to send \"ontarget\" signal" ); + this->broadcast.notice( function, "waiting for TCS operator to send \"ontarget\" signal" ); while ( !is_cancelled() && !this->is_ontarget.load() ) { std::unique_lock lock(cv_mutex); this->cv.wait( lock, [this]() { return( this->is_ontarget.load() || is_cancelled() ); } ); } - this->broadcast( function, Severity::NOTICE, "received " + this->broadcast.notice( function, "received " +(is_cancelled() ? std::string("cancel") : std::string("ontarget")) +" signal!" ); } @@ -2790,7 +2757,7 @@ namespace Sequencer { // if ( std::isnan( radec_to_decimal( seq.target.ra_hms, ra_hms ) ) || std::isnan( radec_to_decimal( seq.target.dec_dms, dec_dms ) ) ) { - seq.broadcast( function, Severity::ERROR, "can't handle NaN value for RA, DEC" ); + seq.broadcast.error( function, "can't handle NaN value for RA, DEC" ); seq.thread_error_manager.set( THR_NOTIFY_TCS ); return; } @@ -2876,7 +2843,7 @@ namespace Sequencer { // const auto &calinfo = this->caltarget.get_info(calname); - this->broadcast( function, Severity::NOTICE, "configuring calibrator for "+calname ); + this->broadcast.notice( function, "configuring calibrator for "+calname ); // set the calib door and cover // @@ -2888,7 +2855,7 @@ namespace Sequencer { logwrite( function, "calib: "+cmd.str() ); if ( !is_cancelled() && this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "moving calib door and/or cover" ); + this->broadcast.error( function, "moving calib door and/or cover" ); throw std::runtime_error("moving calib door and/or cover"); } @@ -2901,7 +2868,7 @@ namespace Sequencer { logwrite( function, message.str() ); std::string reply; if ( this->powerd.send( cmd.str(), reply ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, ""+message.str() ); + this->broadcast.error( function, ""+message.str() ); throw std::runtime_error("setting lamp "+message.str()); } } @@ -2925,13 +2892,13 @@ namespace Sequencer { if ( is_cancelled() ) break; cmd.str(""); cmd << CALIBD_LAMPMOD << " " << mod << " " << (state?1:0) << " 1000"; if ( this->calibd.command( cmd.str() ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, ""+cmd.str() ); + this->broadcast.error( function, ""+cmd.str() ); throw std::runtime_error("setting lamp modulator "+cmd.str()); } } if ( is_cancelled() ) { - this->broadcast( function, Severity::NOTICE, "abort may have left calib system partially set" ); + this->broadcast.notice( function, "abort may have left calib system partially set" ); } this->thread_error_manager.clear( THR_CALIBRATOR_SET ); // success @@ -2988,7 +2955,7 @@ namespace Sequencer { // this->do_once.store(true); - this->broadcast( function, Severity::NOTICE, "cancel signal sent" ); + this->broadcast.notice( function, "cancel signal sent" ); } /***** Sequencer::Sequence::stop_exposure *********************************/ @@ -3005,7 +2972,7 @@ namespace Sequencer { // This function is only used while exposing // if ( ! this->wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) { - this->broadcast( function, Severity::NOTICE, "not currently exposing" ); + this->broadcast.notice( function, "not currently exposing" ); return; } @@ -3022,12 +2989,12 @@ namespace Sequencer { else if ( error == NOTHING ) { // if not exposing, this is a way to ensure WAIT_EXPOSE bit can be cleared - this->broadcast( function, Severity::NOTICE, "not exposing" ); + this->broadcast.notice( function, "not exposing" ); this->wait_state_manager.clear( Sequencer::SEQ_WAIT_EXPOSE ); } else if ( error == BUSY ) { - this->broadcast( function, Severity::NOTICE, "too late to stop exposure" ); + this->broadcast.notice( function, "too late to stop exposure" ); // can't stop in the last 5 sec so wait that long and it should stop on its own std::this_thread::sleep_for(std::chrono::seconds(5)); } @@ -3052,7 +3019,7 @@ namespace Sequencer { // can only repeat when state is READY // if ( ! seq_state_manager.is_set( Sequencer::SEQ_READY ) ) { - this->broadcast( function, Severity::ERROR, "cannot repeat: system not ready" ); + this->broadcast.error( function, "cannot repeat: system not ready" ); return ERROR; } @@ -3131,7 +3098,7 @@ namespace Sequencer { // if ( this->camerad.async( message.str() ) != NO_ERROR ) { // if ( this->camerad.send( message.str(), reply ) != NO_ERROR ) { if ( this->camerad.command_timeout( message.str(), reply, 30000 ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending camera "+message.str() ); + this->broadcast.error( function, "sending camera "+message.str() ); this->thread_error_manager.set( THR_TRIGGER_EXPOSURE ); // tell the world this thread had an error this->target.update_state( Sequencer::TARGET_PENDING ); // return the target state to pending this->wait_state_manager.clear( Sequencer::SEQ_WAIT_EXPOSE ); // clear EXPOSE bit @@ -3193,7 +3160,7 @@ namespace Sequencer { // This function is only used while exposing // if ( ! this->wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) { - this->broadcast( function, Severity::ERROR, "cannot update exposure time when not currently exposing" ); + this->broadcast.error( function, "cannot update exposure time when not currently exposing" ); error = ERROR; } @@ -3339,7 +3306,7 @@ namespace Sequencer { error = run( { THR_POWER_INIT, [this]{ return power_init(); }, { } }, function ); if ( error != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "from power control. Will try to continue (but don't hold your breath)" ); + this->broadcast.error( function, "from power control. Will try to continue (but don't hold your breath)" ); } // container of shutdown threads to launch, @@ -4149,7 +4116,7 @@ namespace Sequencer { } // connection failed too many times if (attempt > maxattempts) { - broadcast( function, Severity::ERROR, "exceeded max attempts connecting to " + daemon.name ); + this->broadcast.error( function, "exceeded max attempts connecting to " + daemon.name ); return ERROR; } @@ -4158,7 +4125,7 @@ namespace Sequencer { error |= daemon.send( "isopen", reply ); error |= this->parse_state( function, reply, isopen ); if ( error != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "opening "+daemon.name+" hardware" ); + this->broadcast.error( function, "opening "+daemon.name+" hardware" ); return ERROR; } @@ -4168,7 +4135,7 @@ namespace Sequencer { logwrite( function, "opening "+daemon.name+" hardware connections with " +std::to_string(opentimeout)+" ms timeout" ); if ( daemon.command_timeout( opencmd, reply, opentimeout ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "opening connection to "+daemon.name+" hardware" ); + this->broadcast.error( function, "opening connection to "+daemon.name+" hardware" ); return ERROR; } was_opened=true; @@ -4197,7 +4164,7 @@ namespace Sequencer { if ( !daemon.socket.isconnected() ) { logwrite( function, "connecting to "+daemon.name+" daemon" ); if ( daemon.connect() != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "connecting to "+daemon.name ); + this->broadcast.error( function, "connecting to "+daemon.name ); return ERROR; } } diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 656f8861..928ae9e0 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -527,8 +527,6 @@ namespace Sequencer { this->cv.notify_all(); } - std::map telemetry_providers; ///< map of port[daemon_name] for external telemetry providers - double acquisition_timeout; ///< timeout for target acquisition (in sec) set by configuration parameter ACAM_ACQUIRE_TIMEOUT int acquisition_max_retrys; ///< max number of acquisition loop attempts double tcs_offsetrate_ra; ///< TCS offset rate RA ("MRATE") in arcsec per second @@ -658,12 +656,8 @@ namespace Sequencer { void publish_daemonstate(); void publish_threadstate(); - /** @brief publishes a narrative operator message on Topic::BROADCAST and logs it */ - void broadcast( const std::string &function, - const std::string &severity, - const std::string &message ); - std::unique_ptr publisher; ///< publisher object + Common::Broadcaster broadcast { this->publisher, Sequencer::DAEMON_NAME }; ///< logs and publishes a narrative message on Topic::BROADCAST std::string publisher_address; ///< publish socket endpoint std::string publisher_topic; ///< my default topic for publishing std::unique_ptr subscriber; ///< subscriber object diff --git a/sequencerd/sequence_acquisition.cpp b/sequencerd/sequence_acquisition.cpp index e794bd77..66f99c7e 100644 --- a/sequencerd/sequence_acquisition.cpp +++ b/sequencerd/sequence_acquisition.cpp @@ -29,17 +29,17 @@ namespace Sequencer { double angle_in = this->target.slitangle; if ( std::isnan(ra_in) || std::isnan(dec_in) ) { - this->broadcast( function, Severity::ERROR, "converting target coordinates to decimal" ); + this->broadcast.error( function, "converting target coordinates to decimal" ); return ERROR; } std::ostringstream cmd; cmd << ACAMD_ACQUIRE << " " << ra_in << " " << dec_in << " " << angle_in; - this->broadcast( function, Severity::NOTICE, "starting ACAM acquisition" ); + this->broadcast.notice( function, "starting ACAM acquisition" ); if ( this->acamd.command( cmd.str(), reply ) != NO_ERROR ) { - this->broadcast( function, Severity::ERROR, "sending acquire command to acamd" ); + this->broadcast.error( function, "sending acquire command to acamd" ); return ERROR; } @@ -58,7 +58,7 @@ namespace Sequencer { if (this->cancel_flag.load()) return ABORT; if (use_timeout && !this->is_acam_guiding.load()) { - this->broadcast( function, Severity::ERROR, "ACAM acquisition timed out!" ); + this->broadcast.error( function, "ACAM acquisition timed out!" ); return TIMEOUT; } @@ -82,7 +82,7 @@ namespace Sequencer { // TODO don't hard-code the arguments here: std::string reply; if (this->slicecamd.command( SLICECAMD_FINEACQUIRE+" start L", reply ) != NO_ERROR) { - this->broadcast( function, Severity::ERROR, "starting slicecam fine acquisition" ); + this->broadcast.error( function, "starting slicecam fine acquisition" ); return ERROR; } @@ -91,7 +91,7 @@ namespace Sequencer { return ERROR; } - this->broadcast( function, Severity::NOTICE, "slicecam fine acquisition started" ); + this->broadcast.notice( function, "slicecam fine acquisition started" ); const bool use_timeout = ( this->acquisition_timeout > 0 ); const auto timeout_time = std::chrono::steady_clock::now() @@ -108,7 +108,7 @@ namespace Sequencer { if (this->cancel_flag.load()) return ABORT; if (use_timeout && !this->is_fineacquire_locked.load()) { - this->broadcast( function, Severity::ERROR, "slicecam fine acquisition timed out!" ); + this->broadcast.error( function, "slicecam fine acquisition timed out!" ); return TIMEOUT; } @@ -132,7 +132,7 @@ namespace Sequencer { // ---------- ACAM acquire ----------------------------- // if ( this->do_acam_acquire() != NO_ERROR ) { - this->broadcast( caller, Severity::WARNING, "acam acquisition failed" ); + this->broadcast.warning( caller, "acam acquisition failed" ); // on ACAM acquisition failure wait for user to continue or cancel if ( this->wait_for_user(caller) == ABORT ) return ABORT; @@ -143,7 +143,7 @@ namespace Sequencer { // ---------- SLICECAM fineacquire --------------------- // if ( this->do_slicecam_fineacquire() != NO_ERROR ) { - this->broadcast( caller, Severity::WARNING, "slicecam fine acquisition failed" ); + this->broadcast.warning( caller, "slicecam fine acquisition failed" ); } return NO_ERROR; diff --git a/sequencerd/sequence_wait.cpp b/sequencerd/sequence_wait.cpp index a9f0a459..feba92fc 100644 --- a/sequencerd/sequence_wait.cpp +++ b/sequencerd/sequence_wait.cpp @@ -21,7 +21,7 @@ namespace Sequencer { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCSOP ); - this->broadcast( caller, Severity::NOTICE, "waiting for TCS operator to send 'ontarget' signal" ); + this->broadcast.notice( caller, "waiting for TCS operator to send 'ontarget' signal" ); while ( !this->cancel_flag.load() && !this->is_ontarget.load() ) { @@ -31,7 +31,7 @@ namespace Sequencer { this->cancel_flag.load() ); } ); } - this->broadcast( caller, Severity::NOTICE, "received " + this->broadcast.notice( caller, "received " +(this->cancel_flag.load() ? std::string("cancel") : std::string("ontarget")) +" signal!" ); @@ -56,20 +56,20 @@ namespace Sequencer { { ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); - this->broadcast( caller, Severity::NOTICE, "waiting for USER to send 'continue' signal" ); + this->broadcast.notice( caller, "waiting for USER to send 'continue' signal" ); while ( !this->cancel_flag.load() && !this->is_usercontinue.load() ) { std::unique_lock lock(cv_mutex); this->cv.wait( lock, [this]() { return( this->is_usercontinue.load() || this->cancel_flag.load() ); } ); } - this->broadcast( caller, Severity::NOTICE, "received " + this->broadcast.notice( caller, "received " +(this->cancel_flag.load() ? std::string("cancel") : std::string("continue")) +" signal!" ); } // end scope for wait_state = WAIT_USER if ( this->cancel_flag.load() ) { - this->broadcast( caller, Severity::NOTICE, "sequence cancelled" ); + this->broadcast.notice( caller, "sequence cancelled" ); return ABORT; } @@ -97,7 +97,7 @@ namespace Sequencer { } if (this->cancel_flag.load()) { - this->broadcast( caller, Severity::NOTICE, "exposure cancelled" ); + this->broadcast.notice( caller, "exposure cancelled" ); return ABORT; } @@ -128,7 +128,7 @@ namespace Sequencer { } if (this->cancel_flag.load()) { - this->broadcast( caller, Severity::NOTICE, "wait for readout cancelled" ); + this->broadcast.notice( caller, "wait for readout cancelled" ); return ABORT; } @@ -146,7 +146,7 @@ namespace Sequencer { */ long Sequence::wait_for_canexpose(std::string caller) { - this->broadcast( caller, Severity::NOTICE, "waiting for camera to be ready to expose" ); + this->broadcast.notice( caller, "waiting for camera to be ready to expose" ); while ( !this->cancel_flag.load() && !this->can_expose.load() ) { @@ -157,7 +157,7 @@ namespace Sequencer { } if (this->cancel_flag.load()) { - this->broadcast( caller, Severity::NOTICE, "wait for can_expose cancelled" ); + this->broadcast.notice( caller, "wait for can_expose cancelled" ); return ABORT; } diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index 7206e859..20be3f59 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -867,31 +867,6 @@ namespace Sequencer { } } - // TELEM_PROVIDER : contains daemon name and port to contact for header telemetry info - // - if ( config.param[entry] == "TELEM_PROVIDER" ) { - std::vector tokens; - Tokenize( config.arg[entry], tokens, " " ); - try { - if ( tokens.size() == 2 ) { - this->sequence.telemetry_providers[tokens.at(0)] = std::stod(tokens.at(1)); - } - else { - message.str(""); message << "ERROR bad format TELEM_PROVIDER=\"" << config.arg[entry] << "\": expected "; - logwrite( function, message.str() ); - return ERROR; - } - } - catch ( const std::exception &e ) { - message.str(""); message << "ERROR parsing TELEM_PROVIDER from " << config.arg[entry] << ": " << e.what(); - logwrite( function, message.str() ); - return ERROR; - } - message.str(""); message << "config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( "SEQUENCERD", function, message.str() ); - applied++; - } - } // end loop through the entries in the configuration file message.str(""); diff --git a/slicecamd/slicecam_interface.cpp b/slicecamd/slicecam_interface.cpp index 7f26c795..e6e86bd6 100644 --- a/slicecamd/slicecam_interface.cpp +++ b/slicecamd/slicecam_interface.cpp @@ -462,16 +462,16 @@ namespace Slicecam { // Common::extract_telemetry_value( jmessage, "TCSNAME", telem.tcsname ); Common::extract_telemetry_value( jmessage, "ISOPEN", telem.is_tcs_open ); - Common::extract_telemetry_value( jmessage, "CASANGLE", telem.angle_scope ); - Common::extract_telemetry_value( jmessage, "TELRA", telem.ra_scope_hms ); - Common::extract_telemetry_value( jmessage, "TELDEC", telem.dec_scope_dms ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::CASANGLE, telem.angle_scope ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::TELRA, telem.ra_scope_hms ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::TELDEC, telem.dec_scope_dms ); Common::extract_telemetry_value( jmessage, "RA", telem.ra_scope_h ); Common::extract_telemetry_value( jmessage, "DEC", telem.dec_scope_d ); Common::extract_telemetry_value( jmessage, "RAOFFSET", telem.offsetra ); Common::extract_telemetry_value( jmessage, "DECLOFFS", telem.offsetdec ); - Common::extract_telemetry_value( jmessage, "AZ", telem.az ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::AZ, telem.az ); Common::extract_telemetry_value( jmessage, "TELFOCUS", telem.telfocus ); - Common::extract_telemetry_value( jmessage, "AIRMASS", telem.airmass ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::AIRMASS, telem.airmass ); } /***** Slicecam::Interface::handletopic_tcsd ********************************/ diff --git a/tcsd/tcs_interface.cpp b/tcsd/tcs_interface.cpp index ed59ae1f..576ba9b8 100644 --- a/tcsd/tcs_interface.cpp +++ b/tcsd/tcs_interface.cpp @@ -42,28 +42,32 @@ namespace TCS { this->get_tcs_info(); nlohmann::json jmessage_out; - jmessage_out["source"] = "tcsd"; - - jmessage_out["ISOPEN"] = this->tcs_info.isopen; - jmessage_out["TCSNAME"] = this->tcs_info.tcsname; - - jmessage_out["PA"] = this->tcs_info.pa; // double - jmessage_out["CASANGLE"] = this->tcs_info.cassangle; // double - jmessage_out["HA"] = this->tcs_info.ha; // string - jmessage_out["RAOFFSET"] = this->tcs_info.offsetra; // double - jmessage_out["DECLOFFS"] = this->tcs_info.offsetdec; // double - jmessage_out["TELRA"] = this->tcs_info.ra_hms; // string "hh:mm:ss.s" - jmessage_out["TELDEC"] = this->tcs_info.dec_dms; // string "dd:mm:ss.s" - jmessage_out["RA"] = radec_to_decimal( this->tcs_info.ra_hms ); - jmessage_out["DEC"] = radec_to_decimal( this->tcs_info.dec_dms ); - jmessage_out["AZ"] = this->tcs_info.azimuth; - jmessage_out["ALT"] = 90. - this->tcs_info.zenithangle; - jmessage_out["ZENANGLE"] = this->tcs_info.zenithangle; - jmessage_out["DOMEAZ"] = this->tcs_info.domeazimuth; - jmessage_out["DOMESHUT"] = this->tcs_info.domeshutters==1?"open":"closed"; - jmessage_out["TELFOCUS"] = this->tcs_info.focus; - jmessage_out["AIRMASS"] = this->tcs_info.airmass; - jmessage_out["MOTION"] = this->tcs_info.motion; + jmessage_out[Key::SOURCE] = Daemon::TCSD; + + { + std::lock_guard lock(tcs_info_mtx); + + jmessage_out["ISOPEN"] = this->tcs_info.isopen; + jmessage_out["TCSNAME"] = this->tcs_info.tcsname; + + jmessage_out["PA"] = this->tcs_info.pa; // double + jmessage_out[Key::Tcsd::CASANGLE] = this->tcs_info.cassangle; // double + jmessage_out["HA"] = this->tcs_info.ha; // string + jmessage_out["RAOFFSET"] = this->tcs_info.offsetra; // double + jmessage_out["DECLOFFS"] = this->tcs_info.offsetdec; // double + jmessage_out[Key::Tcsd::TELRA] = this->tcs_info.ra_hms; // string "hh:mm:ss.s" + jmessage_out[Key::Tcsd::TELDEC] = this->tcs_info.dec_dms; // string "dd:mm:ss.s" + jmessage_out["RA"] = radec_to_decimal( this->tcs_info.ra_hms ); + jmessage_out["DEC"] = radec_to_decimal( this->tcs_info.dec_dms ); + jmessage_out[Key::Tcsd::AZ] = this->tcs_info.azimuth; + jmessage_out[Key::Tcsd::ALT] = 90. - this->tcs_info.zenithangle; + jmessage_out["ZENANGLE"] = this->tcs_info.zenithangle; + jmessage_out["DOMEAZ"] = this->tcs_info.domeazimuth; + jmessage_out["DOMESHUT"] = this->tcs_info.domeshutters==1?"open":"closed"; + jmessage_out["TELFOCUS"] = this->tcs_info.focus; + jmessage_out[Key::Tcsd::AIRMASS] = this->tcs_info.airmass; + jmessage_out["MOTION"] = this->tcs_info.motion; + } // for backwards compatibility jmessage_out["messagetype"] = "tcsinfo"; @@ -82,6 +86,27 @@ namespace TCS { /***** TCS::Interface::publish_snapshot *************************************/ + /***** TCS::Interface::do_continuous_snapshot *******************************/ + /** + * @brief publish snapshot at 1 Hz when connected + * + */ + void Interface::do_continuous_snapshot() { + auto next = std::chrono::steady_clock::now(); + while (should_publish.load()) { + bool isopen = false; + { + std::lock_guard lock(tcs_info_mtx); + isopen = this->tcs_info.isopen; + } + if (isopen) publish_snapshot(); + next += std::chrono::seconds(1); + std::this_thread::sleep_until(next); + } + } + /***** TCS::Interface::do_continuous_snapshot *******************************/ + + /***** TCS::Interface::get_tcs_info *****************************************/ /** * @brief fills the tcs_info class @@ -92,6 +117,8 @@ namespace TCS { long error = NO_ERROR; std::string retstring; + std::lock_guard lock(tcs_info_mtx); + // erase the class because it's all or nothing. If something fails partway // through, we don't want to mix values from a command now with values from // an earlier command. E.G. if reqpos fails here but reqstat and weather @@ -371,10 +398,12 @@ namespace TCS { } } + { + std::lock_guard lock(tcs_info_mtx); this->tcs_info.isopen = ( ! _name.empty() ? true : false ); this->tcs_info.tcsname = _name; - retstring = ( this->tcs_info.isopen ? "true" : "false" ); // return string is the state + } asyncmsg.str(""); asyncmsg << "TCSD:open:" << retstring; this->async.enqueue( asyncmsg.str() ); // broadcast the state @@ -1137,10 +1166,12 @@ namespace TCS { // parse the reply which stores it in the TcsInfo class // - this->tcs_info.parse_pa(tcsreply); - std::ostringstream oss; + this->tcs_info.parse_pa(tcsreply); + { + std::lock_guard lock(tcs_info_mtx); oss << this->tcs_info.pa; + } retstring = oss.str(); if ( !retstring.empty() && !silent ) logwrite( function, retstring ); diff --git a/tcsd/tcs_interface.h b/tcsd/tcs_interface.h index 9c76b03f..e451f6a1 100644 --- a/tcsd/tcs_interface.h +++ b/tcsd/tcs_interface.h @@ -13,6 +13,7 @@ #include "common.h" #include "tcs_constants.h" #include "tcsd_commands.h" +#include "message_keys.h" #include #include #include @@ -427,6 +428,7 @@ logwrite(function,message.str()); private: zmqpp::context context; std::string default_tcs; ///< default TCS to use specified in .cfg + std::mutex tcs_info_mtx; ///< protects tcs_info public: inline void set_default_tcs(const std::string &which) { this->default_tcs=which; } @@ -445,13 +447,13 @@ logwrite(function,message.str()); std::condition_variable publish_condition; std::condition_variable collect_condition; - std::atomic publish_enable; + std::atomic should_publish; std::atomic collect_enable; Interface() : context(), offsetrate(0), - publish_enable(false), + should_publish(true), collect_enable(false), subscriber(std::make_unique(context, Common::PubSub::Mode::SUB)), is_subscriber_thread_running(false), @@ -487,6 +489,7 @@ logwrite(function,message.str()); void publish_snapshot(); void publish_snapshot(std::string &retstring); + void do_continuous_snapshot(); /** * These are the functions for communicating with the TCS diff --git a/tcsd/tcsd.cpp b/tcsd/tcsd.cpp index e7d19000..c21c20b5 100644 --- a/tcsd/tcsd.cpp +++ b/tcsd/tcsd.cpp @@ -132,6 +132,10 @@ int main(int argc, char **argv) { // publish my snapshot so the world knows I'm online tcsd.interface.publish_snapshot(); + // thread to publish snapshot when connected + std::thread( &TCS::Interface::do_continuous_snapshot, + std::ref(tcsd.interface) ).detach(); + // This will pre-thread N_THREADS threads, a little differently from other // daemons. There will be N_THREADS-1 non-blocking threads as before then // loop forever on Accept to dynamically spawn a new thread for each blocking From c6ce5b39793d188fbca4aef09b903e5859516159 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 11 May 2026 23:54:54 -0700 Subject: [PATCH 20/35] Adds more work done in parallel on the fineacquire-update-sequencer-pub branch in support of fine acquisition and general pub-sub improvement, and other fixes along the way. These changes are not pertinent to the work being done for the sequencer-scripting branch but must be included so that this branch keeps up with other progress while awaiting PR approval. --- Config/slicecamd.cfg.in | 34 ++ acamd/acam_interface.cpp | 126 +++---- acamd/acam_interface.h | 10 +- common/common.cpp | 1 + common/common.h | 13 +- common/fits_header_defs.h | 129 ++++++++ common/message_keys.h | 70 +++- common/sequencerd_commands.h | 2 + common/tcsd_commands.h | 2 + sequencerd/sequence.cpp | 134 ++++++-- sequencerd/sequence.h | 27 +- sequencerd/sequence_acquisition.cpp | 190 +++++++++-- sequencerd/sequencer_server.cpp | 4 + sequencerd/sequencerd.cpp | 8 +- slicecamd/slicecam_interface.cpp | 266 ++++++++++++--- slicecamd/slicecam_interface.h | 47 ++- tcsd/tcs_interface.cpp | 114 +++---- tcsd/tcs_interface.h | 3 + tcsd/tcs_server.cpp | 7 + utils/logentry.cpp | 2 +- utils/logentry.h | 2 +- utils/network.cpp | 4 +- utils/seqmon.cpp | 492 ++++++++++++++++++++++++++++ utils/utilities.h | 7 + 24 files changed, 1447 insertions(+), 247 deletions(-) create mode 100644 common/fits_header_defs.h create mode 100644 utils/seqmon.cpp diff --git a/Config/slicecamd.cfg.in b/Config/slicecamd.cfg.in index e3abb098..46c35ca1 100644 --- a/Config/slicecamd.cfg.in +++ b/Config/slicecamd.cfg.in @@ -77,6 +77,40 @@ FINE_ACQUIRE_AIMPOINT=(L 150.0 115.5) # FINE_ACQUIRE_BACKGROUND=(80 165 30 210) +# FINE_ACQUIRE_MIN_SAMPLES= +# Minimum centroid samples before scatter-gated early exit is evaluated. +# Once this many samples are collected and the MAD scatter per axis is below +# the precision threshold, a correction is issued without waiting for MAX_SAMPLES. +# +FINE_ACQUIRE_MIN_SAMPLES=3 + +# FINE_ACQUIRE_SETTLE_FRAMES= +# frames discarded after each telescope move to allow settling before +# centroid measurements resume. +# +FINE_ACQUIRE_SETTLE_FRAMES=2 + +# FINE_ACQUIRE_GAIN= +# Proportional gain = {0..1} applied to the commanded offset when the residual +# is at or below FINE_ACQUIRE_GAIN_THRESHOLD arcsec. +# +FINE_ACQUIRE_GAIN=0.7 + +# FINE_ACQUIRE_GAIN_LARGE= +# Proportional gain applied when the residual exceeds FINE_ACQUIRE_GAIN_THRESHOLD. +# Set to 1.0 for a full-correction step on large offsets to reduce +# the number of correction cycles needed. Set equal to FINE_ACQUIRE_GAIN to +# disable gain scheduling. +# +FINE_ACQUIRE_GAIN_LARGE=1.0 + +# FINE_ACQUIRE_GAIN_THRESHOLD= +# Offset magnitude (arcsec) above which FINE_ACQUIRE_GAIN_LARGE is used instead +# of FINE_ACQUIRE_GAIN. Set to 0 to disable gain scheduling (GAIN is used for +# all corrections. +# +FINE_ACQUIRE_GAIN_THRESHOLD=2.0 + # SkySimulator options: # SKYSIM_IMAGE_SIZE= where is integer # Sets the keyword argument "IMAGE_SIZE=" diff --git a/acamd/acam_interface.cpp b/acamd/acam_interface.cpp index 86df30be..796719e7 100644 --- a/acamd/acam_interface.cpp +++ b/acamd/acam_interface.cpp @@ -9,6 +9,7 @@ */ #include "acam_interface.h" +#include "fits_header_defs.h" namespace Acam { @@ -1492,6 +1493,7 @@ namespace Acam { jmessage_out[Key::Acamd::ATTEMPTS] = this->target.attempts; jmessage_out[Key::Acamd::SEEING] = this->astrometry.get_seeing(); jmessage_out[Key::Acamd::BACKGROUND] = this->astrometry.get_background(); + jmessage_out[Key::PUBTIME] = get_time_us(); try { this->publisher->publish( jmessage_out, Topic::ACAMD ); @@ -1507,7 +1509,7 @@ namespace Acam { /***** Acam::Interface::request_snapshot ************************************/ /** - * @brief publises request for snapshot + * @brief [obsolete] publises request for snapshot * @details publishing Topic::SNAPSHOT induces subscribers to publish a * snapshot of their telemetry * @@ -1534,7 +1536,7 @@ namespace Acam { /***** Acam::Interface::wait_for_snapshots **********************************/ /** - * @brief wait for everyone to publish their snaphots + * @brief [obsolete] wait for everyone to publish their snaphots * @details When forcing subscribers to publish their telemetry, * this waits until they have done so. * @@ -1599,33 +1601,32 @@ namespace Acam { * */ void Interface::handletopic_tcsd( const nlohmann::json &jmessage ) { - { - std::lock_guard lock(snapshot_mtx); - snapshot_status[Topic::TCSD]=true; - } + + std::lock_guard lock(tcsdata_mtx); + // extract and store values in the class // - Common::extract_telemetry_value( jmessage, "TCSNAME", telem.tcsname ); - Common::extract_telemetry_value( jmessage, "ISOPEN", telem.is_tcs_open ); - Common::extract_telemetry_value( jmessage, Key::Tcsd::CASANGLE, telem.angle_scope ); - Common::extract_telemetry_value( jmessage, Key::Tcsd::TELRA, telem.ra_scope_hms ); - Common::extract_telemetry_value( jmessage, Key::Tcsd::TELDEC, telem.dec_scope_dms ); - Common::extract_telemetry_value( jmessage, "RA", telem.ra_scope_h ); - Common::extract_telemetry_value( jmessage, "DEC", telem.dec_scope_d ); - Common::extract_telemetry_value( jmessage, "RAOFFSET", telem.offsetra ); - Common::extract_telemetry_value( jmessage, "DECLOFFS", telem.offsetdec ); - Common::extract_telemetry_value( jmessage, Key::Tcsd::AZ, telem.az ); - Common::extract_telemetry_value( jmessage, "TELFOCUS", telem.telfocus ); - Common::extract_telemetry_value( jmessage, Key::Tcsd::AIRMASS, telem.airmass ); + Common::extract_telemetry_value( jmessage, "TCSNAME", tcsdata.tcsname ); + Common::extract_telemetry_value( jmessage, "ISOPEN", tcsdata.is_tcs_open ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::CASANGLE, tcsdata.angle_scope ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::TELRA, tcsdata.ra_scope_hms ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::TELDEC, tcsdata.dec_scope_dms ); + Common::extract_telemetry_value( jmessage, "RA", tcsdata.ra_scope_h ); + Common::extract_telemetry_value( jmessage, "DEC", tcsdata.dec_scope_d ); + Common::extract_telemetry_value( jmessage, "RAOFFSET", tcsdata.offsetra ); + Common::extract_telemetry_value( jmessage, "DECLOFFS", tcsdata.offsetdec ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::AZ, tcsdata.az ); + Common::extract_telemetry_value( jmessage, "TELFOCUS", tcsdata.telfocus ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::AIRMASS, tcsdata.airmass ); // save them to the database // - this->database.add_key_val( "CASANGLE", telem.angle_scope ); - this->database.add_key_val( "RAtel", telem.ra_scope_h ); - this->database.add_key_val( "DECLtel", telem.dec_scope_d ); - this->database.add_key_val( "AZ", telem.az ); - this->database.add_key_val( "focus", telem.telfocus ); - this->database.add_key_val( "AIRMASS", telem.airmass ); + this->database.add_key_val( "CASANGLE", tcsdata.angle_scope ); + this->database.add_key_val( "RAtel", tcsdata.ra_scope_h ); + this->database.add_key_val( "DECLtel", tcsdata.dec_scope_d ); + this->database.add_key_val( "AZ", tcsdata.az ); + this->database.add_key_val( "focus", tcsdata.telfocus ); + this->database.add_key_val( "AIRMASS", tcsdata.airmass ); } /***** Acam::Interface::handletopic_tcsd ************************************/ @@ -1638,10 +1639,6 @@ namespace Acam { * */ void Interface::handletopic_targetinfo( const nlohmann::json &jmessage ) { - { - std::lock_guard lock(snapshot_mtx); - snapshot_status[Topic::TARGETINFO]=true; - } this->database.add_from_json( jmessage, "OBS_ID" ); this->database.add_from_json( jmessage, "NAME" ); this->database.add_from_json( jmessage, "POINTMODE" ); @@ -1659,12 +1656,10 @@ namespace Acam { * */ void Interface::handletopic_slitd( const nlohmann::json &jmessage ) { - { - std::lock_guard lock(snapshot_mtx); - snapshot_status[Topic::SLITD]=true; + for ( const auto &keyinfo : FitsHeaderKeys::SlitdTelemKeys ) { + telemkeys.add_json_key(jmessage, keyinfo.jkey, keyinfo.keyword, + keyinfo.comment, keyinfo.type, false); } - this->telemkeys.add_json_key(jmessage, "SLITO", "SLITO", "slit offset in arcsec", "FLOAT", false); - this->telemkeys.add_json_key(jmessage, "SLITW", "SLITW", "slit width in arcsec", "FLOAT", false); } /***** Acam::Interface::handletopic_slitd ***********************************/ @@ -2706,7 +2701,7 @@ namespace Acam { do { if ( iface.camera.andor.camera_info.exptime == 0 ) continue; // wait for non-zero exposure time - if ( iface.collect_header_info() == ERROR ) { // collect header information + if ( iface.assemble_header_info() == ERROR ) { // assemble header keyword information logwrite(function,"ERROR collecting header info"); continue; } @@ -3377,7 +3372,11 @@ logwrite( function, message.str() ); this->acquire_mode = requested_mode; - iface->publish_status(); + // acam needs to publish status on every acquire command to initialize + // a freshness timer in the sequencer. The extra pub noise is a small + // price to pay for the simplicity of not adding another mechanism. + // + iface->publish_status(true); return NO_ERROR; } @@ -4083,6 +4082,10 @@ logwrite( function, message.str() ); // if ( this->motion.is_open() ) error |= this->motion.cover( "close", dontcare ); + // diable target acquisition + // + error |= this->acquire( "stop", dontcare); + // stop the framegrab thread // error |= this->framegrab( "stop", dontcare ); @@ -4375,7 +4378,7 @@ logwrite( function, message.str() ); do { logwrite( function, std::to_string(nacquires) ); error |= this->camera.andor.acquire_one(); // acquire a single image - error |= this->collect_header_info(); // collect header information + error |= this->assemble_header_info(); // assemble header keyword information error |= this->camera.write_frame( "", this->imagename, this->tcs_online.load() ); // write to FITS file @@ -4420,7 +4423,7 @@ logwrite( function, message.str() ); do { logwrite( function, std::to_string(nacquires) ); error |= this->camera.andor.get_recent(3000); // - error |= this->collect_header_info(); // collect header information + error |= this->assemble_header_info(); // assemble header keyword information error |= this->camera.write_frame( "", this->imagename, this->tcs_online.load() ); // write to FITS file @@ -4454,7 +4457,7 @@ logwrite( function, message.str() ); // if ( tokens[1]=="getrecent" ) { error = this->camera.andor.get_recent(3000); - error |= this->collect_header_info(); // collect header information + error |= this->assemble_header_info(); // assemble header keyword information error |= this->camera.write_frame( "", this->imagename, this->tcs_online.load() ); // write to FITS file @@ -4583,7 +4586,7 @@ logwrite( function, message.str() ); retstring.append( " Gather information and add it to the internal keyword database.\n" ); return HELP; } - else error = this->collect_header_info(); // collect header information + else error = this->assemble_header_info(); // assemble header keyword information } else // -------------------------------- @@ -5005,7 +5008,7 @@ logwrite( function, message.str() ); /***** Acam::Interface::solve ***********************************************/ - /***** Acam::Interface::collect_header_info *********************************/ + /***** Acam::Interface::assemble_header_info ********************************/ /** * @brief gather information and add it to the internal keyword database * @details Some of the keys are fixed, some come from the Andor::Information @@ -5016,24 +5019,24 @@ logwrite( function, message.str() ); * @return ERROR or NO_ERROR * */ - long Interface::collect_header_info() { - // force subscribers to publish now, then wait - // esults in struct telem. - this->request_snapshot(); - this->wait_for_snapshots(); - - bool _tcs = telem.is_tcs_open; - std::string tcsname = ( _tcs ? telem.tcsname : "offline" ); + long Interface::assemble_header_info() { double angle_acam=NAN, ra_acam=NAN, dec_acam=NAN; // outputs from fpoffsets - if ( _tcs ) this->target.save_casangle( telem.angle_scope ); // store in the Target class, required for acquisition + // ---------- scope lock tcsdata -------------- + { + std::lock_guard lock(tcsdata_mtx); + + bool _tcs = tcsdata.is_tcs_open; + std::string tcsname = ( _tcs ? tcsdata.tcsname : "offline" ); + + if ( _tcs ) this->target.save_casangle( tcsdata.angle_scope ); // store in the Target class, required for acquisition // Compute FP offsets from TCS coordinates (SCOPE) to ACAM coodinates. // compute_offset() always wants degrees and get_coords() returns RA hours. // Results in degrees. // - if ( _tcs ) this->fpoffsets.compute_offset( "SCOPE", "ACAM", (telem.ra_scope_h*TO_DEGREES), telem.dec_scope_d, telem.angle_scope, + if ( _tcs ) this->fpoffsets.compute_offset( "SCOPE", "ACAM", (tcsdata.ra_scope_h*TO_DEGREES), tcsdata.dec_scope_d, tcsdata.angle_scope, ra_acam, dec_acam, angle_acam ); // Get some info from the Andor::Information class, @@ -5054,11 +5057,21 @@ logwrite( function, message.str() ); this->camera.fitsinfo.fitskeys.addkey( "TCS", tcsname, "" ); + this->camera.fitsinfo.fitskeys.addkey( "TELFOCUS", tcsdata.telfocus, "telescope focus (mm)" ); + this->camera.fitsinfo.fitskeys.addkey( "AIRMASS", tcsdata.airmass, "" ); + this->camera.fitsinfo.fitskeys.addkey( "RA", tcsdata.ra_scope_hms, "Telecscope Right Ascension" ); + this->camera.fitsinfo.fitskeys.addkey( "DEC", tcsdata.dec_scope_dms, "Telescope Declination" ); + this->camera.fitsinfo.fitskeys.addkey( "TELRA", tcsdata.ra_scope_h, "Telecscope Right Ascension hours" ); + this->camera.fitsinfo.fitskeys.addkey( "TELDEC", tcsdata.dec_scope_d, "Telescope Declination degrees" ); + this->camera.fitsinfo.fitskeys.addkey( "RAOFFS", tcsdata.offsetra, "Telescope RA offset" ); + this->camera.fitsinfo.fitskeys.addkey( "DECLOFFS", tcsdata.offsetdec, "Telescope DEC offset" ); + this->camera.fitsinfo.fitskeys.addkey( "CASANGLE", tcsdata.angle_scope, "Cassegrain ring angle" ); + } + // ---------- end scope lock tcsdata ---------- + this->camera.fitsinfo.fitskeys.addkey( "CREATOR", "acamd", "file creator" ); this->camera.fitsinfo.fitskeys.addkey( "INSTRUME", "NGPS", "name of instrument" ); this->camera.fitsinfo.fitskeys.addkey( "TELESCOP", "P200", "name of telescope" ); - this->camera.fitsinfo.fitskeys.addkey( "TELFOCUS", telem.telfocus, "telescope focus (mm)" ); - this->camera.fitsinfo.fitskeys.addkey( "AIRMASS", telem.airmass, "" ); // get parameters from FPOffsets, results are stored in the class // @@ -5109,13 +5122,6 @@ logwrite( function, message.str() ); this->camera.fitsinfo.fitskeys.addkey( "POSANG", angle_acam, "" ); this->camera.fitsinfo.fitskeys.addkey( "TARGET", this->target.get_name(), "target name" ); - this->camera.fitsinfo.fitskeys.addkey( "RA", telem.ra_scope_hms, "Telecscope Right Ascension" ); - this->camera.fitsinfo.fitskeys.addkey( "DEC", telem.dec_scope_dms, "Telescope Declination" ); - this->camera.fitsinfo.fitskeys.addkey( "TELRA", telem.ra_scope_h, "Telecscope Right Ascension hours" ); - this->camera.fitsinfo.fitskeys.addkey( "TELDEC", telem.dec_scope_d, "Telescope Declination degrees" ); - this->camera.fitsinfo.fitskeys.addkey( "RAOFFS", telem.offsetra, "Telescope RA offset" ); - this->camera.fitsinfo.fitskeys.addkey( "DECLOFFS", telem.offsetdec, "Telescope DEC offset" ); - this->camera.fitsinfo.fitskeys.addkey( "CASANGLE", telem.angle_scope, "Cassegrain ring angle" ); this->camera.fitsinfo.fitskeys.addkey( "WCSAXES", 2, "" ); this->camera.fitsinfo.fitskeys.addkey( "RADESYSA", "ICRS", "" ); this->camera.fitsinfo.fitskeys.addkey( "CTYPE1", "RA---TAN", "" ); @@ -5137,7 +5143,7 @@ logwrite( function, message.str() ); return NO_ERROR; } - /***** Acam::Interface::collect_header_info *********************************/ + /***** Acam::Interface::assemble_header_info ********************************/ /***** Acam::Interface::target_coords ***************************************/ diff --git a/acamd/acam_interface.h b/acamd/acam_interface.h index 3cb5d4f6..04e29eb8 100644 --- a/acamd/acam_interface.h +++ b/acamd/acam_interface.h @@ -286,7 +286,7 @@ namespace Acam { * @details the script pushes the indicated file to the Guider GUI display * @param[in] filename fits file to send */ - void push_guider_image( std::string filename ) { + void push_guider_image( std::string_view filename ) { std::string function = "Acam::GuideManager::push_guider_image"; std::stringstream cmd; cmd << push_image << " " @@ -304,7 +304,7 @@ namespace Acam { * @brief calls the push_message script with the supplied message string * @param[in] message message to send */ - void push_guider_message( std::string message ) { + void push_guider_message( std::string_view message ) { std::string function = "Acam::GuideManager::push_guider_message"; std::stringstream cmd; cmd << push_message << " " @@ -555,7 +555,9 @@ namespace Acam { double az; double telfocus; double airmass; - } telem; + } tcsdata; + + std::mutex tcsdata_mtx; std::mutex snapshot_mtx; std::unordered_map snapshot_status; @@ -694,7 +696,7 @@ namespace Acam { long exptime( const std::string args, std::string &retstring ); long fan_mode( std::string args, std::string &retstring ); - long collect_header_info(); + long assemble_header_info(); inline void init_names() { imagename=""; wcsname=""; return; } // TODO still needed? diff --git a/common/common.cpp b/common/common.cpp index 464fd76d..bfd417e8 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -25,6 +25,7 @@ namespace Common { logwrite( function, severity+": "+message ); if ( ! this->publisher ) return; + nlohmann::json jmessage; jmessage[Key::SOURCE] = this->source; jmessage[Key::Broadcast::SEVERITY] = severity; diff --git a/common/common.h b/common/common.h index b8c768f9..2e5d8f8e 100644 --- a/common/common.h +++ b/common/common.h @@ -475,7 +475,8 @@ namespace Common { } else if constexpr ( std::is_same::value || std::is_same::value || std::is_same::value ) { - if ( jvalue.type() == json::value_t::number_unsigned ) { + if ( jvalue.type() == json::value_t::number_integer || + jvalue.type() == json::value_t::number_unsigned ) { value = jvalue.template get(); } } @@ -968,9 +969,13 @@ namespace Common { * @brief template class adds key,value,comment to indicated keydb from json message * @details This extracts the value from a JSON message and uses add_key to add the * keyword to the indicated database map. - * @param[in] type reference to FitsKeys database object - * @param[in] jmessage JSON message is the source of the value - * @param[in] comment comment string for header keyword + * @param[in] jmessage JSON message is the source of the value + * @param[in] jkey key to index jmessage + * @param[in] keyword FITS header keyword + * @param[in] comment comment string for header keyword + * @param[in] type type of key can be optionally specified + * @param[in] use_extension extension or primary + * @param[in] chan channel selects keyword db * */ void add_json_key( const json &jmessage, diff --git a/common/fits_header_defs.h b/common/fits_header_defs.h new file mode 100644 index 00000000..a439ef3d --- /dev/null +++ b/common/fits_header_defs.h @@ -0,0 +1,129 @@ +/** + * @file fits_header_defs.h + * @brief shared FITS header keyword metadata for add_json_key consumers + * @author David Hale + * + */ +#pragma once + +#include "message_keys.h" + +namespace FitsHeaderKeys { + + /** + * @struct Primary + * @brief holds metadata for a primary-header keyword extracted from JSON + */ + struct Primary { + const char* jkey; ///< key to extract from JSON message + const char* keyword; ///< FITS header keyword (uses jkey if empty) + const char* comment; ///< FITS keyword comment + const char* type = ""; ///< optional explicit datatype + }; + + /** + * @struct Extension + * @brief holds metadata for an extension-header keyword extracted from JSON + */ + struct Extension { + const char* chan; ///< extension channel name + const char* jkey; ///< key to extract from JSON message + const char* keyword; ///< FITS header keyword (uses jkey if empty) + const char* comment; ///< FITS keyword comment + const char* type = ""; ///< optional explicit datatype + }; + + const Primary CalibInfoKeys[] = { + { Key::Calibd::MODFEAR.c_str(), "", "FeAr lamp modulator pow dut per" }, + { Key::Calibd::MODTHAR.c_str(), "", "ThAr lamp modulator pow dut per" }, + { Key::Calibd::MODBLCON.c_str(), "", "Blue continuum modulator pow dut per" }, + { Key::Calibd::MODBLBYP.c_str(), "", "Blue bypass modulator pow dut per" }, + { Key::Calibd::MODRDCON.c_str(), "", "Red continuum modulator pow dut per" }, + { Key::Calibd::MODRDBYP.c_str(), "", "Red bypass modulator pow dut per" }, + { Key::Calibd::CALCOVER.c_str(), "", "calib cover state" }, + { Key::Calibd::CALDOOR.c_str(), "", "calib door state" } + }; + + const Extension FlexureInfoKeys[] = { + { "I", Key::Flexured::FLXSPE_I.c_str(), "FLXSPE", "I flexure spectral axis 2 (X) in um", "" }, + { "I", Key::Flexured::FLXSPA_I.c_str(), "FLXSPA", "I flexure spatial axis 3 (Y) in um", "" }, + { "I", Key::Flexured::FLXPIS_I.c_str(), "FLXPIS", "I flexure piston axis 1 (Z) in um", "" }, + { "R", Key::Flexured::FLXSPE_R.c_str(), "FLXSPE", "R flexure spectral axis 2 (X) in um", "" }, + { "R", Key::Flexured::FLXSPA_R.c_str(), "FLXSPA", "R flexure spatial axis 3 (Y) in um", "" }, + { "R", Key::Flexured::FLXPIS_R.c_str(), "FLXPIS", "R flexure piston axis 1 (Z) in um", "" }, + { "G", Key::Flexured::FLXSPE_G.c_str(), "FLXSPE", "G flexure spectral axis 2 (X) in um", "" }, + { "G", Key::Flexured::FLXSPA_G.c_str(), "FLXSPA", "G flexure spatial axis 3 (Y) in um", "" }, + { "G", Key::Flexured::FLXPIS_G.c_str(), "FLXPIS", "G flexure piston axis 1 (Z) in um", "" }, + { "U", Key::Flexured::FLXSPE_U.c_str(), "FLXSPE", "U flexure spectral axis 2 (X) in um", "" }, + { "U", Key::Flexured::FLXSPA_U.c_str(), "FLXSPA", "U flexure spatial axis 3 (Y) in um", "" }, + { "U", Key::Flexured::FLXPIS_U.c_str(), "FLXPIS", "U flexure piston axis 1 (Z) in um", "" } + }; + + const Extension FocusInfoKeys[] = { + { "I", Key::Focusd::FOCUSI.c_str(), "FOCUS", "science camera I focus position in mm", "" }, + { "R", Key::Focusd::FOCUSR.c_str(), "FOCUS", "science camera R focus position in mm", "" }, + { "G", Key::Focusd::FOCUSG.c_str(), "FOCUS", "science camera G focus position in mm", "" }, + { "U", Key::Focusd::FOCUSU.c_str(), "FOCUS", "science camera U focus position in mm", "" } + }; + + const Primary PowerInfoKeys[] = { + { Key::Powerd::LAMPTHAR.c_str(), "", "is ThAr lamp on" }, + { Key::Powerd::LAMPFEAR.c_str(), "", "is FeAr lamp on" }, + { Key::Powerd::LAMPBLUC.c_str(), "", "is blue Xe continuum lamp on" }, + { Key::Powerd::LAMPREDC.c_str(), "", "is red continuum lamp on" }, + { Key::Powerd::LAMPXE.c_str(), "", "is Xe lamp on" }, + { Key::Powerd::LAMPINCA.c_str(), "", "is Incandescent lamp on" } + }; + + const Primary SlitInfoKeys[] = { + { Key::Slitd::SLITW.c_str(), "", "slit width in arcsec" }, + { Key::Slitd::SLITO.c_str(), "", "slit offset in arcsec" }, + { Key::Slitd::SLITPOSA.c_str(), "", "slit actuator A position in mm" }, + { Key::Slitd::SLITPOSB.c_str(), "", "slit actuator B position in mm" } + }; + + const Primary TargetInfoKeys[] = { + { "OBS_ID", "", "Observation ID", "INT" }, + { "NAME", "", "target name", "STRING" }, + { "SLITA", "", "slit angle in deg", "FLOAT" }, + { "POINTMDE", "", "pointing mode", "STRING" }, + { "RA", "", "requested Right Ascension in J2000", "STRING" }, + { "DECL", "", "requested Declination in J2000", "STRING" } + }; + + const Primary TcsInfoKeys[] = { + { Key::Tcsd::CASANGLE.c_str(), "", "TCS reported Cassegrain angle in deg", "FLOAT" }, + { Key::Tcsd::HA.c_str(), "", "hour angle" }, + { Key::Tcsd::RAOFFSET.c_str(), "", "offset Right Ascension" }, + { Key::Tcsd::DECLOFFS.c_str(), "", "offset Declination" }, + { Key::Tcsd::TELRA.c_str(), "", "TCS reported Right Ascension" }, + { Key::Tcsd::TELDEC.c_str(), "", "TCS reported Declination" }, + { Key::Tcsd::AZ.c_str(), "", "TCS reported azimuth" }, + { Key::Tcsd::ZENANGLE.c_str(), "", "TCS reported Zenith angle", "FLOAT" }, + { Key::Tcsd::DOMEAZ.c_str(), "", "TCS reported dome azimuth", "FLOAT" }, + { Key::Tcsd::DOMESHUT.c_str(), "", "dome shutters" }, + { Key::Tcsd::TELFOCUS.c_str(), "", "TCS reported telescope focus position in mm", "FLOAT" } + }; + + const Extension ThermalInfoKeys[] = { + { "I", Key::Thermald::TCCD_I.c_str(), "CCDTEMP", "I CCD temperature in Kelvin", "FLOAT" }, + { "R", Key::Thermald::TCCD_R.c_str(), "CCDTEMP", "R CCD temperature in Kelvin", "FLOAT" }, + { "G", Key::Thermald::TCCD_G.c_str(), "CCDTEMP", "G CCD temperature in Kelvin", "FLOAT" }, + { "U", Key::Thermald::TCCD_U.c_str(), "CCDTEMP", "U CCD temperature in Kelvin", "FLOAT" }, + + { "I", Key::Thermald::TCOLL_I.c_str(), "COLTEMP", "I collimator temp in deg C", "FLOAT" }, + { "R", Key::Thermald::TCOLL_R.c_str(), "COLTEMP", "R collimator temp in deg C", "FLOAT" }, + { "G", Key::Thermald::TCOLL_G.c_str(), "COLTEMP", "G collimator temp in deg C", "FLOAT" }, + + { "I", Key::Thermald::TFOCUS_I.c_str(), "FOCTEMP", "I focus temp in deg C", "FLOAT" }, + { "R", Key::Thermald::TFOCUS_R.c_str(), "FOCTEMP", "R focus temp in deg C", "FLOAT" }, + { "G", Key::Thermald::TFOCUS_G.c_str(), "FOCTEMP", "G focus temp in deg C", "FLOAT" }, + { "U", Key::Thermald::TFOCUS_U.c_str(), "FOCTEMP", "U focus temp in deg C", "FLOAT" } + }; + + const Primary SlitdTelemKeys[] = { + { Key::Slitd::SLITO.c_str(), "SLITO", "slit offset in arcsec", "FLOAT" }, + { Key::Slitd::SLITW.c_str(), "SLITW", "slit width in arcsec", "FLOAT" } + }; + +} diff --git a/common/message_keys.h b/common/message_keys.h index ef738a7e..fa731040 100644 --- a/common/message_keys.h +++ b/common/message_keys.h @@ -50,7 +50,8 @@ namespace Topic { namespace Key { - inline const std::string SOURCE = "source"; + inline const std::string SOURCE = "source"; + inline const std::string PUBTIME = "pubtime"; namespace Broadcast { inline const std::string SEVERITY = "severity"; @@ -58,7 +59,8 @@ namespace Key { } namespace Sequencer { - inline const std::string SEQSTATE = "seqstate"; + inline const std::string SEQSTATE = "seqstate"; + inline const std::string SHOULD_FINEACQUIRE = "should_fineacquire"; } namespace Camerad { @@ -104,6 +106,70 @@ namespace Key { inline const std::string AZ = "AZ"; inline const std::string AIRMASS = "AIRMASS"; inline const std::string CASANGLE = "CASANGLE"; + inline const std::string MOTION = "MOTION"; + inline const std::string HA = "HA"; + inline const std::string RAOFFSET = "RAOFFSET"; + inline const std::string DECLOFFS = "DECLOFFS"; + inline const std::string ZENANGLE = "ZENANGLE"; + inline const std::string DOMEAZ = "DOMEAZ"; + inline const std::string DOMESHUT = "DOMESHUT"; + inline const std::string TELFOCUS = "TELFOCUS"; + } + + namespace Calibd { + inline const std::string MODFEAR = "MODFEAR"; + inline const std::string MODTHAR = "MODTHAR"; + inline const std::string MODBLCON = "MODBLCON"; + inline const std::string MODBLBYP = "MODBLBYP"; + inline const std::string MODRDCON = "MODRDCON"; + inline const std::string MODRDBYP = "MODRDBYP"; + inline const std::string CALCOVER = "CALCOVER"; + inline const std::string CALDOOR = "CALDOOR"; + } + + namespace Flexured { + inline const std::string FLXSPE_I = "FLXSPE_I"; + inline const std::string FLXSPA_I = "FLXSPA_I"; + inline const std::string FLXPIS_I = "FLXPIS_I"; + inline const std::string FLXSPE_R = "FLXSPE_R"; + inline const std::string FLXSPA_R = "FLXSPA_R"; + inline const std::string FLXPIS_R = "FLXPIS_R"; + inline const std::string FLXSPE_G = "FLXSPE_G"; + inline const std::string FLXSPA_G = "FLXSPA_G"; + inline const std::string FLXPIS_G = "FLXPIS_G"; + inline const std::string FLXSPE_U = "FLXSPE_U"; + inline const std::string FLXSPA_U = "FLXSPA_U"; + inline const std::string FLXPIS_U = "FLXPIS_U"; + } + + namespace Focusd { + inline const std::string FOCUSI = "FOCUSI"; + inline const std::string FOCUSR = "FOCUSR"; + inline const std::string FOCUSG = "FOCUSG"; + inline const std::string FOCUSU = "FOCUSU"; + } + + namespace Powerd { + inline const std::string LAMPTHAR = "LAMPTHAR"; + inline const std::string LAMPFEAR = "LAMPFEAR"; + inline const std::string LAMPBLUC = "LAMPBLUC"; + inline const std::string LAMPREDC = "LAMPREDC"; + inline const std::string LAMPXE = "LAMPXE"; + inline const std::string LAMPINCA = "LAMPINCA"; + } + + namespace Thermald { + inline const std::string TCCD_I = "TCCD_I"; + inline const std::string TCCD_R = "TCCD_R"; + inline const std::string TCCD_G = "TCCD_G"; + inline const std::string TCCD_U = "TCCD_U"; + inline const std::string TCOLL_I = "TCOLL_I"; + inline const std::string TCOLL_R = "TCOLL_R"; + inline const std::string TCOLL_G = "TCOLL_G"; + inline const std::string TFOCUS_I = "TFOCUS_I"; + inline const std::string TFOCUS_R = "TFOCUS_R"; + inline const std::string TFOCUS_G = "TFOCUS_G"; + inline const std::string TFOCUS_U = "TFOCUS_U"; } } diff --git a/common/sequencerd_commands.h b/common/sequencerd_commands.h index 3a6504e2..40fcdd14 100644 --- a/common/sequencerd_commands.h +++ b/common/sequencerd_commands.h @@ -12,6 +12,7 @@ const std::string SEQUENCERD_CONFIG = "config"; const std::string SEQUENCERD_DOTYPE = "do"; const std::string SEQUENCERD_ENGINEERING = "engineering"; const std::string SEQUENCERD_EXIT = "exit"; +const std::string SEQUENCERD_FINEACQUIRE= "fineacquire"; const std::string SEQUENCERD_GETONETARGET = "getone"; const std::string SEQUENCERD_GUIDE = "guide"; const std::string SEQUENCERD_MODEXPTIME = "modexptime"; @@ -60,6 +61,7 @@ const std::vector SEQUENCERD_SYNTAX = { SEQUENCERD_DOTYPE+" [ one | all ]", SEQUENCERD_ENGINEERING+" [ true | false ]", SEQUENCERD_EXIT, + SEQUENCERD_FINEACQUIRE+" [ enable | disable ]", SEQUENCERD_GETONETARGET, SEQUENCERD_GUIDE, SEQUENCERD_MODEXPTIME+" ", diff --git a/common/tcsd_commands.h b/common/tcsd_commands.h index e8cffebc..01fa1f85 100644 --- a/common/tcsd_commands.h +++ b/common/tcsd_commands.h @@ -26,6 +26,7 @@ const std::string TCSD_NATIVE = "native"; const std::string TCSD_OFFSETRATE = "offsetrate"; const std::string TCSD_OPEN = "open"; const std::string TCSD_PTOFFSET = "offset"; +const std::string TCSD_PUBLISHSTATE = "publishstate"; const std::string TCSD_RETOFFSETS = "retoffsets"; const std::string TCSD_RINGGO = "ringgo"; const std::string TCSD_SET_FOCUS = "setfocus"; @@ -51,6 +52,7 @@ const std::vector TCSD_SYNTAX = { TCSD_OFFSETRATE+" [ ? | ]", TCSD_OPEN+" ? | ", TCSD_PTOFFSET+" ? | ", + TCSD_PUBLISHSTATE+" ? | on | off", TCSD_RETOFFSETS+" [ ? ]", TCSD_RINGGO+" ? | ", TCSD_SET_FOCUS+" ? | ", diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index d0b164fd..1e5fbfd7 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -832,13 +832,21 @@ namespace Sequencer { * */ void Sequence::handletopic_slicecamd(const nlohmann::json &jmessage) { - // set is_fineacquire_locked flag - bool fineacquirelocked; - Common::extract_telemetry_value( jmessage, Key::Slicecamd::FINEACQUIRE_LOCKED, fineacquirelocked ); - this->is_fineacquire_locked.store(fineacquirelocked, std::memory_order_relaxed); - - std::lock_guard lock(this->fineacquire_mtx); - this->fineacquire_cv.notify_all(); + bool changed = false; + if ( jmessage.contains( Key::Slicecamd::FINEACQUIRE_RUNNING ) ) { + this->is_fineacquire_running.store( + jmessage[Key::Slicecamd::FINEACQUIRE_RUNNING].get(), std::memory_order_relaxed ); + changed = true; + } + if ( jmessage.contains( Key::Slicecamd::FINEACQUIRE_LOCKED ) ) { + this->is_fineacquire_locked.store( + jmessage[Key::Slicecamd::FINEACQUIRE_LOCKED].get(), std::memory_order_relaxed ); + changed = true; + } + if ( changed ) { + std::lock_guard lock(this->fineacquire_mtx); + this->fineacquire_cv.notify_all(); + } } /***** Sequencer::Sequence::handletopic_slicecamd **************************/ @@ -870,11 +878,20 @@ namespace Sequencer { * */ void Sequence::handletopic_acamd(const nlohmann::json &jmessage) { - // set is_acam_guiding flag bool acquired; Common::extract_telemetry_value( jmessage, Key::Acamd::IS_ACQUIRED, acquired ); this->is_acam_guiding.store(acquired, std::memory_order_relaxed); + // track whether acamd is actively trying to acquire (mode == "acquiring") + if ( jmessage.contains( Key::Acamd::ACQUIRE_MODE ) ) { + const std::string mode = jmessage[Key::Acamd::ACQUIRE_MODE].get(); + this->is_acam_acquiring.store( mode == "acquiring", std::memory_order_relaxed ); + } + + int64_t pubtime=0; + Common::extract_telemetry_value( jmessage, Key::PUBTIME, pubtime ); + this->acam_pubtime.store( pubtime, std::memory_order_relaxed ); + std::lock_guard lock(this->acam_mtx); this->acam_cv.notify_all(); } @@ -902,8 +919,10 @@ namespace Sequencer { /***** Sequencer::Sequence::publish_seqstate ********************************/ /** - * @brief publishes sequencer state with topic "seq_seqstate" - * @details seqstate is a single string + * @brief publishes sequencer state under Topic::SEQ_SEQSTATE + * @details The payload carries the sequencer state (Key::Sequencer::SEQSTATE) + * plus Key::Sequencer::SHOULD_FINEACQUIRE, which is a user-settable + * sequencer config and so rides along on this topic. * */ void Sequence::publish_seqstate() { @@ -915,6 +934,9 @@ namespace Sequencer { rtrim( seqstate ); jmessage_out[Key::Sequencer::SEQSTATE] = seqstate; + // user-settable: should automatic fine acquisition run? + jmessage_out[Key::Sequencer::SHOULD_FINEACQUIRE] = this->should_fineacquire.load(); + try { this->publisher->publish( jmessage_out, Topic::SEQ_SEQSTATE ); } @@ -1021,23 +1043,25 @@ namespace Sequencer { /***** Sequencer::Sequence::broadcast_daemonstate ***************************/ /** * @brief publishes daemonstate and can control seqstate - * @details If not STARTING or STOPPING and not all daemons ready then - * this ensures that the seqstate drops into NOTREADY. + * @details Daemon-readiness changes may only force the sequencer into + * NOTREADY when the current seqstate is itself READY or + * NOTREADY. Lifecycle states (STARTING, STOPPING, RUNNING, + * PAUSED, ABORTING, FAILED) are owned by the lifecycle + * functions and are never overridden here. The one-hot + * seqstate contract is preserved. * */ void Sequence::broadcast_daemonstate() { // always publish daemonstate when called this->publish_daemonstate(); - // If any daemon isn't ready then the sequencer can't be ready, - // but don't override STARTING or STOPPING, unless none are ready. - if ( daemon_manager.are_all_clear() ) { - seq_state_manager.set_only( {Sequencer::SEQ_NOTREADY} ); - } - else - if ( ! seq_state_manager.is_set(SEQ_STARTING) && - ! seq_state_manager.is_set(SEQ_STOPPING) && - ! daemon_manager.are_all_set() ) { + // Only degrade seqstate to NOTREADY when the sequencer is currently + // READY or NOTREADY. Never override an active lifecycle transition + // (STARTING, STOPPING, RUNNING, PAUSED, ABORTING) or FAILED. + // + if ( ! daemon_manager.are_all_set() && + seq_state_manager.are_any_set( Sequencer::SEQ_READY, + Sequencer::SEQ_NOTREADY ) ) { seq_state_manager.set_only( {Sequencer::SEQ_NOTREADY} ); } } @@ -1046,23 +1070,38 @@ namespace Sequencer { /***** Sequencer::Sequence::broadcast_seqstate ******************************/ /** - * @brief publishes seq_state via PUB-SUB - * @details The legacy "SEQSTATE:" UDP async string has been removed; - * seqstate is published exclusively on the PUB-SUB topic now. + * @brief publishes seq_state on the SEQ_SEQSTATE topic + * @details Legacy UDP "SEQSTATE:" async strings have been removed. + * Seqstate is now broadcast only via PUB-SUB. * */ void Sequence::broadcast_seqstate() { + const std::string function("Sequencer::Sequence::broadcast_seqstate"); + + // publish the structured seqstate topic + // this->publish_seqstate(); this->cv.notify_all(); + + // emit a NOTICE on Topic::BROADCAST only when the lifecycle state has + // actually changed, so operators (and logs) get a breadcrumb trail of + // state transitions without noise from repeated identical callbacks. + // + std::string current( this->seq_state_manager.get_set_states() ); + rtrim( current ); + if ( current != this->last_seqstate_str ) { + this->last_seqstate_str = current; + this->broadcast.notice( function, "sequencer state: "+current ); + } } /***** Sequencer::Sequence::broadcast_seqstate ******************************/ /***** Sequencer::Sequence::broadcast_waitstate *****************************/ /** - * @brief publishes wait_state via PUB-SUB - * @details The legacy "WAITSTATE:" UDP async string has been removed; - * waitstate is published exclusively on the PUB-SUB topic now. + * @brief publishes wait_state on the SEQ_WAITSTATE topic + * @details Legacy UDP "WAITSTATE:" async strings have been removed. + * Waitstate is now broadcast only via PUB-SUB. * */ void Sequence::broadcast_waitstate() { @@ -3649,6 +3688,47 @@ namespace Sequencer { /***** Sequencer::Sequence::engineering *************************************/ + /***** Sequencer::Sequence::fine_acquire ************************************/ + /** + * @brief enable or disable automatic fine acquisition step + * @param[in] args enable|disable + * @param[out] retstring state {enabled|disabled} + * @return ERROR|NO_ERROR|HELP + * + */ + long Sequence::fine_acquire(std::string args, std::string &retstring) { + if (args=="help"||args=="?") { + retstring = SLICECAMD_FINEACQUIRE; + retstring.append( " [ enable | disable ]\n" ); + retstring.append( " enables or disables the automatic fine acquisition step\n" ); + retstring.append( " no arg returns state only\n" ); + return HELP; + } + + const bool prev = this->should_fineacquire.load(); + + if (args=="enable") this->should_fineacquire.store(true); + else + if (args=="disable") this->should_fineacquire.store(false); + else + if (!args.empty()) { + logwrite("Sequencer::Sequence::fineacquire", + "ERROR invalid '"+args+"' expected enable|disable"); + return ERROR; + } + + retstring = this->should_fineacquire.load() ? "enabled" : "disabled"; + + // publish on change + if ( this->should_fineacquire.load() != prev ) { + this->publish_seqstate(); + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::fine_acquire ************************************/ + + /***** Sequencer::Sequence::get_dome_position *******************************/ /** * @brief read the dome and telescope positions diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 928ae9e0..64be1376 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -132,8 +132,8 @@ namespace Sequencer { SEQ_STOPPING, ///< set when sequencer is shutting down SEQ_PAUSED, ///< set when sequencer is paused SEQ_STARTING, ///< set when sequencer is starting up - SEQ_FAILED, ///< set when sequencer has failed in an unsafe/indeterminate way - SEQ_ABORTING, ///< transitory state while aborting; cleared via RAII + SEQ_FAILED, ///< set on a fatal/indeterminate failure; cleared only by startup or shutdown + SEQ_ABORTING, ///< transitory; set/cleared via RAII in abort_process() NUM_SEQ_STATES }; @@ -308,8 +308,20 @@ namespace Sequencer { 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 + std::atomic should_fineacquire{true}; ///< should I use fineacquire? (user-switchable) std::atomic is_fineacquire_locked{false}; ///< is slicecam fine acquisition locked? - std::atomic is_acam_guiding{false}; ///< is acam guiding? + std::atomic is_fineacquire_running{false}; ///< is slicecam fine acquisition running? + std::atomic is_acam_guiding{false}; ///< is acam guiding (IS_ACQUIRED)? + std::atomic is_acam_acquiring{false}; ///< is acam in an acquire mode? + std::atomic acam_pubtime{0}; ///< publish time (us) of latest received acamd status + + /** @brief guard-band (us) subtracted from the acquire-command send time + * when computing the freshness boundary in do_acam_acquire. Tolerates + * jitter and the race between the command send and ACAM's forced publish. + * Adjust here to tune. + */ + static constexpr int64_t ACAM_FRESHNESS_GUARD_US = 500'000; + std::atomic engineering_mode{false}; ///< when true, validate_sequence applies per-daemon checks instead of SEQ_READY ThreadPool pool; @@ -681,8 +693,10 @@ namespace Sequencer { /// void set_seqstate_bit( uint32_t mb ); ///< set the specified masked bit in the seqstate word void broadcast_daemonstate(); ///< void broadcast_threadstate(); ///< - void broadcast_seqstate(); ///< writes the seqstate string to the async port - void broadcast_waitstate(); ///< writes the waitstate string to the async port + void broadcast_seqstate(); ///< publishes the seqstate on the seq_seqstate topic + void broadcast_waitstate(); ///< publishes the waitstate on the seq_waitstate topic + + std::string last_seqstate_str; ///< last seqstate string announced via broadcast_seqstate() (for change detection) uint32_t get_reqstate(); ///< get the reqstate word @@ -699,6 +713,7 @@ namespace Sequencer { long parse_calibration_target(); long parse_state( std::string whoami, std::string reply, bool &state ); ///< parse true|false state from reply string void dothread_test_fpoffset(); ///< for testing, calls Python function from thread + long fine_acquire( std::string args, std::string &retstring ); ///< enable|disable fineacquisition step long test( std::string args, std::string &retstring ); ///< handles test commands long extract_tcs_value( std::string reply, int &value ); ///< extract value returned by the TCS via tcsd long parse_tcs_generic( int value ); ///< parse generic TCS reply @@ -761,7 +776,9 @@ namespace Sequencer { * these are in sequence_acquisition.cpp */ long do_acam_acquire(); + long do_acam_stop(); long do_slicecam_fineacquire(); + long do_slicecam_stop(); long do_target_acquisition(std::string caller); long do_target_virtualslit(VirtualSlitMode mode); diff --git a/sequencerd/sequence_acquisition.cpp b/sequencerd/sequence_acquisition.cpp index 66f99c7e..affbf926 100644 --- a/sequencerd/sequence_acquisition.cpp +++ b/sequencerd/sequence_acquisition.cpp @@ -29,7 +29,7 @@ namespace Sequencer { double angle_in = this->target.slitangle; if ( std::isnan(ra_in) || std::isnan(dec_in) ) { - this->broadcast.error( function, "converting target coordinates to decimal" ); + logwrite( function, "ERROR converting target coordinates to decimal" ); return ERROR; } @@ -38,8 +38,15 @@ namespace Sequencer { this->broadcast.notice( function, "starting ACAM acquisition" ); + // Freshness boundary: any ACAMD status publish strictly newer than this + // timestamp is considered fresh for this acquire cycle. The guard-band + // absorbs jitter and the timing race between this command send and + // ACAM's forced publish triggered by the command. + // + const int64_t freshness_boundary_us = get_time_us() - ACAM_FRESHNESS_GUARD_US; + if ( this->acamd.command( cmd.str(), reply ) != NO_ERROR ) { - this->broadcast.error( function, "sending acquire command to acamd" ); + logwrite( function, "ERROR sending acquire command to acamd" ); return ERROR; } @@ -47,27 +54,102 @@ namespace Sequencer { const auto timeout_time = std::chrono::steady_clock::now() + std::chrono::duration( this->acquisition_timeout ); - // wait for is_acam_guiding (I subscribe to this) - // or cancel, or timeout + // wait for is_acam_guiding (I subscribe to this) with a fresh ACAMD publish + // since the acquire command was sent, or acamd stopped without acquiring + // (failure), or cancel, or timeout. + // was_acquiring latches true once acamd enters "acquiring" mode, so that + // !is_acam_acquiring only signals failure after the attempt began — not + // before acamd's first telemetry publish arrives. // + bool was_acquiring = false; std::unique_lock lock(this->acam_mtx); this->acam_cv.wait(lock, [&]() { - return this->is_acam_guiding.load() || this->cancel_flag.load() || - (use_timeout && std::chrono::steady_clock::now() > timeout_time); + if ( this->is_acam_acquiring.load() ) was_acquiring = true; + const bool fresh = this->acam_pubtime.load() > freshness_boundary_us; + return ( fresh && this->is_acam_guiding.load() ) || this->cancel_flag.load() || + ( was_acquiring && !this->is_acam_acquiring.load() ) || + ( use_timeout && std::chrono::steady_clock::now() > timeout_time ); }); if (this->cancel_flag.load()) return ABORT; - if (use_timeout && !this->is_acam_guiding.load()) { - this->broadcast.error( function, "ACAM acquisition timed out!" ); - return TIMEOUT; + + // Determine outcome by reading state. If not guiding, then this is a + // failure of some kind; distinguish a true timeout from acamd stopping + // without acquiring by consulting the clock. This ordering ensures that + // a failure publish arriving at or near the timeout boundary is still + // reported as a failure (not mis-labeled as a timeout). + // + if (!this->is_acam_guiding.load()) { + if (use_timeout && std::chrono::steady_clock::now() >= timeout_time) { + logwrite( function, "ERROR ACAM acquisition timed out!" ); + return TIMEOUT; + } + logwrite( function, "ERROR ACAM acquisition stopped without acquiring target" ); + return ERROR; } - this->async.enqueue_and_log(function, "ACAM target acquired"); + this->broadcast.notice( function, "ACAM target acquired" ); return NO_ERROR; } /***** Sequencer::Sequence::do_acam_acquire **********************************/ + /***** Sequencer::Sequence::do_acam_stop *************************************/ + /** + * @brief stops ACAM guiding + * @return NO_ERROR | ERROR | TIMEOUT + * + */ + long Sequence::do_acam_stop() { + const std::string function("Sequencer::Sequence::do_acam_stop"); + + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_ACAM ); + + // any acamd status newer than this timestamp is considered fresh + const int64_t freshness_boundary_us = get_time_us() - ACAM_FRESHNESS_GUARD_US; + + // send ACQUIRE STOP command to ACAM + std::string reply; + if ( this->acamd.command( ACAMD_ACQUIRE+" stop", reply ) != NO_ERROR ) { + logwrite( function, "ERROR stopping guiding" ); + return ERROR; + } + + if ( reply.find("ERROR") != std::string::npos ) { + logwrite( function, "ERROR acam: "+reply ); + return ERROR; + } + + const bool use_timeout = ( this->acquisition_timeout > 0 ); + const auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::duration( this->acquisition_timeout ); + + // wait for a new is_acam_guiding, or cancel, or timeout + std::unique_lock lock(this->acam_mtx); + this->acam_cv.wait(lock, [&]() { + const bool fresh = this->acam_pubtime.load() > freshness_boundary_us; + return ( fresh && !this->is_acam_guiding.load() ) || this->cancel_flag.load() || + ( use_timeout && std::chrono::steady_clock::now() > timeout_time ); + }); + + // success + if (!this->is_acam_guiding.load()) { + this->broadcast.notice( function, "guiding stopped" ); + return NO_ERROR; + } + + // failure + if (this->cancel_flag.load()) return ABORT; + if (use_timeout) { + logwrite( function, "ERROR timeout stopping guiding" ); + return TIMEOUT; + } + logwrite( function, "ERROR stopping guiding" ); + return ERROR; + } + /***** Sequencer::Sequence::do_acam_stop *************************************/ + + /***** Sequencer::Sequence::do_slicecam_fineacquire **************************/ /** * @brief trigger SLICECAM fine acquisition and wait until locked @@ -79,15 +161,14 @@ namespace Sequencer { ScopedState wait_state(wait_state_manager, Sequencer::SEQ_WAIT_FINEACQUIRE); - // TODO don't hard-code the arguments here: std::string reply; - if (this->slicecamd.command( SLICECAMD_FINEACQUIRE+" start L", reply ) != NO_ERROR) { - this->broadcast.error( function, "starting slicecam fine acquisition" ); + if (this->slicecamd.command( SLICECAMD_FINEACQUIRE+" start", reply ) != NO_ERROR) { + logwrite( function, "ERROR starting slicecam fine acquisition" ); return ERROR; } if ( reply.find("ERROR") != std::string::npos ) { - this->async.enqueue_and_log(function, "slicecam fine acquisition mode: "+reply); + logwrite( function, "ERROR slicecam fine acquisition mode: "+reply ); return ERROR; } @@ -98,26 +179,96 @@ namespace Sequencer { + std::chrono::duration( this->acquisition_timeout ); // wait for is_fineacquire_locked (I subscribe to this) - // or cancel, or timeout + // or slicecam stopped without locking (failure), or cancel, or timeout. + // was_running latches true once slicecamd confirms it started, so that + // !is_fineacquire_running only signals failure after the run began — not + // before slicecamd's first telemetry publish arrives. // + bool was_running = false; std::unique_lock lock(this->fineacquire_mtx); this->fineacquire_cv.wait(lock, [&]() { + if ( this->is_fineacquire_running.load() ) was_running = true; return this->is_fineacquire_locked.load() || this->cancel_flag.load() || + ( was_running && !this->is_fineacquire_running.load() ) || (use_timeout && std::chrono::steady_clock::now() > timeout_time); }); if (this->cancel_flag.load()) return ABORT; - if (use_timeout && !this->is_fineacquire_locked.load()) { - this->broadcast.error( function, "slicecam fine acquisition timed out!" ); - return TIMEOUT; + + // Determine outcome by reading state. If not locked, then this is a + // failure of some kind; distinguish a true timeout from slicecamd + // stopping without locking by consulting the clock. This ordering + // ensures that a failure publish arriving at or near the timeout + // boundary is still reported as a failure (not mis-labeled as a + // timeout). + // + if (!this->is_fineacquire_locked.load()) { + if (use_timeout && std::chrono::steady_clock::now() >= timeout_time) { + logwrite( function, "ERROR slicecam fine acquisition timed out!" ); + return TIMEOUT; + } + logwrite( function, "ERROR slicecam fine acquisition stopped without locking" ); + return ERROR; } - this->async.enqueue_and_log(function, "slicecam fine acquisition target acquired"); + this->broadcast.notice( function, "slicecam fine acquisition target acquired" ); return NO_ERROR; } /***** Sequencer::Sequence::do_slicecam_fineacquire **************************/ + /***** Sequencer::Sequence::do_slicecam_stop **********************************/ + /** + * @brief stops slicecam fineacquire + * @return NO_ERROR | ERROR | TIMEOUT + * + */ + long Sequence::do_slicecam_stop() { + const std::string function("Sequencer::Sequence::do_slicecam_stop"); + + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_SLICECAM ); + + // send STOP command to SLICECAM + std::string reply; + if (this->slicecamd.command( SLICECAMD_FINEACQUIRE+" stop", reply ) != NO_ERROR) { + logwrite( function, "ERROR stopping fine acquisition" ); + return ERROR; + } + + if ( reply.find("ERROR") != std::string::npos ) { + logwrite( function, "ERROR slicecam fine acquisition mode: "+reply ); + return ERROR; + } + + const bool use_timeout = ( this->acquisition_timeout > 0 ); + const auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::duration( this->acquisition_timeout ); + + // wait for !is_fineacquire_locked or cancel, or timeout + std::unique_lock lock(this->fineacquire_mtx); + this->fineacquire_cv.wait(lock, [&]() { + return !this->is_fineacquire_locked.load() || this->cancel_flag.load() || + (use_timeout && std::chrono::steady_clock::now() > timeout_time); + }); + + // success + if (!this->is_fineacquire_locked.load()) { + this->broadcast.notice( function, "slicecam fine acquisition stopped" ); + return NO_ERROR; + } + + // failure + if (this->cancel_flag.load()) return ABORT; + if (use_timeout) { + logwrite( function, "ERROR slicecam fine acquisition timed out!" ); + return TIMEOUT; + } + logwrite( function, "ERROR stopping fine acquisition" ); + return ERROR; + } + /***** Sequencer::Sequence::do_slicecam_stop *********************************/ + + /***** Sequencer::Sequence::do_target_acquisition ****************************/ /** * @brief performs target acquisition @@ -163,4 +314,5 @@ namespace Sequencer { return this->slit_set(mode); } /***** Sequencer::Sequence::do_target_virtualslit ****************************/ + } diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index 20be3f59..5920b3ce 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -1161,6 +1161,10 @@ namespace Sequencer { this->exit_cleanly(); // shutdown the sequencer } else + if ( cmd == SEQUENCERD_FINEACQUIRE ) { + this->sequence.fine_acquire(args, retstring); + } + else // These commands go to acamd // diff --git a/sequencerd/sequencerd.cpp b/sequencerd/sequencerd.cpp index 26f43523..f7c76c3e 100644 --- a/sequencerd/sequencerd.cpp +++ b/sequencerd/sequencerd.cpp @@ -131,15 +131,15 @@ int main(int argc, char **argv) { // if ( sequencerd.sequence.init_pubsub( { Topic::CAMERAD, Topic::ACAMD, - Topic::TCSD, + Topic::SLICECAMD, Topic::SLITD, - Topic::SLICECAMD } ) == ERROR ) { + Topic::TCSD } ) == ERROR ) { logwrite(function, "ERROR initializing publisher-subscriber handler"); sequencerd.exit_cleanly(); } - sequencerd.sequence.seq_state_manager.set(Sequencer::SEQ_NOTREADY); + sequencerd.sequence.seq_state_manager.set_only({Sequencer::SEQ_NOTREADY}); - std::this_thread::sleep_for( std::chrono::milliseconds(100) ); + std::this_thread::sleep_for( std::chrono::milliseconds(200) ); sequencerd.sequence.publish_snapshot(); // This will pre-thread N_THREADS threads. diff --git a/slicecamd/slicecam_interface.cpp b/slicecamd/slicecam_interface.cpp index e6e86bd6..1c57175c 100644 --- a/slicecamd/slicecam_interface.cpp +++ b/slicecamd/slicecam_interface.cpp @@ -10,6 +10,7 @@ #include "slicecam_interface.h" #include "slicecam_math.h" +#include "fits_header_defs.h" namespace Slicecam { @@ -54,21 +55,18 @@ namespace Slicecam { // stop fine acquisition if (action=="stop") { - if (!this->is_fineacquire_running.load(std::memory_order_acquire)) { - logwrite(function, "stopped"); - } - else { - this->is_fineacquire_running.store(false); - this->publish_status(); - logwrite(function, "stop requested"); - } + const bool was_running = this->is_fineacquire_running.load(std::memory_order_acquire); + this->is_fineacquire_locked.store(false, std::memory_order_release); + this->is_fineacquire_running.store(false, std::memory_order_release); + this->publish_status(); + logwrite(function, was_running ? "stop requested" : "stopped"); retstring=this->is_fineacquire_running.load(std::memory_order_acquire)?"running":"stopped"; return NO_ERROR; } else // not empty, stop or start is an error - if (action != "start" || tokens.size() < 2) { + if (action != "start") { logwrite(function, "ERROR expected stop | start [ { L | R } ]"); retstring="invalid_argument"; return ERROR; @@ -90,28 +88,57 @@ namespace Slicecam { return ERROR; } - // ACAM must be guiding - if (!this->is_acam_guiding.load(std::memory_order_acquire)) { - logwrite(function, "ERROR ACAM is not guiding"); - retstring="stopped"; + // Wait briefly for ACAM status to be fresh and guiding. This absorbs the + // pub/sub propagation lag where sequencerd has seen the ACAM acquire + // publish but slicecamd's subscriber thread has not yet delivered it. + { + const auto wait_duration = std::chrono::microseconds( ACAM_WAIT_TIMEOUT_US ); + std::unique_lock lock(this->acam_mtx); + const bool ready = this->acam_cv.wait_for( lock, wait_duration, [this]() { + return this->is_acam_status_fresh() && + this->is_acam_guiding.load(std::memory_order_relaxed); + }); + + if ( !ready ) { + if ( !this->is_acam_status_fresh() ) { + const int64_t age_ms = ( get_time_us() - this->last_acam_pubtime.load() ) / 1000; + message.str(""); message << "ERROR timed out waiting for fresh ACAM status (age=" + << age_ms << " ms)"; + } + else { + message.str(""); message << "ERROR timed out waiting for ACAM to guide"; + } + logwrite( function, message.str() ); + retstring = "stopped"; return ERROR; } + } // are optional but if specified then require all three - if (tokens.size() > 1 && tokens.size() != 4) { - logwrite(function, "ERROR ACAM is not guiding"); - retstring="stopped"; + if ( tokens.size() != 1 && tokens.size() != 4 ) { + logwrite(function, "ERROR expected stop | start [ { L | R } ]"); + retstring="invalid_argument"; return ERROR; } - else - // override the default camera and aimpoint if provided - if (tokens.size() > 1 && tokens.size() == 4) { + + if ( tokens.size() == 1 ) { + // no which/aimpoint args so use defaults from config file + if ( this->default_which.empty() || !this->default_aimpoint.is_valid() ) { + logwrite(function, "ERROR fineacquire defaults not configured"); + retstring="not_configured"; + return ERROR; + } + this->fineacquire_state.which = this->default_which; + this->fineacquire_state.aimpoint = this->default_aimpoint; + } + else { + // use the passed-in values try { const std::string which = tokens.at(1); - double x = std::stod(tokens.at(2)); - double y = std::stod(tokens.at(3)); + const double x = std::stod(tokens.at(2)); + const double y = std::stod(tokens.at(3)); if ( (which != "L" && which != "R") || - std::isnan(x) || std::isnan(y) || x<0 || y<0) { + std::isnan(x) || std::isnan(y) || x<0 || y<0 ) { logwrite(function, "ERROR expected stop | start [ { L | R } ]"); retstring="invalid_argument"; return ERROR; @@ -126,6 +153,12 @@ namespace Slicecam { } } + // log the effective camera and aimpoint for this invocation + message.str(""); message << "using camera=" << this->fineacquire_state.which + << " aimpoint=(" << this->fineacquire_state.aimpoint.x + << ", " << this->fineacquire_state.aimpoint.y << ")"; + logwrite(function, message.str()); + // start the state machine this->fineacquire_state.reset(); this->is_fineacquire_locked.store(false, std::memory_order_release); @@ -134,10 +167,10 @@ namespace Slicecam { // publishes status on change only this->publish_status(); - logwrite(function, "fine target acquisition enabled"); - retstring=is_fineacquire_running.load(std::memory_order_acquire)?"running":"stopped"; + logwrite(function, "fine target acquisition "+retstring); + return NO_ERROR; } /***** Slicecam::Interface::fineacquire *************************************/ @@ -158,8 +191,8 @@ namespace Slicecam { // skip frames if we are waiting for the telescope to settle after a move // - if (this->fineacquire_state.skip_frames > 0) { - this->fineacquire_state.skip_frames--; + if (this->fineacquire_state.settle_frames > 0) { + this->fineacquire_state.settle_frames--; return; } @@ -197,9 +230,17 @@ namespace Slicecam { this->fineacquire_state.bg_region, this->fineacquire_state.aimpoint, centroid) != NO_ERROR ) { - logwrite(function, "WARNING: failed to find centroid, skipping frame"); + const int max_failures = 3 * this->fineacquire_state.max_samples; + if ( ++this->fineacquire_state.consecutive_centroid_failures >= max_failures ) { + logwrite(function, "ERROR: too many consecutive centroid failures, stopping fine acquisition"); + this->is_fineacquire_running.store( false, std::memory_order_release ); + this->publish_status(); + } else { + logwrite(function, "WARNING: failed to find centroid, skipping frame"); + } return; } + this->fineacquire_state.consecutive_centroid_failures = 0; // convert centroid pixel -> sky using WCS from FITS header // @@ -236,23 +277,44 @@ namespace Slicecam { this->fineacquire_state.dra_samp.push_back( offsets.first ); this->fineacquire_state.ddec_samp.push_back( offsets.second ); - // wait until there are enough samples to evaluate a move - // + const int n = static_cast( this->fineacquire_state.dra_samp.size() ); const int max_samples = this->fineacquire_state.max_samples; - if ( static_cast(this->fineacquire_state.dra_samp.size()) < max_samples ) { - return; - } + const int min_samples = this->fineacquire_state.min_samples; + + // wait for the minimum number of samples before evaluating anything + // + if ( n < min_samples ) return; - // calculate median from accumulated samples + // compute running median // - std::vector dra_sorted = this->fineacquire_state.dra_samp; + std::vector dra_sorted = this->fineacquire_state.dra_samp; std::vector ddec_sorted = this->fineacquire_state.ddec_samp; std::sort( dra_sorted.begin(), dra_sorted.end() ); std::sort( ddec_sorted.begin(), ddec_sorted.end() ); - const double med_dra = dra_sorted[ max_samples / 2 ]; - const double med_ddec = ddec_sorted[ max_samples / 2 ]; + const double med_dra = dra_sorted[ n / 2 ]; + const double med_ddec = ddec_sorted[ n / 2 ]; + + // compute MAD-derived scatter per axis to gate early exit + // + std::vector abs_dra( n ), abs_ddec( n ); + for ( int i = 0; i < n; i++ ) { + abs_dra[i] = std::abs( this->fineacquire_state.dra_samp[i] - med_dra ); + abs_ddec[i] = std::abs( this->fineacquire_state.ddec_samp[i] - med_ddec ); + } + std::sort( abs_dra.begin(), abs_dra.end() ); + std::sort( abs_ddec.begin(), abs_ddec.end() ); + + const double sig_dra = 1.4826 * abs_dra[ n / 2 ] * 3600.0; // arcsec + const double sig_ddec = 1.4826 * abs_ddec[ n / 2 ] * 3600.0; // arcsec + + const double prec = this->fineacquire_state.prec_arcsec; + const bool scatter_ok = ( sig_dra <= prec && sig_ddec <= prec ); + + // keep accumulating if scatter is still too high and max not reached + // + if ( !scatter_ok && n < max_samples ) return; // convert to arcsec only for the threshold comparison and logging // @@ -269,17 +331,26 @@ namespace Slicecam { return; } + // select gain: use gain_large when offset is well above the goal threshold + // + const double effective_gain = ( offset_arcsec > this->fineacquire_state.gain_threshold_arcsec ) + ? this->fineacquire_state.gain_large + : this->fineacquire_state.gain; + // send gain-weighted offsets to acam // std::ostringstream oss; oss << "offset dRA=" << med_dra * 3600.0 << " dDEC=" << med_ddec * 3600.0 << " arcsec (r=" << offset_arcsec - << " arcsec) -- applying correction"; + << " arcsec, n=" << n + << " scatter=(" << sig_dra << "," << sig_ddec << ") arcsec)" + << " gain=" << effective_gain + << " -- applying correction"; logwrite( function, oss.str() ); - const double cmd_dra = this->fineacquire_state.gain * med_dra; - const double cmd_ddec = this->fineacquire_state.gain * med_ddec; + 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 ) { logwrite( function, "ERROR failed to send offset to ACAM" ); @@ -288,9 +359,9 @@ namespace Slicecam { return; } - // reset samples and skip a couple of frames for telescope settling + // reset samples and discard settle_count frames for telescope settling this->fineacquire_state.reset(); - this->fineacquire_state.skip_frames = 2; + this->fineacquire_state.settle_frames = this->fineacquire_state.settle_count; } /***** Slicecam::Interface::do_fineacquire **********************************/ @@ -421,10 +492,34 @@ namespace Slicecam { bool acquired; Common::extract_telemetry_value( jmessage, Key::Acamd::IS_ACQUIRED, acquired ); this->is_acam_guiding.store(acquired, std::memory_order_relaxed); + + // acam's publish time + int64_t pubtime=0; + Common::extract_telemetry_value( jmessage, Key::PUBTIME, pubtime ); + this->last_acam_pubtime.store( pubtime, std::memory_order_relaxed ); + + // wake any thread waiting on ACAM state (e.g. fineacquire) + std::lock_guard lock(this->acam_mtx); + this->acam_cv.notify_all(); } /***** Slicecam::Interface::handletopic_acamd *******************************/ + /***** Slicecam::Interface::is_acam_status_fresh ****************************/ + /** + * @brief check whether cached ACAM status is fresh enough to trust + * @return true if cached status has been received at least once and is + * within ACAM_STATUS_MAX_AGE_US of the current time + * + */ + bool Interface::is_acam_status_fresh() const { + const int64_t last = this->last_acam_pubtime.load(); + if ( last == 0 ) return false; + return ( get_time_us() - last ) <= ACAM_STATUS_MAX_AGE_US; + } + /***** Slicecam::Interface::is_acam_status_fresh ****************************/ + + /***** Slicecam::Interface::handletopic_slitd *******************************/ /** * @brief what to do when the topic is Topic::SLITD @@ -437,11 +532,13 @@ namespace Slicecam { std::lock_guard lock(snapshot_mtx); snapshot_status[Topic::SLITD]=true; } - Common::extract_telemetry_value( jmessage, "SLITO", telem.slitoffset ); - Common::extract_telemetry_value( jmessage, "SLITW", telem.slitwidth ); + Common::extract_telemetry_value( jmessage, Key::Slitd::SLITO, telem.slitoffset ); + Common::extract_telemetry_value( jmessage, Key::Slitd::SLITW, telem.slitwidth ); - this->telemkeys.add_json_key(jmessage, "SLITO", "SLITO", "slit offset in arcsec", "FLOAT", false); - this->telemkeys.add_json_key(jmessage, "SLITW", "SLITW", "slit width in arcsec", "FLOAT", false); + for ( const auto &keyinfo : FitsHeaderKeys::SlitdTelemKeys ) { + this->telemkeys.add_json_key(jmessage, keyinfo.jkey, keyinfo.keyword, + keyinfo.comment, keyinfo.type, false); + } } /***** Slicecam::Interface::handletopic_slitd *******************************/ @@ -762,6 +859,13 @@ namespace Slicecam { logwrite(function, "ERROR invalid FINE_ACQUIRE_AIMPOINT='"+config.arg[entry]+"' expected "); return ERROR; } + if ( which != "L" && which != "R" ) { + message.str(""); message << "ERROR invalid FINE_ACQUIRE_AIMPOINT which=\"" << which + << "\" : expected { L R }"; + logwrite( function, message.str() ); + return ERROR; + } + // store in the class for validation this->fineacquire_state.which = which; this->fineacquire_state.aimpoint = { x, y }; } @@ -775,12 +879,82 @@ namespace Slicecam { } this->fineacquire_state.bg_region = { x1, x2, y1, y2 }; } + else + if ( config.param[entry] == "FINE_ACQUIRE_MIN_SAMPLES" ) { + try { this->fineacquire_state.min_samples = std::stoi( config.arg[entry] ); } + catch ( const std::exception &e ) { + message.str(""); message << "ERROR invalid FINE_ACQUIRE_MIN_SAMPLES " + << config.arg[entry] << ": " << e.what(); + logwrite( function, message.str() ); + return ERROR; + } + message.str(""); message << "SLICECAMD:config:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + applied++; + } + else + if ( config.param[entry] == "FINE_ACQUIRE_SETTLE_FRAMES" ) { + try { this->fineacquire_state.settle_count = std::stoi( config.arg[entry] ); } + catch ( const std::exception &e ) { + message.str(""); message << "ERROR invalid FINE_ACQUIRE_SETTLE_FRAMES " + << config.arg[entry] << ": " << e.what(); + logwrite( function, message.str() ); + return ERROR; + } + message.str(""); message << "SLICECAMD:config:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + applied++; + } + else + if ( config.param[entry] == "FINE_ACQUIRE_GAIN" ) { + try { this->fineacquire_state.gain = std::stod( config.arg[entry] ); } + catch ( const std::exception &e ) { + message.str(""); message << "ERROR invalid FINE_ACQUIRE_GAIN " + << config.arg[entry] << ": " << e.what(); + logwrite( function, message.str() ); + return ERROR; + } + message.str(""); message << "SLICECAMD:config:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + applied++; + } + else + if ( config.param[entry] == "FINE_ACQUIRE_GAIN_LARGE" ) { + try { this->fineacquire_state.gain_large = std::stod( config.arg[entry] ); } + catch ( const std::exception &e ) { + message.str(""); message << "ERROR invalid FINE_ACQUIRE_GAIN_LARGE " + << config.arg[entry] << ": " << e.what(); + logwrite( function, message.str() ); + return ERROR; + } + message.str(""); message << "SLICECAMD:config:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + applied++; + } + else + if ( config.param[entry] == "FINE_ACQUIRE_GAIN_THRESHOLD" ) { + try { this->fineacquire_state.gain_threshold_arcsec = std::stod( config.arg[entry] ); } + catch ( const std::exception &e ) { + message.str(""); message << "ERROR invalid FINE_ACQUIRE_GAIN_THRESHOLD " + << config.arg[entry] << ": " << e.what(); + logwrite( function, message.str() ); + return ERROR; + } + message.str(""); message << "SLICECAMD:config:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + applied++; + } } // FINE_ACQUIRE parameters must have been configured properly // - if (!this->fineacquire_state.is_valid()) { + if (this->fineacquire_state.is_valid()) { + // if so then save these as the defaults + this->default_which = this->fineacquire_state.which; + this->default_aimpoint = this->fineacquire_state.aimpoint; + } + else { logwrite(function, "ERROR bad or missing FINE_ACQUIRE configuration"); return ERROR; } diff --git a/slicecamd/slicecam_interface.h b/slicecamd/slicecam_interface.h index 0ca7625e..0785aa84 100644 --- a/slicecamd/slicecam_interface.h +++ b/slicecamd/slicecam_interface.h @@ -76,21 +76,28 @@ namespace Slicecam { * goal_arcsec - convergence threshold; loop stops when median offset * magnitude falls below this value * gain - fraction of the median offset commanded each cycle (0..1) - * skip_frames - counts down frames to discard while telescope settles - * after a commanded move + * settle_count - configured frames to discard after each commanded move + * settle_frames - runtime countdown; decremented to zero before resuming */ struct FineAcqState { std::string which; - Point aimpoint; ///< 1-based pixel aim point + Point aimpoint { NAN, NAN }; ///< 1-based pixel aim point; NAN until configured Rect bg_region; ///< background ROI (1-based) std::vector dra_samp; ///< dRA*cos(dec) samples, degrees std::vector ddec_samp; ///< dDEC samples, degrees int max_samples = 10; ///< samples before evaluating a move + int min_samples = 3; ///< minimum samples before scatter-gated early exit + double prec_arcsec = 0.1; ///< MAD scatter threshold per axis for early exit (arcsec) double goal_arcsec = 0.3; ///< convergence threshold, arcsec - double gain = 0.7; ///< gain applied to commanded offset - int skip_frames = 0; ///< frames to skip after a telescope move - - void reset() { dra_samp.clear(); ddec_samp.clear(); skip_frames = 0; } + double gain = 0.7; ///< gain applied when offset <= gain_threshold_arcsec + double gain_large = 1.0; ///< gain applied when offset > gain_threshold_arcsec + double gain_threshold_arcsec = 2.0; ///< offset above which gain_large is used + int settle_frames = 0; ///< countdown of frames to discard while telescope settles + int settle_count = 2; ///< configured: frames to discard after each move + int consecutive_centroid_failures = 0; ///< counts consecutive centroid failures + + void reset() { dra_samp.clear(); ddec_samp.clear(); settle_frames = 0; + consecutive_centroid_failures = 0; } bool is_valid() const noexcept { return !which.empty() && aimpoint.is_valid() && bg_region.is_valid(); } @@ -119,8 +126,14 @@ namespace Slicecam { std::mutex framegrab_mtx; std::condition_variable cv; + std::mutex acam_mtx; ///< guards waiters on acam_cv + std::condition_variable acam_cv; ///< notified when cached ACAM state updates + FineAcqState fineacquire_state; + std::string default_which; ///< configured default camera for fineacquire + Point default_aimpoint { NAN, NAN }; ///< configured default aimpoint for fineacquire + public: std::unique_ptr publisher; ///< publisher object std::string publisher_address; ///< publish socket endpoint @@ -140,6 +153,16 @@ namespace Slicecam { std::atomic is_fineacquire_locked; ///< set when fine acquire target acquired std::atomic is_acam_guiding; ///< is acam guiding? + std::atomic last_acam_pubtime{0}; ///< pubtime (us) of latest received acamd status + + /// Max acceptable age (us) for cached ACAM status used by fineacquire. + static constexpr int64_t ACAM_STATUS_MAX_AGE_US = 10'000'000; + + /// Max time (us) fineacquire() will wait for a fresh, guiding ACAM status before failing. + static constexpr int64_t ACAM_WAIT_TIMEOUT_US = 2'000'000; + + bool is_acam_status_fresh() const; + /** these are set by Interface::saveframes() */ std::atomic nsave_preserve_frames; ///< number of frames to preserve (normally overwritten) @@ -207,14 +230,8 @@ namespace Slicecam { inline std::string get_imagename() { return this->imagename; } inline std::string get_wcsname() { return this->wcsname; } - inline void set_imagename( std::string name_in ) { - this->imagename = ( name_in.empty() ? DEFAULT_IMAGENAME : std::move(name_in) ); - return; - } - inline void set_wcsname( std::string name_in ) { - this->wcsname = std::move(name_in); - return; - } + inline void set_imagename( std::string name_in ) { this->imagename = ( name_in.empty() ? DEFAULT_IMAGENAME : name_in ); return; } + inline void set_wcsname( std::string name_in ) { this->wcsname = name_in; return; } Slicecam::FitsInfo fitsinfo; diff --git a/tcsd/tcs_interface.cpp b/tcsd/tcs_interface.cpp index 576ba9b8..deee79b0 100644 --- a/tcsd/tcs_interface.cpp +++ b/tcsd/tcs_interface.cpp @@ -44,9 +44,12 @@ namespace TCS { nlohmann::json jmessage_out; jmessage_out[Key::SOURCE] = Daemon::TCSD; + std::string motion; { std::lock_guard lock(tcs_info_mtx); + motion = this->tcs_info.motion; + jmessage_out[Key::Tcsd::MOTION] = motion; jmessage_out["ISOPEN"] = this->tcs_info.isopen; jmessage_out["TCSNAME"] = this->tcs_info.tcsname; @@ -66,7 +69,13 @@ namespace TCS { jmessage_out["DOMESHUT"] = this->tcs_info.domeshutters==1?"open":"closed"; jmessage_out["TELFOCUS"] = this->tcs_info.focus; jmessage_out[Key::Tcsd::AIRMASS] = this->tcs_info.airmass; - jmessage_out["MOTION"] = this->tcs_info.motion; + } + + // broadcast motion status if it changed + if (!motion.empty() && + motion != this->last_published_motion) { + this->broadcast.notice("TCS::Interface::publish_snapshot", "telescope "+motion); + this->last_published_motion = motion; } // for backwards compatibility @@ -107,6 +116,37 @@ namespace TCS { /***** TCS::Interface::do_continuous_snapshot *******************************/ + /***** TCS::Interface::publish_state ****************************************/ + /** + * @brief set | get snapshot publish state + * @param[in] arg on|off + * @param[out] retstring reference to string to contain the state + * @return NO_ERROR | HELP + * + */ + long Interface::publish_state( const std::string &arg, std::string &retstring ) { + const std::string function = "TCS::Interface::publish_state"; + + // help + if ( arg == "?" || arg == "help" ) { + retstring = TCSD_PUBLISHSTATE; + retstring.append( " on | off\n" ); + retstring.append( " set | get continuous snapshot publish state\n" ); + return HELP; + } + // on + else if ( arg == "on" ) should_publish.store(true); + + // off + else if ( arg == "off" ) should_publish.store(false); + + retstring = should_publish.load() ? "on" : "off"; + + return NO_ERROR; + } + /***** TCS::Interface::publish_state ****************************************/ + + /***** TCS::Interface::get_tcs_info *****************************************/ /** * @brief fills the tcs_info class @@ -203,7 +243,7 @@ namespace TCS { */ long Interface::llist( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::llist"; - std::stringstream message, asyncmsg; + std::stringstream message; // Help // @@ -226,9 +266,6 @@ namespace TCS { retstring = message.str(); - asyncmsg << "TCSD:llist:" << retstring; - this->async.enqueue( asyncmsg.str() ); - return NO_ERROR; } /***** TCS::Interface::llist ************************************************/ @@ -243,7 +280,7 @@ namespace TCS { */ long Interface::open( std::string arg, std::string &retstring ) { const std::string function("TCS::Interface::open"); - std::stringstream message, asyncmsg; + std::stringstream message; long error = NO_ERROR; // Help @@ -378,7 +415,7 @@ namespace TCS { */ long Interface::isopen( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::isopen"; - std::stringstream message, asyncmsg; + std::stringstream message; // Help // @@ -405,12 +442,6 @@ namespace TCS { retstring = ( this->tcs_info.isopen ? "true" : "false" ); // return string is the state } - asyncmsg.str(""); asyncmsg << "TCSD:open:" << retstring; - this->async.enqueue( asyncmsg.str() ); // broadcast the state - - asyncmsg.str(""); asyncmsg << "TCSD:name:" << ( ! _name.empty() ? _name : "offline" ); - this->async.enqueue( asyncmsg.str() ); // broadcast the name - return NO_ERROR; } /***** TCS::Interface::isopen ***********************************************/ @@ -424,7 +455,7 @@ namespace TCS { */ long Interface::close( ) { std::string function = "TCS::Interface::close"; - std::stringstream message, asyncmsg; + std::stringstream message; long error = NO_ERROR; for ( const auto &[key,tcs] : this->tcsmap ) { @@ -474,14 +505,11 @@ namespace TCS { logwrite( function, "connection open to "+tcs->getname() ); } retstring = tcs->getname(); // Found the connected TCS - this->async.enqueue( "TCSD:name:"+retstring ); return NO_ERROR; } } retstring="offline"; - this->async.enqueue( "TCSD:name:offline" ); - return NO_ERROR; } /***** TCS::Interface::get_name *********************************************/ @@ -498,7 +526,7 @@ namespace TCS { */ long Interface::get_weather_coords( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::get_weather_coords"; - std::stringstream message, asyncmsg; + std::stringstream message; std::string weather; long error = NO_ERROR; @@ -555,9 +583,6 @@ namespace TCS { retstring = message.str(); - asyncmsg << "TCSD:weathercoords:" << ( error==NO_ERROR ? message.str() : "ERROR" ); - this->async.enqueue( asyncmsg.str() ); - return error; } /***** TCS::Interface::get_weather_coords ***********************************/ @@ -575,7 +600,7 @@ namespace TCS { */ long Interface::get_coords( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::get_coords"; - std::stringstream message, asyncmsg; + std::stringstream message; long error = NO_ERROR; // Help @@ -650,9 +675,6 @@ namespace TCS { if ( !retstring.empty() && !silent ) logwrite( function, retstring ); - asyncmsg << "TCSD:coords:" << ( !retstring.empty() ? retstring : "ERROR" ); - this->async.enqueue( asyncmsg.str() ); - return error; } /***** TCS::Interface::get_coords *******************************************/ @@ -670,7 +692,7 @@ namespace TCS { */ long Interface::get_cass( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::get_cass"; - std::stringstream message, asyncmsg; + std::stringstream message; std::stringstream reply; long error = NO_ERROR; @@ -747,9 +769,6 @@ namespace TCS { if ( !retstring.empty() && !silent ) logwrite( function, retstring ); - asyncmsg << "TCSD:cassangle:" << ( !retstring.empty() ? retstring : "ERROR" ); - this->async.enqueue( asyncmsg.str() ); - return error; } /***** TCS::Interface::get_cass *********************************************/ @@ -767,7 +786,7 @@ namespace TCS { */ long Interface::get_dome( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::get_dome"; - std::stringstream message, asyncmsg; + std::stringstream message; std::string weather; long error = NO_ERROR; @@ -819,9 +838,6 @@ namespace TCS { if ( !retstring.empty() ) logwrite( function, retstring ); - asyncmsg << "TCSD:dome:" << ( !retstring.empty() ? retstring : "ERROR" ); - this->async.enqueue( asyncmsg.str() ); - return error; } /***** TCS::Interface::get_dome *********************************************/ @@ -838,7 +854,7 @@ namespace TCS { */ long Interface::set_focus( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::set_focus"; - std::stringstream message, asyncmsg; + std::stringstream message; long error = NO_ERROR; // Help @@ -852,7 +868,7 @@ namespace TCS { if ( arg.empty() ) { retstring="missing_argument"; return ERROR; } - double value; + double value=NAN; try { value = std::stod( arg ); @@ -918,7 +934,7 @@ namespace TCS { */ long Interface::get_focus( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::get_focus"; - std::stringstream message, asyncmsg; + std::stringstream message; std::stringstream reply; long error = NO_ERROR; @@ -1003,9 +1019,6 @@ namespace TCS { if ( !retstring.empty() && !silent ) logwrite( function, retstring ); - asyncmsg << "TCSD:focus:" << ( !retstring.empty() ? retstring : "ERROR" ); - this->async.enqueue( asyncmsg.str() ); - return error; } /***** TCS::Interface::get_focus ********************************************/ @@ -1043,9 +1056,6 @@ namespace TCS { logwrite( function, retstring ); } - message.str(""); message << "TCSD:offsets:" << ( !retstring.empty() ? retstring : "ERROR" ); - this->async.enqueue( message.str() ); - return NO_ERROR; } /***** TCS::Interface::get_offsets ******************************************/ @@ -1176,10 +1186,6 @@ namespace TCS { if ( !retstring.empty() && !silent ) logwrite( function, retstring ); - std::stringstream asyncmsg; - asyncmsg << "TCSD:parallactic:" << ( !retstring.empty() ? retstring : "ERROR" ); - this->async.enqueue( asyncmsg.str() ); - return NO_ERROR; } /***** TCS::Interface::get_pa ***********************************************/ @@ -1196,7 +1202,7 @@ namespace TCS { */ long Interface::pt_offsetrate( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::pt_offsetrate"; - std::stringstream message, asyncmsg; + std::stringstream message; long error = NO_ERROR; // Help @@ -1264,7 +1270,7 @@ namespace TCS { */ long Interface::get_motion( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::get_motion"; - std::stringstream message, asyncmsg; + std::stringstream message; long error = NO_ERROR; // Help @@ -1283,9 +1289,6 @@ namespace TCS { error = ERROR; } - asyncmsg << "TCSD:motion:" << ( !retstring.empty() ? retstring : "ERROR" ); - this->async.enqueue( asyncmsg.str() ); - return error; } /***** TCS::Interface::get_motion *******************************************/ @@ -1302,7 +1305,7 @@ namespace TCS { */ long Interface::ringgo( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::ringgo"; - std::stringstream message, asyncmsg; + std::stringstream message; long error = ERROR; double angle = NAN; @@ -1385,7 +1388,7 @@ namespace TCS { long Interface::coords( std::string args, std::string &retstring ) { std::string function = "TCS::Interface::coords"; std::string retcode; - std::stringstream message, asyncmsg; + std::stringstream message; // Help // @@ -1414,9 +1417,6 @@ namespace TCS { long error = this->send_command( cmd.str(), retstring, TCS::FAST_RESPONSE ); - asyncmsg << "TCSD:coords:" << ( error == ERROR ? "ERROR" : retstring ); - this->async.enqueue( asyncmsg.str() ); - return error; } /***** TCS::Interface::coords ***********************************************/ @@ -1524,7 +1524,7 @@ namespace TCS { long Interface::zero_offsets( const std::string args, std::string &retstring ) { std::string function = "TCS::Interface::zero_offsets"; std::string retcode; - std::stringstream message, asyncmsg; + std::stringstream message; // Help // diff --git a/tcsd/tcs_interface.h b/tcsd/tcs_interface.h index e451f6a1..6e73e6bd 100644 --- a/tcsd/tcs_interface.h +++ b/tcsd/tcs_interface.h @@ -429,6 +429,7 @@ logwrite(function,message.str()); zmqpp::context context; std::string default_tcs; ///< default TCS to use specified in .cfg std::mutex tcs_info_mtx; ///< protects tcs_info + std::string last_published_motion; ///< last published motion (publish on change) public: inline void set_default_tcs(const std::string &which) { this->default_tcs=which; } @@ -502,6 +503,7 @@ logwrite(function,message.str()); long isopen( std::string &retstring ); long isopen( const std::string &arg, std::string &retstring ); long close(); + long publish_state( const std::string &arg, std::string &retstring ); long get_name( const std::string &arg, std::string &retstring ); long get_weather_coords( const std::string &arg, std::string &retstring ); long get_coords( const std::string &arg, std::string &retstring ); @@ -530,6 +532,7 @@ logwrite(function,message.str()); long get_tcs_info(); ///< fills the tcs_info class Common::Queue async; ///< asynchronous message queue object + Common::Broadcaster broadcast { this->publisher, Daemon::TCSD }; }; /***** TCS::Interface *******************************************************/ diff --git a/tcsd/tcs_server.cpp b/tcsd/tcs_server.cpp index 6139981e..96882f93 100644 --- a/tcsd/tcs_server.cpp +++ b/tcsd/tcs_server.cpp @@ -668,6 +668,13 @@ void doit(TcsIO &tcs_io, const std::string &client_cmd, bool is_slow_command) { } else + // publishstate + // + if ( cmd.compare( TCSD_PUBLISHSTATE ) == 0 ) { + ret = this->interface.publish_state( args, retstring ); + } + else + // llist // if ( cmd.compare( TCSD_LLIST ) == 0 ) { diff --git a/utils/logentry.cpp b/utils/logentry.cpp index b4acb65f..a384ee9e 100644 --- a/utils/logentry.cpp +++ b/utils/logentry.cpp @@ -175,7 +175,7 @@ void close_log() { * log filestream isn't open. * */ -void logwrite( std::string function, std::string message ) { +void logwrite( const std::string &function, std::string message ) { std::ostringstream logmsg; std::lock_guard lock(loglock); // lock mutex to protect from multiple access diff --git a/utils/logentry.h b/utils/logentry.h index a5c464e3..ddf95890 100644 --- a/utils/logentry.h +++ b/utils/logentry.h @@ -21,6 +21,6 @@ extern unsigned int nextday; /// number of seconds long init_log( std::string logpath, std::string name ); /// initialize the logging system long init_log( std::string logpath, std::string name, bool stderr_in ); /// initialize the logging system void close_log(); /// close the log file stream -void logwrite(std::string function, std::string message); /// create a time-stamped log entry "message" from "function" +void logwrite(const std::string &function, std::string message); /// create a time-stamped log entry "message" from "function" #endif diff --git a/utils/network.cpp b/utils/network.cpp index 6ad3e8eb..5b6a2402 100644 --- a/utils/network.cpp +++ b/utils/network.cpp @@ -176,12 +176,12 @@ namespace Network { */ int UdpSocket::Send(std::string message) { std::string function = "Network::UdpSocket::Send"; - std::ostringstream errstm; + std::stringstream errstm; ssize_t nbytes; if ( !this->is_running() ) return 0; // silently do nothing if the UDP multicast socket isn't running - if ( ( nbytes = sendto( this->fd, std::string(message).c_str(), (size_t)message.length(), 0, + if ( ( nbytes = sendto( this->fd, message.c_str(), (size_t)message.length(), 0, (struct sockaddr*) &this->addr, (socklen_t)sizeof(this->addr) ) ) < 0 ) { errstm << "error " << errno << " calling sendto: " << strerror(errno); logwrite(function, errstm.str()); diff --git a/utils/seqmon.cpp b/utils/seqmon.cpp new file mode 100644 index 00000000..8220cd7f --- /dev/null +++ b/utils/seqmon.cpp @@ -0,0 +1,492 @@ +/** + * @file seqmon.cpp + * @brief terminal-based subscriber that mimics the sequencer operator display + * @author David Hale + * + * Subscribes to sequencer topics on the ZMQ broker and renders a simple + * color-coded terminal display: + * + * seq_seqstate lifecycle state (top line, color by value) + * seq_waitstate active wait bits (second line, list of set labels) + * seq_daemonstate daemon online/offline status + * acamd acquisition mode, acquired flag, seeing + * slicecamd fine-acquisition locked/running flags + * broadcast operator messages (scrolling log, color by severity) + * + * On startup a Topic::SNAPSHOT request is published so that sequencerd, + * acamd, and slicecamd immediately re-publish their current telemetry, + * avoiding a stale display until the next natural update. + * + * This is a standalone utility intended as a stand-in for the GUI message + * window during testing. It has no dependencies on the sequencer sources; + * it only shares the message_keys.h contract. + * + */ + +#include +#include +#include "message_keys.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + + constexpr const char *BROKER_ENDPOINT = "tcp://localhost:5556"; // subscribers connect here + constexpr const char *BROKER_PUB_ENDPOINT = "tcp://localhost:5555"; // publishers connect here + + // ANSI escape codes + // + constexpr const char *ANSI_RESET = "\033[0m"; + constexpr const char *ANSI_GRAY = "\033[90m"; + constexpr const char *ANSI_RED = "\033[31m"; + constexpr const char *ANSI_GREEN = "\033[32m"; + constexpr const char *ANSI_YELLOW = "\033[33m"; + constexpr const char *ANSI_BLUE = "\033[34m"; + constexpr const char *ANSI_CYAN = "\033[36m"; + constexpr const char *ANSI_BOLDYEL = "\033[1;33m"; + + // cursor control: save/restore, move up 3 lines, clear entire line + // + constexpr const char *CURSOR_SAVE = "\033[s"; + constexpr const char *CURSOR_RESTORE = "\033[u"; + constexpr const char *CURSOR_HOME = "\033[H"; + constexpr const char *CLEAR_SCREEN = "\033[2J"; + constexpr const char *CLEAR_LINE = "\033[2K"; + + // DECSTBM scrolling-region control. The header occupies HEADER_ROWS fixed + // rows at the top of the terminal; broadcast messages scroll within the + // region starting at row HEADER_ROWS+1 and extending to the bottom. Using + // 999 as the bottom bound lets the terminal clamp to its actual height. + // + // NOTE: the "6" in SCROLL_REGION_SET and CURSOR_LOG_START is HEADER_ROWS+1. + // If HEADER_ROWS changes, update these literals to match. + // + constexpr int HEADER_ROWS = 5; + constexpr const char *SCROLL_REGION_SET = "\033[6;999r"; // top = HEADER_ROWS+1 + constexpr const char *SCROLL_REGION_RESET = "\033[r"; // full-screen scrolling + constexpr const char *CURSOR_LOG_START = "\033[6;1H"; // row HEADER_ROWS+1, col 1 + constexpr const char *CURSOR_LOG_END = "\033[999;1H"; // bottom of scroll region + + std::atomic running{true}; + + // Cached last-known state so a redraw after a broadcast message can + // reprint the fixed top lines. + // + std::string last_seqstate = "(unknown)"; + std::string last_waitstate = "(none)"; + std::string last_daemonstate = "(none)"; + std::string last_acq_mode = "?"; // "stopped" | "guiding" | "acquiring" + bool last_is_acquired = false; // Key::Acamd::IS_ACQUIRED + bool last_fa_locked = false; // Key::Slicecamd::FINEACQUIRE_LOCKED + bool last_fa_running = false; // Key::Slicecamd::FINEACQUIRE_RUNNING + double last_seeing = 0.0; // Key::Acamd::SEEING + + /***** color_for_state ******************************************************/ + /** + * @brief map a lifecycle state label to an ANSI color + * @param state state string (e.g. "NOTREADY", "RUNNING") + * @return ANSI escape sequence + */ + const char *color_for_state( const std::string &state ) { + if ( state.find("FAILED") != std::string::npos ) return ANSI_RED; + if ( state.find("ABORTING") != std::string::npos ) return ANSI_BOLDYEL; + if ( state.find("RUNNING") != std::string::npos ) return ANSI_GREEN; + if ( state.find("READY") != std::string::npos ) return ANSI_YELLOW; + if ( state.find("STARTING") != std::string::npos ) return ANSI_BLUE; + if ( state.find("STOPPING") != std::string::npos ) return ANSI_BLUE; + if ( state.find("PAUSED") != std::string::npos ) return ANSI_CYAN; + if ( state.find("NOTREADY") != std::string::npos ) return ANSI_GRAY; + return ANSI_RESET; + } + /***** color_for_state ******************************************************/ + + + /***** color_for_severity ***************************************************/ + /** + * @brief map a broadcast severity label to an ANSI color + * @param severity severity string (NOTICE|WARNING|ERROR) + * @return ANSI escape sequence + */ + const char *color_for_severity( const std::string &severity ) { + if ( severity == Severity::ERROR ) return ANSI_RED; + if ( severity == Severity::WARNING ) return ANSI_YELLOW; + return ANSI_RESET; + } + /***** color_for_severity ***************************************************/ + + + /***** color_for_daemonstate ************************************************/ + /** + * @brief map a broadcast severity label to an ANSI color + * @param severity severity string (NOTICE|WARNING|ERROR) + * @return ANSI escape sequence + */ + const char *color_for_daemonstate( bool state) { + return ( state ? ANSI_GREEN : ANSI_RED ); + } + /***** color_for_daemonstate ************************************************/ + + + /***** color_for_acqmode ****************************************************/ + /** + * @brief map an acquisition mode string to an ANSI color + * @param mode "acquiring" | "guiding" | "stopped" + * @return ANSI escape sequence + */ + const char *color_for_acqmode( const std::string &mode ) { + if ( mode == "acquiring" ) return ANSI_GREEN; + if ( mode == "guiding" ) return ANSI_CYAN; + return ANSI_GRAY; // "stopped" or unknown + } + /***** color_for_acqmode ****************************************************/ + + + /***** timestamp ************************************************************/ + /** + * @brief local time as HH:MM:SS + * @return formatted string + */ + std::string timestamp() { + std::time_t now = std::time(nullptr); + std::tm tm_local{}; + localtime_r( &now, &tm_local ); + char buf[16]; + std::strftime( buf, sizeof(buf), "%H:%M:%S", &tm_local ); + return std::string(buf); + } + /***** timestamp ************************************************************/ + + + /***** redraw_status ********************************************************/ + /** + * @brief reprint the fixed status header (lines 1 and 2) in place + * @details Uses ANSI save/restore so the current scroll position for + * broadcast messages is not disturbed. + */ + void redraw_status() { + std::cout << CURSOR_SAVE + << CURSOR_HOME + << CLEAR_LINE + << "STATE: " << color_for_state(last_seqstate) + << last_seqstate << ANSI_RESET << "\n" + << CLEAR_LINE + << "WAITING: " << last_waitstate << ANSI_RESET << "\n" + << CLEAR_LINE + << "SUBSYSTEMS: " << last_daemonstate << "\n" + << CLEAR_LINE + << "ACQUISITION: " + << "ACAM: " << color_for_acqmode(last_acq_mode) + << last_acq_mode << ANSI_RESET + << " " << ( last_is_acquired ? ANSI_GREEN : ANSI_GRAY ) + << "[acquired]" << ANSI_RESET + << " SLICECAM: " + << ( last_fa_locked ? ANSI_GREEN : ANSI_GRAY ) << "locked" << ANSI_RESET + << " " + << ( last_fa_running ? ANSI_GREEN : ANSI_GRAY ) << "running" << ANSI_RESET + << " SEEING: " << std::fixed << std::setprecision(2) + << ANSI_BLUE << last_seeing << ANSI_RESET + << "\n" + << std::string(60, '-') << "\n" + << CURSOR_RESTORE + << std::flush; + } + /***** redraw_status ********************************************************/ + + + /***** handle_seqstate ******************************************************/ + /** + * @brief parse a seq_seqstate payload and update the top status line + * @param payload JSON payload as string + */ + void handle_seqstate( const std::string &payload ) { + try { + auto j = nlohmann::json::parse( payload ); + if ( j.contains(Key::Sequencer::SEQSTATE) ) { + last_seqstate = j[Key::Sequencer::SEQSTATE].get(); + if ( last_seqstate.empty() ) last_seqstate = "(none)"; + redraw_status(); + } + } + catch ( const std::exception &e ) { + std::cerr << "seqstate parse error: " << e.what() << "\n"; + } + } + /***** handle_seqstate ******************************************************/ + + + /***** handle_waitstate *****************************************************/ + /** + * @brief parse a seq_waitstate payload and update the wait-bit line + * @param payload JSON payload as string + * @details Payload contains one boolean per wait-bit label; collect the + * labels whose value is true. + */ + void handle_waitstate( const std::string &payload ) { + try { + auto j = nlohmann::json::parse( payload ); + std::ostringstream oss; + bool first = true; + for ( auto it = j.begin(); it != j.end(); ++it ) { + if ( ! it.value().is_boolean() ) continue; + if ( it.value().get() ) { + if ( !first ) oss << " "; + oss << it.key(); + first = false; + } + } + last_waitstate = oss.str(); + if ( last_waitstate.empty() ) last_waitstate = "(idle)"; + redraw_status(); + } + catch ( const std::exception &e ) { + std::cerr << "waitstate parse error: " << e.what() << "\n"; + } + } + /***** handle_waitstate *****************************************************/ + + + void handle_daemonstate( const std::string &payload ) { + try { + auto j = nlohmann::json::parse(payload); + std::ostringstream oss; + for (auto it = j.begin(); it != j.end(); ++it) { + if (!it.value().is_boolean()) continue; + bool daemonstate = it.value().get(); + oss << color_for_daemonstate(daemonstate) + << it.key() << ANSI_RESET << " "; + } + last_daemonstate = oss.str(); + redraw_status(); + } + catch (const std::exception &e) { + std::cerr << "daemonstate parse error: " << e.what() << "\n"; + } + } + + + /***** handle_acamd *********************************************************/ + /** + * @brief parse an acamd payload and update acquisition mode/status + * @param payload JSON payload as string + * @details Handles partial payloads; only keys present are updated. + */ + void handle_acamd( const std::string &payload ) { + try { + auto j = nlohmann::json::parse( payload ); + bool changed = false; + if ( j.contains( Key::Acamd::ACQUIRE_MODE ) ) { + last_acq_mode = j[Key::Acamd::ACQUIRE_MODE].get(); + changed = true; + } + if ( j.contains( Key::Acamd::IS_ACQUIRED ) ) { + last_is_acquired = j[Key::Acamd::IS_ACQUIRED].get(); + changed = true; + } + if ( j.contains( Key::Acamd::SEEING ) && !j[Key::Acamd::SEEING].is_null() ) { + last_seeing = j[Key::Acamd::SEEING].get(); + changed = true; + } + if ( changed ) redraw_status(); + } + catch ( const std::exception &e ) { + std::cerr << "acamd parse error: " << e.what() << "\n"; + } + } + /***** handle_acamd *********************************************************/ + + + /***** handle_slicecamd *****************************************************/ + /** + * @brief parse a slicecamd payload and update fine-acquisition status + * @param payload JSON payload as string + * @details Handles partial payloads; only keys present are updated. + */ + void handle_slicecamd( const std::string &payload ) { + try { + auto j = nlohmann::json::parse( payload ); + bool changed = false; + if ( j.contains( Key::Slicecamd::FINEACQUIRE_LOCKED ) ) { + last_fa_locked = j[Key::Slicecamd::FINEACQUIRE_LOCKED].get(); + changed = true; + } + if ( j.contains( Key::Slicecamd::FINEACQUIRE_RUNNING ) ) { + last_fa_running = j[Key::Slicecamd::FINEACQUIRE_RUNNING].get(); + changed = true; + } + if ( changed ) redraw_status(); + } + catch ( const std::exception &e ) { + std::cerr << "slicecamd parse error: " << e.what() << "\n"; + } + } + /***** handle_slicecamd *****************************************************/ + + + /***** handle_broadcast *****************************************************/ + /** + * @brief parse a broadcast payload and append a colored line to the log + * @param payload JSON payload as string + */ + void handle_broadcast( const std::string &payload ) { + try { + auto j = nlohmann::json::parse( payload ); + std::string severity = j.value( Key::Broadcast::SEVERITY, std::string("") ); + std::string message = j.value( Key::Broadcast::MESSAGE, std::string("(empty)") ); + std::string source = j.value( Key::SOURCE, std::string("unknown") ); + // Park the cursor at the bottom of the scroll region before emitting the + // newline so the message lands inside the region and the header above + // it is not scrolled. + // + std::cout << CURSOR_LOG_END + << color_for_severity(severity) + << "[" << timestamp() << "] " + << "[" << source << "] " + << (severity != Severity::NOTICE ? severity+": " : "") + << message + << ANSI_RESET << "\n" + << std::flush; + } + catch ( const std::exception &e ) { + std::cerr << "broadcast parse error: " << e.what() << "\n"; + } + } + /***** handle_broadcast *****************************************************/ + + + /***** request_snapshot *****************************************************/ + /** + * @brief publish a Topic::SNAPSHOT request to named daemons + * @details Each daemon whose name appears as a key in the payload will + * respond by re-publishing its current telemetry snapshot, allowing + * seqmon to populate the status header immediately on startup rather + * than waiting for the next natural update from each daemon. + * @param publisher connected ZMQ publish socket + */ + void request_snapshot( zmqpp::socket &publisher ) { + nlohmann::json j; + j[Daemon::SEQUENCER] = true; + j[Daemon::ACAMD] = true; + j[Daemon::SLICECAMD] = true; + zmqpp::message zmsg; + zmsg.add( Topic::SNAPSHOT ); + zmsg.add( j.dump() ); + publisher.send( zmsg ); + } + /***** request_snapshot *****************************************************/ + + + /***** signal_handler *******************************************************/ + /** + * @brief SIGINT handler; triggers clean exit of the main loop + */ + void signal_handler( int /*signum*/ ) { + running.store(false); + } + /***** signal_handler *******************************************************/ + +} // anonymous namespace + + +void usage( const char *exe ) { + std::cout << "usage: " << exe << " [endpoint]\n" + << " endpoint ZMQ broker address (default " << BROKER_ENDPOINT << ")\n"; +} + + +int main( int argc, char *argv[] ) { + + std::string endpoint = BROKER_ENDPOINT; + + if ( argc > 1 ) { + std::string arg = argv[1]; + if ( arg == "-h" || arg == "--help" ) { + usage(argv[0]); + return 0; + } + endpoint = arg; + } + + std::signal( SIGINT, signal_handler ); + std::signal( SIGTERM, signal_handler ); + + // set up ZMQ subscriber and publisher + // + zmqpp::context context; + zmqpp::socket subscriber( context, zmqpp::socket_type::subscribe ); + zmqpp::socket publisher ( context, zmqpp::socket_type::publish ); + + try { + subscriber.connect( endpoint ); + subscriber.subscribe( Topic::BROADCAST ); + subscriber.subscribe( Topic::SEQ_SEQSTATE ); + subscriber.subscribe( Topic::SEQ_WAITSTATE ); + subscriber.subscribe( Topic::SEQ_DAEMONSTATE ); + subscriber.subscribe( Topic::ACAMD ); + subscriber.subscribe( Topic::SLICECAMD ); + + publisher.connect( BROKER_PUB_ENDPOINT ); + } + catch ( const std::exception &e ) { + std::cerr << "ERROR connecting to " << endpoint << ": " << e.what() << "\n"; + return 1; + } + + // initial screen: clear, draw status header, leave room for scrolling log + // + std::cout << CLEAR_SCREEN << CURSOR_HOME + << "STATE: " << last_seqstate << "\n" + << "WAITING: " << last_waitstate << "\n" + << "SUBSYSTEMS: " << last_daemonstate << "\n" + << "ACQUISITION: ACAM: " << last_acq_mode + << " SLICECAM: locked running" + << " SEEING: " << std::fixed << std::setprecision(2) << last_seeing << "\n" + << std::string(60, '-') << "\n" + << SCROLL_REGION_SET + << CURSOR_LOG_START + << "seqmon subscribed to " << endpoint + << " (Ctrl-C to exit)\n" + << std::flush; + + // ZMQ PUB sockets have a slow-joiner problem: messages sent immediately + // after connect are dropped before the broker registers the connection. + // A brief sleep ensures the publisher is ready before we send the snapshot + // request. + // + std::this_thread::sleep_for( std::chrono::milliseconds(200) ); + request_snapshot( publisher ); + + // poll so Ctrl-C can break out promptly + // + zmqpp::poller poller; + poller.add( subscriber, zmqpp::poller::poll_in ); + + while ( running.load() ) { + if ( poller.poll(500) == 0 ) continue; + if ( ! poller.has_input(subscriber) ) continue; + + zmqpp::message zmsg; + subscriber.receive( zmsg ); + if ( zmsg.parts() < 2 ) continue; // guard against malformed/partial messages + std::string topic, payload; + zmsg >> topic >> payload; + + if ( topic == Topic::SEQ_SEQSTATE ) handle_seqstate( payload ); + else if ( topic == Topic::SEQ_WAITSTATE ) handle_waitstate( payload ); + else if ( topic == Topic::SEQ_DAEMONSTATE ) handle_daemonstate( payload ); + else if ( topic == Topic::ACAMD ) handle_acamd( payload ); + else if ( topic == Topic::SLICECAMD ) handle_slicecamd( payload ); + else if ( topic == Topic::BROADCAST ) handle_broadcast( payload ); + // unrecognized topics silently ignored + } + + std::cout << SCROLL_REGION_RESET << "\nseqmon exiting\n" << std::flush; + return 0; +} diff --git a/utils/utilities.h b/utils/utilities.h index f53f10f9..41dcb866 100644 --- a/utils/utilities.h +++ b/utils/utilities.h @@ -43,6 +43,7 @@ #include #include #include +#include #define TO_DEGREES ( 360. / 24. ) #define TO_HOURS ( 24. / 360. ) @@ -77,6 +78,12 @@ std::chrono::system_clock::time_point next_occurrence( int hour, int minute, int long get_time( int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec ); long get_time( const std::string &tmzone_in, int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec ); +inline int64_t get_time_us() { + struct timespec ts; + clock_gettime( CLOCK_REALTIME, &ts ); + return int64_t(ts.tv_sec) * 1000000 + ts.tv_nsec/1000; +} + std::string timestamp_from( struct timespec &time_n ); /// return time from input timespec struct in formatted string "YYYY-MM-DDTHH:MM:SS.sss" std::string timestamp_from( const std::string &tmzone_in, struct timespec &time_in ); From 8b22e826ea65cba4b76fba5f7035f89c193be9bb Mon Sep 17 00:00:00 2001 From: David Hale Date: Tue, 12 May 2026 23:59:54 -0700 Subject: [PATCH 21/35] fixes improper CMakeLists.txt files wraps md5.c in extern "C" { } rewrites sendcmd.c -> sendcmd.cpp --- Andor/CMakeLists.txt | 6 +- CMakeLists.txt | 12 +-- PI/CMakeLists.txt | 2 +- acamd/CMakeLists.txt | 15 +-- camerad/CMakeLists.txt | 2 +- common/CMakeLists.txt | 4 + emulator/CMakeLists.txt | 2 +- flexured/CMakeLists.txt | 5 + sequencerd/CMakeLists.txt | 13 ++- slicecamd/CMakeLists.txt | 5 +- tcsd/CMakeLists.txt | 4 +- telemd/CMakeLists.txt | 2 +- thermald/CMakeLists.txt | 2 +- utils/CMakeLists.txt | 21 ++++- utils/md5.c | 7 ++ utils/md5.cpp | 193 -------------------------------------- utils/md5.h | 6 ++ utils/sendcmd.cpp | 139 +++++++++++++++++++++++++++ 18 files changed, 213 insertions(+), 227 deletions(-) delete mode 100644 utils/md5.cpp create mode 100644 utils/sendcmd.cpp diff --git a/Andor/CMakeLists.txt b/Andor/CMakeLists.txt index 973fea2e..1af81c1a 100644 --- a/Andor/CMakeLists.txt +++ b/Andor/CMakeLists.txt @@ -10,7 +10,7 @@ set( ANDOR_DIR ${PROJECT_BASE_DIR}/Andor ) set( CMAKE_CXX_STANDARD 17 ) -add_definitions( -Wall -ansi -O0 -Wno-variadic-macros -ggdb ) +add_definitions( -Wall -O0 -Wno-variadic-macros -ggdb ) add_definitions(-D_LP64) include_directories( ${PROJECT_BASE_DIR}/utils ) @@ -30,12 +30,14 @@ find_library( CFITS_LIB cfitsio NAMES libcfitsio PATHS /usr/local/lib ) find_path( PYTHON_DEV "Python.h" PATHS /usr/include/python3.9 ) find_library( PYTHON_LIB python3.9 NAMES libpython3.9 PATHS /usr/lib64 ) include_directories( ${PYTHON_DEV} ) + add_link_options( -L/usr/lib64 -lpython3.9 -lcrypt -lpthread -ldl -lutil -lm -lm ) +include_directories( ${PYTHON_DEV} ) + add_library( andor STATIC ${ANDOR_DIR}/andor.cpp ${ANDOR_DIR}/andor_emulator.cpp - ${PYTHON_DEV} ) target_link_libraries( andor diff --git a/CMakeLists.txt b/CMakeLists.txt index 27ac4ab1..c2f900ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,23 +4,23 @@ # @author David Hale # ---------------------------------------------------------------------------- -add_definitions(-DLOGLEVEL_DEBUG) - cmake_minimum_required( VERSION 3.12 ) +project( NGPS ) + +add_definitions(-DLOGLEVEL_DEBUG) + set( CMAKE_CXX_STANDARD 17 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) set( CMAKE_CXX_EXTENSIONS OFF ) -project( NGPS ) - -set( PROJECT_BASE_DIR $ENV{PWD}/../ ) +set( PROJECT_BASE_DIR ${CMAKE_SOURCE_DIR}) set( PROJECT_SRC_DIR ${PROJECT_BASE_DIR}/src ) set( PROJECT_INC_DIR ${PROJECT_BASE_DIR}/include ) set( EXECUTABLE_OUTPUT_PATH ${PROJECT_BASE_DIR}/bin ) set( LIBRARY_OUTPUT_PATH ${PROJECT_BASE_DIR}/lib ) -set( CMAKE_C_COMPILER /usr/bin/g++ ) +set( CMAKE_C_COMPILER /usr/bin/gcc ) set( CMAKE_CXX_COMPILER /usr/bin/g++ ) find_package( Threads ) diff --git a/PI/CMakeLists.txt b/PI/CMakeLists.txt index cb6e897d..abb08161 100644 --- a/PI/CMakeLists.txt +++ b/PI/CMakeLists.txt @@ -10,7 +10,7 @@ set( PI_DIR ${PROJECT_BASE_DIR}/PI ) set( CMAKE_CXX_STANDARD 17 ) -add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -ggdb ) +add_definitions( -Wall -O1 -Wno-variadic-macros -ggdb ) include_directories( ${PROJECT_BASE_DIR}/utils ) include_directories( ${PROJECT_BASE_DIR}/common ) diff --git a/acamd/CMakeLists.txt b/acamd/CMakeLists.txt index 8c509f63..acea97ce 100644 --- a/acamd/CMakeLists.txt +++ b/acamd/CMakeLists.txt @@ -56,13 +56,15 @@ add_executable(acamd ${ACAMD_DIR}/acam_interface.cpp ${ACAMD_DIR}/acam_fits.cpp ${ACAMD_DIR}/motion_interface.cpp + ) +target_include_directories(acamd PRIVATE ${PYTHON_DEV} ${MYSQL_INCLUDES} ) target_link_libraries(acamd andor - python3.9 + ${PYTHON_LIB} physik_instrumente network common @@ -73,32 +75,31 @@ target_link_libraries(acamd database ${ZMQPP_LIB} ${ZMQ_LIB} - ${PYTHON_LIB} ${CCFITS_LIB} ${CFITS_LIB} ${CMAKE_THREAD_LIBS_INIT} ${MYSQL_LIB} ) -target_compile_options(acamd PRIVATE -g -Wall -O1 -Wno-variadic-macros -ggdb) +target_compile_options(acamd PRIVATE -g -Wall -O1 -Wno-deprecated-declarations -Wno-variadic-macros -ggdb) add_executable( newpytest ${ACAMD_DIR}/newpytest.cpp + ) +target_include_directories( newpytest PRIVATE ${PYTHON_DEV} ) - target_link_libraries( newpytest - python3.9 ${PYTHON_LIB} ) add_executable( pytest ${ACAMD_DIR}/pytest.cpp + ) +target_include_directories( pytest PRIVATE ${PYTHON_DEV} ) - target_link_libraries( pytest - python3.9 ${PYTHON_LIB} ) diff --git a/camerad/CMakeLists.txt b/camerad/CMakeLists.txt index f4fcc0a0..bb14f828 100644 --- a/camerad/CMakeLists.txt +++ b/camerad/CMakeLists.txt @@ -20,7 +20,7 @@ link_directories( ${PROJECT_BASE_DIR}/lib ) message( STATUS "compiling for AstroCam GenIII PCI/PCIe") set( INTERFACE_TARGET astrocam) set( ARCAPI_DIR "${PROJECT_BASE_DIR}/ARC") -add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -ggdb ) +add_definitions( -Wall -O1 -Wno-variadic-macros -ggdb ) add_definitions( -DASTROCAM) add_definitions( -DARC66_PCIE) find_path( ARCAPI_BASE "CArcBase.h" PATHS ${ARCAPI_DIR}/CArcBase/inc) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 3fc96925..5cd16789 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -23,10 +23,14 @@ include_directories( ${PYTHON_DEV} ) add_library(common STATIC ${PROJECT_UTILS_DIR}/common.cpp + ) +target_link_libraries(common PRIVATE ${ZMQPP_LIB} ${ZMQ_LIB} ) add_library(skyinfo STATIC ${PROJECT_UTILS_DIR}/skyinfo.cpp + ) +target_link_libraries(skyinfo PRIVATE ${PYTHON_LIB} ) diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt index 91dd386e..1f1f5c9e 100644 --- a/emulator/CMakeLists.txt +++ b/emulator/CMakeLists.txt @@ -10,7 +10,7 @@ set( EMULATOR_DIR ${PROJECT_BASE_DIR}/emulator ) set( CMAKE_CXX_STANDARD 17 ) -add_definitions( -Wall -ansi -O0 -Wno-variadic-macros ) +add_definitions( -Wall -O0 -Wno-variadic-macros ) include_directories( ${EMULATOR_DIR} ) include_directories( ${PROJECT_BASE_DIR}/utils ) diff --git a/flexured/CMakeLists.txt b/flexured/CMakeLists.txt index 8522b400..ae161f4a 100644 --- a/flexured/CMakeLists.txt +++ b/flexured/CMakeLists.txt @@ -16,6 +16,9 @@ include_directories( ${PROJECT_BASE_DIR}/common ) link_directories( ${PROJECT_BASE_DIR}/lib ) +find_library( ZMQPP_LIB zmqpp NAMES libzmqpp PATHS /usr/local/lib ) +find_library( ZMQ_LIB zmq NAMES libzmq PATHS /usr/local/lib ) + add_executable(flexured ${FLEXURED_DIR}/flexured.cpp ${FLEXURED_DIR}/flexure_server.cpp @@ -28,6 +31,8 @@ target_link_libraries(flexured common logentry utilities + ${ZMQPP_LIB} + ${ZMQ_LIB} ${CMAKE_THREAD_LIBS_INIT} ) diff --git a/sequencerd/CMakeLists.txt b/sequencerd/CMakeLists.txt index 7e50812c..cc9d8a41 100644 --- a/sequencerd/CMakeLists.txt +++ b/sequencerd/CMakeLists.txt @@ -10,7 +10,7 @@ set( SEQUENCER_DIR ${PROJECT_BASE_DIR}/sequencerd ) set( CMAKE_CXX_STANDARD 17 ) -add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -ggdb ) +add_definitions( -Wall -O1 -Wno-variadic-macros -Wno-deprecated-declarations -ggdb ) include_directories( ${PROJECT_BASE_DIR}/utils ) include_directories( ${PROJECT_BASE_DIR}/common ) @@ -18,10 +18,14 @@ include_directories( ${PROJECT_BASE_DIR}/acamd ) link_directories( ${PROJECT_BASE_DIR}/lib ) +# C-Python API Development libraries and headers +# find_path( PYTHON_DEV "Python.h" PATHS /usr/include/python3.9 ) find_library( PYTHON_LIB python3.9 NAMES libpython3.9 PATHS /usr/lib64 ) include_directories( ${PYTHON_DEV} ) +# ZMQ +# find_library( ZMQPP_LIB zmqpp NAMES libzmqpp PATHS /usr/local/lib ) find_library( ZMQ_LIB zmq NAMES libzmq PATHS /usr/local/lib ) @@ -38,20 +42,21 @@ add_executable(sequencerd ${SEQUENCER_DIR}/sequencer_server.cpp ${SEQUENCER_DIR}/sequencer_interface.cpp ${SEQUENCER_DIR}/sequence_acquisition.cpp + ${SEQUENCER_DIR}/sequence.cpp ${SEQUENCER_DIR}/sequence_wait.cpp ${SEQUENCER_DIR}/sequence_builder.cpp ${SEQUENCER_DIR}/operation_builders.cpp ${SEQUENCER_DIR}/sequence_operations.cpp - ${SEQUENCER_DIR}/sequence.cpp - ${MYSQL_INCLUDES} + ) +target_include_directories(sequencerd PRIVATE ${PYTHON_DEV} ) - target_link_libraries(sequencerd network common logentry utilities + md5 skyinfo database ${CMAKE_THREAD_LIBS_INIT} diff --git a/slicecamd/CMakeLists.txt b/slicecamd/CMakeLists.txt index 42969138..7a6ec2c8 100644 --- a/slicecamd/CMakeLists.txt +++ b/slicecamd/CMakeLists.txt @@ -40,21 +40,20 @@ add_executable(slicecamd ${SLICECAMD_DIR}/slicecam_camera.cpp ${SLICECAMD_DIR}/slicecam_fits.cpp ${SLICECAMD_DIR}/slicecam_math.cpp - ${PYTHON_DEV} ) target_link_libraries(slicecamd andor - python3.9 + ${PYTHON_LIB} network common tcsd_client logentry utilities + md5 skyinfo ${ZMQPP_LIB} ${ZMQ_LIB} - ${PYTHON_LIB} ${CCFITS_LIB} ${CFITS_LIB} ${CMAKE_THREAD_LIBS_INIT} diff --git a/tcsd/CMakeLists.txt b/tcsd/CMakeLists.txt index 5412d5cb..d1008d74 100644 --- a/tcsd/CMakeLists.txt +++ b/tcsd/CMakeLists.txt @@ -10,8 +10,8 @@ set( TCSD_DIR ${PROJECT_BASE_DIR}/tcsd ) set( CMAKE_CXX_STANDARD 17 ) -add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -ggdb ) -#add_definitions( -Wall -Wextra -Wconversion -Wshadow -ansi -O1 -Wno-variadic-macros -ggdb ) +add_definitions( -Wall -O1 -Wno-variadic-macros -ggdb ) +#add_definitions( -Wall -Wextra -Wconversion -Wshadow -O1 -Wno-variadic-macros -ggdb ) include_directories( ${PROJECT_BASE_DIR}/utils ) include_directories( ${PROJECT_BASE_DIR}/common ) diff --git a/telemd/CMakeLists.txt b/telemd/CMakeLists.txt index fbb93bc7..0d5844a7 100644 --- a/telemd/CMakeLists.txt +++ b/telemd/CMakeLists.txt @@ -10,7 +10,7 @@ set( TELEMD_DIR ${PROJECT_BASE_DIR}/telemd ) set( CMAKE_CXX_STANDARD 17 ) -add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -ggdb ) +add_definitions( -Wall -O1 -Wno-variadic-macros -ggdb ) include_directories( ${PROJECT_BASE_DIR}/utils ) include_directories( ${PROJECT_BASE_DIR}/common ) diff --git a/thermald/CMakeLists.txt b/thermald/CMakeLists.txt index f21492b1..c4a0144d 100644 --- a/thermald/CMakeLists.txt +++ b/thermald/CMakeLists.txt @@ -10,7 +10,7 @@ set( THERMALD_DIR ${PROJECT_BASE_DIR}/thermald ) set( CMAKE_CXX_STANDARD 17 ) -add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -ggdb ) +add_definitions( -Wall -O1 -Wno-deprecated-declarations -Wno-variadic-macros -ggdb ) include_directories( ${PROJECT_BASE_DIR}/utils ) include_directories( ${PROJECT_BASE_DIR}/LKS ) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index baa453ba..fb6c0401 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -10,13 +10,12 @@ set(PROJECT_UTILS_DIR ${PROJECT_BASE_DIR}/utils) set( CMAKE_CXX_STANDARD 17 ) -add_definitions( -Wall -Wextra -Wconversion -Wshadow -ansi -Og -Wno-variadic-macros -ggdb ) +add_definitions( -Wall -Wextra -Wconversion -Wshadow -Og -Wno-variadic-macros -ggdb ) add_library(utilities STATIC ${PROJECT_UTILS_DIR}/utilities.cpp - md5 ) -target_link_libraries(utilities PRIVATE stdc++fs) + target_link_libraries(utilities PUBLIC md5 stdc++fs) add_library(logentry STATIC ${PROJECT_UTILS_DIR}/logentry.cpp @@ -49,11 +48,11 @@ include_directories( ${MYSQL_INCLUDES} ) add_library(database STATIC ${PROJECT_UTILS_DIR}/database.cpp - ${MYSQL_LIB} ) +target_link_libraries(database PRIVATE ${MYSQL_LIB}) add_executable(socksend - ${PROJECT_UTILS_DIR}/sendcmd.c + ${PROJECT_UTILS_DIR}/sendcmd.cpp ) add_executable(listener @@ -66,3 +65,15 @@ target_link_libraries( listener PRIVATE utilities ) find_library( ZMQPP_LIB zmqpp NAMES libzmqpp PATHS /usr/local/lib ) find_library( ZMQ_LIB zmq NAMES libzmq PATHS /usr/local/lib ) +add_executable( seqmon + ${PROJECT_UTILS_DIR}/seqmon.cpp + ) +target_include_directories( seqmon PRIVATE + ${PROJECT_BASE_DIR}/utils + ${PROJECT_BASE_DIR}/common + ) +target_link_libraries( seqmon PRIVATE + ${ZMQPP_LIB} + ${ZMQ_LIB} + ) + diff --git a/utils/md5.c b/utils/md5.c index 565a2e6b..fea8fad8 100644 --- a/utils/md5.c +++ b/utils/md5.c @@ -11,6 +11,10 @@ * obtained from https://github.com/B-Con/crypto-algorithms *********************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif + /*************************** HEADER FILES ***************************/ #include #include @@ -189,3 +193,6 @@ void md5_final(MD5_CTX *ctx, BYTE hash[]) hash[i + 12] = (ctx->state[3] >> (i * 8)) & 0x000000ff; } } +#ifdef __cplusplus +} +#endif diff --git a/utils/md5.cpp b/utils/md5.cpp deleted file mode 100644 index 47feae42..00000000 --- a/utils/md5.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/********************************************************************* -* Filename: md5.cpp -* Author: Brad Conte (brad AT bradconte.com) -* Copyright: -* Disclaimer: This code is presented "as is" without any guarantees. -* Details: Implementation of the MD5 hashing algorithm. -* Algorithm specification can be found here: -* http://tools.ietf.org/html/rfc1321 -* This implementation uses little endian byte order. -* -* obtained from https://github.com/B-Con/crypto-algorithms -* adopted c++ idiomataic practices, David Hale -*********************************************************************/ - -/*************************** HEADER FILES ***************************/ -#include -#include -#include "md5.h" - -/****************************** MACROS ******************************/ -constexpr uint32_t ROTLEFT(uint32_t a, uint32_t b) { return (a << b) | (a >> (32 - b)); } -constexpr uint32_t F(uint32_t x, uint32_t y, uint32_t z) { return (x & y ) | ( ~x & z ); } -constexpr uint32_t G(uint32_t x, uint32_t y, uint32_t z) { return (x & z) | ( y & ~z ); } -constexpr uint32_t H(uint32_t x, uint32_t y, uint32_t z) { return x ^ y ^ z; } -constexpr uint32_t I(uint32_t x, uint32_t y, uint32_t z) { return y ^ ( x | ~z ); } -constexpr void FF(uint32_t &a, uint32_t b, uint32_t c, uint32_t d, uint32_t m, uint32_t s, uint32_t t) { - a += F(b, c, d) + m + t; - a = b + ROTLEFT(a, s); -} -constexpr void GG(uint32_t &a, uint32_t b, uint32_t c, uint32_t d, uint32_t m, uint32_t s, uint32_t t) { - a += G(b, c, d) + m + t; - a = b + ROTLEFT(a, s); -} -constexpr void HH(uint32_t &a, uint32_t b, uint32_t c, uint32_t d, uint32_t m, uint32_t s, uint32_t t) { - a += H(b, c, d) + m + t; - a = b + ROTLEFT(a, s); -} -constexpr void II(uint32_t &a, uint32_t b, uint32_t c, uint32_t d, uint32_t m, uint32_t s, uint32_t t) { - a += I(b, c, d) + m + t; - a = b + ROTLEFT(a, s); -} - -/*********************** FUNCTION DEFINITIONS ***********************/ -void md5_transform(MD5_CTX *ctx, const BYTE data[]) { - WORD a, b, c, d, m[16], i, j; - - // MD5 specifies big endian byte order, but this implementation assumes a little - // endian byte order CPU. Reverse all the bytes upon input, and re-reverse them - // on output (in md5_final()). - for (i = 0, j = 0; i < 16; ++i, j += 4) - m[i] = (data[j]) + (data[j + 1] << 8) + (data[j + 2] << 16) + (data[j + 3] << 24); - - a = ctx->state[0]; - b = ctx->state[1]; - c = ctx->state[2]; - d = ctx->state[3]; - - FF(a, b, c, d, m[0], 7, 0xd76aa478); - FF(d, a, b, c, m[1], 12, 0xe8c7b756); - FF(c, d, a, b, m[2], 17, 0x242070db); - FF(b, c, d, a, m[3], 22, 0xc1bdceee); - FF(a, b, c, d, m[4], 7, 0xf57c0faf); - FF(d, a, b, c, m[5], 12, 0x4787c62a); - FF(c, d, a, b, m[6], 17, 0xa8304613); - FF(b, c, d, a, m[7], 22, 0xfd469501); - FF(a, b, c, d, m[8], 7, 0x698098d8); - FF(d, a, b, c, m[9], 12, 0x8b44f7af); - FF(c, d, a, b, m[10], 17, 0xffff5bb1); - FF(b, c, d, a, m[11], 22, 0x895cd7be); - FF(a, b, c, d, m[12], 7, 0x6b901122); - FF(d, a, b, c, m[13], 12, 0xfd987193); - FF(c, d, a, b, m[14], 17, 0xa679438e); - FF(b, c, d, a, m[15], 22, 0x49b40821); - - GG(a, b, c, d, m[1], 5, 0xf61e2562); - GG(d, a, b, c, m[6], 9, 0xc040b340); - GG(c, d, a, b, m[11], 14, 0x265e5a51); - GG(b, c, d, a, m[0], 20, 0xe9b6c7aa); - GG(a, b, c, d, m[5], 5, 0xd62f105d); - GG(d, a, b, c, m[10], 9, 0x02441453); - GG(c, d, a, b, m[15], 14, 0xd8a1e681); - GG(b, c, d, a, m[4], 20, 0xe7d3fbc8); - GG(a, b, c, d, m[9], 5, 0x21e1cde6); - GG(d, a, b, c, m[14], 9, 0xc33707d6); - GG(c, d, a, b, m[3], 14, 0xf4d50d87); - GG(b, c, d, a, m[8], 20, 0x455a14ed); - GG(a, b, c, d, m[13], 5, 0xa9e3e905); - GG(d, a, b, c, m[2], 9, 0xfcefa3f8); - GG(c, d, a, b, m[7], 14, 0x676f02d9); - GG(b, c, d, a, m[12], 20, 0x8d2a4c8a); - - HH(a, b, c, d, m[5], 4, 0xfffa3942); - HH(d, a, b, c, m[8], 11, 0x8771f681); - HH(c, d, a, b, m[11], 16, 0x6d9d6122); - HH(b, c, d, a, m[14], 23, 0xfde5380c); - HH(a, b, c, d, m[1], 4, 0xa4beea44); - HH(d, a, b, c, m[4], 11, 0x4bdecfa9); - HH(c, d, a, b, m[7], 16, 0xf6bb4b60); - HH(b, c, d, a, m[10], 23, 0xbebfbc70); - HH(a, b, c, d, m[13], 4, 0x289b7ec6); - HH(d, a, b, c, m[0], 11, 0xeaa127fa); - HH(c, d, a, b, m[3], 16, 0xd4ef3085); - HH(b, c, d, a, m[6], 23, 0x04881d05); - HH(a, b, c, d, m[9], 4, 0xd9d4d039); - HH(d, a, b, c, m[12], 11, 0xe6db99e5); - HH(c, d, a, b, m[15], 16, 0x1fa27cf8); - HH(b, c, d, a, m[2], 23, 0xc4ac5665); - - II(a, b, c, d, m[0], 6, 0xf4292244); - II(d, a, b, c, m[7], 10, 0x432aff97); - II(c, d, a, b, m[14], 15, 0xab9423a7); - II(b, c, d, a, m[5], 21, 0xfc93a039); - II(a, b, c, d, m[12], 6, 0x655b59c3); - II(d, a, b, c, m[3], 10, 0x8f0ccc92); - II(c, d, a, b, m[10], 15, 0xffeff47d); - II(b, c, d, a, m[1], 21, 0x85845dd1); - II(a, b, c, d, m[8], 6, 0x6fa87e4f); - II(d, a, b, c, m[15], 10, 0xfe2ce6e0); - II(c, d, a, b, m[6], 15, 0xa3014314); - II(b, c, d, a, m[13], 21, 0x4e0811a1); - II(a, b, c, d, m[4], 6, 0xf7537e82); - II(d, a, b, c, m[11], 10, 0xbd3af235); - II(c, d, a, b, m[2], 15, 0x2ad7d2bb); - II(b, c, d, a, m[9], 21, 0xeb86d391); - - ctx->state[0] += a; - ctx->state[1] += b; - ctx->state[2] += c; - ctx->state[3] += d; -} - -void md5_init(MD5_CTX *ctx) { - ctx->datalen = 0; - ctx->bitlen = 0; - ctx->state[0] = 0x67452301; - ctx->state[1] = 0xEFCDAB89; - ctx->state[2] = 0x98BADCFE; - ctx->state[3] = 0x10325476; -} - -void md5_update(MD5_CTX *ctx, const BYTE data[], size_t len) { - size_t i; - - for (i = 0; i < len; ++i) { - ctx->data[ctx->datalen] = data[i]; - ctx->datalen++; - if (ctx->datalen == 64) { - md5_transform(ctx, ctx->data); - ctx->bitlen += 512; - ctx->datalen = 0; - } - } -} - -void md5_final(MD5_CTX *ctx, BYTE hash[]) { - size_t i; - - i = ctx->datalen; - - // Pad whatever data is left in the buffer. - if (ctx->datalen < 56) { - ctx->data[i++] = 0x80; - while (i < 56) - ctx->data[i++] = 0x00; - } else if (ctx->datalen >= 56) { - ctx->data[i++] = 0x80; - while (i < 64) - ctx->data[i++] = 0x00; - md5_transform(ctx, ctx->data); - memset(ctx->data, 0, 56); - } - - // Append to the padding the total message's length in bits and transform. - ctx->bitlen += ctx->datalen * 8; - ctx->data[56] = ctx->bitlen; - ctx->data[57] = ctx->bitlen >> 8; - ctx->data[58] = ctx->bitlen >> 16; - ctx->data[59] = ctx->bitlen >> 24; - ctx->data[60] = ctx->bitlen >> 32; - ctx->data[61] = ctx->bitlen >> 40; - ctx->data[62] = ctx->bitlen >> 48; - ctx->data[63] = ctx->bitlen >> 56; - md5_transform(ctx, ctx->data); - - // Since this implementation uses little endian byte ordering and MD uses big endian, - // reverse all the bytes when copying the final state to the output hash. - for (i = 0; i < 4; ++i) { - hash[i] = (ctx->state[0] >> (i * 8)) & 0x000000ff; - hash[i + 4] = (ctx->state[1] >> (i * 8)) & 0x000000ff; - hash[i + 8] = (ctx->state[2] >> (i * 8)) & 0x000000ff; - hash[i + 12] = (ctx->state[3] >> (i * 8)) & 0x000000ff; - } -} diff --git a/utils/md5.h b/utils/md5.h index 09e02978..e88f072b 100644 --- a/utils/md5.h +++ b/utils/md5.h @@ -10,6 +10,9 @@ #ifndef MD5_H #define MD5_H +#ifdef __cplusplus +extern "C" { +#endif /*************************** HEADER FILES ***************************/ #include @@ -33,4 +36,7 @@ void md5_init(MD5_CTX *ctx); void md5_update(MD5_CTX *ctx, const BYTE data[], size_t len); void md5_final(MD5_CTX *ctx, BYTE hash[]); +#ifdef __cplusplus +} +#endif #endif // MD5_H diff --git a/utils/sendcmd.cpp b/utils/sendcmd.cpp new file mode 100644 index 00000000..66b95a16 --- /dev/null +++ b/utils/sendcmd.cpp @@ -0,0 +1,139 @@ +/** + * @file utils/sendcmd.cpp + * @brief TCP command sender + * @author David Hale + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static constexpr size_t RESPONSE_BUFSIZE = 8192; + +static void usage(const char* exe) { + std::cout << "usage: " << exe + << " [-h hostname] [-p port] [-t timeout] [-m mode] command\n"; +} + +int main(int argc, char* argv[]) { + std::string hostname; + std::string message; + int port = 0; + int timeout = 10; + int mode = 0; // 0: wait for response, 1: fire and forget + + try { + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + if (arg.size() == 2 && arg[0] == '-') { + if (i + 1 >= argc) { usage(argv[0]); return 0; } + switch (arg[1]) { + case 'h': hostname = argv[++i]; break; + case 'p': port = std::stoi(argv[++i]); break; + case 't': timeout = std::stoi(argv[++i]); break; + case 'm': mode = std::stoi(argv[++i]); break; + default: usage(argv[0]); return 0; + } + } + else { + message = arg; + } + } + } + catch (const std::exception& ex) { + std::cerr << "ERROR invalid argument: " << ex.what() << "\n"; + return 1; + } + + if (hostname.empty() || port <= 0 || message.empty()) { + usage(argv[0]); + return 1; + } + + struct addrinfo hints{}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo* res = nullptr; + const int gai_ret = getaddrinfo(hostname.c_str(), std::to_string(port).c_str(), &hints, &res); + if (gai_ret != 0) { + std::cerr << "ERROR getting host: " << gai_strerror(gai_ret) << "\n"; + return 1; + } + + int sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock < 0) { + std::cerr << "ERROR " << errno << " creating socket\n"; + freeaddrinfo(res); + return -errno; + } + + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + std::cerr << "ERROR " << errno << " setting socket\n"; + close(sock); + freeaddrinfo(res); + return -errno; + } + + const auto tstart = std::chrono::steady_clock::now(); + auto elapsed_sec = [&]() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - tstart).count(); + }; + + if (connect(sock, res->ai_addr, res->ai_addrlen) == -1 && errno != EINPROGRESS) { + std::cerr << "ERROR " << errno << " connecting\n"; + close(sock); + freeaddrinfo(res); + return -errno; + } + freeaddrinfo(res); + + message += '\n'; + + ssize_t nwrite; + while ((nwrite = write(sock, message.c_str(), message.size())) < 0) { + if (errno != EAGAIN) { + std::cerr << "ERROR " << errno << " writing message\n"; + close(sock); + return -errno; + } + if (elapsed_sec() >= timeout) { + std::cerr << "ERROR " << ETIME << " TIMEOUT\n"; + close(sock); + return -ETIME; + } + } + + if (mode == 0) { + char response[RESPONSE_BUFSIZE]; + ssize_t nread; + while ((nread = read(sock, response, RESPONSE_BUFSIZE - 1)) < 0) { + if (errno != EAGAIN) { + std::cerr << "(sendcmd) ERROR " << errno << " reading\n"; + close(sock); + return -errno; + } + usleep(10000); + if (elapsed_sec() >= timeout) { + std::cerr << "ERROR " << ETIME << " TIMEOUT\n"; + close(sock); + return -ETIME; + } + } + response[nread] = '\0'; + std::cout << response << "\n"; + } + else { + std::cout << "cmd_sent\n"; + } + + return close(sock); +} From 0b6926ef0dc2d9168dc0f399a67843f4084c19a4 Mon Sep 17 00:00:00 2001 From: David Hale Date: Wed, 13 May 2026 00:07:12 -0700 Subject: [PATCH 22/35] fix: on_error must appear before any operations in a group --- sequencerd/sequence_builder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp index 8706c0bb..960ca9a9 100644 --- a/sequencerd/sequence_builder.cpp +++ b/sequencerd/sequence_builder.cpp @@ -84,6 +84,11 @@ namespace Sequencer { if (name == "on_error") { // must be in a group for on_error to mean anything if (!current_group) return fail(command, "'on_error' out of a group"); + // on_error must precede any operations in the group so it cannot + // silently mutate the policy of an already-populated group + if (!current_group->operations.empty()) { + return fail(command, "'on_error' must appear before any operations in the group"); + } if (!command.params.has("action")) return fail(command, "on_error requires action=stop|continue"); const std::string val = command.params.get("action",""); From 51ad2d253c7462203877df5e76a353e339e698b9 Mon Sep 17 00:00:00 2001 From: David Hale Date: Wed, 13 May 2026 12:08:54 -0700 Subject: [PATCH 23/35] * adds set|get commands to powerd * fills out sequencer command specs and transitions but doesn't use them yet -- this is the last stage before changing how it works * comment-out some debug logging --- Andor/andor_emulator.cpp | 2 +- common/powerd_commands.h | 10 +- powerd/power_server.cpp | 15 ++- sequencerd/command_rules.h | 229 ++++++++++++++++++++++++++++-------- slicecamd/slicecam_math.cpp | 12 +- 5 files changed, 209 insertions(+), 59 deletions(-) diff --git a/Andor/andor_emulator.cpp b/Andor/andor_emulator.cpp index dd41a1bb..c179278f 100644 --- a/Andor/andor_emulator.cpp +++ b/Andor/andor_emulator.cpp @@ -1218,7 +1218,7 @@ namespace Andor { } #ifdef LOGLEVEL_DEBUG - log_python_arguments(pFunction, pArgs, pKwArgs); +// log_python_arguments(pFunction, pArgs, pKwArgs); #endif // Call the Python function here diff --git a/common/powerd_commands.h b/common/powerd_commands.h index da8ef3fa..f10fc2c3 100644 --- a/common/powerd_commands.h +++ b/common/powerd_commands.h @@ -13,6 +13,8 @@ const std::string POWERD_ISOPEN = "isopen"; const std::string POWERD_LIST = "list"; const std::string POWERD_OPEN = "open"; const std::string POWERD_REOPEN = "reopen"; +const std::string POWERD_GET = "get"; +const std::string POWERD_SET = "set"; const std::string POWERD_STATUS = "status"; const std::vector POWERD_SYNTAX = { POWERD_CLOSE, @@ -20,9 +22,9 @@ const std::vector POWERD_SYNTAX = { POWERD_LIST+" [?]", POWERD_OPEN, POWERD_REOPEN+" [?]", + POWERD_GET+" | ", + POWERD_SET+" { ON | OFF | BOOT }", + POWERD_SET+" { ON | OFF | BOOT }", POWERD_STATUS+" [?]", - TELEMREQUEST+" [?]", - "", - " [ ON | OFF | BOOT ]", - " [ ON | OFF | BOOT ]" + TELEMREQUEST+" [?]" }; diff --git a/powerd/power_server.cpp b/powerd/power_server.cpp index 3ebd3d29..5b77057d 100644 --- a/powerd/power_server.cpp +++ b/powerd/power_server.cpp @@ -603,10 +603,13 @@ namespace Power { ret = JSON; } } + else - // all other commands go to the powerd interface for parsing + // set|get plug state -- strip command and forward the + // args to Interface::command(), which already parses + // [ON|OFF|BOOT] and [ON|OFF|BOOT]. // - else { + if ( cmd==POWERD_SET || cmd==POWERD_GET ) { try { std::transform( buf.begin(), buf.end(), buf.begin(), ::toupper ); // make uppercase } @@ -616,6 +619,14 @@ namespace Power { } ret = this->interface.command( buf, retstring ); // send the command } + else { + + // unknown commands generate an error + // + logwrite( function, "ERROR unknown command '"+cmd+"'"); + ret = ERROR; + } + // If retstring not empty then append "DONE" or "ERROR" depending on value of ret, // and log the reply along with the command number. Write the reply back to the socket. diff --git a/sequencerd/command_rules.h b/sequencerd/command_rules.h index 2cb086a1..f0254bfc 100644 --- a/sequencerd/command_rules.h +++ b/sequencerd/command_rules.h @@ -22,7 +22,8 @@ namespace Sequencer { READY, ///< open and ready to accept commands EXPOSING, ///< exposure in progress READING, ///< readout in progress - PAUSED ///< exposure paused + PAUSED, ///< exposure paused + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap camerad_specs = { @@ -49,7 +50,27 @@ namespace Sequencer { { CAMERAD_READOUT, {0, 2} }, { CAMERAD_RESUME, {0, 0} }, { CAMERAD_SHUTTER, {0, 1} }, ///< [ enable | 1 | disable | 0 ] - { CAMERAD_STOP, {0, 0} } + { CAMERAD_STOP, {0, 0} }, + // Commands added for structural completeness; arg ranges from camerad_commands.h syntax. + // Transitions for these are all INVALID->INVALID so the sequencer cannot invoke them + // until the from/to states are verified against camerad behavior. + { CAMERAD_AUTODIR, {0, 1} }, + { CAMERAD_BASENAME, {0, 1} }, + { CAMERAD_BIAS, {0, 4} }, + { CAMERAD_BUFFER, {1, 3} }, ///< ? | | [ | ] + { CAMERAD_CONFIG, {0, 1} }, + { CAMERAD_ECHO, {0, 4} }, + { CAMERAD_INTERFACE, {0, 0} }, + { CAMERAD_ISACTIVE, {0, 0} }, + { CAMERAD_ISOPEN, {0, 1} }, + { CAMERAD_LONGERROR, {0, 0} }, + { CAMERAD_MEX, {0, 4} }, + { CAMERAD_MEXAMPS, {0, 4} }, + { CAMERAD_NATIVE, {1, 6} }, ///< ? | [|] [..] + { CAMERAD_PREEXPOSURES, {0, 1} }, + { CAMERAD_TEST, {1, 8} }, ///< ? | ... + { CAMERAD_USEFRAMES, {0, 1} }, + { CAMERAD_WRITEKEYS, {0, 0} } }; const std::vector> camerad_transitions = { @@ -79,7 +100,27 @@ namespace Sequencer { { CameraState::READING, CAMERAD_READOUT, CameraState::READY }, { CameraState::PAUSED, CAMERAD_RESUME, CameraState::EXPOSING }, { CameraState::READY, CAMERAD_SHUTTER, CameraState::READY }, - { CameraState::EXPOSING, CAMERAD_STOP, CameraState::READY } + { CameraState::EXPOSING, CAMERAD_STOP, CameraState::READY }, + // INVALID->INVALID transitions: these commands cannot be invoked by the + // sequencer until from/to states are verified. The unreachable-from-state + // condition rejects them at runtime via CommandClient::send(). + { CameraState::INVALID, CAMERAD_AUTODIR, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_BASENAME, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_BIAS, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_BUFFER, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_CONFIG, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_ECHO, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_INTERFACE, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_ISACTIVE, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_ISOPEN, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_LONGERROR, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_MEX, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_MEXAMPS, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_NATIVE, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_PREEXPOSURES, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_TEST, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_USEFRAMES, CameraState::INVALID }, // TODO: verify and assign real states + { CameraState::INVALID, CAMERAD_WRITEKEYS, CameraState::INVALID } // TODO: verify and assign real states }; @@ -89,7 +130,8 @@ namespace Sequencer { IDLE, ///< not yet opened READY, ///< open and ready to accept commands ACQUIRING, ///< target acquisition in progress - GUIDING ///< guiding in progress + GUIDING, ///< guiding in progress + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap acamd_specs = { @@ -114,7 +156,31 @@ namespace Sequencer { { ACAMD_SHUTDOWN, {0, 0} }, { ACAMD_TCSINIT, {0, 1} }, ///< [ tcs | sim ] { ACAMD_OFFSETGOAL, {0, 2} }, ///< [ ] - { ACAMD_OFFSETPERIOD,{0, 1} } ///< [ ] + { ACAMD_OFFSETPERIOD,{0, 1} }, ///< [ ] + // Commands added for structural completeness; arg ranges from acamd_commands.h syntax. + // Transitions for these are all INVALID->INVALID so the sequencer cannot invoke them + // until from/to states are verified against acamd behavior. + { ACAMD_AVGFRAMES, {0, 1} }, + { ACAMD_CONFIG, {0, 1} }, + { ACAMD_ECHO, {0, 1} }, + { ACAMD_EXIT, {0, 0} }, + { ACAMD_FRAMEGRABFIX, {0, 1} }, + { ACAMD_IMFLIP, {0, 2} }, + { ACAMD_IMROT, {0, 1} }, + { ACAMD_SPEED, {0, 2} }, + { ACAMD_EMULATOR, {0, 1} }, + { ACAMD_FAN, {0, 1} }, + { ACAMD_GUIDESET, {0, 4} }, + { ACAMD_LOADCALIBRATION, {0, 1} }, + { ACAMD_MOTION, {0, 4} }, + { ACAMD_OFFSETCAL, {0, 1} }, + { ACAMD_QUALITY, {0, 1} }, + { ACAMD_SAVEFRAMES, {0, 1} }, + { ACAMD_SKIPFRAMES, {0, 1} }, + { ACAMD_TCSGET, {0, 1} }, + { ACAMD_TCSISCONNECTED, {0, 1} }, + { ACAMD_TCSISOPEN, {0, 1} }, + { ACAMD_TEST, {1, 8} } }; const std::vector> acamd_transitions = { @@ -141,7 +207,29 @@ namespace Sequencer { { AcamState::READY, ACAMD_SHUTDOWN, AcamState::IDLE }, // TODO: verify { AcamState::READY, ACAMD_ISOPEN, AcamState::READY }, // TODO: verify { AcamState::READY, ACAMD_ISHOME, AcamState::READY }, // TODO: verify - { AcamState::READY, ACAMD_ISACQUIRED, AcamState::READY } // TODO: verify + { AcamState::READY, ACAMD_ISACQUIRED, AcamState::READY }, // TODO: verify + // INVALID->INVALID transitions: rejected at runtime until from/to verified. + { AcamState::INVALID, ACAMD_AVGFRAMES, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_CONFIG, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_ECHO, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_EXIT, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_FRAMEGRABFIX, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_IMFLIP, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_IMROT, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_SPEED, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_EMULATOR, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_FAN, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_GUIDESET, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_LOADCALIBRATION, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_MOTION, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_OFFSETCAL, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_QUALITY, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_SAVEFRAMES, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_SKIPFRAMES, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_TCSGET, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_TCSISCONNECTED, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_TCSISOPEN, AcamState::INVALID }, // TODO: verify and assign real states + { AcamState::INVALID, ACAMD_TEST, AcamState::INVALID } // TODO: verify and assign real states }; @@ -150,7 +238,8 @@ namespace Sequencer { enum class CalibState { IDLE, ///< not yet opened READY, ///< open and ready to accept commands - MOVING ///< actuator motion in progress + MOVING, ///< actuator motion in progress + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap calibd_specs = { @@ -162,31 +251,35 @@ namespace Sequencer { { CALIBD_ISHOME, {0, 0} }, { CALIBD_SET, {1, 8} }, ///< =open|close ... (variable) { CALIBD_LAMPMOD, {1, 4} }, ///< open | close | reconnect | default | ... - { CALIBD_NATIVE, {2, 2} } ///< + { CALIBD_NATIVE, {2, 2} }, ///< + // INVALID-transition commands: rejected at runtime until from/to verified. + { CALIBD_EXIT, {0, 0} } }; const std::vector> calibd_transitions = { - { CalibState::IDLE, CALIBD_OPEN, CalibState::READY }, // TODO: verify - { CalibState::READY, CALIBD_CLOSE, CalibState::IDLE }, // TODO: verify - { CalibState::READY, CALIBD_ISOPEN, CalibState::READY }, // TODO: verify - { CalibState::READY, CALIBD_GET, CalibState::READY }, // TODO: verify - { CalibState::READY, CALIBD_HOME, CalibState::MOVING }, // TODO: verify - { CalibState::MOVING, CALIBD_ISHOME, CalibState::MOVING }, // TODO: verify - { CalibState::READY, CALIBD_ISHOME, CalibState::READY }, // TODO: verify - { CalibState::READY, CALIBD_SET, CalibState::MOVING }, // TODO: verify - { CalibState::MOVING, CALIBD_SET, CalibState::READY }, // TODO: verify (completion) - { CalibState::READY, CALIBD_LAMPMOD, CalibState::READY }, // TODO: verify - { CalibState::READY, CALIBD_NATIVE, CalibState::READY } // TODO: verify + { CalibState::IDLE, CALIBD_OPEN, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_CLOSE, CalibState::IDLE }, // TODO: verify + { CalibState::READY, CALIBD_ISOPEN, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_GET, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_HOME, CalibState::MOVING }, // TODO: verify + { CalibState::MOVING, CALIBD_ISHOME, CalibState::MOVING }, // TODO: verify + { CalibState::READY, CALIBD_ISHOME, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_SET, CalibState::MOVING }, // TODO: verify + { CalibState::MOVING, CALIBD_SET, CalibState::READY }, // TODO: verify (completion) + { CalibState::READY, CALIBD_LAMPMOD, CalibState::READY }, // TODO: verify + { CalibState::READY, CALIBD_NATIVE, CalibState::READY }, // TODO: verify + { CalibState::INVALID, CALIBD_EXIT, CalibState::INVALID } // TODO: verify and assign real states }; // ---------- SLITD ---------------------------------------------------------- enum class SlitState { - IDLE, ///< not yet opened - READY, ///< open and ready to accept commands - MOVING, ///< slit motion in progress - HOMED ///< slit has been homed + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + MOVING, ///< slit motion in progress + HOMED, ///< slit has been homed + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap slitd_specs = { @@ -226,11 +319,12 @@ namespace Sequencer { // ---------- TCSD ----------------------------------------------------------- enum class TcsState { - IDLE, ///< not yet opened - READY, ///< open and ready to accept commands - SLEWING, ///< telescope slew in progress - TRACKING, ///< telescope tracking on target - OFFSETTING ///< telescope offset in progress + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + SLEWING, ///< telescope slew in progress + TRACKING, ///< telescope tracking on target + OFFSETTING, ///< telescope offset in progress + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap tcsd_specs = { @@ -255,7 +349,10 @@ namespace Sequencer { { TCSD_NATIVE, {1, 1} }, ///< { TCSD_LIST, {0, 0} }, { TCSD_LLIST, {0, 0} }, - { TCSD_WEATHER_COORDS, {0, 0} } + { TCSD_WEATHER_COORDS, {0, 0} }, + // INVALID-transition commands: rejected at runtime until from/to verified. + { TCSD_EXIT, {0, 0} }, + { TCSD_PUBLISHSTATE, {0, 1} } }; const std::vector> tcsd_transitions = { @@ -300,16 +397,20 @@ namespace Sequencer { { TcsState::READY, TCSD_LIST, TcsState::READY }, // TODO: verify { TcsState::READY, TCSD_LLIST, TcsState::READY }, // TODO: verify { TcsState::READY, TCSD_WEATHER_COORDS, TcsState::READY }, // TODO: verify - { TcsState::TRACKING, TCSD_WEATHER_COORDS, TcsState::TRACKING } // TODO: verify + { TcsState::TRACKING, TCSD_WEATHER_COORDS, TcsState::TRACKING }, // TODO: verify + // INVALID->INVALID transitions: rejected at runtime until from/to verified. + { TcsState::INVALID, TCSD_EXIT, TcsState::INVALID }, // TODO: verify and assign real states + { TcsState::INVALID, TCSD_PUBLISHSTATE, TcsState::INVALID } // TODO: verify and assign real states }; // ---------- FOCUSD --------------------------------------------------------- enum class FocusState { - IDLE, ///< not yet opened - READY, ///< open and ready to accept commands - MOVING ///< focus motion in progress + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + MOVING, ///< focus motion in progress + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap focusd_specs = { @@ -346,9 +447,10 @@ namespace Sequencer { // ---------- FLEXURED ------------------------------------------------------- enum class FlexureState { - IDLE, ///< not yet opened - READY, ///< open and ready to accept commands - MOVING ///< flexure compensation motion in progress + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + MOVING, ///< flexure compensation motion in progress + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap flexured_specs = { @@ -382,8 +484,9 @@ namespace Sequencer { // ---------- POWERD --------------------------------------------------------- enum class PowerState { - IDLE, ///< not yet opened - READY ///< open and ready to accept commands + IDLE, ///< not yet opened + READY, ///< open and ready to accept commands + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap powerd_specs = { @@ -392,10 +495,9 @@ namespace Sequencer { { POWERD_ISOPEN, {0, 0} }, { POWERD_STATUS, {0, 1} }, ///< [ ? ] { POWERD_REOPEN, {0, 0} }, - { POWERD_LIST, {0, 0} } - // NOTE: plug switching commands ( ON|OFF|BOOT or ON|OFF|BOOT) - // do not have a fixed first-word command name and so cannot be registered here by name. - // TODO: decide how plug switching is exposed via SEQUENCERD_OP. + { POWERD_LIST, {0, 0} }, + { POWERD_GET, {1, 2} }, + { POWERD_SET, {1, 3} } ///< [ ON | OFF | BOOT ] or [ ON | OFF | BOOT ] }; const std::vector> powerd_transitions = { @@ -404,7 +506,9 @@ namespace Sequencer { { PowerState::READY, POWERD_ISOPEN, PowerState::READY }, // TODO: verify { PowerState::READY, POWERD_STATUS, PowerState::READY }, // TODO: verify { PowerState::READY, POWERD_REOPEN, PowerState::READY }, // TODO: verify - { PowerState::READY, POWERD_LIST, PowerState::READY } // TODO: verify + { PowerState::READY, POWERD_LIST, PowerState::READY }, // TODO: verify + { PowerState::READY, POWERD_GET, PowerState::READY }, // TODO: verify + { PowerState::READY, POWERD_SET, PowerState::READY } // TODO: verify }; @@ -414,7 +518,8 @@ namespace Sequencer { IDLE, ///< not yet opened READY, ///< open and ready to accept commands ACQUIRING, ///< fine acquisition in progress - GUIDING ///< guiding in progress + GUIDING, ///< guiding in progress + INVALID ///< sentinel for unverified commands; no live client ever holds this value }; const CommandSpecMap slicecamd_specs = { @@ -434,7 +539,24 @@ namespace Sequencer { { SLICECAMD_SHUTDOWN, {0, 0} }, { SLICECAMD_TCSINIT, {0, 1} }, ///< [ tcs | sim ] { SLICECAMD_IMFLIP, {0, 3} }, ///< [ ] - { SLICECAMD_IMROT, {0, 2} } ///< [ ] + { SLICECAMD_IMROT, {0, 2} }, ///< [ ] + // Commands added for structural completeness; arg ranges from slicecamd_commands.h syntax. + // Transitions for these are all INVALID->INVALID so the sequencer cannot invoke them + // until from/to states are verified against slicecamd behavior. + { SLICECAMD_AVGFRAMES, {0, 1} }, + { SLICECAMD_CONFIG, {0, 1} }, + { SLICECAMD_ECHO, {0, 1} }, + { SLICECAMD_EXIT, {0, 0} }, + { SLICECAMD_FRAMEGRABFIX, {0, 1} }, + { SLICECAMD_SPEED, {0, 2} }, + { SLICECAMD_EMULATOR, {0, 1} }, + { SLICECAMD_FAN, {0, 2} }, + { SLICECAMD_GUISET, {0, 2} }, + { SLICECAMD_ISACQUIRED, {0, 0} }, + { SLICECAMD_TCSGET, {0, 1} }, + { SLICECAMD_TCSISCONNECTED, {0, 1} }, + { SLICECAMD_TCSISOPEN, {0, 1} }, + { SLICECAMD_TEST, {1, 8} } }; const std::vector> slicecamd_transitions = { @@ -455,7 +577,22 @@ namespace Sequencer { { SlicecamState::READY, SLICECAMD_SHUTDOWN, SlicecamState::IDLE }, // TODO: verify { SlicecamState::READY, SLICECAMD_TCSINIT, SlicecamState::READY }, // TODO: verify { SlicecamState::READY, SLICECAMD_IMFLIP, SlicecamState::READY }, // TODO: verify - { SlicecamState::READY, SLICECAMD_IMROT, SlicecamState::READY } // TODO: verify + { SlicecamState::READY, SLICECAMD_IMROT, SlicecamState::READY }, // TODO: verify + // INVALID->INVALID transitions: rejected at runtime until from/to verified. + { SlicecamState::INVALID, SLICECAMD_AVGFRAMES, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_CONFIG, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_ECHO, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_EXIT, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_FRAMEGRABFIX, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_SPEED, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_EMULATOR, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_FAN, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_GUISET, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_ISACQUIRED, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_TCSGET, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_TCSISCONNECTED, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_TCSISOPEN, SlicecamState::INVALID }, // TODO: verify and assign real states + { SlicecamState::INVALID, SLICECAMD_TEST, SlicecamState::INVALID } // TODO: verify and assign real states }; } diff --git a/slicecamd/slicecam_math.cpp b/slicecamd/slicecam_math.cpp index a87d9e61..6d73637e 100644 --- a/slicecamd/slicecam_math.cpp +++ b/slicecamd/slicecam_math.cpp @@ -309,12 +309,12 @@ namespace Slicecam { } } - { - std::ostringstream oss; - oss << "[DEBUG] bkg=" << bkg << " sigma=" << sigma - << " best_val=" << best_val << " best_x=" << best_x << " best_y=" << best_y; - logwrite("Slicecam::Math::calculate_centroid", oss.str()); - } +// { +// std::ostringstream oss; +// oss << "[DEBUG] bkg=" << bkg << " sigma=" << sigma +// << " best_val=" << best_val << " best_x=" << best_x << " best_y=" << best_y; +// logwrite("Slicecam::Math::calculate_centroid", oss.str()); +// } if ( best_x < 0 ) return ERROR; // no source found From f577429d15747487bcc900e1d5f6301156208f27 Mon Sep 17 00:00:00 2001 From: David Hale Date: Wed, 13 May 2026 12:42:27 -0700 Subject: [PATCH 24/35] fix to support new powerd set|get commands --- powerd/power_server.cpp | 4 ++-- sequencerd/sequence.cpp | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/powerd/power_server.cpp b/powerd/power_server.cpp index 5b77057d..f4f75c25 100644 --- a/powerd/power_server.cpp +++ b/powerd/power_server.cpp @@ -611,13 +611,13 @@ namespace Power { // if ( cmd==POWERD_SET || cmd==POWERD_GET ) { try { - std::transform( buf.begin(), buf.end(), buf.begin(), ::toupper ); // make uppercase + std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase } catch( ...) { logwrite( function, "ERROR converting command to uppercase" ); ret = ERROR; } - ret = this->interface.command( buf, retstring ); // send the command + ret = this->interface.command( args, retstring ); // send the command } else { diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 1e5fbfd7..9ebfa20a 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -2902,11 +2902,10 @@ namespace Sequencer { // for ( const auto &[lamp,state] : calinfo.lamp ) { if ( is_cancelled() ) break; - cmd.str(""); cmd << lamp << " " << (state?"on":"off"); - message.str(""); message << "power " << cmd.str(); - logwrite( function, message.str() ); + std::string setpower = POWERD_SET+" "+lamp+" "+(state?"on":"off"); + logwrite( function, "power "+setpower); std::string reply; - if ( this->powerd.send( cmd.str(), reply ) != NO_ERROR ) { + if ( this->powerd.send( setpower, reply ) != NO_ERROR ) { this->broadcast.error( function, ""+message.str() ); throw std::runtime_error("setting lamp "+message.str()); } @@ -4059,7 +4058,8 @@ namespace Sequencer { for ( const auto &plug : this->power_switch[which].plugname ) { std::string reply; - if ( this->powerd.send( plug, reply ) == NO_ERROR ) { + std::string cmd = POWERD_GET+" "+plug; + if ( this->powerd.send( cmd, reply ) == NO_ERROR ) { if ( reply.find(reqstatestr) != std::string::npos ) { is_set=true; break; @@ -4079,9 +4079,8 @@ namespace Sequencer { // for ( const auto &plug : this->power_switch[which].plugname ) { std::string reply; - std::stringstream cmd; - cmd << plug; - error = this->powerd.send( cmd.str(), reply ); + std::string cmd = POWERD_GET+" "+plug; + error = this->powerd.send( cmd, reply ); if ( error != NO_ERROR || reply.find(" DONE") == std::string::npos ) { logwrite( function, "ERROR checking plug: "+plug ); continue; @@ -4100,9 +4099,9 @@ namespace Sequencer { std::string reqstatestr = ( reqstate==ON ? "ON" : "OFF" ); if ( state != reqstate ) { - cmd << " " << reqstatestr; + cmd = POWERD_SET+" "+plug+" "+reqstatestr; logwrite( function, "switching plug "+plug+" "+reqstatestr ); - error = this->powerd.send( cmd.str(), reply ); + error = this->powerd.send( cmd, reply ); if ( error != NO_ERROR || reply.find(" DONE") != std::string::npos ) { logwrite( function, "ERROR switching plug: "+plug+" "+reqstatestr ); continue; From 9174374c5045b95372fcd4e88343b8f2a0e8df4c Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 10:22:38 -0700 Subject: [PATCH 25/35] * wires CommandClient into all DSL passthrough op_builders * replaces daemon_passthrough() placeholder with translate_command + CommandClient::send(), adds per-command arg count and state machine validation at execution time * rewrites parse_command with strict key=val grammar * fixes ltrim bug * fixes move_to_target bug for CAL targets * fixes abort_process for clean state transitions * adds missing request_snapshot to get status on startup --- sequencerd/command.h | 52 +++++- sequencerd/command_rules.h | 90 ++++++++-- sequencerd/operation_builders.cpp | 286 +++++++++++++++++++++++------- sequencerd/sequence.cpp | 181 +++++++++++++++---- sequencerd/sequence.h | 121 +++++++++++-- sequencerd/sequence_builder.cpp | 2 +- sequencerd/sequencerd.cpp | 1 + utils/utilities.h | 2 +- 8 files changed, 600 insertions(+), 135 deletions(-) diff --git a/sequencerd/command.h b/sequencerd/command.h index b9a37c5d..d03dff3e 100644 --- a/sequencerd/command.h +++ b/sequencerd/command.h @@ -73,7 +73,13 @@ namespace Sequencer { const CommandSpecMap &specs; State state; const std::vector> &transitions; + const std::unordered_map &state_names; + /** + * @brief rejects cmd if its name is unknown or its arg count is out of range + * @param[in] cmd command to check + * @throws std::runtime_error on unknown command name or arg count out of range + */ void validate_args( const Command &cmd ) const { auto it = specs.find( cmd.name ); if (it == specs.end()) throw std::runtime_error("unknown command: "+cmd.name); @@ -83,15 +89,40 @@ namespace Sequencer { } } + /** + * @brief rejects cmd if it is not permitted from the current state + * @param[in] cmd command to check + * @throws std::runtime_error + */ void validate_order( const Command &cmd ) const { for (const auto &transition : transitions) { - if (transition.from == state && transition.command == cmd.name) { - return; + if (transition.from == state && transition.command == cmd.name) return; + } + // not valid from current state; collect all valid from-states for the error message + auto name_of = [&](State s) -> std::string { + auto it = state_names.find(s); + return (it != state_names.end()) ? it->second : "?"; + }; + std::string valid; + for (const auto &transition : transitions) { + if (transition.command == cmd.name) { + if (!valid.empty()) valid += ", "; + valid += name_of(transition.from); } } - throw std::runtime_error("invalid command order: "+cmd.name); + if (valid.empty()) valid = "(none registered)"; + throw std::runtime_error( + "invalid command order: '"+cmd.name+"' not valid in state " + +name_of(state)+"; valid from: {"+valid+"}"); } + /** + * @brief advances internal state after a successful send + * @param[in] cmd command that was just forwarded to the daemon + * @details Searches the transition table for a matching (from==state, + * command==cmd.name) entry and sets state to transition.to. + * No-ops silently when no matching transition exists. + */ void advance_state( const Command &cmd ) { for (const auto &transition : transitions) { if (transition.from == state && transition.command == cmd.name) { @@ -102,14 +133,24 @@ namespace Sequencer { } public: + /** + * @brief constructs a CommandClient bound to a daemon connection + * @param[in] client DaemonClient used to forward wire commands + * @param[in] specs per-command arg-count constraints + * @param[in] initial_state starting state of the state machine + * @param[in] transitions valid (from, command, to) state transitions + * @param[in] state_names human-readable names for each State value, used in errors + */ CommandClient( Common::DaemonClient &client, const CommandSpecMap &specs, State initial_state, - const std::vector> &transitions ) + const std::vector> &transitions, + const std::unordered_map &state_names ) : client(client), specs(specs), state(initial_state), - transitions(transitions) { } + transitions(transitions), + state_names(state_names) { } /** * @brief primary interface to sending commands @@ -128,6 +169,7 @@ namespace Sequencer { return ret; } + /** @brief returns the current state-machine state */ State get_state() const { return state; } }; } diff --git a/sequencerd/command_rules.h b/sequencerd/command_rules.h index f0254bfc..f1f1d370 100644 --- a/sequencerd/command_rules.h +++ b/sequencerd/command_rules.h @@ -11,6 +11,7 @@ #pragma once #include "command.h" +#include #include namespace Sequencer { @@ -26,6 +27,15 @@ namespace Sequencer { INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map camerad_state_names = { + { CameraState::IDLE, "IDLE" }, + { CameraState::READY, "READY" }, + { CameraState::EXPOSING, "EXPOSING" }, + { CameraState::READING, "READING" }, + { CameraState::PAUSED, "PAUSED" }, + { CameraState::INVALID, "INVALID" }, + }; + const CommandSpecMap camerad_specs = { { CAMERAD_ABORT, {0, 0} }, { CAMERAD_ACTIVATE, {0, 4} }, ///< 0 or more of: [ { U G R I } ] @@ -134,6 +144,14 @@ namespace Sequencer { INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map acamd_state_names = { + { AcamState::IDLE, "IDLE" }, + { AcamState::READY, "READY" }, + { AcamState::ACQUIRING, "ACQUIRING" }, + { AcamState::GUIDING, "GUIDING" }, + { AcamState::INVALID, "INVALID" }, + }; + const CommandSpecMap acamd_specs = { { ACAMD_OPEN, {0, 2} }, ///< [ motion ] [ camera [] ] { ACAMD_CLOSE, {0, 0} }, @@ -242,6 +260,13 @@ namespace Sequencer { INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map calibd_state_names = { + { CalibState::IDLE, "IDLE" }, + { CalibState::READY, "READY" }, + { CalibState::MOVING, "MOVING" }, + { CalibState::INVALID, "INVALID" }, + }; + const CommandSpecMap calibd_specs = { { CALIBD_OPEN, {0, 1} }, ///< [ motion | lampmod ] { CALIBD_CLOSE, {0, 1} }, ///< [ motion | lampmod ] @@ -282,6 +307,14 @@ namespace Sequencer { INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map slitd_state_names = { + { SlitState::IDLE, "IDLE" }, + { SlitState::READY, "READY" }, + { SlitState::MOVING, "MOVING" }, + { SlitState::HOMED, "HOMED" }, + { SlitState::INVALID, "INVALID" }, + }; + const CommandSpecMap slitd_specs = { { SLITD_OPEN, {0, 0} }, { SLITD_CLOSE, {0, 0} }, @@ -327,6 +360,15 @@ namespace Sequencer { INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map tcsd_state_names = { + { TcsState::IDLE, "IDLE" }, + { TcsState::READY, "READY" }, + { TcsState::SLEWING, "SLEWING" }, + { TcsState::TRACKING, "TRACKING" }, + { TcsState::OFFSETTING, "OFFSETTING" }, + { TcsState::INVALID, "INVALID" }, + }; + const CommandSpecMap tcsd_specs = { { TCSD_OPEN, {1, 1} }, ///< { TCSD_CLOSE, {0, 0} }, @@ -413,6 +455,13 @@ namespace Sequencer { INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map focusd_state_names = { + { FocusState::IDLE, "IDLE" }, + { FocusState::READY, "READY" }, + { FocusState::MOVING, "MOVING" }, + { FocusState::INVALID, "INVALID" }, + }; + const CommandSpecMap focusd_specs = { { FOCUSD_OPEN, {0, 0} }, { FOCUSD_CLOSE, {0, 0} }, @@ -453,6 +502,13 @@ namespace Sequencer { INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map flexured_state_names = { + { FlexureState::IDLE, "IDLE" }, + { FlexureState::READY, "READY" }, + { FlexureState::MOVING, "MOVING" }, + { FlexureState::INVALID, "INVALID" }, + }; + const CommandSpecMap flexured_specs = { { FLEXURED_OPEN, {0, 0} }, { FLEXURED_CLOSE, {0, 0} }, @@ -483,12 +539,18 @@ namespace Sequencer { // ---------- POWERD --------------------------------------------------------- - enum class PowerState { + enum class PowerdState { IDLE, ///< not yet opened READY, ///< open and ready to accept commands INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map powerd_state_names = { + { PowerdState::IDLE, "IDLE" }, + { PowerdState::READY, "READY" }, + { PowerdState::INVALID, "INVALID" }, + }; + const CommandSpecMap powerd_specs = { { POWERD_OPEN, {0, 0} }, { POWERD_CLOSE, {0, 0} }, @@ -500,15 +562,15 @@ namespace Sequencer { { POWERD_SET, {1, 3} } ///< [ ON | OFF | BOOT ] or [ ON | OFF | BOOT ] }; - const std::vector> powerd_transitions = { - { PowerState::IDLE, POWERD_OPEN, PowerState::READY }, // TODO: verify - { PowerState::READY, POWERD_CLOSE, PowerState::IDLE }, // TODO: verify - { PowerState::READY, POWERD_ISOPEN, PowerState::READY }, // TODO: verify - { PowerState::READY, POWERD_STATUS, PowerState::READY }, // TODO: verify - { PowerState::READY, POWERD_REOPEN, PowerState::READY }, // TODO: verify - { PowerState::READY, POWERD_LIST, PowerState::READY }, // TODO: verify - { PowerState::READY, POWERD_GET, PowerState::READY }, // TODO: verify - { PowerState::READY, POWERD_SET, PowerState::READY } // TODO: verify + const std::vector> powerd_transitions = { + { PowerdState::IDLE, POWERD_OPEN, PowerdState::READY }, // TODO: verify + { PowerdState::READY, POWERD_CLOSE, PowerdState::IDLE }, // TODO: verify + { PowerdState::READY, POWERD_ISOPEN, PowerdState::READY }, // TODO: verify + { PowerdState::READY, POWERD_STATUS, PowerdState::READY }, // TODO: verify + { PowerdState::READY, POWERD_REOPEN, PowerdState::READY }, // TODO: verify + { PowerdState::READY, POWERD_LIST, PowerdState::READY }, // TODO: verify + { PowerdState::READY, POWERD_GET, PowerdState::READY }, // TODO: verify + { PowerdState::READY, POWERD_SET, PowerdState::READY } // TODO: verify }; @@ -522,6 +584,14 @@ namespace Sequencer { INVALID ///< sentinel for unverified commands; no live client ever holds this value }; + const std::unordered_map slicecamd_state_names = { + { SlicecamState::IDLE, "IDLE" }, + { SlicecamState::READY, "READY" }, + { SlicecamState::ACQUIRING, "ACQUIRING" }, + { SlicecamState::GUIDING, "GUIDING" }, + { SlicecamState::INVALID, "INVALID" }, + }; + const CommandSpecMap slicecamd_specs = { { SLICECAMD_OPEN, {0, 2} }, ///< [ motion ] [ camera [] ] { SLICECAMD_CLOSE, {0, 0} }, diff --git a/sequencerd/operation_builders.cpp b/sequencerd/operation_builders.cpp index 38d32f1a..52dc43ed 100644 --- a/sequencerd/operation_builders.cpp +++ b/sequencerd/operation_builders.cpp @@ -13,65 +13,159 @@ * the member function via Sequence::current_op_params (set by * Sequence::run() immediately before op.func() is invoked). * - * 2. Passthrough : per-daemon validated passthrough (e.g. "camera exptime 30000" - * or "slit set 2.0 0.0"). These are built inline here (no Ops:: - * method) and forward the positional args (cmd.args) to the - * target daemon. The first arg is the daemon subcommand; the - * remainder are forwarded as-is in daemon native syntax. - * - * NOTE: Passthrough commands currently forward via Common::DaemonClient::send() as a - * PLACEHOLDER. This will be replaced by CommandClient::send() - * later, at which point per-command state-machine validation and - * arg-count checks will be performed at execution time. + * 2. Passthrough : per-daemon translated passthrough. The sequencer DSL form + * "subsystem [do=verb] [key1=val1 ... keyN=valN]" is + * translated to a daemon-native Command{name, arglist} by + * translate_command(), which dispatches to either a + * daemon-specific builder (e.g. build_powerd_command) or + * build_generic_command(). The translated Command is then + * forwarded via CommandClient::send(), which performs + * per-command arg-count and state-machine validation before + * forwarding to the daemon. * */ +#include +#include +#include +#include + #include "sequence.h" #include "sequencerd_commands.h" +#include "powerd_commands.h" namespace Sequencer { - namespace { - - /***** Sequencer::daemon_passthrough *************************************/ - /** - * @brief TEMPORARY daemon passthrough helper - * @param[in] function calling function name for logging - * @param[in] daemon DaemonClient to forward to - * @param[in] args positional args from ParsedCommand (first is subcommand) - * @return ERROR|NO_ERROR - * @details Builds a daemon-native command string from args[0..N-1] and - * forwards via Common::DaemonClient::send(). Self-identifies in - * log as a placeholder so the pending replacement is unambiguous. - * - * TEMPORARY -- REMOVE ONCE CommandClient IS WIRED IN (Steps 2+3). - * Each Passthrough op_builder lambda will then call - * CommandClient::send() directly, performing per-command - * state-machine validation and arg-count checks at execution - * time, and this helper becomes unnecessary. - * - */ - long daemon_passthrough( const std::string &function, - Common::DaemonClient &daemon, - const std::vector &args ) { - if (args.empty()) { - logwrite(function, "ERROR no subcommand provided to "+daemon.name); - return ERROR; + /***** Sequencer::Sequence::validate_keys **********************************/ + /** + * @brief reject any params key not in the allowed whitelist + * @param[in] cmd ParsedCommand whose params are being checked + * @param[in] allowed initializer list of permitted key names + * @throws std::runtime_error if any key in cmd.params is not in allowed + * @details Called from daemon-specific builders after the verb and + * required keys have been matched. Ensures that DSL lines like + * "power do=set name=A state=ON garbage=oops" are rejected + * rather than silently forwarded. + * + */ + void Sequence::validate_keys( const ParsedCommand &cmd, + std::initializer_list allowed ) { + for (const auto &[key, val] : cmd.params) { + if (std::find(allowed.begin(), allowed.end(), + std::string_view{key}) == allowed.end()) { + throw std::runtime_error( + "subsystem '"+cmd.subsystem+"': unexpected key '"+key+"'"); } - std::string cmd_str = args[0]; - for (size_t i=1; i "+daemon.name+ - "): "+cmd_str); - long error = daemon.send(cmd_str, reply); - if (error != NO_ERROR) { - logwrite(function, "ERROR forwarding \""+cmd_str+"\" to "+daemon.name+": "+reply); + } + } + /***** Sequencer::Sequence::validate_keys **********************************/ + + + /***** Sequencer::Sequence::build_generic_command **************************/ + /** + * @brief default ParsedCommand -> Command translation + * @param[in] cmd ParsedCommand to translate + * @return Command{ name, arglist } ready for CommandClient::send() + * @throws std::runtime_error if cmd does not match one of the two + * supported generic forms + * @details Two cases only: + * 1. verb non-empty, params empty + * -> Command{ verb, {} } + * e.g. "acam do=isopen" -> isopen + * 2. verb empty, params has exactly one key=val + * -> Command{ key, { val } } + * e.g. "camera exptime=30000" -> exptime 30000 + * Any other shape (verb + params, multiple params, etc.) is + * a translation error; add a daemon-specific builder. + * + */ + Command Sequence::build_generic_command( const ParsedCommand &cmd ) { + if (!cmd.verb.empty() && cmd.params.size() == 0) { + return Command{ cmd.verb, {} }; + } + if (cmd.verb.empty() && cmd.params.size() == 1) { + const auto &[key, val] = *cmd.params.begin(); + return Command{ key, { val } }; + } + throw std::runtime_error( + "subsystem '"+cmd.subsystem+"': cannot build command" + " (verb='"+cmd.verb+"', "+std::to_string(cmd.params.size())+" params)" + " -- add a daemon-specific translator"); + } + /***** Sequencer::Sequence::build_generic_command **************************/ + + + /***** Sequencer::Sequence::build_powerd_command ***************************/ + /** + * @brief powerd-specific ParsedCommand -> Command translation + * @param[in] cmd ParsedCommand to translate + * @return Command{ name, arglist } in powerd native positional syntax + * @throws std::runtime_error if required keys are missing or unknown + * keys are present + * @details Powerd accepts the following DSL forms: + * power do=set name= state= + * power do=set unit= plug=

state= + * power do=get name= + * power do=get unit= plug=

+ * All other powerd subcommands (open, close, list, status, + * isopen, reopen) fall through to build_generic_command(). + * + */ + Command Sequence::build_powerd_command( const ParsedCommand &cmd ) { + if (cmd.verb == POWERD_SET) { + if (cmd.params.contains("name") && cmd.params.contains("state")) { + validate_keys(cmd, {"name", "state"}); + return Command{ POWERD_SET, + { cmd.params.at("name"), cmd.params.at("state") } }; + } + if (cmd.params.contains("unit") && + cmd.params.contains("plug") && + cmd.params.contains("state")) { + validate_keys(cmd, {"unit", "plug", "state"}); + return Command{ POWERD_SET, + { cmd.params.at("unit"), + cmd.params.at("plug"), + cmd.params.at("state") } }; + } + throw std::runtime_error( + "powerd "+POWERD_SET+": requires name+state or unit+plug+state"); + } + if (cmd.verb == POWERD_GET) { + if (cmd.params.contains("name")) { + validate_keys(cmd, {"name"}); + return Command{ POWERD_GET, { cmd.params.at("name") } }; + } + if (cmd.params.contains("unit") && cmd.params.contains("plug")) { + validate_keys(cmd, {"unit", "plug"}); + return Command{ POWERD_GET, + { cmd.params.at("unit"), cmd.params.at("plug") } }; } - return error; + throw std::runtime_error( + "powerd "+POWERD_GET+": requires name or unit+plug"); } - /***** Sequencer::daemon_passthrough *************************************/ + return build_generic_command(cmd); + } + /***** Sequencer::Sequence::build_powerd_command ***************************/ + - } // anonymous namespace + /***** Sequencer::Sequence::translate_command ******************************/ + /** + * @brief central ParsedCommand -> Command dispatcher + * @param[in] cmd ParsedCommand from the DSL parser + * @return Command in daemon native syntax + * @throws std::runtime_error from the selected builder + * @details Dispatches to a daemon-specific builder when one exists; + * otherwise falls through to build_generic_command(). Plain + * if/else dispatch -- no registry, no table. + * + */ + Command Sequence::translate_command( const ParsedCommand &cmd ) { + if (cmd.subsystem == SEQUENCERD_POWER) { + return build_powerd_command(cmd); + } + return build_generic_command(cmd); + } + /***** Sequencer::Sequence::translate_command ******************************/ /***** Sequencer::Sequence::init_operation_builders ************************/ @@ -194,80 +288,136 @@ namespace Sequencer { // ---------- PASSTHROUGH --------------------------------------------------- - // per-daemon validated passthrough (inline, no Ops method). + // per-daemon translated passthrough (inline, no Ops method). // Command names come from SEQUENCERD_* constants in common/sequencerd_commands.h. - // PLACEHOLDER: forwards via Common::DaemonClient::send() until Steps 2+3 - // replace with CommandClient::send() (state-machine validated). + // translate_command() converts the DSL ParsedCommand into a daemon-native + // Command{name, arglist}, which is then forwarded via + // CommandClient::send(). The client performs per-command arg-count + // validation and state-machine validation before forwarding to the daemon. op_builders[SEQUENCERD_ACAM] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_ACAM_INIT; // passthrough uses nearest daemon THR_ identity - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_ACAM+"]"); - return daemon_passthrough(function, this->acamd, args); + try { + return this->acamd_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; op_builders[SEQUENCERD_CALIB] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_CALIB_INIT; - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_CALIB+"]"); - return daemon_passthrough(function, this->calibd, args); + try { + return this->calibd_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; op_builders[SEQUENCERD_CAMERA] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_CAMERA_INIT; - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_CAMERA+"]"); - return daemon_passthrough(function, this->camerad, args); + try { + return this->camerad_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; op_builders[SEQUENCERD_FLEXURE] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_FLEXURE_INIT; - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_FLEXURE+"]"); - return daemon_passthrough(function, this->flexured, args); + try { + return this->flexured_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; op_builders[SEQUENCERD_FOCUS] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_FOCUS_INIT; - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_FOCUS+"]"); - return daemon_passthrough(function, this->focusd, args); + try { + return this->focusd_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; op_builders[SEQUENCERD_POWER] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_POWER_INIT; - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_POWER+"]"); - return daemon_passthrough(function, this->powerd, args); + try { + return this->powerd_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; op_builders[SEQUENCERD_SLICECAM] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_SLICECAM_INIT; - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_SLICECAM+"]"); - return daemon_passthrough(function, this->slicecamd, args); + try { + return this->slicecamd_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; op_builders[SEQUENCERD_SLIT] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_SLIT_INIT; - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_SLIT+"]"); - return daemon_passthrough(function, this->slitd, args); + try { + return this->slitd_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; op_builders[SEQUENCERD_TCS] = [this](Operation &op, const ParsedCommand &cmd) { op.thr = THR_TCS_INIT; - op.func = [this, args=cmd.args]() { + op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_TCS+"]"); - return daemon_passthrough(function, this->tcsd, args); + try { + return this->tcsd_cmd.send(translate_command(cmd_copy)); + } + catch (const std::runtime_error &e) { + logwrite(function, "ERROR "+std::string(e.what())); + return ERROR; + } }; }; diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 9ebfa20a..d7b0f871 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -477,38 +477,68 @@ namespace Sequencer { /***** Sequencer::Sequence::parse_command **********************************/ /** * @brief parses a single command line - * @details This parses a command and any parameters as key=val pairs - * from the supplied string and returns a ParsedCommand struct. - * @param[in] args string containing command and any optional arguments - * @return nullptr | ParsedCommand + * @details The DSL grammar accepted by this parser is strictly: + * command [key1=val1 key2=val2 ... keyN=valN] + * The first token is the subsystem (command). All remaining + * tokens must be key=value pairs — bare tokens, empty keys, + * empty values, duplicate keys, and duplicate do= are all + * hard parse errors and cause the line to be rejected. + * The reserved key DSL_KEY_DO ("do") is extracted to + * ParsedCommand::verb and not stored in params; all other + * pairs are stored in params. + * @param[in] args string containing one command line (consumed by reference) + * @return std::nullopt on empty line or parse error; ParsedCommand otherwise * */ std::optional Sequence::parse_command(std::string &args) { + const std::string function("Sequencer::Sequence::parse_command"); std::istringstream iss(args); std::string word; - // first word is the command + // first word is the subsystem (the command); empty line returns nullopt if (!(iss >> word)) return std::nullopt; ParsedCommand command; - command.name = word; + command.subsystem = word; - // Additional words are either key=val params (INTERNAL) or positional - // args (PASSTHROUGH). Both collections are populated; each op_builder - // chooses which to consume. while (iss >> word) { - // key=value parameters auto eq = word.find('='); - if (eq != std::string::npos) { - std::string key = word.substr(0, eq); - std::string val = word.substr(eq+1); - command.params.map[key] = val; + + if (eq == std::string::npos) { + logwrite(function, "ERROR bare token '"+word+"' is invalid" + " (key=value required) in line: "+args); + return std::nullopt; } - // positional arguments - else { - command.args.push_back(word); + + std::string key = word.substr(0, eq); + std::string val = word.substr(eq+1); + + if (key.empty()) { + logwrite(function, "ERROR empty key in token '"+word+"' in line: "+args); + return std::nullopt; } + if (val.empty()) { + logwrite(function, "ERROR empty value for key '"+key+"' in line: "+args); + return std::nullopt; + } + + if (key == std::string(DSL_KEY_DO)) { + if (!command.verb.empty()) { + logwrite(function, "ERROR duplicate "+std::string(DSL_KEY_DO) + +"= key in line: "+args); + return std::nullopt; + } + command.verb = val; + continue; + } + + if (command.params.contains(key)) { + logwrite(function, "ERROR duplicate key '"+key+"' in line: "+args); + return std::nullopt; + } + + command.params.set(key, val); } return command; @@ -745,7 +775,13 @@ namespace Sequencer { logwrite(function, "ERROR parsing '"+args+"'"); return ERROR; } - auto commands = { *parsed }; + // Wrap the single command in an implicit serial group so build_sequence() + // accepts it without requiring an explicit serial:/end block in the input. + ParsedCommand group_open; + group_open.subsystem = "serial:"; + ParsedCommand group_close; + group_close.subsystem = "end"; + std::vector commands = { group_open, *parsed, group_close }; std::vector sequence; @@ -917,6 +953,28 @@ namespace Sequencer { /***** Sequencer::Sequence::publish_snapshot *******************************/ + /***** Sequencer::Sequence::request_snapshot ********************************/ + /** + * @brief asks other daemons to publish their current status + * @details Publishes a Topic::SNAPSHOT message containing the names of all + * daemons this sequencer subscribes to. Each named daemon will + * respond by re-publishing its own snapshot, ensuring the sequencer + * receives current state (e.g. camera readiness) even if it + * started after those daemons published their initial status. + * + */ + void Sequence::request_snapshot() { + nlohmann::json jmessage; + jmessage[Daemon::CAMERAD] = true; + jmessage[Daemon::ACAMD] = true; + jmessage[Daemon::SLICECAMD] = true; + jmessage[Daemon::SLITD] = true; + jmessage[Daemon::TCSD] = true; + this->publisher->publish( jmessage, Topic::SNAPSHOT ); + } + /***** Sequencer::Sequence::request_snapshot ********************************/ + + /***** Sequencer::Sequence::publish_seqstate ********************************/ /** * @brief publishes sequencer state under Topic::SEQ_SEQSTATE @@ -2574,6 +2632,10 @@ namespace Sequencer { std::stringstream message; long error=NO_ERROR; + // no telescope moves for calibration targets + // + if ( this->target.iscal ) return NO_ERROR; + ScopedState thr_state( thread_state_manager, Sequencer::THR_MOVE_TO_TARGET ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCS ); ScopedState wait_moveto( wait_state_manager, Sequencer::SEQ_WAIT_MOVETO ); @@ -2955,25 +3017,23 @@ namespace Sequencer { ScopedState thr_state( this->thread_state_manager, Sequencer::THR_ABORT_PROCESS ); - // Determine post-abort seqstate based on current seqstate, before we - // enter SEQ_ABORTING. Aborting during RUNNING or PAUSED returns to READY; - // aborting during STARTING or STOPPING is unsafe and transitions to FAILED. - // Otherwise, leave seqstate unchanged (abort bit only; no destruct bit). - const bool abort_during_run = seq_state_manager.are_any_set( Sequencer::SEQ_RUNNING, - Sequencer::SEQ_PAUSED ); - const bool abort_during_lifecycle = seq_state_manager.are_any_set( Sequencer::SEQ_STARTING, - Sequencer::SEQ_STOPPING ); - - // Scoped SEQ_ABORTING bit: set on entry, cleared on exit (RAII). - ScopedState seq_state( this->seq_state_manager, Sequencer::SEQ_ABORTING ); - if ( abort_during_run ) { - seq_state.destruct_set( Sequencer::SEQ_READY ); - } - else if ( abort_during_lifecycle ) { - seq_state.destruct_set( Sequencer::SEQ_FAILED ); - } + // Decide post-abort seqstate before entering SEQ_ABORTING. These snapshots + // must be taken before any seqstate mutation below, because set_only() + // clears all other lifecycle bits. + // + const bool abort_during_run = this->seq_state_manager.are_any_set( Sequencer::SEQ_RUNNING, + Sequencer::SEQ_PAUSED ); + const bool abort_during_lifecycle = this->seq_state_manager.are_any_set( Sequencer::SEQ_STARTING, + Sequencer::SEQ_STOPPING ); - clear_cancel_flag(); + // Enter SEQ_ABORTING as a strict one-hot state. + // + this->seq_state_manager.set_only( {Sequencer::SEQ_ABORTING} ); + + // Clear any prior cancel state so stop_exposure and acquisition-stop calls + // below run to completion rather than bailing early on a stale flag. + // + this->cancel_flag.store(false); // stop any exposure that may be in progress // @@ -2985,15 +3045,62 @@ namespace Sequencer { logwrite( function, "ERROR stop_exposure exception: "+std::string(e.what()) ); } + // aborts incomplete acquisitions in progress + // + if (this->wait_state_manager.is_set(Sequencer::SEQ_WAIT_FINEACQUIRE) && + this->do_slicecam_stop() != NO_ERROR ) { + this->broadcast.warning(function, "stopping fine acquisition"); + } + if (this->wait_state_manager.is_set(Sequencer::SEQ_WAIT_ACAM_ACQUIRE) && + this->do_acam_stop() != NO_ERROR ) { + this->broadcast.warning(function, "stopping guiding"); + } + // set the cancel flag to stop any cancel-able tasks // - set_cancel_flag(); + this->cancel_flag.store(true); + this->cv.notify_all(); + // Wake threads blocked on subsystem CVs so they can check cancel_flag. + { std::lock_guard lock(this->acam_mtx); this->acam_cv.notify_all(); } + { std::lock_guard lock(this->fineacquire_mtx); this->fineacquire_cv.notify_all(); } + { std::lock_guard lock(this->camerad_mtx); this->camerad_cv.notify_all(); } // drop into do-one to prevent auto increment to next target // this->do_once.store(true); this->broadcast.notice( function, "cancel signal sent" ); + + // Wait for sequence_start to fully exit before switching to SEQ_READY. + // Without this, we could have SEQ_READY set while THR_SEQUENCE_START is + // still set. Workers just received cancel_flag + CV notifications above, + // so this loop exits after at most a few iterations. + // + if ( abort_during_run ) { + const auto drain_timeout = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while ( this->thread_state_manager.is_set( Sequencer::THR_SEQUENCE_START ) && + std::chrono::steady_clock::now() < drain_timeout ) { + std::this_thread::sleep_for( std::chrono::milliseconds(20) ); + } + if ( this->thread_state_manager.is_set( Sequencer::THR_SEQUENCE_START ) ) { + logwrite( function, "WARNING: sequence_start did not exit within drain timeout" ); + } + } + + // Exit SEQ_ABORTING to a strict one-hot terminal state chosen from the + // snapshot taken at entry. If neither condition applies (e.g. abort + // invoked while READY/NOTREADY/FAILED) we leave the state at NOTREADY + // so callers never see SEQ_ABORTING linger and no prior bit is retained. + // + if ( abort_during_run ) { + this->seq_state_manager.set_only( {Sequencer::SEQ_READY} ); + } + else if ( abort_during_lifecycle ) { + this->seq_state_manager.set_only( {Sequencer::SEQ_FAILED} ); + } + else { + this->seq_state_manager.set_only( {Sequencer::SEQ_NOTREADY} ); + } } /***** Sequencer::Sequence::stop_exposure *********************************/ diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 64be1376..31cbc0ec 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -35,10 +35,8 @@ #include "sequencerd_commands.h" #include "message_keys.h" #include "thread_pool.h" -/*** Work-In-Progress - * #include "command.h" - * #include "command_rules.h" - */ +#include "command.h" +#include "command_rules.h" #include "tcs_constants.h" #include "acam_interface_shared.h" @@ -53,6 +51,17 @@ namespace Sequencer { constexpr size_t NTHREADS = 10; ///< number of simultaneous operation threads + /** + * @brief DSL reserved keys + * @details DSL_KEY_DO is globally reserved at parse level. It is extracted + * to ParsedCommand::verb and never stored in params. No daemon + * parameter may use this name. + * DSL_KEY_ACTION is semantic-level only — it stays in params and + * is consumed by build_sequence() in the on_error context. + */ + inline constexpr std::string_view DSL_KEY_DO { "do" }; + inline constexpr std::string_view DSL_KEY_ACTION { "action" }; + /** * @enum ErrorCodes * @brief @@ -342,6 +351,31 @@ namespace Sequencer { return map.find(key) != map.end(); } + /** @brief alias for has() — provided for STL-style readability */ + bool contains(const std::string &key) const { + return has(key); + } + + /** @brief throwing accessor — caller must guarantee key presence */ + const std::string &at(const std::string &key) const { + return map.at(key); + } + + /** @brief number of stored key=value pairs */ + size_t size() const { + return map.size(); + } + + /** @brief iteration — ordering unspecified (unordered_map). + * Use only for single-entry access or explicit key checks. */ + auto begin() const { return map.begin(); } + auto end() const { return map.end(); } + + /** @brief insert or overwrite a key=value pair */ + void set(const std::string &key, const std::string &val) { + map[key] = val; + } + template T get(const std::string &key, const T &default_val) const { auto it = map.find(key); @@ -391,14 +425,35 @@ namespace Sequencer { }; /** @brief associates a sequencer command with its parameters + * @details A DSL line of the form + * command [key1=val1 key2=val2 ... keyN=valN] + * parses to: + * subsystem = command (first token) + * verb = value of the reserved do= key, or "" if absent + * params = the remaining key=value pairs (do= removed) + * Block keywords (parallel:, serial:, end, on_error) are + * intercepted by build_sequence() before op_builder dispatch, + * so subsystem here always names a real subsystem after that + * filter has run. */ struct ParsedCommand { - std::string name; - OperationParams params; ///< key=value pairs (for Internal Operations) - std::vector args; ///< positional args (for Passthrough Operations) + std::string subsystem; + std::string verb; + OperationParams params; int linenum{0}; }; + /** @brief DSL -> daemon Command translation helpers + * @details Implemented in operation_builders.cpp. Static because they + * do not depend on Sequence state; declared as members so + * they have access to the private ParsedCommand type. + */ + static void validate_keys( const ParsedCommand &cmd, + std::initializer_list allowed ); + static Command build_generic_command( const ParsedCommand &cmd ); + static Command build_powerd_command( const ParsedCommand &cmd ); + static Command translate_command( const ParsedCommand &cmd ); + /** @brief key=value params of the currently executing operation * @details set by run() just before op.func() is invoked and consumed * by Sequence:: member functions that accept optional overrides @@ -537,6 +592,10 @@ namespace Sequencer { inline void set_cancel_flag() { this->cancel_flag.store(true, std::memory_order_release); this->cv.notify_all(); + // Wake threads blocked on subsystem CVs so they can observe the cancel_flag. + { std::lock_guard lock(this->camerad_mtx); this->camerad_cv.notify_all(); } + { std::lock_guard lock(this->acam_mtx); this->acam_cv.notify_all(); } + { std::lock_guard lock(this->fineacquire_mtx); this->fineacquire_cv.notify_all(); } } double acquisition_timeout; ///< timeout for target acquisition (in sec) set by configuration parameter ACAM_ACQUIRE_TIMEOUT @@ -615,12 +674,47 @@ namespace Sequencer { Common::DaemonClient slitd { "slitd" }; Common::DaemonClient tcsd { "tcsd" }; -/**** Work-In-Progress - CommandClient camerad_cmd { camerad, - camerad_specs, - CameraState::IDLE, - camerad_transitions}; -*****/ + // CommandClient wrappers add per-daemon arg-count checks and + // state-machine validation in front of the DaemonClient TCP send. + // Initial state is IDLE for every daemon -- they transition to READY + // after the first successful "open" (or equivalent) command. + // + CommandClient acamd_cmd { acamd, acamd_specs, + AcamState::IDLE, + acamd_transitions, + acamd_state_names }; + CommandClient calibd_cmd { calibd, calibd_specs, + CalibState::IDLE, + calibd_transitions, + calibd_state_names }; + CommandClient camerad_cmd { camerad, camerad_specs, + CameraState::IDLE, + camerad_transitions, + camerad_state_names }; + CommandClient flexured_cmd { flexured, flexured_specs, + FlexureState::IDLE, + flexured_transitions, + flexured_state_names }; + CommandClient focusd_cmd { focusd, focusd_specs, + FocusState::IDLE, + focusd_transitions, + focusd_state_names }; + CommandClient powerd_cmd { powerd, powerd_specs, + PowerdState::IDLE, + powerd_transitions, + powerd_state_names }; + CommandClient slicecamd_cmd { slicecamd, slicecamd_specs, + SlicecamState::IDLE, + slicecamd_transitions, + slicecamd_state_names }; + CommandClient slitd_cmd { slitd, slitd_specs, + SlitState::IDLE, + slitd_transitions, + slitd_state_names }; + CommandClient tcsd_cmd { tcsd, tcsd_specs, + TcsState::IDLE, + tcsd_transitions, + tcsd_state_names }; std::map power_switch; ///< STL map of PowerSwitch objects maps all plugnames to each subsystem @@ -663,6 +757,7 @@ namespace Sequencer { void handletopic_tcsd( const nlohmann::json &jmessage ); void publish_snapshot(); void publish_snapshot(std::string &retstring); + void request_snapshot(); void publish_seqstate(); void publish_waitstate(); void publish_daemonstate(); diff --git a/sequencerd/sequence_builder.cpp b/sequencerd/sequence_builder.cpp index 960ca9a9..8191b1ff 100644 --- a/sequencerd/sequence_builder.cpp +++ b/sequencerd/sequence_builder.cpp @@ -44,7 +44,7 @@ namespace Sequencer { // for (const auto &command : commands) { - const std::string &name = command.name; + const std::string &name = command.subsystem; // ---------- SPECIAL COMMAND { parallel: | serial: } ------------------ diff --git a/sequencerd/sequencerd.cpp b/sequencerd/sequencerd.cpp index f7c76c3e..df721744 100644 --- a/sequencerd/sequencerd.cpp +++ b/sequencerd/sequencerd.cpp @@ -141,6 +141,7 @@ int main(int argc, char **argv) { std::this_thread::sleep_for( std::chrono::milliseconds(200) ); sequencerd.sequence.publish_snapshot(); + sequencerd.sequence.request_snapshot(); // This will pre-thread N_THREADS threads. // The 0th thread is reserved for the blocking port, and the rest are for the non-blocking port. diff --git a/utils/utilities.h b/utils/utilities.h index 41dcb866..87a21899 100644 --- a/utils/utilities.h +++ b/utils/utilities.h @@ -152,7 +152,7 @@ static inline void rtrim(std::string &s) { /// trim off trailing whites s.erase( std::find_if( s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); } ).base(), s.end() ); } static inline void ltrim(std::string &s) { /// trim off leading white space - s.erase(std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); } ) ); + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); } ) ); } static inline void lrtrim(std::string &s) { rtrim(s); ltrim(s); } From 5c7b79ee8544bdaa2399ea25f92ec17c4c06d989 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 11:36:26 -0700 Subject: [PATCH 26/35] places acam startup in parallel group, while keeping slicecam in a separate serial group following that. Should speed-up startup while maintaining serialization of camera inits --- sequencerd/sequence.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index d7b0f871..b44cc991 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -3389,11 +3389,12 @@ namespace Sequencer { ops.flexure_init(), ops.focus_init(), ops.slit_init(), - ops.tcs_init() } + ops.tcs_init(), + ops.acam_init() } // ACAM camera-open overlaps with parallel group }, + // SLICECAM can't be in parallel with ACAM. separate group enforces this { OperationType::SERIAL, OnError::CONTINUE, - { ops.acam_init(), - ops.slicecam_init() } + { ops.slicecam_init() } } }; From f68f8212353758f6e83286e7a69c01a012705127 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 11:52:23 -0700 Subject: [PATCH 27/35] fixes case where a parallel group could still proceed after error --- sequencerd/sequence.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index b44cc991..d1398333 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -233,8 +233,10 @@ namespace Sequencer { ret = futures[i].get(); oss << ops[i].name(); if (ret==ABORT) { is_abort=true; oss << " cancelled"; } - else - oss << " completed" << ( (ret==NO_ERROR) ? "" : " with error"); + else { + if (ret != NO_ERROR) is_error=true; + oss << " completed" << ( (ret==NO_ERROR) ? "" : " with error"); + } } catch (const std::exception &e) { oss << " received exception: " << e.what(); From 9fcb2f10d001b51d681958634f6c75e0560ee8b6 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 12:29:51 -0700 Subject: [PATCH 28/35] fixes state on shutdown --- sequencerd/sequence.cpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index d1398333..db8e5290 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -3431,13 +3431,21 @@ namespace Sequencer { ScopedState thr_state( this->thread_state_manager, Sequencer::THR_SHUTDOWN ); // this thread is running - // set only STOPPING (and clear everything else, including any prior FAILED) - ScopedState seq_state( seq_state_manager, Sequencer::SEQ_STOPPING, true ); // state=STOPPING (only) + // Enter STOPPING as a strict one-hot state. Explicit set_only is used instead + // of ScopedState because abort_process() below transitions seqstate + // independently + // + this->seq_state_manager.set_only( {Sequencer::SEQ_STOPPING} ); - // stop everything + // stop everything in progress // this->abort_process(); + // abort_process snapshots abort_during_lifecycle=true (STOPPING was set) and + // ends with set_only({SEQ_FAILED}). Restore STOPPING so shutdown can proceed. + // + this->seq_state_manager.set_only( {Sequencer::SEQ_STOPPING} ); + // clear stop flags // clear_cancel_flag(); @@ -3472,17 +3480,22 @@ namespace Sequencer { }, function ); std::ostringstream message; - if (error==NO_ERROR) { // TODO need granularity here + if (error==NO_ERROR) { message << "NOTICE: instrument is shut down"; - seq_state.destruct_set( Sequencer::SEQ_NOTREADY ); // clean shutdown -> NOTREADY } else { message << "ERROR occurred during shutdown and may not have completed"; - seq_state.destruct_set( Sequencer::SEQ_FAILED ); // bad shutdown -> FAILED } this->async.enqueue_and_log( function, message.str() ); + // Always end in NOTREADY regardless of worker errors. SEQ_FAILED is + // reserved for startup failures and externally-aborted lifecycle transitions. + // Worker errors during shutdown are logged above but do not prevent + // the instrument from being considered shut down (not ready). + // + this->seq_state_manager.set_only( {Sequencer::SEQ_NOTREADY} ); + return error; } /***** Sequencer::Sequence::shutdown ****************************************/ From 082e822a3d04238839dbad9ef234b3526f8081f7 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 12:35:06 -0700 Subject: [PATCH 29/35] . --- sequencerd/sequence.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index db8e5290..58b813a6 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -3431,20 +3431,20 @@ namespace Sequencer { ScopedState thr_state( this->thread_state_manager, Sequencer::THR_SHUTDOWN ); // this thread is running - // Enter STOPPING as a strict one-hot state. Explicit set_only is used instead - // of ScopedState because abort_process() below transitions seqstate - // independently + // Enter STOPPING as a strict one-hot state. Explicit set_only is used + // instead of ScopedState because final state is set explicitly before return. // this->seq_state_manager.set_only( {Sequencer::SEQ_STOPPING} ); - // stop everything in progress + // Wake any threads waiting on subsystem CVs so they can exit cleanly. + // Inline here rather than calling abort_process(), which transitions + // seqstate through ABORTING and FAILED — confusing during a normal shutdown. // - this->abort_process(); - - // abort_process snapshots abort_during_lifecycle=true (STOPPING was set) and - // ends with set_only({SEQ_FAILED}). Restore STOPPING so shutdown can proceed. - // - this->seq_state_manager.set_only( {Sequencer::SEQ_STOPPING} ); + this->cancel_flag.store(true); + this->cv.notify_all(); + { std::lock_guard lock(this->acam_mtx); this->acam_cv.notify_all(); } + { std::lock_guard lock(this->fineacquire_mtx); this->fineacquire_cv.notify_all(); } + { std::lock_guard lock(this->camerad_mtx); this->camerad_cv.notify_all(); } // clear stop flags // From e811ab8cce7c661602e948bdccdf5e8cc4ecdc0f Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 13:51:51 -0700 Subject: [PATCH 30/35] fixes bug preventing wait_for_user_continue on fine acquire failure --- sequencerd/sequence.cpp | 2 +- sequencerd/sequence.h | 1 + sequencerd/sequence_acquisition.cpp | 47 ++++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 58b813a6..eff8ca49 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -358,7 +358,7 @@ namespace Sequencer { [this,caller]() { return this->do_target_acquisition(caller); } }, { THR_MOVE_TO_TARGET, - [this]() { return this->target_offset(); } }, + [this,caller]() { return this->do_target_offset(caller); } }, { THR_SLIT_SET, [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } }, diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 31cbc0ec..01eb2f42 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -875,6 +875,7 @@ namespace Sequencer { long do_slicecam_fineacquire(); long do_slicecam_stop(); long do_target_acquisition(std::string caller); + long do_target_offset(std::string caller); long do_target_virtualslit(VirtualSlitMode mode); long acam_init(); ///< initializes connection to acamd diff --git a/sequencerd/sequence_acquisition.cpp b/sequencerd/sequence_acquisition.cpp index affbf926..30458fbb 100644 --- a/sequencerd/sequence_acquisition.cpp +++ b/sequencerd/sequence_acquisition.cpp @@ -272,7 +272,9 @@ namespace Sequencer { /***** Sequencer::Sequence::do_target_acquisition ****************************/ /** * @brief performs target acquisition - * @details First acquire on ACAM, then run slicecam fineacquire + * @details First acquire on ACAM, then run slicecam fineacquire if enabled. + * Waits for user confirmation when fineacquire is disabled or fails. + * @param[in] caller name of calling function for logging * @return NO_ERROR | ABORT * */ @@ -284,18 +286,20 @@ namespace Sequencer { // if ( this->do_acam_acquire() != NO_ERROR ) { this->broadcast.warning( caller, "acam acquisition failed" ); - - // on ACAM acquisition failure wait for user to continue or cancel if ( this->wait_for_user(caller) == ABORT ) return ABORT; - return NO_ERROR; // user chose to continue } // ---------- SLICECAM fineacquire --------------------- // - if ( this->do_slicecam_fineacquire() != NO_ERROR ) { - this->broadcast.warning( caller, "slicecam fine acquisition failed" ); - } + bool dofine = this->should_fineacquire.load(); + long ret = NO_ERROR; + if ( dofine ) ret = this->do_slicecam_fineacquire(); + if ( ret != NO_ERROR ) this->broadcast.warning( caller, "slicecam fine acquisition failed" ); + + // wait for user if fineacquire was disabled or failed + if ( (!dofine || ret != NO_ERROR) && + this->wait_for_user(caller) == ABORT ) return ABORT; return NO_ERROR; } @@ -306,7 +310,7 @@ namespace Sequencer { /** * @brief move to virtual slit position * @param[in] mode VirtualSlitMode - * @return NO_ERROR | NO_ERROR + * @return NO_ERROR * */ long Sequence::do_target_virtualslit(VirtualSlitMode mode) { @@ -315,4 +319,31 @@ namespace Sequencer { } /***** Sequencer::Sequence::do_target_virtualslit ****************************/ + + /***** Sequencer::Sequence::do_target_offset *********************************/ + /** + * @brief send guide offsets to the TCS after successful fine acquisition + * @details Offsets are only sent when fineacquire achieved a lock. + * If the offset fails, the operator is given the opportunity to + * continue or cancel. + * @param[in] caller name of calling function for logging + * @return NO_ERROR | ABORT + * + */ + long Sequence::do_target_offset(std::string caller) { + + if (this->target.iscal) return NO_ERROR; + + // offsets are only meaningful when fineacquire achieved a lock + if ( !this->is_fineacquire_locked.load() ) return NO_ERROR; + + if ( this->target_offset() == ERROR ) { + this->broadcast.warning( caller, "target offset failed" ); + if ( this->wait_for_user(caller) == ABORT ) return ABORT; + } + + return NO_ERROR; + } + /***** Sequencer::Sequence::do_target_offset *********************************/ + } From 620a5874034e48cad6c3eef014dfb599a6e28ef4 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 16:28:23 -0700 Subject: [PATCH 31/35] incorporates bug fixes from PR409-- * fix: camerad was missing topic handler for SNAPSHOT * fix: Sequence::camera_set could not always set wait_state * fix: sequencer PAUSE command checked seq_state_manager for wait state --- camerad/astrocam.cpp | 15 +++++++++++++++ camerad/astrocam.h | 6 ++++++ camerad/simulator-arc.cpp | 30 +++++++++++++++--------------- sequencerd/sequence.cpp | 6 +++--- sequencerd/sequencer_server.cpp | 2 +- sequencerd/sequencerd.cpp | 3 ++- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index fb2ddea2..4757b159 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -51,6 +51,21 @@ namespace AstroCam { /**** AstroCam::Interface::publish_snapshot *********************************/ + /***** AstroCam::Interface::handletopic_snapshot ****************************/ + /** + * @brief what to do when the topic is Topic::SNAPSHOT + * @details This publishes a JSON message containing a snapshot of my + * telemetry info when the subscriber receives the Topic::SNAPSHOT + * topic and the payload contains my name. + * @param[in] jmessage_in subscribed-received JSON message + * + */ + void Interface::handletopic_snapshot( const nlohmann::json &jmessage_in ) { + if ( jmessage_in.contains( Topic::CAMERAD ) ) this->publish_snapshot(); + } + /***** AstroCam::Interface::handletopic_snapshot ****************************/ + + long NewAstroCam::new_expose( std::string nseq_in ) { logwrite( "NewAstroCam::new_expose", nseq_in ); return( NO_ERROR ); diff --git a/camerad/astrocam.h b/camerad/astrocam.h index 4e60f415..78bde549 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -636,6 +636,11 @@ namespace AstroCam { can_expose(true), // am I ready for the next exposure? modeselected(false), useframes(true) { + topic_handlers = { + { Topic::SNAPSHOT, std::function( + [this](const nlohmann::json &msg) { handletopic_snapshot(msg); } ) } + }; + this->pFits.resize( NUM_EXPBUF ); // pre-allocate FITS_file object pointers for each exposure buffer this->fitsinfo.resize( NUM_EXPBUF ); // pre-allocate Camera Information object pointers for each exposure buffer this->writes_pending.resize( NUM_EXPBUF ); // pre-allocate writes_pending vector for each exposure buffer @@ -687,6 +692,7 @@ namespace AstroCam { void start_subscriber_thread() { Common::PubSubHandler::start_subscriber_thread(*this); } void stop_subscriber_thread() { Common::PubSubHandler::stop_subscriber_thread(*this); } void publish_snapshot(std::string* retstring=nullptr); + void handletopic_snapshot(const nlohmann::json &jmessage_in); // vector of pointers to Camera Information containers, one for each exposure number // diff --git a/camerad/simulator-arc.cpp b/camerad/simulator-arc.cpp index a85f4a6c..dece6c3b 100644 --- a/camerad/simulator-arc.cpp +++ b/camerad/simulator-arc.cpp @@ -43,7 +43,7 @@ namespace AstroCam { // If no string is given then use vector of configured devices // if ( devices_in.empty() ) { - this->devnums = this->configured_devnums; + this->connected_devnums = this->configured_devnums; } else { // Otherwise, tokenize the device list string and build devnums from the tokens @@ -53,8 +53,8 @@ namespace AstroCam { for ( const auto &n : tokens ) { // For each token in the devices_in string, try { int dev = std::stoi( n ); // convert to int - if ( std::find( this->devnums.begin(), this->devnums.end(), dev ) == this->devnums.end() ) { // If it's not already in the vector, - this->devnums.push_back( dev ); // then push into devnums vector. + if ( std::find( this->connected_devnums.begin(), this->connected_devnums.end(), dev ) == this->connected_devnums.end() ) { // If it's not already in the vector, + this->connected_devnums.push_back( dev ); // then push into devnums vector. } } catch (std::invalid_argument &) { @@ -76,7 +76,7 @@ namespace AstroCam { // For each requested dev in devnums, if there is a matching controller in the config file, // then get the devname and store it in the controller map. // - for ( const auto &dev : this->devnums ) { + for ( const auto &dev : this->connected_devnums ) { if ( this->controller.find( dev ) != this->controller.end() ) { this->controller[ dev ].devname = "sim"+std::to_string(dev); } @@ -84,7 +84,7 @@ namespace AstroCam { // set the controller connected state true // - for ( const auto &dev : this->devnums ) { + for ( const auto &dev : this->connected_devnums ) { this->controller[dev].connected = true; } @@ -110,7 +110,7 @@ namespace AstroCam { // clear the controller connected state // - for ( const auto &dev : this->devnums ) { + for ( const auto &dev : this->connected_devnums ) { this->controller[dev].connected = false; } @@ -150,7 +150,7 @@ namespace AstroCam { std::stringstream lodfilestream; // But only use it if the device is open // - if ( std::find( this->devnums.begin(), this->devnums.end(), fw->first ) != this->devnums.end() ) { + if ( std::find( this->connected_devnums.begin(), this->connected_devnums.end(), fw->first ) != this->connected_devnums.end() ) { lodfilestream << fw->first << " " << fw->second; // Call do_load_firmware with the built up string. @@ -452,14 +452,14 @@ namespace AstroCam { } /***** AstroCam::Interface::native ******************************************/ - - long Interface::_image_size( std::string args, std::string &retstring, const bool save_as_default ) { - std::string function = "AstroCam::Interface::_image_size"; - std::stringstream message; - logwrite( function, "NOP" ); - return( NO_ERROR ); - } - +/* + *long Interface::_image_size( std::string args, std::string &retstring, const bool save_as_default ) { + * std::string function = "AstroCam::Interface::_image_size"; + * std::stringstream message; + * logwrite( function, "NOP" ); + * return( NO_ERROR ); + *} + */ /***** AstroCam::Simulator::dothread_expose *********************************/ /** diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index eff8ca49..478972d0 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -1486,13 +1486,13 @@ namespace Sequencer { std::stringstream camcmd; long error=NO_ERROR; + ScopedState thr_state( thread_state_manager, Sequencer::THR_CAMERA_SET ); + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); + this->wait_for_canexpose(function); logwrite( function, "setting camera parameters"); - ScopedState thr_state( thread_state_manager, Sequencer::THR_CAMERA_SET ); - ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); - this->thread_error_manager.set( THR_CAMERA_SET ); // assume the worse, clear on success // Controller activate states stored in Sequencer::CalibrationTarget::calinfo map, diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index 5920b3ce..ca6e32ee 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -1509,7 +1509,7 @@ namespace Sequencer { if ( cmd == SEQUENCERD_PAUSE ) { // Can only pause during an exposure // - if ( ! this->sequence.seq_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) { + if ( ! this->sequence.wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) { this->sequence.async.enqueue_and_log( function, "ERROR: can only pause during an active exposure" ); ret = ERROR; } diff --git a/sequencerd/sequencerd.cpp b/sequencerd/sequencerd.cpp index df721744..7125b3a6 100644 --- a/sequencerd/sequencerd.cpp +++ b/sequencerd/sequencerd.cpp @@ -139,8 +139,9 @@ int main(int argc, char **argv) { } sequencerd.sequence.seq_state_manager.set_only({Sequencer::SEQ_NOTREADY}); - std::this_thread::sleep_for( std::chrono::milliseconds(200) ); + std::this_thread::sleep_for( std::chrono::milliseconds(250) ); sequencerd.sequence.publish_snapshot(); + std::this_thread::sleep_for( std::chrono::milliseconds(250) ); sequencerd.sequence.request_snapshot(); // This will pre-thread N_THREADS threads. From 4aa1b3a9a1891c697357b7a9aa456c6a8c6bf0ae Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 16:41:30 -0700 Subject: [PATCH 32/35] another from PR409-- * fixes binning bug * fixes make_directory bug --- camerad/astrocam.cpp | 25 ++++++++++++++++++++----- utils/make_directories.cpp | 18 +++++++++--------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 4757b159..4e37df34 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -5077,6 +5077,21 @@ logwrite(function, message.str()); // if (pcontroller->has_boi()) { skipspat = 0; } + // Capture the unbinned, unskipped detector dimensions before trimming + // so that detrows/detcols remain the full requested size across + // repeated bin commands. These are preserved even when the per-call + // geometry is shortened by the skip-modulo adjustment below. + // + int detrows_new, detcols_new; + pcontroller->logical_to_physical(spat, spec, detrows_new, detcols_new); + + // Capture the original requested overscan in physical (row/col) form + // before any modulo-binning trim below, so that osrows0/oscols0 retain + // the true requested overscan across repeated bin commands. + // + int osrows0_new, oscols0_new; + pcontroller->logical_to_physical(osspat, osspec, osrows0_new, oscols0_new); + // Remove those skipped pixels from the image size spat -= skipspat; spec -= skipspec; @@ -5100,10 +5115,10 @@ logwrite(function, message.str()); // unchanged by binning, so that when reverting to binning=1 from some // binnnig factor, this is the default image size to revert to. // - pcontroller->detrows = rows; - pcontroller->detcols = cols; - pcontroller->osrows0 = osrows; - pcontroller->oscols0 = oscols; + pcontroller->detrows = detrows_new; + pcontroller->detcols = detcols_new; + pcontroller->osrows0 = osrows0_new; + pcontroller->oscols0 = oscols0_new; pcontroller->skipcols = skipcols; pcontroller->skiprows = skiprows; @@ -5852,7 +5867,7 @@ logwrite(function, message.str()); int &spat, int &spec, int &osspat, int &osspec, int &binspat, int &binspec) { if (!pcontroller) return; pcontroller->physical_to_logical( pcontroller->detrows, pcontroller->detcols, spat, spec ); - pcontroller->physical_to_logical( pcontroller->osrows, pcontroller->oscols, osspat, osspec ); + pcontroller->physical_to_logical( pcontroller->osrows0, pcontroller->oscols0, osspat, osspec ); pcontroller->physical_to_logical( pcontroller->info.binning[_ROW_], pcontroller->info.binning[_COL_], binspat, binspec ); } diff --git a/utils/make_directories.cpp b/utils/make_directories.cpp index 99c39aaf..b04fdb83 100644 --- a/utils/make_directories.cpp +++ b/utils/make_directories.cpp @@ -17,23 +17,23 @@ namespace fs = std::filesystem; std::string get_date() { std::stringstream current_date; // String to contain the return value std::time_t t=std::time(nullptr); // Container for system time - struct timespec timenow;; // Time of day container struct tm mytime; // time container - // Get the system time, return a bad datestamp on error - if ( clock_gettime( CLOCK_REALTIME, &timenow ) != 0 ) return( "" ); + // UTC now + if ( gmtime_r( &t, &mytime ) == nullptr ) return( "" ); - // Convert the time of day to local or GMT - t = timenow.tv_sec; - if ( localtime_r( &t, &mytime ) == nullptr ) return( "" ); + // at local noon we want tonight's UTC + mytime.tm_mday += 1; + + // normalize struct handles rollovers + if (timegm(&mytime)==-1) return ""; - current_date.setf(std::ios_base::right); current_date << std::setfill('0') << std::setprecision(0) << std::setw(4) << mytime.tm_year + 1900 << std::setw(2) << mytime.tm_mon + 1 - << std::setw(2) << mytime.tm_mday + 1; // tomorrow! + << std::setw(2) << mytime.tm_mday; - return( current_date.str() ); + return current_date.str(); } int main() { From dfa18f6ce3ae07822377eb94137dac15d2837a33 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 21:17:36 -0700 Subject: [PATCH 33/35] cleaned up broadcast and log messages (had already done this in fineacquire-update-sequencer-pub branch, now repeated here) --- sequencerd/sequence.cpp | 277 ++++++++++++------------------- sequencerd/sequence.h | 2 - sequencerd/sequencer_server.cpp | 279 +++++++++++++------------------- sequencerd/sequencer_server.h | 1 - sequencerd/sequencerd.cpp | 8 +- 5 files changed, 223 insertions(+), 344 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 478972d0..219126e8 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -656,7 +656,7 @@ namespace Sequencer { if ( required.test(bit) && !daemon_manager.is_set(static_cast(bit)) ) { std::ostringstream oss; - oss << "sequence requires daemon '" + oss << "sequence requires '" << daemon_name.at(static_cast(bit)) << "' but it is not ready"; this->broadcast.error(function, oss.str()); @@ -1364,8 +1364,7 @@ namespace Sequencer { this->dotype( "ONE" ); // single-target mode must set dotype=ONE } - message.str(""); message << "NOTICE: " << targetstatus; - this->async.enqueue( message.str() ); // broadcast target status + logwrite(function, "targetstatus: "+targetstatus); if ( targetstate == TargetInfo::TARGET_FOUND ) { // target found, get the threads going @@ -1374,9 +1373,9 @@ namespace Sequencer { // if ( ! this->daemon_manager.is_set( Sequencer::DAEMON_TCS ) ) { if ( ! this->target.ra_hms.empty() || ! this->target.dec_dms.empty() ) { - message.str(""); message << "ERROR cannot move to target " << this->target.name + message.str(""); message << "cannot move to target " << this->target.name << " because TCS is not connected"; - this->async.enqueue_and_log( function, message.str() ); + this->broadcast.error( function, message.str() ); this->thread_error_manager.set( THR_SEQUENCE_START ); // report error break; } @@ -1387,20 +1386,10 @@ namespace Sequencer { this->thread_error_manager.set( THR_SEQUENCE_START ); // report any error break; } - - // let the world know of the state change - // - message.str(""); message << "TARGETSTATE:" << this->target.state - << " TARGET:" << this->target.name - << " OBSID:" << this->target.obsid; - this->async.enqueue( message.str() ); -#ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] target found, starting threads" ); -#endif } else // targetstate not TARGET_FOUND if ( targetstate == TargetInfo::TARGET_NOT_FOUND ) { // no target found is an automatic stop - logwrite( function, "NOTICE: no targets found. stopping" ); + this->broadcast.notice( function, "no targets found. stopping" ); break; } else @@ -1425,21 +1414,10 @@ namespace Sequencer { // Update this target's state in the database // - if (error==NO_ERROR) error = this->target.update_state( Sequencer::TARGET_COMPLETE ); - else - if (error==ABORT) error = this->target.update_state( Sequencer::TARGET_UNASSIGNED ); - + error = this->target.update_state( Sequencer::TARGET_COMPLETE ); // update the active target table if (error==NO_ERROR) error = this->target.insert_completed(); // insert into the completed table if (error!=NO_ERROR) this->thread_error_manager.set( THR_SEQUENCE_START ); // report any error - // let the world know of the state change - // - std::ostringstream oss; - oss << "TARGETSTATE:" << this->target.state - << " TARGET:" << this->target.name - << " OBSID:" << this->target.obsid; - this->async.enqueue_and_log(function, oss.str()); - // abort sequence on error // if ( this->thread_error_manager.are_any_set() ) break; @@ -1513,14 +1491,14 @@ namespace Sequencer { std::string cmd = CAMERAD_ACTIVATE + activechans.str(); /*** if ( camerad_cmd.send( { CAMERAD_ACTIVATE, { activechans.str() } } ) != NO_ERROR ) { WIP ***/ if (this->camerad.send(cmd, reply)!=NO_ERROR) { - this->broadcast.error( function, "sending \""+cmd+"\": "+reply ); + logwrite( function, "ERROR sending \""+cmd+"\": "+reply ); throw std::runtime_error("camera returned "+reply); } } if (!deactivechans.str().empty()) { std::string cmd = CAMERAD_DEACTIVATE + deactivechans.str(); if (this->camerad.send(cmd, reply)!=NO_ERROR) { - this->broadcast.error( function, "sending \""+cmd+"\": "+reply ); + logwrite( function, "ERROR sending \""+cmd+"\": "+reply ); throw std::runtime_error("camera returned "+reply); } } @@ -1533,7 +1511,7 @@ namespace Sequencer { long exptime_msec = (long)( this->target.exptime_req * 1000 ); camcmd.str(""); camcmd << CAMERAD_EXPTIME << " " << exptime_msec; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->broadcast.error( function, "sending \""+camcmd.str()+"\": "+reply ); + logwrite( function, "ERROR sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } @@ -1541,12 +1519,12 @@ namespace Sequencer { // camcmd.str(""); camcmd << CAMERAD_BIN << " spat " << this->target.binspat; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->broadcast.error( function, "sending \""+camcmd.str()+"\": "+reply ); + logwrite( function, "ERROR sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } camcmd.str(""); camcmd << CAMERAD_BIN << " spec " << this->target.binspect; if (error==NO_ERROR && (error=this->camerad.send( camcmd.str(), reply ))!=NO_ERROR) { - this->broadcast.error( function, "sending \""+camcmd.str()+"\": "+reply ); + logwrite( function, "ERROR sending \""+camcmd.str()+"\": "+reply ); throw std::runtime_error( "camera returned "+reply ); } @@ -1602,12 +1580,10 @@ namespace Sequencer { break; } - this->async.enqueue( "NOTICE: moving slit to "+modestr+" position" ); - - logwrite( function, "moving slit to "+slitcmd.str()+" for "+modestr+"position" ); + this->broadcast.notice(function, "moving slit to "+modestr+" position"); if ( this->slitd.command_timeout( slitcmd.str(), reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "setting slit" ); + logwrite( function, "ERROR setting slit" ); this->thread_error_manager.set( THR_SLIT_SET ); throw std::runtime_error("slit returned: "+reply); } @@ -1633,7 +1609,7 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_POWER ); // powerd not ready if ( this->reopen_hardware(this->powerd, POWERD_REOPEN, 10000 ) != NO_ERROR ) { - this->broadcast.error( function, "initializing power control" ); + logwrite( function, "ERROR initializing power control" ); throw std::runtime_error("could not initialize power control"); } @@ -1685,13 +1661,13 @@ namespace Sequencer { this->thread_error_manager.set( THR_SLIT_INIT ); // assume the worst, clear on success if ( this->set_power_switch(ON, POWER_SLIT, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast.error( function, "powering slit hardware" ); + logwrite( function, "ERROR powering slit hardware" ); throw std::runtime_error("could not power slit hardware"); } bool was_opened=false; if ( this->open_hardware(this->slitd, was_opened) != NO_ERROR ) { - this->broadcast.error( function, "connecting to slit" ); + logwrite( function, "ERROR connecting to slit" ); throw std::runtime_error("could not open connection to slit hardware"); } @@ -1700,7 +1676,7 @@ namespace Sequencer { bool ishomed=false; std::string reply; if ( this->slitd.command( SLITD_ISHOME, reply ) ) { - this->broadcast.error( function, "communicating with slit hardware" ); + logwrite( function, "ERROR communicating with slit hardware" ); throw std::runtime_error("could not communicate with slit hardware: "+reply); } this->parse_state( function, reply, ishomed ); @@ -1710,7 +1686,7 @@ namespace Sequencer { if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->slitd.command_timeout( SLITD_HOME, reply, SLITD_HOME_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "communicating with slit hardware" ); + logwrite( function, "ERROR communicating with slit hardware" ); throw std::runtime_error("could not home slit hardware: "+reply); } } @@ -1720,7 +1696,7 @@ namespace Sequencer { if ( was_opened && !this->config_init["SLIT"].empty() ) { std::string cmd = SLITD_SET+" "+this->config_init["SLIT"]; if ( this->slitd.command_timeout( cmd, reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "sending \""+cmd+"\" to slit" ); + logwrite( function, "ERROR sending \""+cmd+"\" to slit" ); throw std::runtime_error("slit "+cmd+" returned: "+reply); } } @@ -1774,7 +1750,7 @@ namespace Sequencer { if (error==NO_ERROR && !this->config_shutdown["SLIT"].empty() ) { std::string cmd = SLITD_SET+" "+this->config_shutdown["SLIT"]; if ( this->slitd.command_timeout( cmd, reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "sending \""+cmd+"\" to slit" ); + logwrite( function, "ERROR sending \""+cmd+"\" to slit" ); throw std::runtime_error(cmd+" returned: "+reply); } } @@ -1784,7 +1760,7 @@ namespace Sequencer { logwrite( function, "closing slit hardware" ); error = this->slitd.command( SLITD_CLOSE, reply ); if ( error != NO_ERROR ) { - this->broadcast.error( function, "closing connection to slit hardware" ); + logwrite( function, "ERROR closing connection to slit hardware" ); throw std::runtime_error("closing slit connection returned: "+reply); } @@ -1821,14 +1797,14 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_SLICECAM, std::chrono::seconds(10)) != NO_ERROR ) { - this->broadcast.error( function, "initializing slicecam control" ); + logwrite( function, "ERROR initializing slicecam control" ); throw std::runtime_error("could not power slicecam hardware"); } // open connection is all that is needed, slicecamd takes care of everything // if ( this->open_hardware(this->slicecamd, SLICECAMD_OPEN, SLICECAMD_OPEN_TIMEOUT) != NO_ERROR ) { - this->broadcast.error( function, "starting slicecam" ); + logwrite( function, "ERROR starting slicecam" ); throw SlicecamException("could not start slicecam"); } @@ -1861,7 +1837,7 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_ACAM, std::chrono::seconds(10)) != NO_ERROR ) { - this->broadcast.error( function, "powering acam hardware" ); + logwrite( function, "ERROR powering acam hardware" ); throw std::runtime_error("could not power acam hardware"); } @@ -1869,7 +1845,7 @@ namespace Sequencer { // bool was_opened=false; if ( this->open_hardware(this->acamd, ACAMD_OPEN, ACAMD_OPEN_TIMEOUT, was_opened) != NO_ERROR ) { - this->broadcast.error( function, "opening acam camera" ); + logwrite( function, "ERROR opening acam camera" ); throw AcamException(ErrorCode::ERROR_ACAM_CAMERA, "could not open acam camera"); } @@ -1880,14 +1856,14 @@ namespace Sequencer { if ( ! this->config_init["ACAM_FILTER"].empty() ) { cmd = ACAMD_FILTER+" "+this->config_init["ACAM_FILTER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "sending \""+cmd+"\" to acamd: "+reply ); + logwrite( function, "ERROR sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } if ( ! this->config_init["ACAM_COVER"].empty() ) { cmd = ACAMD_COVER+" "+this->config_init["ACAM_COVER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "sending \""+cmd+"\" to acamd: "+reply ); + logwrite( function, "ERROR sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } @@ -1936,14 +1912,14 @@ namespace Sequencer { } if ( (error=this->connect_to_daemon(this->slicecamd)) != NO_ERROR ) { - this->broadcast.error( function, "connecting to slicecamd" ); + logwrite( function, "ERROR connecting to slicecamd" ); } // close connections between slicecamd and the hardware with which it communicates // logwrite( function, "closing slicecam hardware" ); if ( (error=this->slicecamd.command_timeout( SLICECAMD_SHUTDOWN, reply, SLICECAMD_SHUTDOWN_TIMEOUT )) != NO_ERROR ) { - this->broadcast.error( function, "closing connection to slicecam hardware" ); + logwrite( function, "ERROR closing connection to slicecam hardware" ); } // disconnect me from slicecamd, irrespective of any previous error @@ -1954,7 +1930,7 @@ namespace Sequencer { // Turn off power to slicecam hardware. // if ( this->set_power_switch(OFF, POWER_SLICECAM, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast.error( function, "switching off slicecam" ); + logwrite( function, "ERROR switching off slicecam" ); throw std::runtime_error("could not power off slicecam hardware"); } @@ -1994,14 +1970,14 @@ namespace Sequencer { if ( ! this->config_shutdown["ACAM_FILTER"].empty() ) { cmd = ACAMD_FILTER+" "+this->config_shutdown["ACAM_FILTER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "sending \""+cmd+"\" to acamd: "+reply ); + logwrite( function, "ERROR sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } if ( ! this->config_shutdown["ACAM_COVER"].empty() ) { cmd = ACAMD_COVER+" "+this->config_shutdown["ACAM_COVER"]; if ( this->acamd.command_timeout( cmd, reply, ACAMD_MOVE_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "sending \""+cmd+"\" to acamd: "+reply ); + logwrite( function, "ERROR sending \""+cmd+"\" to acamd: "+reply ); throw std::runtime_error("acam "+cmd+" returned: "+reply); } } @@ -2014,7 +1990,7 @@ namespace Sequencer { if ( error==NO_ERROR ) { logwrite( function, "closing acam hardware" ); error = this->acamd.command_timeout( ACAMD_SHUTDOWN, ACAMD_SHUTDOWN_TIMEOUT ); - if ( error != NO_ERROR ) this->broadcast.error( function, "shutting down acam" ); + if ( error != NO_ERROR ) logwrite( function, "ERROR shutting down acam" ); } // disconnect me from acamd, irrespective of any previous error @@ -2025,7 +2001,7 @@ namespace Sequencer { // Turn off power to acam hardware. // if ( this->set_power_switch(OFF, POWER_ACAM, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast.error( function, "switching off acam" ); + logwrite( function, "ERROR switching off acam" ); throw std::runtime_error("could not switch off acam"); } @@ -2055,14 +2031,14 @@ namespace Sequencer { // make sure calib hardware is powered if ( this->set_power_switch(ON, POWER_CALIB, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast.error( function, "powering focus control" ); + logwrite( function, "ERROR powering focus control" ); throw std::runtime_error("could not power focus control"); } // connect to calibd bool was_opened=false; if ( this->open_hardware(this->calibd, was_opened) != NO_ERROR ) { - this->broadcast.error( function, "initializing calib control" ); + logwrite( function, "ERROR initializing calib control" ); throw std::runtime_error("could not power calib control"); } @@ -2074,14 +2050,14 @@ namespace Sequencer { std::string reply; long error = this->calibd.command( CALIBD_ISHOME, reply ); if ( error!=NO_ERROR || this->parse_state( function, reply, ishomed ) != NO_ERROR ) { - this->broadcast.error( function, "communicating with calib hardware" ); + logwrite( function, "ERROR communicating with calib hardware" ); throw std::runtime_error("could not communicate with calib hardware: "+reply); } // home calib actuators if not already homed if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->calibd.command_timeout( CALIBD_HOME, reply, CALIBD_HOME_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "communicating with calib hardware" ); + logwrite( function, "ERROR communicating with calib hardware" ); throw std::runtime_error("could not communicate with calib hardware: "+reply); } } @@ -2094,7 +2070,7 @@ namespace Sequencer { if ( !this->config_init["CALIB_DOOR"].empty() ) cmd << " door=" << this->config_init["CALIB_DOOR"]; logwrite( function, "calib default: "+cmd.str() ); if ( this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "moving calib door and/or cover" ); + logwrite( function, "ERROR moving calib door and/or cover" ); throw std::runtime_error("could not move calib door and/or cover"); } } @@ -2131,7 +2107,7 @@ namespace Sequencer { // bool poweron=false; if ( check_power_switch(ON, POWER_CALIB, poweron ) != NO_ERROR ) { - this->broadcast.error( function, "checking calib power switch" ); + logwrite( function, "ERROR checking calib power switch" ); throw std::runtime_error("checking calib power switch"); } @@ -2149,7 +2125,7 @@ namespace Sequencer { if ( !this->config_shutdown["CALIB_DOOR"].empty() ) cmd << " door=" << this->config_shutdown["CALIB_DOOR"]; logwrite( function, "calib default: "+cmd.str() ); if ( this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "moving calib door and/or cover" ); + logwrite( function, "ERROR moving calib door and/or cover" ); throw std::runtime_error("moving calib door and/or cover"); } } @@ -2162,7 +2138,7 @@ namespace Sequencer { std::string reply; logwrite( function, "closing calib hardware" ); error = this->calibd.send( CALIBD_CLOSE, reply ); - if ( error != NO_ERROR ) this->async.enqueue_and_log( function, "ERROR closing connection to calib hardware" ); + if ( error != NO_ERROR ) logwrite( function, "ERROR closing connection to calib hardware" ); } // disconnect me from calibd, irrespective of any previous error @@ -2173,14 +2149,14 @@ namespace Sequencer { // Turn off power to calib hardware. // if ( this->set_power_switch(OFF, POWER_CALIB, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast.error( function, "switching off calib hardware" ); + logwrite( function, "ERROR switching off calib hardware" ); error=ERROR; } // always turn off power to lamps // if ( this->set_power_switch(OFF, POWER_LAMP, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast.error( function, "powering off lamps" ); + logwrite( function, "ERROR powering off lamps" ); error=ERROR; } @@ -2214,7 +2190,7 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_TCS ); // tcsd not ready if ( this->open_hardware(this->tcsd) != NO_ERROR ) { - this->broadcast.error( "Sequencer::Sequence::tcs_init", "initializing TCS" ); + logwrite( "Sequencer::Sequence::tcs_init", "ERROR initializing TCS" ); this->thread_error_manager.set( THR_TCS_INIT ); throw std::runtime_error("could not initialize TCS"); } @@ -2258,7 +2234,7 @@ namespace Sequencer { std::string reply; error = this->tcsd.send( TCSD_CLOSE, reply ); if ( error != NO_ERROR ) { - this->broadcast.error( function, "closing connection to TCS" ); + logwrite( function, "ERROR closing connection to TCS" ); throw std::runtime_error("closing TCS connection: "+reply); } } @@ -2292,13 +2268,13 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_FLEXURE, std::chrono::seconds(21)) != NO_ERROR ) { - this->broadcast.error( function, "powering flexure control" ); + logwrite( function, "ERROR powering flexure control" ); this->thread_error_manager.set( THR_FLEXURE_INIT ); throw std::runtime_error("could not power flexure control"); } if ( this->open_hardware(this->flexured) != NO_ERROR ) { - this->broadcast.error( function, "initializing flexure control" ); + logwrite( function, "ERROR initializing flexure control" ); this->thread_error_manager.set( THR_FLEXURE_INIT ); throw std::runtime_error("could not initialize flexure control"); } @@ -2345,7 +2321,7 @@ namespace Sequencer { } if ( this->connect_to_daemon(this->flexured) != NO_ERROR ) { - this->broadcast.error( function, "connecting to flexure hardware" ); + logwrite( function, "ERROR connecting to flexure hardware" ); error=ERROR; } @@ -2354,7 +2330,7 @@ namespace Sequencer { // logwrite( function, "closing flexure hardware" ); if (error==NO_ERROR && (error=this->flexured.command( FLEXURED_CLOSE, reply )) != NO_ERROR) { - this->broadcast.error( function, "closing connection to flexure hardware" ); + logwrite( function, "ERROR closing connection to flexure hardware" ); } // disconnect me from flexured, irrespective of any previous error @@ -2365,7 +2341,7 @@ namespace Sequencer { // Turn off power to flexure hardware. // if ( this->set_power_switch(OFF, POWER_FLEXURE, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast.error( function, "switching off flexure" ); + logwrite( function, "ERROR switching off flexure" ); throw std::runtime_error("switching off flexure hardware"); } @@ -2392,14 +2368,14 @@ namespace Sequencer { this->thread_error_manager.set( THR_FOCUS_INIT ); // assume failure, clear on success if ( this->set_power_switch(ON, POWER_FOCUS, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast.error( function, "powering focus control" ); + logwrite( function, "ERROR powering focus control" ); throw std::runtime_error("could not power focus control"); } // connect to focusd bool was_opened=false; if ( this->open_hardware(this->focusd, was_opened) != NO_ERROR ) { - this->broadcast.error( function, "initializing focus control" ); + logwrite( function, "ERROR initializing focus control" ); throw std::runtime_error("could not open focus hardware"); } @@ -2411,14 +2387,14 @@ namespace Sequencer { std::string reply; long error = this->focusd.command( FOCUSD_ISHOME, reply ); if ( error!=NO_ERROR || this->parse_state( function, reply, ishomed ) != NO_ERROR ) { - this->broadcast.error( function, "communicating with focus hardware" ); + logwrite( function, "ERROR communicating with focus hardware" ); throw std::runtime_error("focus "+FOCUSD_ISHOME+" returned: "+reply); } // home focus actuators if not already homed if ( !ishomed ) { logwrite( function, "sending home command" ); if ( this->focusd.command_timeout( FOCUSD_HOME, reply, FOCUSD_HOME_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "communicating with focus hardware" ); + logwrite( function, "ERROR communicating with focus hardware" ); throw std::runtime_error("focus "+FOCUSD_HOME+" returned: "+reply); } } @@ -2428,7 +2404,7 @@ namespace Sequencer { for ( const auto &chan : chans ) { std::string command = "set " + chan + " nominal"; if ( this->focusd.command_timeout( command, reply, FOCUSD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "setting focus "+chan ); + logwrite( function, "ERROR setting focus "+chan ); throw std::runtime_error("focus "+command+" returned: "+reply); } } @@ -2474,7 +2450,7 @@ namespace Sequencer { } if ( this->connect_to_daemon(this->focusd) != NO_ERROR ) { - this->broadcast.error( function, "connecting to focus hardware" ); + logwrite( function, "ERROR connecting to focus hardware" ); error=ERROR; } @@ -2483,7 +2459,7 @@ namespace Sequencer { // logwrite( function, "closing focus hardware" ); if (error==NO_ERROR && (error=this->focusd.command( FOCUSD_CLOSE, reply )) != NO_ERROR) { - this->broadcast.error( function, "closing connection to focus hardware" ); + logwrite( function, "ERROR closing connection to focus hardware" ); } // disconnect me from focusd, irrespective of any previous error @@ -2494,7 +2470,7 @@ namespace Sequencer { // Turn off power to focus hardware. // if ( this->set_power_switch(OFF, POWER_FOCUS, std::chrono::seconds(0)) != NO_ERROR ) { - this->broadcast.error( function, "switching off focus" ); + logwrite( function, "ERROR switching off focus" ); throw std::runtime_error("switching off focus hardware"); } @@ -2522,13 +2498,13 @@ namespace Sequencer { // make sure hardware is powered on // if ( this->set_power_switch(ON, POWER_CAMERA, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast.error( function, "powering camera" ); + logwrite( function, "ERROR powering camera" ); throw std::runtime_error("switching on camera"); } bool was_opened=false; if ( this->open_hardware(this->camerad, "open", 12000, was_opened) != NO_ERROR ) { - this->broadcast.error( function, "initializing camera" ); + logwrite( function, "ERROR initializing camera" ); throw std::runtime_error("initializing camera"); } @@ -2538,7 +2514,7 @@ namespace Sequencer { if ( was_opened) { for ( const auto &cmd : this->camera_prologue ) { if ( this->camerad.command_timeout( cmd, reply, CAMERA_PROLOG_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "sending \""+cmd+"\" to camera" ); + logwrite( function, "ERROR sending \""+cmd+"\" to camera" ); throw std::runtime_error("sending \""+cmd+"\" to camera"); } } @@ -2609,7 +2585,7 @@ namespace Sequencer { // turn off power to camera hardware // if ( this->set_power_switch(OFF, POWER_CAMERA, std::chrono::seconds(5)) != NO_ERROR ) { - this->broadcast.error( function, "powering off camera" ); + logwrite( function, "ERROR powering off camera" ); throw std::runtime_error("switching off camera"); } @@ -2709,7 +2685,7 @@ namespace Sequencer { if ( ra_isnan ) { message << " RA=\"" << this->target.ra_hms << "\""; } if ( dec_isnan ) { message << " DEC=\"" << this->target.dec_dms << "\""; } message << " to decimal"; - this->broadcast.error( function, ""+message.str() ); + logwrite( function, "ERROR "+message.str() ); this->thread_error_manager.set( THR_MOVE_TO_TARGET ); throw std::runtime_error(message.str()); } @@ -2727,9 +2703,9 @@ namespace Sequencer { double _solved_angle = ( angle_out < 0 ? angle_out + 360.0 : angle_out ); if ( std::abs(_solved_angle) - std::abs(this->target.casangle) > 0.01 ) { - message.str(""); message << "NOTICE: Calculated angle " << angle_out + message.str(""); message << "Calculated angle " << angle_out << " is not equivalent to casangle " << this->target.casangle; - this->async.enqueue_and_log( function, message.str() ); + this->broadcast.notice( function, message.str() ); } // Send coordinates using TCS-native COORDS command. @@ -2759,8 +2735,8 @@ namespace Sequencer { error = this->tcsd.send( coords_cmd.str(), coords_reply ); // send to the TCS // second failure return error if ( error != NO_ERROR || coords_reply.compare( 0, strlen(TCS_SUCCESS_STR), TCS_SUCCESS_STR ) != 0 ) { - message.str(""); message << "ERROR sending COORDS command. TCS reply: " << coords_reply; - this->async.enqueue_and_log( function, message.str() ); + message.str(""); message << "sending COORDS command. TCS reply: " << coords_reply; + logwrite( function, "ERROR "+message.str() ); this->thread_error_manager.set( THR_MOVE_TO_TARGET ); throw std::runtime_error("sending COORDS to TCS: "+coords_reply); } @@ -2773,7 +2749,7 @@ namespace Sequencer { std::stringstream ringgo_cmd; std::string noreply("DONTWAIT"); // indicates don't wait for reply ringgo_cmd << TCSD_RINGGO << " " << angle_out; // this is calculated cass angle - this->async.enqueue_and_log( function, "sending "+ringgo_cmd.str()+" to TCS" ); + this->broadcast.notice( function, "sending "+ringgo_cmd.str()+" to TCS" ); error = this->tcsd.send( ringgo_cmd.str(), noreply ); } @@ -2958,7 +2934,7 @@ namespace Sequencer { logwrite( function, "calib: "+cmd.str() ); if ( !is_cancelled() && this->calibd.command_timeout( cmd.str(), CALIBD_SET_TIMEOUT ) != NO_ERROR ) { - this->broadcast.error( function, "moving calib door and/or cover" ); + logwrite( function, "ERROR moving calib door and/or cover" ); throw std::runtime_error("moving calib door and/or cover"); } @@ -2970,7 +2946,7 @@ namespace Sequencer { logwrite( function, "power "+setpower); std::string reply; if ( this->powerd.send( setpower, reply ) != NO_ERROR ) { - this->broadcast.error( function, ""+message.str() ); + logwrite( function, "ERROR "+message.str() ); throw std::runtime_error("setting lamp "+message.str()); } } @@ -2983,7 +2959,7 @@ namespace Sequencer { // if ( is_cancelled() ) break; // cmd.str(""); cmd << TCSD_NATIVE << " NPS " << lamp << " " << (state?1:0); // if ( this->tcsd.command( cmd.str() ) != NO_ERROR ) { -// this->async.enqueue_and_log( function, "ERROR "+cmd.str() ); +// logwrite( function, "ERROR "+cmd.str() ); // throw std::runtime_error("setting dome lamp: "+cmd.str()); // } // } @@ -2994,7 +2970,7 @@ namespace Sequencer { if ( is_cancelled() ) break; cmd.str(""); cmd << CALIBD_LAMPMOD << " " << mod << " " << (state?1:0) << " 1000"; if ( this->calibd.command( cmd.str() ) != NO_ERROR ) { - this->broadcast.error( function, ""+cmd.str() ); + logwrite( function, "ERROR "+cmd.str() ); throw std::runtime_error("setting lamp modulator "+cmd.str()); } } @@ -3245,7 +3221,7 @@ namespace Sequencer { // if ( this->camerad.async( message.str() ) != NO_ERROR ) { // if ( this->camerad.send( message.str(), reply ) != NO_ERROR ) { if ( this->camerad.command_timeout( message.str(), reply, 30000 ) != NO_ERROR ) { - this->broadcast.error( function, "sending camera "+message.str() ); + logwrite( function, "ERROR sending camera "+message.str() ); this->thread_error_manager.set( THR_TRIGGER_EXPOSURE ); // tell the world this thread had an error this->target.update_state( Sequencer::TARGET_PENDING ); // return the target state to pending this->wait_state_manager.clear( Sequencer::SEQ_WAIT_EXPOSE ); // clear EXPOSE bit @@ -3329,15 +3305,10 @@ namespace Sequencer { if ( error==NO_ERROR ) { this->target.exptime_req = updated_exptime; - message.str(""); message << "NOTICE: updated exptime to " << updated_exptime << " sec"; - this->async.enqueue_and_log( function, message.str() ); + message.str(""); message << "updated exptime to " << updated_exptime << " sec"; + this->broadcast.notice( function, message.str() ); } - // announce the success or failure in an asynchronous broadcast message - // - message.str(""); message << "MODIFY_EXPTIME: " << this->target.exptime_req << ( error==NO_ERROR ? " DONE" : " ERROR" ); - this->async.enqueue( message.str() ); - return; } /***** Sequencer::Sequence::modify_exptime **********************************/ @@ -3356,9 +3327,9 @@ namespace Sequencer { if ( ! seq_state_manager.are_any_set( Sequencer::SEQ_READY, Sequencer::SEQ_NOTREADY, Sequencer::SEQ_FAILED ) ) { - message << "ERROR cannot perform system startup while " + message << "cannot perform system startup while " << seq_state_manager.get_set_states(); - this->async.enqueue_and_log( function, message.str() ); + this->broadcast.error( function, message.str() ); return ERROR; } @@ -3479,16 +3450,13 @@ namespace Sequencer { { THR_TCS_SHUTDOWN, [this]{ return tcs_shutdown(); }, { } } }, function ); - std::ostringstream message; if (error==NO_ERROR) { - message << "NOTICE: instrument is shut down"; + this->broadcast.notice(function, "instrument is shut down"); } else { - message << "ERROR occurred during shutdown and may not have completed"; + this->broadcast.error(function, "shut down may not be complete"); } - this->async.enqueue_and_log( function, message.str() ); - // Always end in NOTREADY regardless of worker errors. SEQ_FAILED is // reserved for startup failures and externally-aborted lifecycle transitions. // Worker errors during shutdown are logged above but do not prevent @@ -3751,10 +3719,7 @@ namespace Sequencer { retstring = ( this->do_once.load() ? "ONE" : "ALL" ); - // send an async message with the current type - // - message.str(""); message << "DOTYPE: " << retstring; - this->async.enqueue( message.str() ); + // @TODO publish dotype return( error ); } @@ -3802,8 +3767,7 @@ namespace Sequencer { retstring = ( this->engineering_mode.load() ? "true" : "false" ); - message.str(""); message << "ENGINEERING: " << retstring; - this->async.enqueue( message.str() ); + logwrite(function, retstring); return( error ); } @@ -4318,7 +4282,7 @@ namespace Sequencer { } // connection failed too many times if (attempt > maxattempts) { - this->broadcast.error( function, "exceeded max attempts connecting to " + daemon.name ); + logwrite( function, "ERROR exceeded max attempts connecting to " + daemon.name ); return ERROR; } @@ -4327,7 +4291,7 @@ namespace Sequencer { error |= daemon.send( "isopen", reply ); error |= this->parse_state( function, reply, isopen ); if ( error != NO_ERROR ) { - this->broadcast.error( function, "opening "+daemon.name+" hardware" ); + logwrite( function, "ERROR opening "+daemon.name+" hardware" ); return ERROR; } @@ -4337,7 +4301,7 @@ namespace Sequencer { logwrite( function, "opening "+daemon.name+" hardware connections with " +std::to_string(opentimeout)+" ms timeout" ); if ( daemon.command_timeout( opencmd, reply, opentimeout ) != NO_ERROR ) { - this->broadcast.error( function, "opening connection to "+daemon.name+" hardware" ); + logwrite( function, "ERROR opening connection to "+daemon.name+" hardware" ); return ERROR; } was_opened=true; @@ -4366,7 +4330,7 @@ namespace Sequencer { if ( !daemon.socket.isconnected() ) { logwrite( function, "connecting to "+daemon.name+" daemon" ); if ( daemon.connect() != NO_ERROR ) { - this->broadcast.error( function, "connecting to "+daemon.name ); + logwrite( function, "ERROR connecting to "+daemon.name ); return ERROR; } } @@ -4499,34 +4463,6 @@ namespace Sequencer { } else - // ---------------------------------------------------- - // async -- queue an asynchronous message - // ---------------------------------------------------- - // - if ( testname == "async" ) { - if ( tokens.size() > 1 && tokens[1] == "?" ) { - retstring = "test async [ ]\n"; - retstring.append( " Queue and broadcast optional . If not supplied\n" ); - retstring.append( " then broadcast \"test\".\n" ); - return HELP; - } - if ( tokens.size() > 1 ) { - bool first=true; - message.str(""); - for ( const auto &word : tokens ) { - if ( first ) { first=false; continue; } // skip the testname - message << word << " "; - } - logwrite( function, message.str() ); - this->async.enqueue( message.str() ); - } - else { - logwrite( function, "test" ); - this->async.enqueue( "test" ); - } - } - else - // ---------------------------------------------------- // prologue -- show the camera prologue commands // ---------------------------------------------------- @@ -4560,16 +4496,16 @@ namespace Sequencer { // write to the log (textually) which bits are set // retstring.clear(); - message.str(""); message << "NOTICE: daemons ready: " << this->daemon_manager.get_set_states(); - this->async.enqueue_and_log( function, message.str() ); + message.str(""); message << "daemons ready: " << this->daemon_manager.get_set_states(); + this->broadcast.notice( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); - message.str(""); message << "NOTICE: daemons not ready: " << this->daemon_manager.get_cleared_states(); - this->async.enqueue_and_log( function, message.str() ); + message.str(""); message << "daemons not ready: " << this->daemon_manager.get_cleared_states(); + this->broadcast.notice( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); - message.str(""); message << "NOTICE: camera ready to expose: " << (this->can_expose.load() ? "yes" : "no"); - this->async.enqueue_and_log( function, message.str() ); + message.str(""); message << "camera ready to expose: " << (this->can_expose.load() ? "yes" : "no"); + this->broadcast.notice( function, message.str() ); retstring.append( message.str() ); error = NO_ERROR; @@ -4649,7 +4585,7 @@ namespace Sequencer { } message.str(""); message << "STATES: " << this->seq_state_manager.get_set_states(); - this->async.enqueue( message.str() ); + this->broadcast.notice( function, message.str() ); logwrite( function, message.str() ); message.str(""); message << "THREADS: " << this->thread_state_manager.get_set_states(); @@ -4756,8 +4692,7 @@ namespace Sequencer { } error = NO_ERROR; - message.str(""); message << "NOTICE: " << targetstatus; - this->async.enqueue( message.str() ); // broadcast target status + this->broadcast.notice(function, targetstatus); if ( ret == TargetInfo::TargetState::TARGET_FOUND ) { rts << "name obsid order ra dec casangle slitangle airmasslim\n"; @@ -4915,10 +4850,10 @@ namespace Sequencer { // let the world know of the state change // - message.str(""); message << "TARGETSTATE:" << this->target.state - << " TARGET:" << this->target.name - << " OBSID:" << this->target.obsid; - this->async.enqueue( message.str() ); + message.str(""); message << this->target.state + << " TARGET:" << this->target.name + << " (" << this->target.obsid << ")"; + this->broadcast.notice( function, message.str() ); } else @@ -4946,10 +4881,10 @@ namespace Sequencer { // let the world know of the state change // - message.str(""); message << "TARGETSTATE:" << this->target.state - << " TARGET:" << this->target.name - << " OBSID:" << this->target.obsid; - this->async.enqueue( message.str() ); + message.str(""); message << this->target.state + << " TARGET:" << this->target.name + << " (" << this->target.obsid << ")"; + this->broadcast.notice( function, message.str() ); } } else @@ -5056,9 +4991,9 @@ namespace Sequencer { else this->test_solver_args.clear(); // clear previous solver args if not specified if ( !this->test_solver_args.empty() ) { - message.str(""); message << "NOTICE: test solver args: " << this->test_solver_args; + message.str(""); message << "test solver args: " << this->test_solver_args; } - this->async.enqueue_and_log( function, message.str() ); + this->broadcast.notice( function, message.str() ); // clear stop flags // @@ -5112,12 +5047,12 @@ namespace Sequencer { bool cas_isnan = std::isnan( angle_in ); if ( ra_isnan || dec_isnan || cas_isnan ) { - message.str(""); message << "ERROR: converting"; + message.str(""); message << "converting"; if ( ra_isnan ) { message << " RA=\"" << this->target.ra_hms << "\""; } if ( dec_isnan ) { message << " DEC=\"" << this->target.dec_dms << "\""; } if ( cas_isnan ) { message << " CASS=\"" << cass_now << "\""; } message << " to decimal"; - this->async.enqueue_and_log( function, message.str() ); + this->broadcast.error( function, message.str() ); return ERROR; } diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 01eb2f42..280cdfd1 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -659,8 +659,6 @@ namespace Sequencer { std::string daemon_control; ///< daemon control script - Common::Queue async; ///< asynchronous message queue - // Here are all the daemon client objects that the Sequencer connects to. // Common::DaemonClient acamd { "acamd" }; diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index ca6e32ee..f56da104 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -27,13 +27,10 @@ namespace Sequencer { case SIGTERM: case SIGINT: logwrite(function, "received termination signal"); - message << "NOTICE:" << Sequencer::DAEMON_NAME << " exit"; - Server::instance->sequence.async.enqueue( message.str() ); Server::instance->exit_cleanly(); // shutdown the daemon break; case SIGHUP: // TODO reconfigure? - Server::instance->sequence.async.enqueue_and_log( function, - "ERROR: caught unhandled HUP signal" ); + logwrite( function, "ERROR: caught unhandled HUP signal" ); break; case SIGPIPE: logwrite(function, "ignored SIGPIPE"); @@ -41,8 +38,6 @@ namespace Sequencer { default: message << "received unknown signal " << strsignal(signo); logwrite( function, message.str() ); - message.str(""); message << "NOTICE:" << Sequencer::DAEMON_NAME << " exit"; - Server::instance->sequence.async.enqueue( message.str() ); break; } return; @@ -58,6 +53,7 @@ namespace Sequencer { void Server::exit_cleanly(void) { std::string function = "Sequencer::Server::exit_cleanly"; logwrite( function, "exiting" ); + Server::instance->sequence.broadcast.notice(function, "sequencerd terminated"); exit(EXIT_SUCCESS); } @@ -90,7 +86,7 @@ namespace Sequencer { catch (const std::exception &e) { // should be impossible message.str(""); message << "ERROR parsing entry " << entry << " of " << this->config.n_entries << ": " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } @@ -106,11 +102,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing NBPORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -121,11 +117,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing BLKPORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -136,11 +132,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing ASYNCPORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -151,11 +147,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing MESSAGEPORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -163,7 +159,7 @@ namespace Sequencer { if (config.param[entry] == "MESSAGEGROUP") { this->messagegroup = config.arg[entry]; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -172,7 +168,7 @@ namespace Sequencer { if ( config.param[entry] == "PUB_ENDPOINT" ) { this->sequence.publisher_address = config.arg[entry]; this->sequence.publisher_topic = DAEMON_NAME; - this->sequence.async.enqueue_and_log(function, "SEQUENCERD:config:"+config.param[entry]+"="+config.arg[entry]); + logwrite(function, "SEQUENCERD:config:"+config.param[entry]+"="+config.arg[entry]); applied++; } @@ -180,14 +176,14 @@ namespace Sequencer { // if ( config.param[entry] == "SUB_ENDPOINT" ) { this->sequence.subscriber_address = config.arg[entry]; - this->sequence.async.enqueue_and_log(function, "SEQUENCERD:config:"+config.param[entry]+"="+config.arg[entry]); + logwrite(function, "SEQUENCERD:config:"+config.param[entry]+"="+config.arg[entry]); applied++; } // DAEMON_CONTROL_SCRIPT if (config.param[entry] == "DAEMON_CONTROL_SCRIPT") { this->sequence.daemon_control = config.arg[entry]; - this->sequence.async.enqueue_and_log(function, "SEQUENCERD:config:"+config.param[entry]+"="+config.arg[entry]); + logwrite(function, "SEQUENCERD:config:"+config.param[entry]+"="+config.arg[entry]); applied++; } @@ -198,11 +194,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing ACAMD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -213,11 +209,11 @@ namespace Sequencer { } catch (const std::invalid_argument &e) { message.str(""); message << "ERROR parsing CAMERAD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -228,11 +224,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing CAMERAD_NBPORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -243,11 +239,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing FLEXURED_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -258,11 +254,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing POWERD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -273,11 +269,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing SLICECAMD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -288,11 +284,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing SLITD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -303,11 +299,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing TCSD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -318,11 +314,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing CALIBD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -333,11 +329,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing FILTERD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -348,11 +344,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing FOCUSD_PORT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -363,11 +359,11 @@ namespace Sequencer { } catch (const std::exception &e) { message.str(""); message << "ERROR parsing ACQUIRE_TIMEOUT: " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return ERROR; } message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -379,27 +375,27 @@ namespace Sequencer { } catch (std::invalid_argument &) { message.str(""); message << "ACQUIRE_RETRYS: unable to convert " << config.arg[entry] << " to integer. retry limit disabled."; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); this->sequence.acquisition_max_retrys = -1; } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ACQUIRE_RETRYS number out of integer range. retry limit disabled." ); + logwrite( function, "ACQUIRE_RETRYS number out of integer range. retry limit disabled." ); this->sequence.acquisition_max_retrys = -1; } this->sequence.acquisition_max_retrys = rt; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } // TCS_WHICH -- which TCS to connect to, defults to real if not specified if ( config.param[entry] == "TCS_WHICH" ) { if ( config.arg[entry] != "sim" && config.arg[entry] != "real" ) { - this->sequence.async.enqueue_and_log( function, "ERROR TCS_WHICH expected { sim real }" ); + logwrite( function, "ERROR TCS_WHICH expected { sim real }" ); return ERROR; } this->sequence.tcs_which = config.arg[entry]; - this->sequence.async.enqueue_and_log( function, "SEQUENCERD:config:"+config.param[entry]+"="+config.arg[entry] ); + logwrite( function, "SEQUENCERD:config:"+config.param[entry]+"="+config.arg[entry] ); applied++; } @@ -410,22 +406,22 @@ namespace Sequencer { mrate = std::stod( config.arg[entry] ); if ( mrate < 0 || mrate > 60 ) { message.str(""); message << "ERROR: TCS_OFFSET_RATE_RA " << mrate << " out of range {0:60}"; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return( ERROR ); } } catch (std::invalid_argument &) { message.str(""); message << "ERROR: bad TCS_OFFSET_RATE_RA: unable to convert " << config.arg[entry] << " to double"; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: TCS_OFFSET_RATE_RA number out of double range" ); + logwrite( function, "ERROR: TCS_OFFSET_RATE_RA number out of double range" ); return(ERROR); } this->sequence.tcs_offsetrate_ra = mrate; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -436,22 +432,22 @@ namespace Sequencer { mrate = std::stod( config.arg[entry] ); if ( mrate < 0 || mrate > 60 ) { message.str(""); message << "ERROR: TCS_OFFSET_RATE_DEC " << mrate << " out of range {0:60}"; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return( ERROR ); } } catch (std::invalid_argument &) { message.str(""); message << "ERROR: bad TCS_OFFSET_RATE_DEC: unable to convert " << config.arg[entry] << " to double"; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: TCS_OFFSET_RATE_DEC number out of double range" ); + logwrite( function, "ERROR: TCS_OFFSET_RATE_DEC number out of double range" ); return(ERROR); } this->sequence.tcs_offsetrate_dec = mrate; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -463,16 +459,16 @@ namespace Sequencer { } catch (std::invalid_argument &) { message.str(""); message << "ERROR: bad TCS_SETTLE_TIMEOUT: unable to convert " << config.arg[entry] << " to double"; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: TCS_SETTLE_TIMEOUT number out of double range" ); + logwrite( function, "ERROR: TCS_SETTLE_TIMEOUT number out of double range" ); return(ERROR); } this->sequence.tcs_settle_timeout = to; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -484,16 +480,16 @@ namespace Sequencer { } catch (std::invalid_argument &) { message.str(""); message << "ERROR: bad TCS_SETTLE_STABLE: unable to convert " << config.arg[entry] << " to double"; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: TCS_SETTLE_STABLE number out of double range" ); + logwrite( function, "ERROR: TCS_SETTLE_STABLE number out of double range" ); return(ERROR); } this->sequence.tcs_settle_stable = stablet; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -505,16 +501,16 @@ namespace Sequencer { } catch (std::invalid_argument &) { message.str(""); message << "ERROR: bad TCS_DOMEAZI_READY: unable to convert " << config.arg[entry] << " to double"; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: TCS_DOMEAZI_READY number out of double range" ); + logwrite( function, "ERROR: TCS_DOMEAZI_READY number out of double range" ); return(ERROR); } this->sequence.tcs_domeazi_ready = domeazi; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -525,16 +521,16 @@ namespace Sequencer { to = std::stod( config.arg[entry] ); } catch (std::invalid_argument &) { - this->sequence.async.enqueue_and_log( function, "ERROR: bad TCS_PREAUTH_TIME: unable to convert to double" ); + logwrite( function, "ERROR: bad TCS_PREAUTH_TIME: unable to convert to double" ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: TCS_PREAUTH_TIME number out of double range" ); + logwrite( function, "ERROR: TCS_PREAUTH_TIME number out of double range" ); return(ERROR); } this->sequence.tcs_preauth_time = to; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -545,16 +541,16 @@ namespace Sequencer { offset = std::stod( config.arg[entry] ); } catch (std::invalid_argument &) { - this->sequence.async.enqueue_and_log( function, "ERROR: bad ACQUIRE_OFFSET_THRESHOLD: unable to convert to double" ); + logwrite( function, "ERROR: bad ACQUIRE_OFFSET_THRESHOLD: unable to convert to double" ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: ACQUIRE_OFFSET_THRESHOLD number out of double range" ); + logwrite( function, "ERROR: ACQUIRE_OFFSET_THRESHOLD number out of double range" ); return(ERROR); } this->sequence.target.offset_threshold = offset; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -565,16 +561,16 @@ namespace Sequencer { repeat = std::stoi( config.arg[entry] ); } catch (std::invalid_argument &) { - this->sequence.async.enqueue_and_log( function, "ERROR: bad ACQUIRE_MIN_REPEAT: unable to convert to int" ); + logwrite( function, "ERROR: bad ACQUIRE_MIN_REPEAT: unable to convert to int" ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: ACQUIRE_MIN_REPEAT number out of int range" ); + logwrite( function, "ERROR: ACQUIRE_MIN_REPEAT number out of int range" ); return(ERROR); } this->sequence.target.min_repeat = repeat; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -585,16 +581,16 @@ namespace Sequencer { offset = std::stod( config.arg[entry] ); } catch (std::invalid_argument &) { - this->sequence.async.enqueue_and_log( function, "ERROR: bad ACQUIRE_TCS_MAX_OFFSET: unable to convert to double" ); + logwrite( function, "ERROR: bad ACQUIRE_TCS_MAX_OFFSET: unable to convert to double" ); return(ERROR); } catch (std::out_of_range &) { - this->sequence.async.enqueue_and_log( function, "ERROR: ACQUIRE_TCS_MAX_OFFSET number out of double range" ); + logwrite( function, "ERROR: ACQUIRE_TCS_MAX_OFFSET number out of double range" ); return(ERROR); } this->sequence.target.max_tcs_offset = offset; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); applied++; } @@ -609,7 +605,7 @@ namespace Sequencer { if ( this->sequence.target.configure_db( DB_HOST, config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -618,7 +614,7 @@ namespace Sequencer { if ( this->sequence.target.configure_db( DB_PORT, config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -627,7 +623,7 @@ namespace Sequencer { if ( this->sequence.target.configure_db( DB_USER, config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -636,7 +632,7 @@ namespace Sequencer { if ( this->sequence.target.configure_db( DB_PASS, config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -645,7 +641,7 @@ namespace Sequencer { if ( this->sequence.target.configure_db( DB_SCHEMA, config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -654,7 +650,7 @@ namespace Sequencer { if ( this->sequence.target.configure_db( DB_ACTIVE, config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -663,7 +659,7 @@ namespace Sequencer { if ( this->sequence.target.configure_db( DB_COMPLETED, config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -672,7 +668,7 @@ namespace Sequencer { if ( this->sequence.target.configure_db( DB_SETS, config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -681,7 +677,7 @@ namespace Sequencer { this->sequence.camera_prologue.push_back( this->config.arg[entry] ); applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } // CAMERA_EPILOGUE @@ -689,7 +685,7 @@ namespace Sequencer { this->sequence.camera_epilogue.push_back( this->config.arg[entry] ); applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } // *__INIT @@ -701,7 +697,7 @@ namespace Sequencer { this->sequence.config_init[key] = this->config.arg[entry]; applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } // *__SHUTDOWN @@ -713,7 +709,7 @@ namespace Sequencer { this->sequence.config_shutdown[key] = this->config.arg[entry]; applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } // VIRTUAL_SLITW_ACQUIRE @@ -755,7 +751,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_LAMP].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -764,7 +760,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_SLIT].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -773,7 +769,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_CAMERA].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -782,7 +778,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_CALIB].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -791,7 +787,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_FLEXURE].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -800,7 +796,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_FILTER].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -809,7 +805,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_FOCUS].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -818,7 +814,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_TELEM].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -827,7 +823,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_THERMAL].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -836,7 +832,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_ACAM].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -845,7 +841,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_ACAM_CAM].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -854,7 +850,7 @@ namespace Sequencer { if ( this->sequence.power_switch[POWER_SLICECAM].configure( this->config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -863,7 +859,7 @@ namespace Sequencer { if ( this->sequence.caltarget.configure( config.arg[entry] ) == NO_ERROR ) { applied++; message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } } @@ -993,49 +989,6 @@ namespace Sequencer { /**** Server::gui_main ******************************************************/ - /***** Server::async_main ***************************************************/ - /** - * @brief asynchronous message sending thread - * @param[in] seq reference to Sequencer::Server object - * @param[in] sock Network::udpSocket socket object - * - * Loops forever, when a message arrives in the status message queue it is - * sent out via multi-cast UDP datagram. - * - */ - void Server::async_main( Sequencer::Server &seq, Network::UdpSocket sock ) { - std::string function = "Sequencer::Server::async_main"; - std::stringstream message; - int retval; - - retval = sock.Create(); // create the UDP socket - if (retval < 0) { - logwrite(function, "error creating UDP multicast socket for asynchronous messages"); - seq.exit_cleanly(); // do not continue on error - } - if (retval==1) { // exit this thread but continue with daemon - logwrite(function, "asyncrhonous message port disabled by request"); - } - - while (1) { - auto message = seq.sequence.async.dequeue(); // get the latest message from the queue (blocks) - retval = sock.Send(message); // transmit the message - if (retval < 0) { - std::stringstream errstm; - errstm << "error sending UDP message: " << message; - logwrite(function, errstm.str()); - } - if (message=="exit") { // terminate this thread - sock.Close(); - return; - } - } - - return; - } - /***** Server::async_main ***************************************************/ - - /***** Server::doit *********************************************************/ /** * @brief the workhorse of each thread connetion @@ -1071,11 +1024,11 @@ namespace Sequencer { if ( ( pollret=sock.Poll() ) <= 0 ) { if (pollret==0) { message.str(""); message << "ERROR: Poll timeout on fd " << sock.getfd() << " thread " << sock.id; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } if ( pollret <0 && errno ) { message.str(""); message << "ERROR: Poll error on fd " << sock.getfd() << " thread " << sock.id << ": " << strerror(errno); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } break; // this will close the connection } @@ -1091,11 +1044,11 @@ namespace Sequencer { #endif if ( ret<0 && errno ) { // could be an actual read error message.str(""); message << "ERROR: Read error on fd " << sock.getfd() << ": " << strerror(errno); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } if (ret==-2) { // or a timeout message.str(""); message << "ERROR: timeout reading from fd " << sock.getfd(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); } break; // Breaking out of the while loop will close the connection. // This probably means that the client has terminated abruptly, @@ -1134,12 +1087,12 @@ namespace Sequencer { catch ( std::runtime_error &e ) { std::stringstream errstream; errstream << e.what(); message.str(""); message << "ERROR: parsing arguments: " << errstream.str(); - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); ret = -1; } catch ( ... ) { message.str(""); message << "ERROR: unknown error parsing arguments: " << args; - this->sequence.async.enqueue_and_log( function, message.str() ); + logwrite( function, message.str() ); ret = -1; } @@ -1392,7 +1345,7 @@ namespace Sequencer { // if ( cmd == SEQUENCERD_REPEAT ) { if ( !this->sequence.seq_state_manager.is_set( Sequencer::SEQ_READY ) ) { - this->sequence.async.enqueue_and_log( function, "ERROR cannot start exposure: not ready" ); + this->sequence.broadcast.error( function, "cannot start exposure: not ready" ); ret = ERROR; } else { @@ -1499,7 +1452,7 @@ namespace Sequencer { if ( cmd == SEQUENCERD_TARGETSET ) { ret= this->sequence.target.targetset( args, retstring ); message.str(""); message << "TARGETSET: " << retstring; - this->sequence.async.enqueue( message.str() ); + logwrite(function, message.str()); retstring.append( " " ); } else @@ -1510,14 +1463,14 @@ namespace Sequencer { // Can only pause during an exposure // if ( ! this->sequence.wait_state_manager.is_set( Sequencer::SEQ_WAIT_EXPOSE ) ) { - this->sequence.async.enqueue_and_log( function, "ERROR: can only pause during an active exposure" ); + this->sequence.broadcast.error( function, "can only pause during an active exposure" ); ret = ERROR; } else // Can't already be paused // if ( this->sequence.seq_state_manager.is_set( Sequencer::SEQ_PAUSED ) ) { - this->sequence.async.enqueue_and_log( function, "ERROR: already paused" ); + this->sequence.broadcast.error( function, "already paused" ); ret = ERROR; } else { @@ -1536,7 +1489,7 @@ namespace Sequencer { // Can only resume when paused // if ( ! this->sequence.seq_state_manager.is_set( Sequencer::SEQ_PAUSED ) ) { - this->sequence.async.enqueue_and_log( function, "ERROR: can only resume when paused" ); + this->sequence.broadcast.error( function, "can only resume when paused" ); ret = ERROR; } else { @@ -1563,7 +1516,7 @@ namespace Sequencer { if ( cmd.compare( SEQUENCERD_MODEXPTIME ) == 0 ) { Tokenize( args, tokens, " " ); if ( tokens.size() != 1 ) { - this->sequence.async.enqueue_and_log( function, "ERROR: expected MODEXPTIME " ); + logwrite( function, "ERROR expected MODEXPTIME " ); ret = ERROR; } else { @@ -1572,13 +1525,13 @@ namespace Sequencer { double exptime_req=0; try { exptime_req = std::stod( tokens.at(0) ); } catch( std::out_of_range &e ) { - message.str(""); message << "ERROR: out of range parsing args " << args << ": " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + message.str(""); message << "ERROR out of range parsing args " << args << ": " << e.what(); + logwrite( function, message.str() ); ret = ERROR; } catch( std::invalid_argument &e ) { - message.str(""); message << "ERROR: invalid argument parsing args " << args << ": " << e.what(); - this->sequence.async.enqueue_and_log( function, message.str() ); + message.str(""); message << "ERROR invalid argument parsing args " << args << ": " << e.what(); + logwrite( function, message.str() ); ret = ERROR; } @@ -1618,8 +1571,8 @@ namespace Sequencer { // Unknown commands generate an error // else { - message.str(""); message << "ERROR: unknown command: " << cmd; - this->sequence.async.enqueue_and_log( function, message.str() ); + message.str(""); message << "ERROR unknown command: " << cmd; + logwrite( function, message.str() ); ret = ERROR; } diff --git a/sequencerd/sequencer_server.h b/sequencerd/sequencer_server.h index aa3440a8..f24bdc6f 100644 --- a/sequencerd/sequencer_server.h +++ b/sequencerd/sequencer_server.h @@ -145,7 +145,6 @@ namespace Sequencer { static void block_main( Sequencer::Server &server, std::shared_ptr ); ///< main function for blocking connection thread static void thread_main( Sequencer::Server &server, std::shared_ptr sock); ///< main function for all non-blocked threads static void gui_main( Sequencer::Server &server, std::shared_ptr sock ); ///< main function for gui threads - static void async_main( Sequencer::Server &server, Network::UdpSocket sock ); ///< asynchronous message sending thread void handle_signal( int signo ); diff --git a/sequencerd/sequencerd.cpp b/sequencerd/sequencerd.cpp index 7125b3a6..4e13530e 100644 --- a/sequencerd/sequencerd.cpp +++ b/sequencerd/sequencerd.cpp @@ -224,16 +224,10 @@ int main(int argc, char **argv) { std::ref(socklist[thrid]) ).detach(); // spawn a thread to handle requests on this socket *****/ - // Instantiate a multicast UDP object and spawn a thread to send asynchronous messages - // - Network::UdpSocket msg(sequencerd.messageport, sequencerd.messagegroup); - std::thread( std::ref(Sequencer::Server::async_main), - std::ref(sequencerd), - msg ).detach(); - // Create my own asynchronous listener thread. // This thread allows the sequencer to listen for asynchronous messages. // + Network::UdpSocket msg(sequencerd.messageport, sequencerd.messagegroup); std::thread( std::ref( Sequencer::Sequence::dothread_sequencer_async_listener ), std::ref( sequencerd.sequence), msg From 673b3d5c34e16291fc70c500ce847bf936119d16 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 14 May 2026 23:07:52 -0700 Subject: [PATCH 34/35] noisey commit but mostly vocabularly change-- * "threads" to "operations" * "xxxx_set" functions to "xxxx_setup" --- sequencerd/operation_builders.cpp | 82 +++-- sequencerd/sequence.cpp | 521 ++++++++++++---------------- sequencerd/sequence.h | 230 ++++++------ sequencerd/sequence_acquisition.cpp | 4 +- sequencerd/sequence_operations.cpp | 92 ++--- 5 files changed, 435 insertions(+), 494 deletions(-) diff --git a/sequencerd/operation_builders.cpp b/sequencerd/operation_builders.cpp index 52dc43ed..d387cd9f 100644 --- a/sequencerd/operation_builders.cpp +++ b/sequencerd/operation_builders.cpp @@ -180,107 +180,106 @@ namespace Sequencer { // ---------- INTERNAL ------------------------------------------------------ // sequencer-level orchestration (Ops factory methods) - // Command names come from the THR_ -> thread_names map in sequence.h. - // I probably should rename these from "thread" to something else (operation?) + // Command names come from the OP_* -> op_names map in sequence.h. - op_builders[thread_names.at(THR_ACAM_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_ACAM_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.acam_init(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_CALIB_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_CALIB_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.calib_init(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_CALIB_SET)] = [this](Operation &op, const ParsedCommand &cmd) { - op = ops.calib_set(); + op_builders[op_names.at(OP_CALIB_SETUP)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.calib_setup(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_CAMERA_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_CAMERA_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.camera_init(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_CAMERA_SET)] = [this](Operation &op, const ParsedCommand &cmd) { - op = ops.camera_set(); + op_builders[op_names.at(OP_CAMERA_SETUP)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.camera_setup(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_EXPOSURE)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_EXPOSURE)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.do_expose(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_FLEXURE_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_FLEXURE_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.flexure_init(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_FLEXURE_SET)] = [this](Operation &op, const ParsedCommand &cmd) { - op = ops.flexure_set(); + op_builders[op_names.at(OP_FLEXURE_SETUP)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.flexure_setup(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_FOCUS_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_FOCUS_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.focus_init(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_FOCUS_SET)] = [this](Operation &op, const ParsedCommand &cmd) { - op = ops.focus_set(); + op_builders[op_names.at(OP_FOCUS_SETUP)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.focus_setup(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_MOVE_TO_TARGET)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_MOVE_TO_TARGET)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.move_to_target(); op.params = cmd.params; // may carry optional ra= / dec= overrides }; - op_builders[thread_names.at(THR_POWER_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_POWER_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.power_init(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_REPEAT_EXPOSURE)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_REPEAT_EXPOSURE)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.repeat_exposure(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_SHUTDOWN)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_SHUTDOWN)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.do_shutdown(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_SLICECAM_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_SLICECAM_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.slicecam_init(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_SLIT_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_SLIT_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.slit_init(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_SLIT_SET)] = [this](Operation &op, const ParsedCommand &cmd) { - op = ops.slit_set(); + op_builders[op_names.at(OP_SLIT_SETUP)] = [this](Operation &op, const ParsedCommand &cmd) { + op = ops.slit_setup(); op.params = cmd.params; // may carry optional mode= override }; - op_builders[thread_names.at(THR_STARTUP)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_STARTUP)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.do_startup(); op.params = cmd.params; }; - op_builders[thread_names.at(THR_TCS_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { + op_builders[op_names.at(OP_TCS_INIT)] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.tcs_init(); op.params = cmd.params; }; - // target_offset shares THR_MOVE_TO_TARGET with move_to_target, so it needs a + // target_offset shares OP_MOVE_TO_TARGET with move_to_target, so it needs a // distinct CLI/DSL name. Use its Sequence:: member-function name "target_offset" - // (there is no separate entry in thread_names for it). + // (there is no separate entry in op_names for it). op_builders["target_offset"] = [this](Operation &op, const ParsedCommand &cmd) { op = ops.target_offset(); op.params = cmd.params; @@ -296,7 +295,8 @@ namespace Sequencer { // validation and state-machine validation before forwarding to the daemon. op_builders[SEQUENCERD_ACAM] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_ACAM_INIT; // passthrough uses nearest daemon THR_ identity + op.id = OP_ACAM_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_ACAM+"]"); try { @@ -310,7 +310,8 @@ namespace Sequencer { }; op_builders[SEQUENCERD_CALIB] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_CALIB_INIT; + op.id = OP_CALIB_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_CALIB+"]"); try { @@ -324,7 +325,8 @@ namespace Sequencer { }; op_builders[SEQUENCERD_CAMERA] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_CAMERA_INIT; + op.id = OP_CAMERA_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_CAMERA+"]"); try { @@ -338,7 +340,8 @@ namespace Sequencer { }; op_builders[SEQUENCERD_FLEXURE] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_FLEXURE_INIT; + op.id = OP_FLEXURE_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_FLEXURE+"]"); try { @@ -352,7 +355,8 @@ namespace Sequencer { }; op_builders[SEQUENCERD_FOCUS] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_FOCUS_INIT; + op.id = OP_FOCUS_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_FOCUS+"]"); try { @@ -366,7 +370,8 @@ namespace Sequencer { }; op_builders[SEQUENCERD_POWER] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_POWER_INIT; + op.id = OP_POWER_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_POWER+"]"); try { @@ -380,7 +385,8 @@ namespace Sequencer { }; op_builders[SEQUENCERD_SLICECAM] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_SLICECAM_INIT; + op.id = OP_SLICECAM_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_SLICECAM+"]"); try { @@ -394,7 +400,8 @@ namespace Sequencer { }; op_builders[SEQUENCERD_SLIT] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_SLIT_INIT; + op.id = OP_SLIT_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_SLIT+"]"); try { @@ -408,7 +415,8 @@ namespace Sequencer { }; op_builders[SEQUENCERD_TCS] = [this](Operation &op, const ParsedCommand &cmd) { - op.thr = THR_TCS_INIT; + op.id = OP_TCS_CMD; + op.params = cmd.params; op.func = [this, cmd_copy=cmd]() { const std::string function("Sequencer::Sequence::op_builders["+SEQUENCERD_TCS+"]"); try { diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 219126e8..873165b5 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -20,85 +20,93 @@ namespace Sequencer { namespace { // ------------------------------------------------------------------------ - // Maps each Operation::thr to the DaemonBit it depends on. Operations + // Maps each Operation::id to the DaemonBit it depends on. Operations // that manage daemon state themselves (startup, shutdown, power_init, // power_shutdown) are intentionally absent from this map. // Used exclusively by validate_sequence(). // ------------------------------------------------------------------------ - const std::unordered_map thr_to_daemon = { - { THR_EXPOSURE, DAEMON_CAMERA }, - { THR_CAMERA_SET, DAEMON_CAMERA }, - { THR_CAMERA_INIT, DAEMON_CAMERA }, - { THR_CAMERA_SHUTDOWN, DAEMON_CAMERA }, - { THR_SLIT_SET, DAEMON_SLIT }, - { THR_SLIT_INIT, DAEMON_SLIT }, - { THR_SLIT_SHUTDOWN, DAEMON_SLIT }, - { THR_MOVE_TO_TARGET, DAEMON_TCS }, - { THR_TCS_INIT, DAEMON_TCS }, - { THR_TCS_SHUTDOWN, DAEMON_TCS }, - { THR_FLEXURE_SET, DAEMON_FLEXURE }, - { THR_FLEXURE_INIT, DAEMON_FLEXURE }, - { THR_FLEXURE_SHUTDOWN, DAEMON_FLEXURE }, - { THR_FOCUS_SET, DAEMON_FOCUS }, - { THR_FOCUS_INIT, DAEMON_FOCUS }, - { THR_FOCUS_SHUTDOWN, DAEMON_FOCUS }, - { THR_CALIB_SET, DAEMON_CALIB }, - { THR_CALIBRATOR_SET, DAEMON_CALIB }, - { THR_CALIB_INIT, DAEMON_CALIB }, - { THR_CALIB_SHUTDOWN, DAEMON_CALIB }, - { THR_ACAM_INIT, DAEMON_ACAM }, - { THR_ACAM_SHUTDOWN, DAEMON_ACAM }, - { THR_SLICECAM_INIT, DAEMON_SLICECAM }, - { THR_SLICECAM_SHUTDOWN,DAEMON_SLICECAM } + const std::unordered_map opid_to_daemon = { + { OP_EXPOSURE, DAEMON_CAMERA }, + { OP_CAMERA_SETUP, DAEMON_CAMERA }, + { OP_CAMERA_INIT, DAEMON_CAMERA }, + { OP_CAMERA_SHUTDOWN, DAEMON_CAMERA }, + { OP_SLIT_SETUP, DAEMON_SLIT }, + { OP_SLIT_INIT, DAEMON_SLIT }, + { OP_SLIT_SHUTDOWN, DAEMON_SLIT }, + { OP_MOVE_TO_TARGET, DAEMON_TCS }, + { OP_TCS_INIT, DAEMON_TCS }, + { OP_TCS_SHUTDOWN, DAEMON_TCS }, + { OP_FLEXURE_SETUP, DAEMON_FLEXURE }, + { OP_FLEXURE_INIT, DAEMON_FLEXURE }, + { OP_FLEXURE_SHUTDOWN, DAEMON_FLEXURE }, + { OP_FOCUS_SETUP, DAEMON_FOCUS }, + { OP_FOCUS_INIT, DAEMON_FOCUS }, + { OP_FOCUS_SHUTDOWN, DAEMON_FOCUS }, + { OP_CALIB_SETUP, DAEMON_CALIB }, + { OP_CALIB_INIT, DAEMON_CALIB }, + { OP_CALIB_SHUTDOWN, DAEMON_CALIB }, + { OP_ACAM_INIT, DAEMON_ACAM }, + { OP_ACAM_SHUTDOWN, DAEMON_ACAM }, + { OP_SLICECAM_INIT, DAEMON_SLICECAM }, + { OP_SLICECAM_SHUTDOWN, DAEMON_SLICECAM }, + { OP_ACAM_CMD, DAEMON_ACAM }, + { OP_CALIB_CMD, DAEMON_CALIB }, + { OP_CAMERA_CMD, DAEMON_CAMERA }, + { OP_FLEXURE_CMD, DAEMON_FLEXURE }, + { OP_FOCUS_CMD, DAEMON_FOCUS }, + { OP_POWER_CMD, DAEMON_POWER }, + { OP_SLICECAM_CMD, DAEMON_SLICECAM }, + { OP_SLIT_CMD, DAEMON_SLIT }, + { OP_TCS_CMD, DAEMON_TCS } }; // ------------------------------------------------------------------------ - // Returns true if the two Operation::thr values are unsafe to run in the + // Returns true if the two Operation::id values are unsafe to run in the // same PARALLEL group. Serial ordering of any such pair is fine. // ------------------------------------------------------------------------ - inline bool is_parallel_unsafe_pair(ThreadStatusBits a, ThreadStatusBits b) { + inline bool is_parallel_unsafe_pair(OperationStatusBits a, OperationStatusBits b) { // normalize ordering so each pair is checked once if (a > b) std::swap(a, b); // global sequence ops conflict with anything big - if (a == THR_STARTUP && b == THR_SHUTDOWN) return true; - if (a == THR_STARTUP && b == THR_EXPOSURE) return true; + if (a == OP_STARTUP && b == OP_SHUTDOWN) return true; + if (a == OP_STARTUP && b == OP_EXPOSURE) return true; // startup runs all inits internally; parallelling any init with startup is a double-init - auto is_init = [](ThreadStatusBits t) { - return t == THR_ACAM_INIT || t == THR_CALIB_INIT || - t == THR_CAMERA_INIT || t == THR_FLEXURE_INIT|| - t == THR_FOCUS_INIT || t == THR_POWER_INIT || - t == THR_SLICECAM_INIT || t == THR_SLIT_INIT || - t == THR_TCS_INIT; + auto is_init = [](OperationStatusBits t) { + return t == OP_ACAM_INIT || t == OP_CALIB_INIT || + t == OP_CAMERA_INIT || t == OP_FLEXURE_INIT|| + t == OP_FOCUS_INIT || t == OP_POWER_INIT || + t == OP_SLICECAM_INIT || t == OP_SLIT_INIT || + t == OP_TCS_INIT; }; - auto is_shutdown = [](ThreadStatusBits t) { - return t == THR_ACAM_SHUTDOWN || t == THR_CALIB_SHUTDOWN || - t == THR_CAMERA_SHUTDOWN || t == THR_FLEXURE_SHUTDOWN|| - t == THR_FOCUS_SHUTDOWN || t == THR_POWER_SHUTDOWN || - t == THR_SLICECAM_SHUTDOWN || t == THR_SLIT_SHUTDOWN || - t == THR_TCS_SHUTDOWN; + auto is_shutdown = [](OperationStatusBits t) { + return t == OP_ACAM_SHUTDOWN || t == OP_CALIB_SHUTDOWN || + t == OP_CAMERA_SHUTDOWN || t == OP_FLEXURE_SHUTDOWN|| + t == OP_FOCUS_SHUTDOWN || t == OP_POWER_SHUTDOWN || + t == OP_SLICECAM_SHUTDOWN || t == OP_SLIT_SHUTDOWN || + t == OP_TCS_SHUTDOWN; }; - if (a == THR_STARTUP && is_init(b)) return true; - if (a == THR_SHUTDOWN && is_init(b)) return true; + if (a == OP_STARTUP && is_init(b)) return true; + if (a == OP_SHUTDOWN && is_init(b)) return true; // power_init vs power_shutdown - if (a == THR_POWER_INIT && b == THR_POWER_SHUTDOWN) return true; + if (a == OP_POWER_INIT && b == OP_POWER_SHUTDOWN) return true; // matching init/shutdown pair for the same subsystem if (is_init(a) && is_shutdown(b)) { - auto subsystem_of = [](ThreadStatusBits t) -> int { + auto subsystem_of = [](OperationStatusBits t) -> int { switch (t) { - case THR_ACAM_INIT: case THR_ACAM_SHUTDOWN: return 1; - case THR_CALIB_INIT: case THR_CALIB_SHUTDOWN: return 2; - case THR_CAMERA_INIT: case THR_CAMERA_SHUTDOWN: return 3; - case THR_FLEXURE_INIT: case THR_FLEXURE_SHUTDOWN: return 4; - case THR_FOCUS_INIT: case THR_FOCUS_SHUTDOWN: return 5; - case THR_POWER_INIT: case THR_POWER_SHUTDOWN: return 6; - case THR_SLICECAM_INIT: case THR_SLICECAM_SHUTDOWN: return 7; - case THR_SLIT_INIT: case THR_SLIT_SHUTDOWN: return 8; - case THR_TCS_INIT: case THR_TCS_SHUTDOWN: return 9; + case OP_ACAM_INIT: case OP_ACAM_SHUTDOWN: return 1; + case OP_CALIB_INIT: case OP_CALIB_SHUTDOWN: return 2; + case OP_CAMERA_INIT: case OP_CAMERA_SHUTDOWN: return 3; + case OP_FLEXURE_INIT: case OP_FLEXURE_SHUTDOWN: return 4; + case OP_FOCUS_INIT: case OP_FOCUS_SHUTDOWN: return 5; + case OP_POWER_INIT: case OP_POWER_SHUTDOWN: return 6; + case OP_SLICECAM_INIT: case OP_SLICECAM_SHUTDOWN: return 7; + case OP_SLIT_INIT: case OP_SLIT_SHUTDOWN: return 8; + case OP_TCS_INIT: case OP_TCS_SHUTDOWN: return 9; default: return 0; } }; @@ -332,7 +340,7 @@ namespace Sequencer { if (this->target.pointmode == Acam::POINTMODE_ACAM) { this->dotype("ONE"); sequence.push_back( { OperationType::PARALLEL, OnError::STOP, - { { THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); + { { OP_MOVE_TO_TARGET, [this]{ return move_to_target(); } } } } ); } else { this->target.pointmode = Acam::POINTMODE_SLIT; @@ -340,13 +348,13 @@ namespace Sequencer { // these are the default operations prior to exposure, // they can be done in parallel sequence.push_back( { OperationType::PARALLEL, OnError::STOP, - { { THR_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, - { THR_CAMERA_SET, [this]{ return camera_set(); } }, - { THR_FOCUS_SET, [this]{ return focus_set(); } }, - { THR_FLEXURE_SET, [this]{ return flexure_set(); } }, - { THR_CALIB_SET, [this]{ return calib_set(); } }, - { THR_SLIT_SET, [this]{ return slit_set(this->target.iscal ? VSM_DATABASE - : VSM_ACQUIRE); } } + { { OP_MOVE_TO_TARGET, [this]{ return move_to_target(); } }, + { OP_CAMERA_SETUP, [this]{ return camera_setup(); } }, + { OP_FOCUS_SETUP, [this]{ return focus_setup(); } }, + { OP_FLEXURE_SETUP, [this]{ return flexure_setup(); } }, + { OP_CALIB_SETUP, [this]{ return calib_setup(); } }, + { OP_SLIT_SETUP, [this]{ return slit_setup(this->target.iscal ? VSM_DATABASE + : VSM_ACQUIRE); } } } } ); } @@ -354,16 +362,16 @@ namespace Sequencer { if (this->target.pointmode != Acam::POINTMODE_ACAM) { sequence.push_back( { OperationType::SERIAL, OnError::STOP, - { { THR_ACQUISITION, + { { OP_ACQUISITION, [this,caller]() { return this->do_target_acquisition(caller); } }, - { THR_MOVE_TO_TARGET, + { OP_MOVE_TO_TARGET, [this,caller]() { return this->do_target_offset(caller); } }, - { THR_SLIT_SET, + { OP_SLIT_SETUP, [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } }, - { THR_EXPOSURE, + { OP_EXPOSURE, [this,caller]() { return this->do_exposure(caller); } } } } ); @@ -602,30 +610,30 @@ namespace Sequencer { } // ---------- RULE ------------------------------------------------------- - // Collect presence flags + counts by Operation::thr identifier. + // Collect presence flags + counts by Operation::id identifier. // Used by subsequent rules. - bool has_expose = false; - bool has_camera_set = false; - bool has_slit_set = false; - int expose_count = 0; + bool has_expose = false; + bool has_camera_setup = false; + bool has_slit_setup = false; + int expose_count = 0; for (const auto &group : sequence) { for (const auto &op : group.operations) { - switch (op.thr) { - case THR_EXPOSURE: has_expose = true; ++expose_count; break; - case THR_CAMERA_SET: has_camera_set = true; break; - case THR_SLIT_SET: has_slit_set = true; break; + switch (op.id) { + case OP_EXPOSURE: has_expose = true; ++expose_count; break; + case OP_CAMERA_SETUP: has_camera_setup = true; break; + case OP_SLIT_SETUP: has_slit_setup = true; break; default: break; } } } // ---------- RULE ------------------------------------------------------- - // If expose is present, camera_set must also be present. + // If expose is present, camera_setup must also be present. - if ( has_expose && !has_camera_set ) { - this->broadcast.error( function, "sequence contains 'expose' without 'camera_set'" ); + if ( has_expose && !has_camera_setup ) { + this->broadcast.error( function, "sequence contains 'expose' without 'camera_setup'" ); error = ERROR; } @@ -643,8 +651,8 @@ namespace Sequencer { std::bitset required; for (const auto &group : sequence) { for (const auto &op : group.operations) { - auto it = thr_to_daemon.find(op.thr); - if ( it != thr_to_daemon.end() ) required.set(it->second); + auto it = opid_to_daemon.find(op.id); + if ( it != opid_to_daemon.end() ) required.set(it->second); } } @@ -691,12 +699,12 @@ namespace Sequencer { if ( group.type != OperationType::PARALLEL ) continue; for (std::size_t i = 0; i < group.operations.size(); ++i) { for (std::size_t j = i+1; j < group.operations.size(); ++j) { - if ( is_parallel_unsafe_pair(group.operations[i].thr, - group.operations[j].thr) ) { + if ( is_parallel_unsafe_pair(group.operations[i].id, + group.operations[j].id) ) { std::ostringstream oss; oss << "parallel group contains unsafe pair: '" - << thread_names.at(group.operations[i].thr) << "' and '" - << thread_names.at(group.operations[j].thr) << "'"; + << op_names.at(group.operations[i].id) << "' and '" + << op_names.at(group.operations[j].id) << "'"; this->broadcast.error(function, oss.str()); error = ERROR; } @@ -706,18 +714,18 @@ namespace Sequencer { // science-mode cross-group check: reject parallel-unsafe pairs anywhere if ( !this->engineering_mode.load() ) { - std::vector all_thrs; - all_thrs.reserve(NUM_THREAD_STATES); + std::vector all_ops; + all_ops.reserve(NUM_OP_STATES); for (const auto &group : sequence) { - for (const auto &op : group.operations) all_thrs.push_back(op.thr); + for (const auto &op : group.operations) all_ops.push_back(op.id); } - for (std::size_t i = 0; i < all_thrs.size(); ++i) { - for (std::size_t j = i+1; j < all_thrs.size(); ++j) { - if ( is_parallel_unsafe_pair(all_thrs[i], all_thrs[j]) ) { + for (std::size_t i = 0; i < all_ops.size(); ++i) { + for (std::size_t j = i+1; j < all_ops.size(); ++j) { + if ( is_parallel_unsafe_pair(all_ops[i], all_ops[j]) ) { std::ostringstream oss; oss << "sequence contains unsafe pair in science mode: '" - << thread_names.at(all_thrs[i]) << "' and '" - << thread_names.at(all_thrs[j]) + << op_names.at(all_ops[i]) << "' and '" + << op_names.at(all_ops[j]) << "' (use 'engineering true' to allow in SERIAL groups)"; this->broadcast.error(function, oss.str()); error = ERROR; @@ -1083,8 +1091,8 @@ namespace Sequencer { // iterate through map of thread state bits, add each as a key in the JSON message, // and set true|false if the bit is set or not - std::string active_states( this->thread_state_manager.get_set_states() ); - for ( const auto &[bit,state] : Sequencer::thread_names ) { + std::string active_states( this->op_state_manager.get_set_states() ); + for ( const auto &[bit,state] : Sequencer::op_names ) { jmessage_out[state] = ( active_states.find(state)!=std::string::npos ? true : false); } @@ -1191,7 +1199,7 @@ namespace Sequencer { void Sequence::dothread_sequencer_async_listener( Sequencer::Sequence &seq, Network::UdpSocket udp ) { const std::string function("Sequencer::Sequence::dothread_sequencer_async_listener"); - ScopedState thr_state( seq.thread_state_manager, Sequencer::THR_SEQUENCER_ASYNC_LISTENER ); + ScopedState thr_state( seq.op_state_manager, Sequencer::OP_SEQUENCER_ASYNC_LISTENER ); int retval = udp.Listener(); @@ -1306,7 +1314,7 @@ namespace Sequencer { // The Sequencer can only be started once // - if ( thread_state_manager.is_set( Sequencer::THR_SEQUENCE_START ) ) { + if ( op_state_manager.is_set( Sequencer::OP_SEQUENCE_START ) ) { this->broadcast.error( function, "sequencer already running" ); return; } @@ -1320,7 +1328,7 @@ namespace Sequencer { // ---------- SEQUENCER IS RUNNING --------------------- // - ScopedState thr_state( thread_state_manager, Sequencer::THR_SEQUENCE_START ); // this thread is running + ScopedState thr_state( op_state_manager, Sequencer::OP_SEQUENCE_START ); // this thread is running ScopedState seq_state( seq_state_manager, Sequencer::SEQ_RUNNING, true ); // state = RUNNING (only) seq_state.destruct_set( Sequencer::SEQ_READY ); // set state=READY on exit @@ -1330,7 +1338,7 @@ namespace Sequencer { // clear the thread error state // - this->thread_error_manager.clear_all(); + this->op_error_manager.clear_all(); // clear stop flags // @@ -1376,14 +1384,14 @@ namespace Sequencer { message.str(""); message << "cannot move to target " << this->target.name << " because TCS is not connected"; this->broadcast.error( function, message.str() ); - this->thread_error_manager.set( THR_SEQUENCE_START ); // report error + this->op_error_manager.set( OP_SEQUENCE_START ); // report error break; } } error = this->target.update_state( Sequencer::TARGET_ACTIVE ); // set ACTIVE state in database (this says we are using this target) if (error!=NO_ERROR) { - this->thread_error_manager.set( THR_SEQUENCE_START ); // report any error + this->op_error_manager.set( OP_SEQUENCE_START ); // report any error break; } } @@ -1403,7 +1411,7 @@ namespace Sequencer { error = run_default_sequence(function); if (error != NO_ERROR) { - this->thread_error_manager.set(THR_SEQUENCE_START); + this->op_error_manager.set(OP_SEQUENCE_START); break; } @@ -1416,11 +1424,11 @@ namespace Sequencer { // error = this->target.update_state( Sequencer::TARGET_COMPLETE ); // update the active target table if (error==NO_ERROR) error = this->target.insert_completed(); // insert into the completed table - if (error!=NO_ERROR) this->thread_error_manager.set( THR_SEQUENCE_START ); // report any error + if (error!=NO_ERROR) this->op_error_manager.set( OP_SEQUENCE_START ); // report any error // abort sequence on error // - if ( this->thread_error_manager.are_any_set() ) break; + if ( this->op_error_manager.are_any_set() ) break; // Check the "dotype" -- // If this was "do one" then do_once is set and get out now. @@ -1432,14 +1440,14 @@ namespace Sequencer { } // end while true - if ( this->thread_error_manager.are_any_set() ) { + if ( this->op_error_manager.are_any_set() ) { logwrite(function, "ERROR stopping sequencer due to error in: "+ - this->thread_error_manager.get_set_states()); + this->op_error_manager.get_set_states()); // If this target was flagged as active, then change it to unassigned on error. if ( this->target.get_next( Sequencer::TARGET_ACTIVE, targetstatus ) == TargetInfo::TARGET_FOUND ) { this->target.update_state( Sequencer::TARGET_UNASSIGNED ); } - this->thread_error_manager.clear_all(); // clear the thread error state + this->op_error_manager.clear_all(); // clear the thread error state this->do_once.store(true); } @@ -1449,7 +1457,7 @@ namespace Sequencer { /***** Sequencer::Sequence::sequence_start **********************************/ - /***** Sequencer::Sequence::camera_set **************************************/ + /***** Sequencer::Sequence::camera_setup **************************************/ /** * @brief sets the camera according to the parameters in the target entry * @return NO_ERROR @@ -1458,20 +1466,20 @@ namespace Sequencer { * At the moment, this is only exposure time. * */ - long Sequence::camera_set() { - const std::string function("Sequencer::Sequence::camera_set"); + long Sequence::camera_setup() { + const std::string function("Sequencer::Sequence::camera_setup"); std::string reply; std::stringstream camcmd; long error=NO_ERROR; - ScopedState thr_state( thread_state_manager, Sequencer::THR_CAMERA_SET ); + ScopedState thr_state( op_state_manager, Sequencer::OP_CAMERA_SETUP ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); this->wait_for_canexpose(function); logwrite( function, "setting camera parameters"); - this->thread_error_manager.set( THR_CAMERA_SET ); // assume the worse, clear on success + this->op_error_manager.set( OP_CAMERA_SETUP ); // assume the worse, clear on success // Controller activate states stored in Sequencer::CalibrationTarget::calinfo map, // indexed by name. Calibration targets use target.name for the index, or @@ -1528,14 +1536,14 @@ namespace Sequencer { throw std::runtime_error( "camera returned "+reply ); } - this->thread_error_manager.clear( THR_CAMERA_SET ); // success + this->op_error_manager.clear( OP_CAMERA_SETUP ); // success return NO_ERROR; } - /***** Sequencer::Sequence::camera_set **************************************/ + /***** Sequencer::Sequence::camera_setup **************************************/ - /***** Sequencer::Sequence::slit_set ****************************************/ + /***** Sequencer::Sequence::slit_setup ****************************************/ /** * @brief sets the slit width and offset * @details Slit width is always set according to the value in the target @@ -1545,12 +1553,12 @@ namespace Sequencer { * @throws std::runtime_error * */ - long Sequence::slit_set(VirtualSlitMode mode) { - const std::string function("Sequencer::Sequence::slit_set"); + long Sequence::slit_setup(VirtualSlitMode mode) { + const std::string function("Sequencer::Sequence::slit_setup"); std::string reply, modestr; std::stringstream slitcmd, message; - ScopedState thr_state( thread_state_manager, Sequencer::THR_SLIT_SET ); + ScopedState thr_state( op_state_manager, Sequencer::OP_SLIT_SETUP ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_SLIT ); // send the SET command to slitd @@ -1584,13 +1592,13 @@ namespace Sequencer { if ( this->slitd.command_timeout( slitcmd.str(), reply, SLITD_SET_TIMEOUT ) != NO_ERROR ) { logwrite( function, "ERROR setting slit" ); - this->thread_error_manager.set( THR_SLIT_SET ); + this->op_error_manager.set( OP_SLIT_SETUP ); throw std::runtime_error("slit returned: "+reply); } return NO_ERROR; } - /***** Sequencer::Sequence::slit_set ****************************************/ + /***** Sequencer::Sequence::slit_setup ****************************************/ /***** Sequencer::Sequence::power_init **************************************/ @@ -1603,7 +1611,7 @@ namespace Sequencer { long Sequence::power_init() { const std::string function("Sequencer::Sequence::power_init"); - ScopedState thr_state( thread_state_manager, Sequencer::THR_POWER_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_POWER_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_POWER ); this->daemon_manager.clear( Sequencer::DAEMON_POWER ); // powerd not ready @@ -1630,7 +1638,7 @@ namespace Sequencer { long Sequence::power_shutdown() { const std::string function("Sequencer::Sequence::power_shutdown"); - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_POWER_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_POWER_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_POWER ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_POWER ); @@ -1653,12 +1661,12 @@ namespace Sequencer { long Sequence::slit_init() { const std::string function("Sequencer::Sequence::slit_init"); - ScopedState thr_state( thread_state_manager, Sequencer::THR_SLIT_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_SLIT_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_SLIT ); this->daemon_manager.clear( Sequencer::DAEMON_SLIT ); // slitd not ready - this->thread_error_manager.set( THR_SLIT_INIT ); // assume the worst, clear on success + this->op_error_manager.set( OP_SLIT_INIT ); // assume the worst, clear on success if ( this->set_power_switch(ON, POWER_SLIT, std::chrono::seconds(5)) != NO_ERROR ) { logwrite( function, "ERROR powering slit hardware" ); @@ -1701,7 +1709,7 @@ namespace Sequencer { } } - this->thread_error_manager.clear( THR_SLIT_INIT ); // success + this->op_error_manager.clear( OP_SLIT_INIT ); // success this->daemon_manager.set( Sequencer::DAEMON_SLIT ); // slitd ready return NO_ERROR; @@ -1722,11 +1730,11 @@ namespace Sequencer { std::string reply; long error=NO_ERROR; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_SLIT_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_SLIT_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_SLIT ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_SLIT ); - this->thread_error_manager.set( THR_SLIT_SHUTDOWN ); // assume the worst, clear on success + this->op_error_manager.set( OP_SLIT_SHUTDOWN ); // assume the worst, clear on success // already off? // @@ -1769,7 +1777,7 @@ namespace Sequencer { logwrite( function, "disconnecting from slitd" ); this->slitd.disconnect(); - this->thread_error_manager.clear( THR_SLIT_SHUTDOWN ); // success + this->op_error_manager.clear( OP_SLIT_SHUTDOWN ); // success return NO_ERROR; } @@ -1789,10 +1797,10 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_SLICECAM ); // slicecamd not ready - ScopedState thr_state( thread_state_manager, Sequencer::THR_SLICECAM_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_SLICECAM_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_SLICECAM ); - this->thread_error_manager.set( THR_SLICECAM_INIT ); // assume the worst, clear on success + this->op_error_manager.set( OP_SLICECAM_INIT ); // assume the worst, clear on success // make sure hardware is powered on // @@ -1809,7 +1817,7 @@ namespace Sequencer { } this->daemon_manager.set( Sequencer::DAEMON_SLICECAM ); // slicecamd ready - this->thread_error_manager.clear( THR_SLICECAM_INIT ); // success + this->op_error_manager.clear( OP_SLICECAM_INIT ); // success return NO_ERROR; } @@ -1829,10 +1837,10 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_ACAM ); // acamd not ready - ScopedState thr_state( thread_state_manager, Sequencer::THR_ACAM_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_ACAM_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_ACAM ); - this->thread_error_manager.set( THR_ACAM_INIT ); // assume the worst, clear on success + this->op_error_manager.set( OP_ACAM_INIT ); // assume the worst, clear on success // make sure hardware is powered on // @@ -1869,7 +1877,7 @@ namespace Sequencer { } } - this->thread_error_manager.clear( THR_ACAM_INIT ); // success + this->op_error_manager.clear( OP_ACAM_INIT ); // success this->daemon_manager.set( Sequencer::DAEMON_ACAM ); // acamd ready return NO_ERROR; @@ -1890,11 +1898,11 @@ namespace Sequencer { std::string reply; long error=NO_ERROR; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_SLICECAM_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_SLICECAM_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_SLICECAM ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_SLICECAM ); - this->thread_error_manager.set( THR_SLICECAM_SHUTDOWN ); // assume the worst, clear on success + this->op_error_manager.set( OP_SLICECAM_SHUTDOWN ); // assume the worst, clear on success // already off? // @@ -1934,7 +1942,7 @@ namespace Sequencer { throw std::runtime_error("could not power off slicecam hardware"); } - this->thread_error_manager.clear( THR_SLICECAM_SHUTDOWN ); // success + this->op_error_manager.clear( OP_SLICECAM_SHUTDOWN ); // success return NO_ERROR; } /***** Sequencer::Sequence::slicecam_shutdown *******************************/ @@ -1953,11 +1961,11 @@ namespace Sequencer { std::string reply; long error=NO_ERROR; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_ACAM_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_ACAM_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_ACAM ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_ACAM ); - this->thread_error_manager.set( THR_ACAM_SHUTDOWN ); // assume the worst, clear on success + this->op_error_manager.set( OP_ACAM_SHUTDOWN ); // assume the worst, clear on success // ensure a connection to the daemon // @@ -2005,7 +2013,7 @@ namespace Sequencer { throw std::runtime_error("could not switch off acam"); } - this->thread_error_manager.clear( THR_ACAM_SHUTDOWN ); // success + this->op_error_manager.clear( OP_ACAM_SHUTDOWN ); // success return NO_ERROR; } @@ -2024,10 +2032,10 @@ namespace Sequencer { this->daemon_manager.clear( Sequencer::DAEMON_CALIB ); - ScopedState thr_state( thread_state_manager, Sequencer::THR_CALIB_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_CALIB_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_CALIB ); - this->thread_error_manager.set( THR_CALIB_INIT ); // assume the worst, clear on success + this->op_error_manager.set( OP_CALIB_INIT ); // assume the worst, clear on success // make sure calib hardware is powered if ( this->set_power_switch(ON, POWER_CALIB, std::chrono::seconds(5)) != NO_ERROR ) { @@ -2079,7 +2087,7 @@ namespace Sequencer { // calibd is ready this->daemon_manager.set( Sequencer::DAEMON_CALIB ); - this->thread_error_manager.clear( THR_CALIB_INIT ); + this->op_error_manager.clear( OP_CALIB_INIT ); return NO_ERROR; } @@ -2097,11 +2105,11 @@ namespace Sequencer { const std::string function("Sequencer::Sequence::calib_shutdown"); long error=NO_ERROR; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_CALIB_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_CALIB_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_CALIB ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_CALIB ); - this->thread_error_manager.set( THR_CALIB_SHUTDOWN ); // assume the worst, clear on success + this->op_error_manager.set( OP_CALIB_SHUTDOWN ); // assume the worst, clear on success // is calib hardware powered? // @@ -2163,11 +2171,11 @@ namespace Sequencer { // set this thread's error status // if (error!=NO_ERROR) { - this->thread_error_manager.set( THR_CALIB_SHUTDOWN ); + this->op_error_manager.set( OP_CALIB_SHUTDOWN ); throw std::runtime_error("shutting down calib control"); } - this->thread_error_manager.clear( THR_CALIB_SHUTDOWN ); // success + this->op_error_manager.clear( OP_CALIB_SHUTDOWN ); // success return NO_ERROR; } /***** Sequencer::Sequence::calib_shutdown **********************************/ @@ -2184,14 +2192,14 @@ namespace Sequencer { * */ long Sequence::tcs_init() { - ScopedState thr_state( thread_state_manager, Sequencer::THR_TCS_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_TCS_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCS ); this->daemon_manager.clear( Sequencer::DAEMON_TCS ); // tcsd not ready if ( this->open_hardware(this->tcsd) != NO_ERROR ) { logwrite( "Sequencer::Sequence::tcs_init", "ERROR initializing TCS" ); - this->thread_error_manager.set( THR_TCS_INIT ); + this->op_error_manager.set( OP_TCS_INIT ); throw std::runtime_error("could not initialize TCS"); } @@ -2219,7 +2227,7 @@ namespace Sequencer { const std::string function("Sequencer::Sequence::tcs_shutdown"); std::stringstream message; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_TCS_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_TCS_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_TCS ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_TCS ); @@ -2260,7 +2268,7 @@ namespace Sequencer { long Sequence::flexure_init() { const std::string function("Sequencer::Sequence::flexure_init"); - ScopedState thr_state( thread_state_manager, Sequencer::THR_FLEXURE_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_FLEXURE_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_FLEXURE ); this->daemon_manager.clear( Sequencer::DAEMON_FLEXURE ); // flexured not ready @@ -2269,13 +2277,13 @@ namespace Sequencer { // if ( this->set_power_switch(ON, POWER_FLEXURE, std::chrono::seconds(21)) != NO_ERROR ) { logwrite( function, "ERROR powering flexure control" ); - this->thread_error_manager.set( THR_FLEXURE_INIT ); + this->op_error_manager.set( OP_FLEXURE_INIT ); throw std::runtime_error("could not power flexure control"); } if ( this->open_hardware(this->flexured) != NO_ERROR ) { logwrite( function, "ERROR initializing flexure control" ); - this->thread_error_manager.set( THR_FLEXURE_INIT ); + this->op_error_manager.set( OP_FLEXURE_INIT ); throw std::runtime_error("could not initialize flexure control"); } @@ -2301,7 +2309,7 @@ namespace Sequencer { std::string reply; long error=NO_ERROR; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_FLEXURE_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_FLEXURE_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_FLEXURE ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_FLEXURE ); @@ -2360,12 +2368,12 @@ namespace Sequencer { long Sequence::focus_init() { const std::string function("Sequencer::Sequence::focus_init"); - ScopedState thr_state( thread_state_manager, Sequencer::THR_FOCUS_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_FOCUS_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_FOCUS ); this->daemon_manager.clear( Sequencer::DAEMON_FOCUS ); // focusd not ready - this->thread_error_manager.set( THR_FOCUS_INIT ); // assume failure, clear on success + this->op_error_manager.set( OP_FOCUS_INIT ); // assume failure, clear on success if ( this->set_power_switch(ON, POWER_FOCUS, std::chrono::seconds(5)) != NO_ERROR ) { logwrite( function, "ERROR powering focus control" ); @@ -2410,7 +2418,7 @@ namespace Sequencer { } } - this->thread_error_manager.clear( THR_FOCUS_INIT ); // success + this->op_error_manager.clear( OP_FOCUS_INIT ); // success this->daemon_manager.set( Sequencer::DAEMON_FOCUS ); // focusd is ready return NO_ERROR; @@ -2430,7 +2438,7 @@ namespace Sequencer { std::string reply; long error=NO_ERROR; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_FOCUS_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_FOCUS_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_FOCUS ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_FOCUS ); @@ -2489,11 +2497,11 @@ namespace Sequencer { long Sequence::camera_init() { const std::string function("Sequencer::Sequence::camera_init"); - ScopedState thr_state( thread_state_manager, Sequencer::THR_CAMERA_INIT ); + ScopedState thr_state( op_state_manager, Sequencer::OP_CAMERA_INIT ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); this->daemon_manager.clear( Sequencer::DAEMON_CAMERA ); // camerad not ready - this->thread_error_manager.set( THR_CAMERA_INIT ); // assume failure, clear on success + this->op_error_manager.set( OP_CAMERA_INIT ); // assume failure, clear on success // make sure hardware is powered on // @@ -2525,7 +2533,7 @@ namespace Sequencer { this->camerad.send( CAMERAD_FRAMETRANSFER+" all", reply ); this->is_science_frame_transfer = ( reply.find("yes") != std::string::npos ); - this->thread_error_manager.clear( THR_CAMERA_INIT ); // success + this->op_error_manager.clear( OP_CAMERA_INIT ); // success this->daemon_manager.set( Sequencer::DAEMON_CAMERA ); // camerad ready return NO_ERROR; @@ -2543,11 +2551,11 @@ namespace Sequencer { long Sequence::camera_shutdown() { const std::string function("Sequencer::Sequence::camera_shutdown"); - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_CAMERA_SHUTDOWN ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_CAMERA_SHUTDOWN ); ScopedState wait_state( this->wait_state_manager, Sequencer::SEQ_WAIT_CAMERA ); ScopedState daemon_state( this->daemon_manager, Sequencer::DAEMON_CAMERA ); - this->thread_error_manager.set( THR_CAMERA_SHUTDOWN ); // assume failure, clear on success + this->op_error_manager.set( OP_CAMERA_SHUTDOWN ); // assume failure, clear on success // Are any cameras on? // @@ -2589,7 +2597,7 @@ namespace Sequencer { throw std::runtime_error("switching off camera"); } - this->thread_error_manager.clear( THR_CAMERA_SHUTDOWN ); // success + this->op_error_manager.clear( OP_CAMERA_SHUTDOWN ); // success return NO_ERROR; } /***** Sequencer::Sequence::camera_shutdown *********************************/ @@ -2614,7 +2622,7 @@ namespace Sequencer { // if ( this->target.iscal ) return NO_ERROR; - ScopedState thr_state( thread_state_manager, Sequencer::THR_MOVE_TO_TARGET ); + ScopedState thr_state( op_state_manager, Sequencer::OP_MOVE_TO_TARGET ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_TCS ); ScopedState wait_moveto( wait_state_manager, Sequencer::SEQ_WAIT_MOVETO ); @@ -2686,7 +2694,7 @@ namespace Sequencer { if ( dec_isnan ) { message << " DEC=\"" << this->target.dec_dms << "\""; } message << " to decimal"; logwrite( function, "ERROR "+message.str() ); - this->thread_error_manager.set( THR_MOVE_TO_TARGET ); + this->op_error_manager.set( OP_MOVE_TO_TARGET ); throw std::runtime_error(message.str()); } @@ -2737,7 +2745,7 @@ namespace Sequencer { if ( error != NO_ERROR || coords_reply.compare( 0, strlen(TCS_SUCCESS_STR), TCS_SUCCESS_STR ) != 0 ) { message.str(""); message << "sending COORDS command. TCS reply: " << coords_reply; logwrite( function, "ERROR "+message.str() ); - this->thread_error_manager.set( THR_MOVE_TO_TARGET ); + this->op_error_manager.set( OP_MOVE_TO_TARGET ); throw std::runtime_error("sending COORDS to TCS: "+coords_reply); } } @@ -2800,7 +2808,7 @@ namespace Sequencer { const std::string function("Sequencer::Sequence::dothread_notify_tcs"); std::stringstream message; - ScopedState thr_state( seq.thread_state_manager, Sequencer::THR_NOTIFY_TCS ); + ScopedState thr_state( seq.op_state_manager, Sequencer::OP_NOTIFY_TCS ); // If this target is the same as the last then this section gets skipped; // no need to repoint the telescope and wait for a slew if it's already @@ -2837,7 +2845,7 @@ namespace Sequencer { if ( std::isnan( radec_to_decimal( seq.target.ra_hms, ra_hms ) ) || std::isnan( radec_to_decimal( seq.target.dec_dms, dec_dms ) ) ) { seq.broadcast.error( function, "can't handle NaN value for RA, DEC" ); - seq.thread_error_manager.set( THR_NOTIFY_TCS ); + seq.op_error_manager.set( OP_NOTIFY_TCS ); return; } @@ -2861,59 +2869,59 @@ namespace Sequencer { /***** Sequencer::Sequence::dothread_notify_tcs *****************************/ - /***** Sequencer::Sequence::focus_set ***************************************/ + /***** Sequencer::Sequence::focus_setup ***************************************/ /** * @brief set the focus * @return NO_ERROR * @todo focus not yet implemented * */ - long Sequence::focus_set() { - const std::string function("Sequencer::Sequence::focus_set"); + long Sequence::focus_setup() { + const std::string function("Sequencer::Sequence::focus_setup"); - ScopedState thr_state( thread_state_manager, Sequencer::THR_FOCUS_SET ); + ScopedState thr_state( op_state_manager, Sequencer::OP_FOCUS_SETUP ); logwrite( function, "focus not yet implemented." ); return NO_ERROR; } - /***** Sequencer::Sequence::focus_set ***************************************/ + /***** Sequencer::Sequence::focus_setup ***************************************/ - /***** Sequencer::Sequence::flexure_set *************************************/ + /***** Sequencer::Sequence::flexure_setup *************************************/ /** * @brief set the flexure * @return NO_ERROR * @todo flexure not yet implemented * */ - long Sequence::flexure_set() { - const std::string function("Sequencer::Sequence::flexure_set"); + long Sequence::flexure_setup() { + const std::string function("Sequencer::Sequence::flexure_setup"); - ScopedState thr_state( thread_state_manager, Sequencer::THR_FLEXURE_SET ); + ScopedState thr_state( op_state_manager, Sequencer::OP_FLEXURE_SETUP ); logwrite( function, "flexure not yet implemented." ); return NO_ERROR; } - /***** Sequencer::Sequence::flexure_set *************************************/ + /***** Sequencer::Sequence::flexure_setup *************************************/ - /***** Sequencer::Sequence::calib_set ***************************************/ + /***** Sequencer::Sequence::calib_setup ***************************************/ /** * @brief set the calibrator * @return NO_ERROR * @throws std::runtime_error * */ - long Sequence::calib_set() { - const std::string function("Sequencer::Sequence::calib_set"); + long Sequence::calib_setup() { + const std::string function("Sequencer::Sequence::calib_setup"); std::stringstream message; - ScopedState thr_state( thread_state_manager, Sequencer::THR_CALIBRATOR_SET ); + ScopedState thr_state( op_state_manager, Sequencer::OP_CALIB_SETUP ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_CALIB ); - this->thread_error_manager.set( THR_CALIBRATOR_SET ); // assume the worse, clear on success + this->op_error_manager.set( OP_CALIB_SETUP ); // assume the worse, clear on success const std::string calname = std::string(this->target.iscal ? this->target.name : "SCIENCE"); @@ -2979,10 +2987,10 @@ namespace Sequencer { this->broadcast.notice( function, "abort may have left calib system partially set" ); } - this->thread_error_manager.clear( THR_CALIBRATOR_SET ); // success + this->op_error_manager.clear( OP_CALIB_SETUP ); // success return NO_ERROR; } - /***** Sequencer::Sequence::calib_set ***************************************/ + /***** Sequencer::Sequence::calib_setup ***************************************/ /***** Sequencer::Sequence::abort_process *********************************/ @@ -2993,7 +3001,7 @@ namespace Sequencer { void Sequence::abort_process() { const std::string function("Sequencer::Sequence::abort_process"); - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_ABORT_PROCESS ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_ABORT_PROCESS ); // Decide post-abort seqstate before entering SEQ_ABORTING. These snapshots // must be taken before any seqstate mutation below, because set_only() @@ -3050,17 +3058,17 @@ namespace Sequencer { this->broadcast.notice( function, "cancel signal sent" ); // Wait for sequence_start to fully exit before switching to SEQ_READY. - // Without this, we could have SEQ_READY set while THR_SEQUENCE_START is + // Without this, we could have SEQ_READY set while OP_SEQUENCE_START is // still set. Workers just received cancel_flag + CV notifications above, // so this loop exits after at most a few iterations. // if ( abort_during_run ) { const auto drain_timeout = std::chrono::steady_clock::now() + std::chrono::seconds(5); - while ( this->thread_state_manager.is_set( Sequencer::THR_SEQUENCE_START ) && + while ( this->op_state_manager.is_set( Sequencer::OP_SEQUENCE_START ) && std::chrono::steady_clock::now() < drain_timeout ) { std::this_thread::sleep_for( std::chrono::milliseconds(20) ); } - if ( this->thread_state_manager.is_set( Sequencer::THR_SEQUENCE_START ) ) { + if ( this->op_state_manager.is_set( Sequencer::OP_SEQUENCE_START ) ) { logwrite( function, "WARNING: sequence_start did not exit within drain timeout" ); } } @@ -3090,7 +3098,7 @@ namespace Sequencer { void Sequence::stop_exposure() { const std::string function("Sequencer::Sequence::stop_exposure"); - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_STOP_EXPOSURE ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_STOP_EXPOSURE ); // This function is only used while exposing // @@ -3146,7 +3154,7 @@ namespace Sequencer { return ERROR; } - ScopedState thr_state( thread_state_manager, Sequencer::THR_REPEAT_EXPOSURE ); + ScopedState thr_state( op_state_manager, Sequencer::OP_REPEAT_EXPOSURE ); ScopedState seq_state( seq_state_manager, Sequencer::SEQ_RUNNING, true ); // set state=RUNNING (only) seq_state.destruct_set( Sequencer::SEQ_READY ); // set state=READY on exit @@ -3163,9 +3171,9 @@ namespace Sequencer { std::vector sequence = { { OperationType::SERIAL, OnError::STOP, { - { THR_SLIT_SET, + { OP_SLIT_SETUP, [this]() { return this->do_target_virtualslit(Sequencer::VSM_EXPOSE); } }, - { THR_EXPOSURE, + { OP_EXPOSURE, [this,function]() { return this->do_exposure(function); } } } } }; @@ -3199,7 +3207,7 @@ namespace Sequencer { this->wait_for_canexpose(function); - ScopedState thr_state( thread_state_manager, Sequencer::THR_TRIGGER_EXPOSURE ); + ScopedState thr_state( op_state_manager, Sequencer::OP_TRIGGER_EXPOSURE ); // Check tcs_preauth_time and set notify_tcs_next_target -- // When the preauth_time is non-zero, set this flag to true in order @@ -3222,7 +3230,7 @@ namespace Sequencer { // if ( this->camerad.send( message.str(), reply ) != NO_ERROR ) { if ( this->camerad.command_timeout( message.str(), reply, 30000 ) != NO_ERROR ) { logwrite( function, "ERROR sending camera "+message.str() ); - this->thread_error_manager.set( THR_TRIGGER_EXPOSURE ); // tell the world this thread had an error + this->op_error_manager.set( OP_TRIGGER_EXPOSURE ); // tell the world this thread had an error this->target.update_state( Sequencer::TARGET_PENDING ); // return the target state to pending this->wait_state_manager.clear( Sequencer::SEQ_WAIT_EXPOSE ); // clear EXPOSE bit this->arm_readout_flag = false; // disarm async_listener from looking for readout @@ -3278,7 +3286,7 @@ namespace Sequencer { long error = NO_ERROR; double updated_exptime=0; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_MODIFY_EXPTIME ); + ScopedState thr_state( this->op_state_manager, Sequencer::OP_MODIFY_EXPTIME ); // This function is only used while exposing // @@ -3333,12 +3341,12 @@ namespace Sequencer { return ERROR; } - ScopedState thread_state( thread_state_manager, Sequencer::THR_STARTUP ); // this thread is running + ScopedState thread_state( op_state_manager, Sequencer::OP_STARTUP ); // this thread is running // set only STARTING (and clear everything else) ScopedState seq_state( seq_state_manager, Sequencer::SEQ_STARTING, true ); // state=STARTING (only) - this->thread_error_manager.clear_all(); // clear the thread error state + this->op_error_manager.clear_all(); // clear the thread error state // clear stop flags // @@ -3400,7 +3408,7 @@ namespace Sequencer { const std::string function("Sequencer::Sequence::shutdown"); long error=ERROR; - ScopedState thr_state( this->thread_state_manager, Sequencer::THR_SHUTDOWN ); // this thread is running + ScopedState thr_state( this->op_state_manager, Sequencer::OP_SHUTDOWN ); // this thread is running // Enter STOPPING as a strict one-hot state. Explicit set_only is used // instead of ScopedState because final state is set explicitly before return. @@ -3425,12 +3433,12 @@ namespace Sequencer { // clear the thread error state // - this->thread_error_manager.clear_all(); + this->op_error_manager.clear_all(); // Everything (except TCS) needs the power control to be running // so make sure power control is initialized before continuing. // - error = run( { THR_POWER_INIT, [this]{ return power_init(); }, { } }, function ); + error = run( { OP_POWER_INIT, [this]{ return power_init(); }, { } }, function ); if ( error != NO_ERROR ) { this->broadcast.error( function, "from power control. Will try to continue (but don't hold your breath)" ); @@ -3440,14 +3448,14 @@ namespace Sequencer { // pair their ThreadStatusBit with the function to call // error = run_parallel( { - { THR_ACAM_SHUTDOWN, [this]{ return acam_shutdown(); }, { } }, - { THR_CALIB_SHUTDOWN, [this]{ return calib_shutdown(); }, { } }, - { THR_CAMERA_SHUTDOWN, [this]{ return camera_shutdown(); }, { } }, - { THR_FLEXURE_SHUTDOWN, [this]{ return flexure_shutdown(); }, { } }, - { THR_FOCUS_SHUTDOWN, [this]{ return focus_shutdown(); }, { } }, - { THR_SLIT_SHUTDOWN, [this]{ return slit_shutdown(); }, { } }, - { THR_SLICECAM_SHUTDOWN, [this]{ return slicecam_shutdown(); }, { } }, - { THR_TCS_SHUTDOWN, [this]{ return tcs_shutdown(); }, { } } + { OP_ACAM_SHUTDOWN, [this]{ return acam_shutdown(); }, { } }, + { OP_CALIB_SHUTDOWN, [this]{ return calib_shutdown(); }, { } }, + { OP_CAMERA_SHUTDOWN, [this]{ return camera_shutdown(); }, { } }, + { OP_FLEXURE_SHUTDOWN, [this]{ return flexure_shutdown(); }, { } }, + { OP_FOCUS_SHUTDOWN, [this]{ return focus_shutdown(); }, { } }, + { OP_SLIT_SHUTDOWN, [this]{ return slit_shutdown(); }, { } }, + { OP_SLICECAM_SHUTDOWN, [this]{ return slicecam_shutdown(); }, { } }, + { OP_TCS_SHUTDOWN, [this]{ return tcs_shutdown(); }, { } } }, function ); if (error==NO_ERROR) { @@ -4432,8 +4440,6 @@ namespace Sequencer { retstring.append( " addrow ? | \n" ); retstring.append( " async [ ? | ]\n" ); retstring.append( " acquire [ ? ]\n" ); - retstring.append( " calibset [ ? ]\n" ); - retstring.append( " cameraset [ ? ]\n" ); retstring.append( " cancel [ ? ]\n" ); retstring.append( " clearlasttarget\n" ); retstring.append( " completed [ ? ]\n" ); @@ -4512,52 +4518,6 @@ namespace Sequencer { } else - // ---------------------------------------------------- - // calibset -- sets the calibrator according to the parameters in the target entry - // ---------------------------------------------------- - // - if ( testname == "calibset" ) { - if ( tokens.size() > 1 && tokens[1] == "?" ) { - retstring = "test calibset\n"; - retstring.append( " Set only the calib according to the parameters in the target row.\n" ); - return HELP; - } - // launch thread and wait for it to return - auto calibset = std::async(std::launch::async, &Sequence::calib_set, this); - try { - error = calibset.get(); - } - catch (const std::exception& e) { - logwrite( function, "ERROR calib_set exception: "+std::string(e.what()) ); - return ERROR; - } - return error; - } - else - - // ---------------------------------------------------- - // cameraset -- sets the camera according to the parameters in the target entry - // ---------------------------------------------------- - // - if ( testname == "cameraset" ) { - if ( tokens.size() > 1 && tokens[1] == "?" ) { - retstring = "test cameraset\n"; - retstring.append( " Set only the camera according to the parameters in the target row.\n" ); - return HELP; - } - // launch thread and wait for it to return - auto cameraset = std::async(std::launch::async, &Sequence::camera_set, this); - try { - error = cameraset.get(); - } - catch (const std::exception& e) { - logwrite( function, "ERROR camera_set exception: "+std::string(e.what()) ); - return ERROR; - } - return error; - } - else - // ---------------------------------------------------- // expose -- trigger exposure // ---------------------------------------------------- @@ -4588,7 +4548,7 @@ namespace Sequencer { this->broadcast.notice( function, message.str() ); logwrite( function, message.str() ); - message.str(""); message << "THREADS: " << this->thread_state_manager.get_set_states(); + message.str(""); message << "THREADS: " << this->op_state_manager.get_set_states(); logwrite( function, message.str() ); message.str(""); message << "DAEMONS NOT READY: " << this->daemon_manager.get_cleared_states(); @@ -5138,49 +5098,6 @@ namespace Sequencer { } else - // --------------------------------------------------------- - // slitset -- spawn slit_set with the selected mode - // --------------------------------------------------------- - // - if ( testname == "slitset" ) { - - if ( tokens.size() > 1 && ( tokens[1] == "?" || tokens[1] == "help" || tokens[1] == "-h" ) ) { - retstring = "test slitset \n"; - retstring.append( " Spawn slit_set for = { expose, acquire, database }\n" ); - return HELP; - } - - if ( tokens.size() < 2 ) { - logwrite( function, "ERROR expected slitset " ); - retstring="invalid_argument"; - return ERROR; - } - - Sequencer::VirtualSlitMode mode; - - if ( tokens[1]=="expose" ) { mode = Sequencer::VSM_EXPOSE; } - else - if ( tokens[1]=="acquire" ) { mode = Sequencer::VSM_ACQUIRE; } - else - if ( tokens[1]=="database" ) { mode = Sequencer::VSM_DATABASE; } - else { - logwrite( function, "ERROR invalid mode "+tokens[1]+": expected { expose acquire database }" ); - retstring="invalid_argument"; - return ERROR; - } - - auto slitset = std::async(std::launch::async, &Sequence::slit_set, this, mode); - try { - error = slitset.get(); - } - catch (const std::exception& e) { - retstring="slit_set_exception"; - logwrite( function, "ERROR slit set exception: "+std::string(e.what()) ); - return ERROR; - } - } - else - // --------------------------------------------------------- // startup -- startup a single specified module // --------------------------------------------------------- diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 280cdfd1..427b5d2f 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -205,99 +205,115 @@ namespace Sequencer { }; /** - * @enum ThreadStatusBits - * @brief assigns each thread a bit in a threadstate word - * @details bit is set while thread is running + * @enum OperationStatusBits + * @brief assigns each operation a bit in an operation-state word + * @details bit is set while the operation is running */ - enum ThreadStatusBits : size_t { - THR_SEQUENCER_ASYNC_LISTENER=0, - THR_TRIGGER_EXPOSURE, - THR_EXPOSURE, - THR_REPEAT_EXPOSURE, - THR_STOP_EXPOSURE, - THR_ABORT_PROCESS, - THR_SEQUENCE_START, - THR_RUN_SCRIPT, - THR_MONITOR_READY_STATE, - THR_CALIB_SET, - THR_CAMERA_SET, - THR_SLIT_SET, - THR_MOVE_TO_TARGET, - THR_NOTIFY_TCS, - THR_FOCUS_SET, - THR_FLEXURE_SET, - THR_CALIBRATOR_SET, - THR_ACAM_INIT, - THR_ANDOR_INIT, - THR_SLICECAM_INIT, - THR_CALIB_INIT, - THR_TCS_INIT, - THR_SLIT_INIT, - THR_CAMERA_INIT, - THR_FLEXURE_INIT, - THR_FOCUS_INIT, - THR_POWER_INIT, - THR_ACAM_SHUTDOWN, - THR_ANDOR_SHUTDOWN, - THR_SLICECAM_SHUTDOWN, - THR_CALIB_SHUTDOWN, - THR_CAMERA_SHUTDOWN, - THR_FLEXURE_SHUTDOWN, - THR_FOCUS_SHUTDOWN, - THR_SLIT_SHUTDOWN, - THR_TCS_SHUTDOWN, - THR_POWER_SHUTDOWN, - THR_MODIFY_EXPTIME, - THR_ACQUISITION, - THR_GUIDING, - THR_STARTUP, - THR_SHUTDOWN, - NUM_THREAD_STATES + enum OperationStatusBits : size_t { + OP_SEQUENCER_ASYNC_LISTENER=0, + OP_TRIGGER_EXPOSURE, + OP_EXPOSURE, + OP_REPEAT_EXPOSURE, + OP_STOP_EXPOSURE, + OP_ABORT_PROCESS, + OP_SEQUENCE_START, + OP_RUN_SCRIPT, + OP_MONITOR_READY_STATE, + OP_CALIB_SETUP, + OP_CAMERA_SETUP, + OP_SLIT_SETUP, + OP_MOVE_TO_TARGET, + OP_NOTIFY_TCS, + OP_FOCUS_SETUP, + OP_FLEXURE_SETUP, + OP_ACAM_INIT, + OP_ANDOR_INIT, + OP_SLICECAM_INIT, + OP_CALIB_INIT, + OP_TCS_INIT, + OP_SLIT_INIT, + OP_CAMERA_INIT, + OP_FLEXURE_INIT, + OP_FOCUS_INIT, + OP_POWER_INIT, + OP_ACAM_SHUTDOWN, + OP_ANDOR_SHUTDOWN, + OP_SLICECAM_SHUTDOWN, + OP_CALIB_SHUTDOWN, + OP_CAMERA_SHUTDOWN, + OP_FLEXURE_SHUTDOWN, + OP_FOCUS_SHUTDOWN, + OP_SLIT_SHUTDOWN, + OP_TCS_SHUTDOWN, + OP_POWER_SHUTDOWN, + OP_MODIFY_EXPTIME, + OP_ACQUISITION, + OP_GUIDING, + OP_STARTUP, + OP_SHUTDOWN, + OP_ACAM_CMD, + OP_CALIB_CMD, + OP_CAMERA_CMD, + OP_FLEXURE_CMD, + OP_FOCUS_CMD, + OP_POWER_CMD, + OP_SLICECAM_CMD, + OP_SLIT_CMD, + OP_TCS_CMD, + NUM_OP_STATES }; - const std::map thread_names = { - {THR_SEQUENCER_ASYNC_LISTENER, "async_listener"}, - {THR_TRIGGER_EXPOSURE, "trigger_exposure"}, - {THR_EXPOSURE, "exposure"}, - {THR_REPEAT_EXPOSURE, "repeat_exposure"}, - {THR_STOP_EXPOSURE, "stop_exposure"}, - {THR_ABORT_PROCESS, "abort_process"}, - {THR_SEQUENCE_START, "sequence_start"}, - {THR_RUN_SCRIPT, "run_script"}, - {THR_MONITOR_READY_STATE, "monitor_ready_state"}, - {THR_CALIB_SET, "calib_set"}, - {THR_CAMERA_SET, "camera_set"}, - {THR_SLIT_SET, "slit_set"}, - {THR_MOVE_TO_TARGET, "move_to_target"}, - {THR_NOTIFY_TCS, "notify_tcs"}, - {THR_FOCUS_SET, "focus_set"}, - {THR_FLEXURE_SET, "flexure_set"}, - {THR_CALIBRATOR_SET, "calibrator_set"}, - {THR_ACAM_INIT, "acam_init"}, - {THR_ANDOR_INIT, "andor_init"}, - {THR_SLICECAM_INIT, "slicecam_init"}, - {THR_CALIB_INIT, "calib_init"}, - {THR_TCS_INIT, "tcs_init"}, - {THR_SLIT_INIT, "slit_init"}, - {THR_CAMERA_INIT, "camera_init"}, - {THR_FLEXURE_INIT, "flexure_init"}, - {THR_FOCUS_INIT, "focus_init"}, - {THR_POWER_INIT, "power_init"}, - {THR_ACAM_SHUTDOWN, "acam_shutdown"}, - {THR_ANDOR_SHUTDOWN, "andor_shutdown"}, - {THR_SLICECAM_SHUTDOWN, "slicecam_shutdown"}, - {THR_CALIB_SHUTDOWN, "calib_shutdown"}, - {THR_CAMERA_SHUTDOWN, "camera_shutdown"}, - {THR_FLEXURE_SHUTDOWN, "flexure_shutdown"}, - {THR_FOCUS_SHUTDOWN, "focus_shutdown"}, - {THR_SLIT_SHUTDOWN, "slit_shutdown"}, - {THR_TCS_SHUTDOWN, "tcs_shutdown"}, - {THR_POWER_SHUTDOWN, "power_shutdown"}, - {THR_MODIFY_EXPTIME, "modify_exptime"}, - {THR_ACQUISITION, "acquisition"}, - {THR_GUIDING, "guiding"}, - {THR_STARTUP, "startup"}, - {THR_SHUTDOWN, "shutdown"} + const std::map op_names = { + {OP_SEQUENCER_ASYNC_LISTENER, "async_listener"}, + {OP_TRIGGER_EXPOSURE, "trigger_exposure"}, + {OP_EXPOSURE, "exposure"}, + {OP_REPEAT_EXPOSURE, "repeat_exposure"}, + {OP_STOP_EXPOSURE, "stop_exposure"}, + {OP_ABORT_PROCESS, "abort_process"}, + {OP_SEQUENCE_START, "sequence_start"}, + {OP_RUN_SCRIPT, "run_script"}, + {OP_MONITOR_READY_STATE, "monitor_ready_state"}, + {OP_CALIB_SETUP, "calib_setup"}, + {OP_CAMERA_SETUP, "camera_setup"}, + {OP_SLIT_SETUP, "slit_setup"}, + {OP_MOVE_TO_TARGET, "move_to_target"}, + {OP_NOTIFY_TCS, "notify_tcs"}, + {OP_FOCUS_SETUP, "focus_setup"}, + {OP_FLEXURE_SETUP, "flexure_setup"}, + {OP_ACAM_INIT, "acam_init"}, + {OP_ANDOR_INIT, "andor_init"}, + {OP_SLICECAM_INIT, "slicecam_init"}, + {OP_CALIB_INIT, "calib_init"}, + {OP_TCS_INIT, "tcs_init"}, + {OP_SLIT_INIT, "slit_init"}, + {OP_CAMERA_INIT, "camera_init"}, + {OP_FLEXURE_INIT, "flexure_init"}, + {OP_FOCUS_INIT, "focus_init"}, + {OP_POWER_INIT, "power_init"}, + {OP_ACAM_SHUTDOWN, "acam_shutdown"}, + {OP_ANDOR_SHUTDOWN, "andor_shutdown"}, + {OP_SLICECAM_SHUTDOWN, "slicecam_shutdown"}, + {OP_CALIB_SHUTDOWN, "calib_shutdown"}, + {OP_CAMERA_SHUTDOWN, "camera_shutdown"}, + {OP_FLEXURE_SHUTDOWN, "flexure_shutdown"}, + {OP_FOCUS_SHUTDOWN, "focus_shutdown"}, + {OP_SLIT_SHUTDOWN, "slit_shutdown"}, + {OP_TCS_SHUTDOWN, "tcs_shutdown"}, + {OP_POWER_SHUTDOWN, "power_shutdown"}, + {OP_MODIFY_EXPTIME, "modify_exptime"}, + {OP_ACQUISITION, "acquisition"}, + {OP_GUIDING, "guiding"}, + {OP_STARTUP, "startup"}, + {OP_SHUTDOWN, "shutdown"}, + {OP_ACAM_CMD, "acam_cmd"}, + {OP_CALIB_CMD, "calib_cmd"}, + {OP_CAMERA_CMD, "camera_cmd"}, + {OP_FLEXURE_CMD, "flexure_cmd"}, + {OP_FOCUS_CMD, "focus_cmd"}, + {OP_POWER_CMD, "power_cmd"}, + {OP_SLICECAM_CMD, "slicecam_cmd"}, + {OP_SLIT_CMD, "slit_cmd"}, + {OP_TCS_CMD, "tcs_cmd"} }; /***** Sequencer::Sequence **************************************************/ @@ -396,13 +412,13 @@ namespace Sequencer { /** @brief sequencer operation contains status bit, function and params */ struct Operation { - ThreadStatusBits thr; + OperationStatusBits id; std::function func; OperationParams params; const std::string &name() const { static const std::string empty; - auto it = thread_names.find(thr); - return(it==thread_names.end() ? empty : it->second); + auto it = op_names.find(id); + return(it==op_names.end() ? empty : it->second); } int max_attempts=1; int retry_delay=0; @@ -457,7 +473,7 @@ namespace Sequencer { /** @brief key=value params of the currently executing operation * @details set by run() just before op.func() is invoked and consumed * by Sequence:: member functions that accept optional overrides - * (e.g. move_to_target, slit_set) + * (e.g. move_to_target, slit_setup) */ OperationParams current_op_params; @@ -469,22 +485,22 @@ namespace Sequencer { Operation acam_init(); Operation calib_init(); - Operation calib_set(); + Operation calib_setup(); Operation camera_init(); - Operation camera_set(); + Operation camera_setup(); Operation do_expose(); Operation do_shutdown(); Operation do_startup(); Operation flexure_init(); - Operation flexure_set(); + Operation flexure_setup(); Operation focus_init(); - Operation focus_set(); + Operation focus_setup(); Operation move_to_target(); Operation power_init(); Operation repeat_exposure(); Operation slicecam_init(); Operation slit_init(); - Operation slit_set(); + Operation slit_setup(); Operation target_offset(); Operation tcs_init(); }; @@ -543,7 +559,7 @@ namespace Sequencer { init_operation_builders(); seq_state_manager.set_callback([this](const std::bitset& states) { broadcast_seqstate(); }); wait_state_manager.set_callback([this](const std::bitset& states) { broadcast_waitstate(); }); - thread_state_manager.set_callback([this](const std::bitset& states) { publish_threadstate(); }); + op_state_manager.set_callback([this](const std::bitset& states) { publish_threadstate(); }); daemon_manager.set_callback([this](const std::bitset& states) { broadcast_daemonstate(); }); topic_handlers = { @@ -633,10 +649,10 @@ namespace Sequencer { std::mutex seqstate_mtx; std::condition_variable seqstate_cv; - ImprovedStateManager(Sequencer::NUM_THREAD_STATES)> thread_error_manager{ Sequencer::thread_names }; + ImprovedStateManager(Sequencer::NUM_OP_STATES)> op_error_manager{ Sequencer::op_names }; ImprovedStateManager(Sequencer::NUM_SEQ_STATES)> seq_state_manager{Sequencer::seq_state_names}; ImprovedStateManager(Sequencer::NUM_WAIT_STATES)> wait_state_manager{Sequencer::wait_state_names}; - ImprovedStateManager(Sequencer::NUM_THREAD_STATES)> thread_state_manager{ Sequencer::thread_names }; + ImprovedStateManager(Sequencer::NUM_OP_STATES)> op_state_manager{ Sequencer::op_names }; ImprovedStateManager(Sequencer::NUM_DAEMONS)> daemon_manager{ Sequencer::daemon_name }; TargetInfo target; ///< TargetInfo object contains info for a target row and how to read it @@ -857,13 +873,13 @@ namespace Sequencer { long wait_for_canexpose(std::string caller); ///< wait for camera can_expose void sequence_start(std::string obsid_in=""); ///< main sequence start thread. optional obsid_in for single target obs - long calib_set(); ///< sets calib according to target entry params - long camera_set(); ///< sets camera according to target entry params - long slit_set(VirtualSlitMode mode=VSM_DATABASE); ///< sets slit according to target entry params and mode + long calib_setup(); ///< sets calib according to target entry params + long camera_setup(); ///< sets camera according to target entry params + long slit_setup(VirtualSlitMode mode=VSM_DATABASE); ///< sets slit according to target entry params and mode long move_to_target(); ///< sends request to TCS to move to target coords static void dothread_notify_tcs( Sequencer::Sequence &seq ); ///< like move_to_target but for preauth only - long focus_set(); - long flexure_set(); + long focus_setup(); + long flexure_setup(); /** * these are in sequence_acquisition.cpp diff --git a/sequencerd/sequence_acquisition.cpp b/sequencerd/sequence_acquisition.cpp index 30458fbb..1b613340 100644 --- a/sequencerd/sequence_acquisition.cpp +++ b/sequencerd/sequence_acquisition.cpp @@ -19,7 +19,7 @@ namespace Sequencer { const std::string function("Sequencer::Sequence::do_acam_acquire"); std::string reply; - ScopedState thr_state( thread_state_manager, Sequencer::THR_ACQUISITION ); + ScopedState thr_state( op_state_manager, Sequencer::OP_ACQUISITION ); ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_ACAM_ACQUIRE ); // form and send the ACQUIRE command to ACAM @@ -315,7 +315,7 @@ namespace Sequencer { */ long Sequence::do_target_virtualslit(VirtualSlitMode mode) { if (this->target.iscal) return NO_ERROR; - return this->slit_set(mode); + return this->slit_setup(mode); } /***** Sequencer::Sequence::do_target_virtualslit ****************************/ diff --git a/sequencerd/sequence_operations.cpp b/sequencerd/sequence_operations.cpp index 832fa646..beb2451c 100644 --- a/sequencerd/sequence_operations.cpp +++ b/sequencerd/sequence_operations.cpp @@ -4,7 +4,7 @@ * @author David Hale * * Each Ops factory method here returns a populated Sequence::Operation that - * wraps a Sequence:: member function and identifies itself via its THR_ bit. + * wraps a Sequence:: member function and identifies itself via its OP_ bit. * These are the "Internal" sequencer-level operations (database-driven, * may coordinate multiple daemons). Per-daemon "Passthrough" operations * (e.g. "camera exptime 30000") are built inline by init_operation_builders() @@ -26,7 +26,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::acam_init() { Sequence::Operation op; - op.thr = THR_ACAM_INIT; + op.id = OP_ACAM_INIT; op.func = [this]() { return seq->acam_init(); }; op.params = { }; op.max_attempts = 3; @@ -50,26 +50,26 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::calib_init() { Sequence::Operation op; - op.thr = THR_CALIB_INIT; + op.id = OP_CALIB_INIT; op.func = [this]() { return seq->calib_init(); }; return op; } /***** Sequencer::Sequence::Ops::calib_init ********************************/ - /***** Sequencer::Sequence::Ops::calib_set *********************************/ + /***** Sequencer::Sequence::Ops::calib_setup *********************************/ /** - * @brief defines the calib_set operation + * @brief defines the calib_setup operation * @return Operation * */ - Sequence::Operation Sequence::Ops::calib_set() { + Sequence::Operation Sequence::Ops::calib_setup() { Sequence::Operation op; - op.thr = THR_CALIB_SET; - op.func = [this]() { return seq->calib_set(); }; + op.id = OP_CALIB_SETUP; + op.func = [this]() { return seq->calib_setup(); }; return op; } - /***** Sequencer::Sequence::Ops::calib_set *********************************/ + /***** Sequencer::Sequence::Ops::calib_setup *********************************/ /***** Sequencer::Sequence::Ops::camera_init *******************************/ @@ -80,26 +80,26 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::camera_init() { Sequence::Operation op; - op.thr = THR_CAMERA_INIT; + op.id = OP_CAMERA_INIT; op.func = [this]() { return seq->camera_init(); }; return op; } /***** Sequencer::Sequence::Ops::camera_init *******************************/ - /***** Sequencer::Sequence::Ops::camera_set ********************************/ + /***** Sequencer::Sequence::Ops::camera_setup ********************************/ /** - * @brief defines the camera_set operation + * @brief defines the camera_setup operation * @return Operation * */ - Sequence::Operation Sequence::Ops::camera_set() { + Sequence::Operation Sequence::Ops::camera_setup() { Sequence::Operation op; - op.thr = THR_CAMERA_SET; - op.func = [this]() { return seq->camera_set(); }; + op.id = OP_CAMERA_SETUP; + op.func = [this]() { return seq->camera_setup(); }; return op; } - /***** Sequencer::Sequence::Ops::camera_set ********************************/ + /***** Sequencer::Sequence::Ops::camera_setup ********************************/ /***** Sequencer::Sequence::Ops::do_expose *********************************/ @@ -110,7 +110,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::do_expose() { Sequence::Operation op; - op.thr = THR_EXPOSURE; + op.id = OP_EXPOSURE; op.func = [this]() { const std::string caller("Sequencer::Sequence::Ops::do_expose"); return seq->do_exposure(caller); @@ -128,7 +128,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::do_shutdown() { Sequence::Operation op; - op.thr = THR_SHUTDOWN; + op.id = OP_SHUTDOWN; op.func = [this]() { return seq->shutdown(); }; return op; } @@ -143,7 +143,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::do_startup() { Sequence::Operation op; - op.thr = THR_STARTUP; + op.id = OP_STARTUP; op.func = [this]() { return seq->startup(); }; return op; } @@ -158,26 +158,26 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::flexure_init() { Sequence::Operation op; - op.thr = THR_FLEXURE_INIT; + op.id = OP_FLEXURE_INIT; op.func = [this]() { return seq->flexure_init(); }; return op; } /***** Sequencer::Sequence::Ops::flexure_init ******************************/ - /***** Sequencer::Sequence::Ops::flexure_set *******************************/ + /***** Sequencer::Sequence::Ops::flexure_setup *******************************/ /** - * @brief defines the flexure_set operation + * @brief defines the flexure_setup operation * @return Operation * */ - Sequence::Operation Sequence::Ops::flexure_set() { + Sequence::Operation Sequence::Ops::flexure_setup() { Sequence::Operation op; - op.thr = THR_FLEXURE_SET; - op.func = [this]() { return seq->flexure_set(); }; + op.id = OP_FLEXURE_SETUP; + op.func = [this]() { return seq->flexure_setup(); }; return op; } - /***** Sequencer::Sequence::Ops::flexure_set *******************************/ + /***** Sequencer::Sequence::Ops::flexure_setup *******************************/ /***** Sequencer::Sequence::Ops::focus_init ********************************/ @@ -188,26 +188,26 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::focus_init() { Sequence::Operation op; - op.thr = THR_FOCUS_INIT; + op.id = OP_FOCUS_INIT; op.func = [this]() { return seq->focus_init(); }; return op; } /***** Sequencer::Sequence::Ops::focus_init ********************************/ - /***** Sequencer::Sequence::Ops::focus_set *********************************/ + /***** Sequencer::Sequence::Ops::focus_setup *********************************/ /** - * @brief defines the focus_set operation + * @brief defines the focus_setup operation * @return Operation * */ - Sequence::Operation Sequence::Ops::focus_set() { + Sequence::Operation Sequence::Ops::focus_setup() { Sequence::Operation op; - op.thr = THR_FOCUS_SET; - op.func = [this]() { return seq->focus_set(); }; + op.id = OP_FOCUS_SETUP; + op.func = [this]() { return seq->focus_setup(); }; return op; } - /***** Sequencer::Sequence::Ops::focus_set *********************************/ + /***** Sequencer::Sequence::Ops::focus_setup *********************************/ /***** Sequencer::Sequence::Ops::move_to_target ****************************/ @@ -224,7 +224,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::move_to_target() { Sequence::Operation op; - op.thr = THR_MOVE_TO_TARGET; + op.id = OP_MOVE_TO_TARGET; op.func = [this]() { return seq->move_to_target(); }; return op; } @@ -239,7 +239,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::power_init() { Sequence::Operation op; - op.thr = THR_POWER_INIT; + op.id = OP_POWER_INIT; op.func = [this]() { return seq->power_init(); }; return op; } @@ -254,7 +254,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::repeat_exposure() { Sequence::Operation op; - op.thr = THR_REPEAT_EXPOSURE; + op.id = OP_REPEAT_EXPOSURE; op.func = [this]() { return seq->repeat_exposure(); }; return op; } @@ -269,7 +269,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::slicecam_init() { Sequence::Operation op; - op.thr = THR_SLICECAM_INIT; + op.id = OP_SLICECAM_INIT; op.func = [this]() { return seq->slicecam_init(); }; op.params = { }; op.max_attempts = 3; @@ -293,33 +293,33 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::slit_init() { Sequence::Operation op; - op.thr = THR_SLIT_INIT; + op.id = OP_SLIT_INIT; op.func = [this]() { return seq->slit_init(); }; return op; } /***** Sequencer::Sequence::Ops::slit_init *********************************/ - /***** Sequencer::Sequence::Ops::slit_set **********************************/ + /***** Sequencer::Sequence::Ops::slit_setup **********************************/ /** - * @brief defines the slit_set operation + * @brief defines the slit_setup operation * @details Reads optional mode= param from seq->current_op_params. When * absent the default VSM_DATABASE mode is used (drives slit from * database target entry). * @return Operation * */ - Sequence::Operation Sequence::Ops::slit_set() { + Sequence::Operation Sequence::Ops::slit_setup() { Sequence::Operation op; - op.thr = THR_SLIT_SET; + op.id = OP_SLIT_SETUP; op.func = [this]() { const size_t mode = seq->current_op_params.get("mode", static_cast(VSM_DATABASE)); - return seq->slit_set(static_cast(mode)); + return seq->slit_setup(static_cast(mode)); }; return op; } - /***** Sequencer::Sequence::Ops::slit_set **********************************/ + /***** Sequencer::Sequence::Ops::slit_setup **********************************/ /***** Sequencer::Sequence::Ops::target_offset *****************************/ @@ -330,7 +330,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::target_offset() { Sequence::Operation op; - op.thr = THR_MOVE_TO_TARGET; // no dedicated THR_ for target_offset; reuse move_to_target identity + op.id = OP_MOVE_TO_TARGET; // no dedicated OP_ for target_offset; reuse move_to_target identity op.func = [this]() { return seq->target_offset(); }; return op; } @@ -345,7 +345,7 @@ namespace Sequencer { */ Sequence::Operation Sequence::Ops::tcs_init() { Sequence::Operation op; - op.thr = THR_TCS_INIT; + op.id = OP_TCS_INIT; op.func = [this]() { return seq->tcs_init(); }; return op; } From 8f0df38390b6a4d68f0b8d509b54ebcbe6aeeb72 Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 15 May 2026 09:53:56 -0700 Subject: [PATCH 35/35] fix: correct seq state on subsystem error fix: was missing an error broadcast --- sequencerd/sequence.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 873165b5..1fc11393 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -1441,8 +1441,8 @@ namespace Sequencer { } // end while true if ( this->op_error_manager.are_any_set() ) { - logwrite(function, "ERROR stopping sequencer due to error in: "+ - this->op_error_manager.get_set_states()); + this->broadcast.error( function, "sequence completed with errors in: "+ + this->op_error_manager.get_set_states() ); // If this target was flagged as active, then change it to unassigned on error. if ( this->target.get_next( Sequencer::TARGET_ACTIVE, targetstatus ) == TargetInfo::TARGET_FOUND ) { this->target.update_state( Sequencer::TARGET_UNASSIGNED ); @@ -1451,6 +1451,14 @@ namespace Sequencer { this->do_once.store(true); } + // Override default exit state (SEQ_READY) if any subsystem daemon is down. + // broadcast_daemonstate() suppresses NOTREADY transitions during SEQ_RUNNING, + // so we must check daemon_manager here on the way out. + // + if ( !this->daemon_manager.are_all_set() ) { + seq_state.destruct_set( Sequencer::SEQ_NOTREADY ); + } + logwrite( function, "target list processing has stopped" ); return; }