From a89f02d2d3f72bdc1854122dcd47d08d68ee0933 Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Sun, 1 Feb 2026 02:22:23 -0800 Subject: [PATCH 1/2] Add acquisition automation modes and acqmode command --- Config/sequencerd.cfg.in | 3 + common/sequencerd_commands.h | 2 + sequencerd/sequence.cpp | 173 +++++++++++++++++++++++++++----- sequencerd/sequence.h | 6 ++ sequencerd/sequencer_server.cpp | 87 ++++++++++++++++ 5 files changed, 247 insertions(+), 24 deletions(-) diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index 88fe2a0d..e1421c01 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -160,6 +160,9 @@ ACQUIRE_RETRYS=5 # max number of retrys before acquisition f ACQUIRE_OFFSET_THRESHOLD=0.5 # computed offset below this threshold (in arcsec) defines successful acquisition ACQUIRE_MIN_REPEAT=2 # minimum number of sequential successful acquires ACQUIRE_TCS_MAX_OFFSET=60 # the maximum allowable offset sent to the TCS, in arcsec +ACQ_AUTOMATIC_MODE=1 # 1=legacy, 2=semi-auto, 3=auto +ACQ_FINE_TUNE_CMD=ngps_acq # command to run after guiding for final fine tune +ACQ_OFFSET_SETTLE=0 # seconds to wait after automatic offset # Calibration Settings # CAL_TARGET=(name caldoor calcover lampthar lampfear lampbluc lampredc lolamp hilamp mod1 mod2 ... mod6) diff --git a/common/sequencerd_commands.h b/common/sequencerd_commands.h index cf38c57d..5797ba02 100644 --- a/common/sequencerd_commands.h +++ b/common/sequencerd_commands.h @@ -8,6 +8,7 @@ #ifndef SEQEUNCERD_COMMANDS_H #define SEQEUNCERD_COMMANDS_H const std::string SEQUENCERD_ABORT = "abort"; +const std::string SEQUENCERD_ACQMODE = "acqmode"; const std::string SEQUENCERD_CONFIG = "config"; const std::string SEQUENCERD_DOTYPE = "do"; const std::string SEQUENCERD_EXIT = "exit"; @@ -47,6 +48,7 @@ const std::vector SEQUENCERD_SYNTAX = { SEQUENCERD_TCS+" ...", "", SEQUENCERD_ABORT, + SEQUENCERD_ACQMODE+" [ ? | ]", SEQUENCERD_CONFIG, SEQUENCERD_DOTYPE+" [ one | all ]", SEQUENCERD_EXIT, diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index f321c6a7..12bd67d2 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -13,6 +13,11 @@ #include "sequence.h" +#include +#include +#include +#include + namespace Sequencer { constexpr long CAMERA_PROLOG_TIMEOUT = 6000; ///< timeout msec to send camera prolog command @@ -560,40 +565,160 @@ namespace Sequencer { * std::thread( &Sequencer::Sequence::dothread_acquisition, this ).detach(); ***/ - // If not a calibration target then introduce a pause for the user - // to make adjustments, send offsets, etc. + // If not a calibration target then handle acquisition automation // if ( !this->target.iscal ) { - - // waiting for user signal (or cancel) - // - // The sequencer is effectively paused waiting for user input. This - // gives the user a chance to ensure the correct target is on the slit, - // select offset stars, etc. - // { - ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); + std::stringstream mode_msg; + mode_msg << "NOTICE: acquisition automation mode " << this->acq_automatic_mode; + this->async.enqueue_and_log( function, mode_msg.str() ); + } - this->async.enqueue_and_log( function, "NOTICE: waiting for USER to send \"continue\" signal" ); + auto wait_for_user = [&](const std::string ¬ice) -> bool { + this->is_usercontinue.store(false); + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_USER ); + this->async.enqueue_and_log( function, "NOTICE: "+notice ); + 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->cancel_flag.load() ? std::string("cancel") : std::string("continue")) + +" signal!" ); + if ( this->cancel_flag.load() ) return false; + this->is_usercontinue.store(false); + return true; + }; + + auto wait_for_guiding = [&]() -> long { + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_ACQUIRE ); + this->async.enqueue_and_log( function, "NOTICE: waiting for ACAM guiding" ); + auto start_time = std::chrono::steady_clock::now(); + const bool use_timeout = ( this->acquisition_timeout > 0 ); + const auto timeout = std::chrono::duration( this->acquisition_timeout ); + while ( !this->cancel_flag.load() ) { + std::string reply; + if ( this->acamd.command( ACAMD_ACQUIRE, reply ) != NO_ERROR ) { + logwrite( function, "ERROR reading ACAM acquire state" ); + return ERROR; + } + if ( reply.find( "guiding" ) != std::string::npos ) return NO_ERROR; + if ( reply.find( "stopped" ) != std::string::npos ) return ERROR; + if ( use_timeout && std::chrono::steady_clock::now() > ( start_time + timeout ) ) return TIMEOUT; + std::this_thread::sleep_for( std::chrono::milliseconds(500) ); + } + return ERROR; + }; - 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() ); } ); - } + auto run_fine_tune = [&]() -> long { + if ( this->acq_fine_tune_cmd.empty() ) return NO_ERROR; + this->async.enqueue_and_log( function, "NOTICE: running fine tune command: "+this->acq_fine_tune_cmd ); - this->async.enqueue_and_log( function, "NOTICE: received " - +(this->cancel_flag.load() ? std::string("cancel") : std::string("continue")) - +" signal!" ); - } // end scope for wait_state = WAIT_USER + pid_t pid = fork(); + if ( pid == 0 ) { + execl( "/bin/sh", "sh", "-c", this->acq_fine_tune_cmd.c_str(), (char*)nullptr ); + _exit(127); + } + if ( pid < 0 ) { + logwrite( function, "ERROR starting fine tune command: "+this->acq_fine_tune_cmd ); + return ERROR; + } - if ( this->cancel_flag.load() ) { - this->async.enqueue_and_log( function, "NOTICE: sequence cancelled" ); - return; + int status = 0; + while ( true ) { + pid_t result = waitpid( pid, &status, WNOHANG ); + if ( result == pid ) break; + if ( result < 0 ) { + logwrite( function, "ERROR waiting on fine tune command" ); + return ERROR; + } + if ( this->cancel_flag.load() ) { + this->async.enqueue_and_log( function, "NOTICE: abort requested; terminating fine tune" ); + kill( pid, SIGTERM ); + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while ( std::chrono::steady_clock::now() < deadline ) { + result = waitpid( pid, &status, WNOHANG ); + if ( result == pid ) break; + std::this_thread::sleep_for( std::chrono::milliseconds(100) ); + } + if ( result != pid ) { + kill( pid, SIGKILL ); + waitpid( pid, &status, 0 ); + } + return ERROR; + } + std::this_thread::sleep_for( std::chrono::milliseconds(100) ); + } + + if ( WIFEXITED( status ) && WEXITSTATUS( status ) == 0 ) { + this->async.enqueue_and_log( function, "NOTICE: fine tune complete" ); + return NO_ERROR; + } + + logwrite( function, "ERROR fine tune command failed: "+this->acq_fine_tune_cmd ); + return ERROR; + }; + + if ( this->acq_automatic_mode == 1 ) { + if ( !wait_for_user( "waiting for USER to send \"continue\" signal" ) ) { + this->async.enqueue_and_log( function, "NOTICE: sequence cancelled" ); + return; + } } + else { + if ( this->acq_automatic_mode == 2 ) { + if ( !wait_for_user( "waiting for USER to send \"continue\" signal to start acquisition" ) ) { + this->async.enqueue_and_log( function, "NOTICE: sequence cancelled" ); + return; + } + } - this->is_usercontinue.store(false); + this->async.enqueue_and_log( function, "NOTICE: starting acquisition" ); + std::thread( &Sequencer::Sequence::dothread_acquisition, this ).detach(); - this->async.enqueue_and_log( function, "NOTICE: received USER continue signal!" ); + long acqerr = wait_for_guiding(); + if ( acqerr != NO_ERROR ) { + std::string reason = ( acqerr == TIMEOUT ? "timeout" : "error" ); + this->async.enqueue_and_log( function, "WARNING: failed to reach guiding state ("+reason+"); falling back to manual continue" ); + if ( !wait_for_user( "waiting for USER to send \"continue\" signal to expose (guiding failed)" ) ) { + this->async.enqueue_and_log( function, "NOTICE: sequence cancelled" ); + return; + } + } + else { + bool fine_tune_ok = ( run_fine_tune() == NO_ERROR ); + if ( !fine_tune_ok ) { + this->async.enqueue_and_log( function, "WARNING: fine tune failed; waiting for USER continue to expose" ); + if ( !wait_for_user( "waiting for USER to send \"continue\" signal to expose (fine tune failed)" ) ) { + this->async.enqueue_and_log( function, "NOTICE: sequence cancelled" ); + return; + } + } + + if ( fine_tune_ok ) { + if ( this->acq_automatic_mode == 2 ) { + if ( !wait_for_user( "waiting for USER to send \"continue\" signal to expose" ) ) { + this->async.enqueue_and_log( function, "NOTICE: sequence cancelled" ); + return; + } + } + else if ( this->acq_automatic_mode == 3 ) { + if ( this->target.offset_ra != 0.0 || this->target.offset_dec != 0.0 ) { + this->async.enqueue_and_log( function, "NOTICE: applying target offset automatically" ); + error |= this->target_offset(); + if ( error != NO_ERROR ) { + this->thread_error_manager.set( THR_ACQUISITION ); + return; + } + if ( this->acq_offset_settle > 0 ) { + this->async.enqueue_and_log( function, "NOTICE: waiting for offset settle time" ); + std::this_thread::sleep_for( std::chrono::duration( this->acq_offset_settle ) ); + } + } + } + } + } + } // Ensure slit offset is in "expose" position // diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 8703c1d6..6d7292ea 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -312,6 +312,9 @@ namespace Sequencer { arm_readout_flag(false), acquisition_timeout(0), acquisition_max_retrys(-1), + acq_automatic_mode(1), + acq_fine_tune_cmd("ngps_acq"), + acq_offset_settle(0), tcs_offsetrate_ra(45), tcs_offsetrate_dec(45), tcs_settle_timeout(10), @@ -369,6 +372,9 @@ namespace Sequencer { 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 + int acq_automatic_mode; ///< acquisition automation mode (1=legacy, 2=semi-auto, 3=auto) + std::string acq_fine_tune_cmd; ///< fine-tune command to run after guiding + double acq_offset_settle; ///< seconds to wait after automatic offset double tcs_offsetrate_ra; ///< TCS offset rate RA ("MRATE") in arcsec per second double tcs_offsetrate_dec; ///< TCS offset rate DEC ("MRATE") in arcsec per second double tcs_settle_timeout; ///< timeout for telescope to settle (in sec) set by configuration parameter TCS_SETTLE_TIMEOUT diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index 06514cef..709c0471 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -392,6 +392,58 @@ namespace Sequencer { applied++; } + // ACQ_AUTOMATIC_MODE + if (config.param[entry] == "ACQ_AUTOMATIC_MODE") { + int mode=1; + try { + mode = std::stoi( config.arg[entry] ); + if ( mode < 1 || mode > 3 ) { + message.str(""); message << "ERROR: ACQ_AUTOMATIC_MODE " << mode << " out of range {1:3}"; + this->sequence.async.enqueue_and_log( function, message.str() ); + return ERROR; + } + } + catch (const std::exception &e) { + message.str(""); message << "ERROR parsing ACQ_AUTOMATIC_MODE: " << e.what(); + this->sequence.async.enqueue_and_log( function, message.str() ); + return ERROR; + } + this->sequence.acq_automatic_mode = mode; + message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; + this->sequence.async.enqueue_and_log( function, message.str() ); + applied++; + } + + // ACQ_FINE_TUNE_CMD + if (config.param[entry] == "ACQ_FINE_TUNE_CMD") { + this->sequence.acq_fine_tune_cmd = config.arg[entry]; + message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; + this->sequence.async.enqueue_and_log( function, message.str() ); + applied++; + } + + // ACQ_OFFSET_SETTLE + if (config.param[entry] == "ACQ_OFFSET_SETTLE") { + double settle=0; + try { + settle = std::stod( config.arg[entry] ); + if ( settle < 0 ) { + message.str(""); message << "ERROR: ACQ_OFFSET_SETTLE " << settle << " out of range (>=0)"; + this->sequence.async.enqueue_and_log( function, message.str() ); + return ERROR; + } + } + catch (const std::exception &e) { + message.str(""); message << "ERROR parsing ACQ_OFFSET_SETTLE: " << e.what(); + this->sequence.async.enqueue_and_log( function, message.str() ); + return ERROR; + } + this->sequence.acq_offset_settle = settle; + message.str(""); message << "SEQUENCERD:config:" << config.param[entry] << "=" << config.arg[entry]; + this->sequence.async.enqueue_and_log( 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" ) { @@ -1443,6 +1495,41 @@ namespace Sequencer { } else + // Set/Get acquisition automation mode + // + if ( cmd == SEQUENCERD_ACQMODE ) { + if ( args.empty() || args == "?" || args == "help" ) { + retstring = SEQUENCERD_ACQMODE + " [ ]\n"; + retstring.append( " Set or get acquisition automation mode.\n" ); + retstring.append( " = { 1 (legacy), 2 (semi-auto), 3 (auto) }\n" ); + if ( args.empty() ) { + retstring.append( " current mode = " + std::to_string(this->sequence.acq_automatic_mode) + "\n" ); + } + ret = HELP; + } + else { + try { + int mode = std::stoi( args ); + if ( mode < 1 || mode > 3 ) { + this->sequence.async.enqueue_and_log( function, "ERROR: acqmode out of range {1:3}" ); + ret = ERROR; + } + else { + this->sequence.acq_automatic_mode = mode; + message.str(""); message << "NOTICE: acqmode set to " << mode; + this->sequence.async.enqueue_and_log( function, message.str() ); + retstring = std::to_string( mode ); + ret = NO_ERROR; + } + } + catch ( const std::exception & ) { + this->sequence.async.enqueue_and_log( function, "ERROR: invalid acqmode argument" ); + ret = ERROR; + } + } + } + else + // // if ( cmd == SEQUENCERD_GETONETARGET ) { From a20b5091c360de0a43e08bdfe90dd128599a948e Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Mon, 2 Feb 2026 02:13:28 -0800 Subject: [PATCH 2/2] Fix auto-acq waitstate and fine-tune xterm handling --- Config/sequencerd.cfg.in | 1 + sequencerd/sequence.cpp | 43 ++++++++++++++++++++++++++++++--- sequencerd/sequence.h | 6 +++++ sequencerd/sequencer_server.cpp | 16 ++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index e1421c01..85ada63b 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -162,6 +162,7 @@ ACQUIRE_MIN_REPEAT=2 # minimum number of sequential successful a ACQUIRE_TCS_MAX_OFFSET=60 # the maximum allowable offset sent to the TCS, in arcsec ACQ_AUTOMATIC_MODE=1 # 1=legacy, 2=semi-auto, 3=auto ACQ_FINE_TUNE_CMD=ngps_acq # command to run after guiding for final fine tune +ACQ_FINE_TUNE_XTERM=0 # run fine tune in xterm (0/1) ACQ_OFFSET_SETTLE=0 # seconds to wait after automatic offset # Calibration Settings diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 12bd67d2..71f9039f 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -591,7 +591,7 @@ namespace Sequencer { }; auto wait_for_guiding = [&]() -> long { - ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_ACQUIRE ); + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_GUIDE ); this->async.enqueue_and_log( function, "NOTICE: waiting for ACAM guiding" ); auto start_time = std::chrono::steady_clock::now(); const bool use_timeout = ( this->acquisition_timeout > 0 ); @@ -614,8 +614,19 @@ namespace Sequencer { if ( this->acq_fine_tune_cmd.empty() ) return NO_ERROR; this->async.enqueue_and_log( function, "NOTICE: running fine tune command: "+this->acq_fine_tune_cmd ); + if ( this->acq_fine_tune_xterm ) { + this->async.enqueue_and_log( function, "NOTICE: launching fine tune in xterm" ); + } + pid_t pid = fork(); if ( pid == 0 ) { + // make a dedicated process group so we can signal the whole tree + setpgid( 0, 0 ); + if ( this->acq_fine_tune_xterm ) { + execlp( "xterm", "xterm", "-T", "NGPS Fine Tune", "-e", + "sh", "-lc", this->acq_fine_tune_cmd.c_str(), (char*)nullptr ); + // fall through if xterm is missing + } execl( "/bin/sh", "sh", "-c", this->acq_fine_tune_cmd.c_str(), (char*)nullptr ); _exit(127); } @@ -623,6 +634,9 @@ namespace Sequencer { logwrite( function, "ERROR starting fine tune command: "+this->acq_fine_tune_cmd ); return ERROR; } + // Ensure the child is its own process group (best effort). + setpgid( pid, pid ); + this->fine_tune_pid.store( pid ); int status = 0; while ( true ) { @@ -634,7 +648,8 @@ namespace Sequencer { } if ( this->cancel_flag.load() ) { this->async.enqueue_and_log( function, "NOTICE: abort requested; terminating fine tune" ); - kill( pid, SIGTERM ); + // terminate the whole fine-tune process group + kill( -pid, SIGTERM ); auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); while ( std::chrono::steady_clock::now() < deadline ) { result = waitpid( pid, &status, WNOHANG ); @@ -642,14 +657,16 @@ namespace Sequencer { std::this_thread::sleep_for( std::chrono::milliseconds(100) ); } if ( result != pid ) { - kill( pid, SIGKILL ); + kill( -pid, SIGKILL ); waitpid( pid, &status, 0 ); } + this->fine_tune_pid.store( 0 ); return ERROR; } std::this_thread::sleep_for( std::chrono::milliseconds(100) ); } + this->fine_tune_pid.store( 0 ); if ( WIFEXITED( status ) && WEXITSTATUS( status ) == 0 ) { this->async.enqueue_and_log( function, "NOTICE: fine tune complete" ); return NO_ERROR; @@ -2347,6 +2364,26 @@ namespace Sequencer { this->cancel_flag.store(true); this->cv.notify_all(); + // terminate fine tune process if running + // + pid_t ftpid = this->fine_tune_pid.load(); + if ( ftpid > 0 ) { + logwrite( function, "NOTICE: terminating fine tune process" ); + kill( -ftpid, SIGTERM ); + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + int status = 0; + while ( std::chrono::steady_clock::now() < deadline ) { + pid_t result = waitpid( ftpid, &status, WNOHANG ); + if ( result == ftpid ) break; + std::this_thread::sleep_for( std::chrono::milliseconds(100) ); + } + if ( waitpid( ftpid, &status, WNOHANG ) == 0 ) { + kill( -ftpid, SIGKILL ); + waitpid( ftpid, &status, 0 ); + } + this->fine_tune_pid.store( 0 ); + } + // drop into do-one to prevent auto increment to next target // this->do_once.store(true); diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 6d7292ea..1ff77753 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +154,7 @@ namespace Sequencer { SEQ_WAIT_TCS, ///< set when waiting for tcs // states SEQ_WAIT_ACQUIRE, ///< set when waiting for acquire + SEQ_WAIT_GUIDE, ///< set when waiting for guiding state 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 @@ -173,6 +175,7 @@ namespace Sequencer { {SEQ_WAIT_TCS, "TCS"}, // states {SEQ_WAIT_ACQUIRE, "ACQUIRE"}, + {SEQ_WAIT_GUIDE, "GUIDE"}, {SEQ_WAIT_EXPOSE, "EXPOSE"}, {SEQ_WAIT_READOUT, "READOUT"}, {SEQ_WAIT_TCSOP, "TCSOP"}, @@ -286,6 +289,7 @@ 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 fine_tune_pid{0}; ///< fine tune process pid (process group leader) /** @brief safely runs function in a detached thread using lambda to catch exceptions */ @@ -314,6 +318,7 @@ namespace Sequencer { acquisition_max_retrys(-1), acq_automatic_mode(1), acq_fine_tune_cmd("ngps_acq"), + acq_fine_tune_xterm(false), acq_offset_settle(0), tcs_offsetrate_ra(45), tcs_offsetrate_dec(45), @@ -374,6 +379,7 @@ namespace Sequencer { int acquisition_max_retrys; ///< max number of acquisition loop attempts int acq_automatic_mode; ///< acquisition automation mode (1=legacy, 2=semi-auto, 3=auto) std::string acq_fine_tune_cmd; ///< fine-tune command to run after guiding + bool acq_fine_tune_xterm; ///< run fine-tune command in its own xterm double acq_offset_settle; ///< seconds to wait after automatic offset double tcs_offsetrate_ra; ///< TCS offset rate RA ("MRATE") in arcsec per second double tcs_offsetrate_dec; ///< TCS offset rate DEC ("MRATE") in arcsec per second diff --git a/sequencerd/sequencer_server.cpp b/sequencerd/sequencer_server.cpp index 709c0471..4407225d 100644 --- a/sequencerd/sequencer_server.cpp +++ b/sequencerd/sequencer_server.cpp @@ -422,6 +422,22 @@ namespace Sequencer { applied++; } + // ACQ_FINE_TUNE_XTERM + if (config.param[entry] == "ACQ_FINE_TUNE_XTERM") { + try { + int val = std::stoi( config.arg[entry] ); + this->sequence.acq_fine_tune_xterm = ( val != 0 ); + } + catch (const std::exception &e) { + message.str(""); message << "ERROR parsing ACQ_FINE_TUNE_XTERM: " << e.what(); + this->sequence.async.enqueue_and_log( 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() ); + applied++; + } + // ACQ_OFFSET_SETTLE if (config.param[entry] == "ACQ_OFFSET_SETTLE") { double settle=0;