diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index b825d75d..1c7c0dd5 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -163,30 +163,32 @@ OFFSET_SETTLE_SEC=3 # sec to wait after an offset-star target o ACQUIRE_TCS_MAX_OFFSET=60 # the maximum allowable offset sent to the TCS, in arcsec # Calibration Settings -# CAL_TARGET=(name caldoor calcover U G R I lampthar lampfear lampbluc lampredc lolamp hilamp mod1 mod2 ... mod6 imgtype) +# CAL_TARGET=(name caldoor calcover U G R I lampthar lampfear lampbluc lampredc lolamp hilamp he_arc ultra mod1 mod2 ... mod6 imgtype) # # where name must be "DEFAULT" or start with "CAL_" # caldoor = open | close # calcover = open | close # U,G,R,I = on | off # indicates which channels to enable/disable -# lamp* = on | off # lamp power -# mod* = on | off # lamp modulator +# lamp* = on | off # lamp power state +# mod* = on | off # lamp modulator state # imgtype = # FITS IMGTYPE keyword -# for a total of 20 required parameters +# for a total of 22 required parameters # name=SCIENCE defines science target operation # -# name door cover U G R I thar fear bluc redc llmp hlmp mod1 mod2 mod3 mod4 mod5 mod6 imgtype -CAL_TARGET=(CAL_THAR open close on on on on on on on on off off off off off off off on THAR ) -CAL_TARGET=(CAL_FEAR open close on on on on on on on on off off on off off off off off FEAR ) -CAL_TARGET=(CAL_THAR_UG open close on on off off on on on on off off off off off off off on THAR ) -CAL_TARGET=(CAL_FEAR_UG open close on on off off on on on on off off on off off off off off FEAR ) -CAL_TARGET=(CAL_CONTR open close on on on on on on on on off off off off off on off off CONT ) -CAL_TARGET=(CAL_CONTB open close on on on on on on on on off off off off off off on off CONT ) -CAL_TARGET=(CAL_DOME close open on on on on off off off off off on off off off off off off DOMEFLAT) -CAL_TARGET=(CAL_DOME_UG close open on on off off off off off off off on off off off off off off DOMEFLAT) -CAL_TARGET=(CAL_BIAS close close on on on on off off off off off off off off off off off off BIAS ) -CAL_TARGET=(CAL_DARK close close on on on on off off off off off off off off off off off off DARK ) -CAL_TARGET=(SCIENCE close open on on on on off off off off off off off off off off off off SCI ) +# name door cover U G R I thar fear bluc redc llmp hlmp arc ultr mod1 mod2 mod3 mod4 mod5 mod6 imgtype +CAL_TARGET=(CAL_THAR open close on on on on on on on on off off off off off off off off off on THAR ) +CAL_TARGET=(CAL_FEAR open close on on on on on on on on off off off off on off off off off off FEAR ) +CAL_TARGET=(CAL_THAR_UG open close on on off off on on on on off off off off off off off off off on THAR ) +CAL_TARGET=(CAL_FEAR_UG open close on on off off on on on on off off off off on off off off off off FEAR ) +CAL_TARGET=(CAL_CONTR open close on on on on on on on on off off off off off off off on off off CONT ) +CAL_TARGET=(CAL_CONTB open close on on on on on on on on off off off off off off off off on off CONT ) +CAL_TARGET=(CAL_DOME close open on on on on off off off off off on off off off off off off off off DOMEFLAT) +CAL_TARGET=(CAL_DOME_UG close open on on off off off off off off off on off on off off off off off off DOMEFLAT) +CAL_TARGET=(CAL_DOMEHE close open on on on on off off off off off off on off off off off off off off DOMEHE ) +CAL_TARGET=(CAL_DOMEHE_UG close open on on off off off off off off off off on off off off off off off off DOMEHE ) +CAL_TARGET=(CAL_BIAS close close on on on on off off off off off off off off off off off off off off BIAS ) +CAL_TARGET=(CAL_DARK close close on on on on off off off off off off off off off off off off off off DARK ) +CAL_TARGET=(SCIENCE close open on on on on off off off off off off off off off off off off off off SCI ) # miscellaneous # diff --git a/common/fits_header_defs.h b/common/fits_header_defs.h index 26f8c4a7..9d1f5352 100644 --- a/common/fits_header_defs.h +++ b/common/fits_header_defs.h @@ -102,7 +102,11 @@ namespace FitsHeaderKeys { { 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" } + { Key::Tcsd::TELFOCUS.c_str(), "", "TCS reported telescope focus position in mm", "FLOAT" }, + { Key::Tcsd::LAMP_LO.c_str(), "LAMP_LO", "TCS LO lamp 0=off 1=on", "INT" }, + { Key::Tcsd::LAMP_HI.c_str(), "LAMP_HI", "TCS HI lamp 0=off 1=on", "INT" }, + { Key::Tcsd::LAMP_ARC.c_str(), "LAMP_ARC", "TCS He-arc lamp 0=off 1=on", "INT" }, + { Key::Tcsd::LAMP_ULTRA.c_str(), "LAMP_ULT", "TCS ultra-high lamp 0=off 1=on", "INT" } }; const Extension ThermalInfoKeys[] = { diff --git a/common/message_keys.h b/common/message_keys.h index e2deba0e..6ff0fb38 100644 --- a/common/message_keys.h +++ b/common/message_keys.h @@ -133,6 +133,10 @@ namespace Key { inline const std::string DOMEAZ = "DOMEAZ"; inline const std::string DOMESHUT = "DOMESHUT"; inline const std::string TELFOCUS = "TELFOCUS"; + inline const std::string LAMP_LO = "LAMP_LO"; + inline const std::string LAMP_HI = "LAMP_HI"; + inline const std::string LAMP_ARC = "LAMP_ARC"; + inline const std::string LAMP_ULTRA= "LAMP_ULTRA"; } namespace Calibd { diff --git a/common/tcs_constants.h b/common/tcs_constants.h index 9f88f472..8efd05f4 100644 --- a/common/tcs_constants.h +++ b/common/tcs_constants.h @@ -17,6 +17,14 @@ const int TCS_UNABLE_TO_EXECUTE = -3; const int TCS_HOST_UNAVAILABLE = -4; const int TCS_UNDEFINED = 9999; +// dome lamps +// the names are mine but the associated numbers belong to the TCS +// +inline static const char TCS_LAMPNAME_LO[] = "LO"; const int TCS_LAMPNUM_LO = 1; +inline static const char TCS_LAMPNAME_HI[] = "HI"; const int TCS_LAMPNUM_HI = 2; +inline static const char TCS_LAMPNAME_ARC[] = "ARC"; const int TCS_LAMPNUM_ARC = 3; +inline static const char TCS_LAMPNAME_ULTRA[] = "ULTRA"; const int TCS_LAMPNUM_ULTRA = 4; + // human-readable strings to match the above // inline static const char TCS_SUCCESS_STR[] = "success"; diff --git a/common/tcsd_commands.h b/common/tcsd_commands.h index a34968c6..ae4993c7 100644 --- a/common/tcsd_commands.h +++ b/common/tcsd_commands.h @@ -20,6 +20,7 @@ const std::string TCSD_GET_OFFSETS = "getoffsets"; const std::string TCSD_GET_PA = "getpa"; const std::string TCSD_GET_NAME = "getname"; const std::string TCSD_ISOPEN = "isopen"; +const std::string TCSD_LAMP = "lamp"; const std::string TCSD_LIST = "list"; const std::string TCSD_LLIST = "llist"; const std::string TCSD_NATIVE = "native"; @@ -46,6 +47,7 @@ const std::vector TCSD_SYNTAX = { TCSD_GET_PA+" [ ? ]", TCSD_GET_NAME+" [ ? ]", TCSD_ISOPEN+" [ ? ]", + TCSD_LAMP+" ? | [on|off]", TCSD_LIST+" [ ? ]", TCSD_LLIST+" [ ? ]", TCSD_NATIVE+" ? | ", diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 13d40a97..e15d7e28 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -2429,18 +2429,16 @@ namespace Sequencer { } } -// Not working yet 2025-02-04 -// -// // set the dome lamps -// // -// for ( const auto &[lamp,state] : calinfo.domelamp ) { -// if ( this->cancel_flag.load() ) 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() ); -// throw std::runtime_error("setting dome lamp: "+cmd.str()); -// } -// } + // set the dome lamps + // + for ( const auto &[lamp,state] : calinfo.domelamp ) { + if ( this->cancel_flag.load() ) break; + cmd.str(""); cmd << TCSD_LAMP << lamp << " " << (state==1?"on":"off"); + if ( this->tcsd.command( cmd.str() ) != NO_ERROR ) { + logwrite(function, "ERROR "+cmd.str()); + throw std::runtime_error("setting TCS "+cmd.str()); + } + } // set the lamp modulators // diff --git a/sequencerd/sequencer_interface.cpp b/sequencerd/sequencer_interface.cpp index a4787c2b..78152e5b 100644 --- a/sequencerd/sequencer_interface.cpp +++ b/sequencerd/sequencer_interface.cpp @@ -960,9 +960,9 @@ namespace Sequencer { auto size = Tokenize( args, tokens, " \t" ); - // there must be 20 args. see cfg file for complete description - if ( size != 20 ) { - logwrite(function, "ERROR bad config file. expected 20 but received " + // there must be 22 args. see cfg file for complete description + if ( size != 22 ) { + logwrite(function, "ERROR bad config file. expected 22 but received " +std::to_string(size)+" parameters"); return ERROR; } @@ -996,18 +996,18 @@ namespace Sequencer { info.lamp[lampnames.at(i)] = on_off(tokens.at(7+i)); } - // tokens 11-12 are dome lamps - for (size_t i=0; i < 2; i++) { + // tokens 11-14 are dome lamps: LO, HI, ARC, ULTRA + for (size_t i=0; i < 4; i++) { info.domelamp[i] = on_off(tokens.at(11+i)); } - // tokens 13-18 -- modulator numbers are {1:6} + // tokens 15-20 -- modulator numbers are {1:6} for (size_t i=0; i<6; i++) { - info.lampmod[i+1] = on_off(tokens.at(13+i)); + info.lampmod[i+1] = on_off(tokens.at(15+i)); } - // token[19] is FITS IMGTYPE - info.imgtype = tokens.at(19); + // token[21] is FITS IMGTYPE + info.imgtype = tokens.at(21); } catch (const std::exception &e) { logwrite(function, "ERROR: "+std::string(e.what())); diff --git a/sequencerd/sequencer_interface.h b/sequencerd/sequencer_interface.h index d2ba283b..1690be96 100644 --- a/sequencerd/sequencer_interface.h +++ b/sequencerd/sequencer_interface.h @@ -134,7 +134,7 @@ namespace Sequencer { CalibrationTarget() : chans { "U", "G", "R", "I" }, lampnames { "LAMPTHAR", "LAMPFEAR", "LAMPBLUC", "LAMPREDC" }, - domelampnames { "LOLAMP", "HILAMP" } { } + domelampnames { "LO", "HI", "ARC", "ULTRA" } { } // this is how they ///< struct holds all calibration parameters not in the target database typedef struct { @@ -144,7 +144,7 @@ namespace Sequencer { bool caldoor; // true=open bool calcover; // true=open std::map lamp; // true=on - std::map domelamp; // 1=LOLAMP, 2=HILAMP, true=on + std::map domelamp; // [1=LO, 2=HI, 3=ARC, 4=ULTRA], true=on std::map lampmod; // true=on } calinfo_t; diff --git a/tcsd/tcs_interface.cpp b/tcsd/tcs_interface.cpp index 3e6331da..36f26d6a 100644 --- a/tcsd/tcs_interface.cpp +++ b/tcsd/tcs_interface.cpp @@ -65,6 +65,10 @@ 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; + + for (const auto &[name,info] : this->tcs_info.lampinfo) { // TCS lamp states + jmessage_out[info.key] = info.state; + } } // broadcast motion status if it changed @@ -154,40 +158,56 @@ namespace TCS { */ long Interface::get_tcs_info() { long error = NO_ERROR; - std::string retstring; + std::string reqpos, reqstat, weather, parallactic, motion, lamps; - 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 - // succeed, we don't want the class to contain values from this reqstat and - // an earlier call to reqpos. + // Serialize pollers. Held across the I/O so two overlapping calls can't have + // the slower one commit older data over the newer, but this blocks only other + // pollers -- readers of tcs_info are never delayed by it. Lock order is + // query_mtx then tcs_info_mtx. // - this->tcs_info.init(); + std::lock_guard qlock(query_mtx); - // Call the three native functions and the associated parsing function, - // which will populate the tcs_info class. + // Call the native functions to read everything from the TCS into local + // buffers, without holding the lock that protects tcs_info. Each command + // gets its own buffer so that a command which returns early can't leave the + // previous command's reply to be parsed by the next one. // - error |= this->send_command( "REQPOS", retstring, TCS::FAST_RESPONSE ); - std::replace( retstring.begin(), retstring.end(), '\n', ','); - this->tcs_info.parse_reqpos( retstring ); + error |= this->send_command( "REQPOS", reqpos, TCS::FAST_RESPONSE ); + std::replace( reqpos.begin(), reqpos.end(), '\n', ','); - error |= this->send_command( "REQSTAT", retstring, TCS::FAST_RESPONSE ); - std::replace( retstring.begin(), retstring.end(), '\n', ','); - this->tcs_info.parse_reqstat( retstring ); + error |= this->send_command( "REQSTAT", reqstat, TCS::FAST_RESPONSE ); + std::replace( reqstat.begin(), reqstat.end(), '\n', ','); - error |= this->send_command( "?WEATHER", retstring, TCS::FAST_RESPONSE ); - std::replace( retstring.begin(), retstring.end(), '\n', ','); - this->tcs_info.parse_weather( retstring ); + error |= this->send_command( "?WEATHER", weather, TCS::FAST_RESPONSE ); + std::replace( weather.begin(), weather.end(), '\n', ','); - error |= this->send_command( "?PARALLACTIC", retstring, TCS::FAST_RESPONSE ); - std::replace( retstring.begin(), retstring.end(), '\n', ','); - this->tcs_info.parse_pa( retstring ); + error |= this->send_command( "?PARALLACTIC", parallactic, TCS::FAST_RESPONSE ); + std::replace( parallactic.begin(), parallactic.end(), '\n', ','); - error |= this->send_command( "?MOTION", retstring, TCS::FAST_RESPONSE ); - std::replace( retstring.begin(), retstring.end(), '\n', ','); - this->tcs_info.motion = retstring; + error |= this->send_command( "?MOTION", motion, TCS::FAST_RESPONSE ); + std::replace( motion.begin(), motion.end(), '\n', ','); + + error |= this->send_command( "LAMPS?", lamps, TCS::FAST_RESPONSE ); + std::replace( lamps.begin(), lamps.end(), '\n', ','); + + // Erase the class and parse everything into it in a single critical section, + // because it's all or nothing. If something failed 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 succeed, we don't want + // the class to contain values from this reqstat and an earlier call to + // reqpos. Committing all at once also means no reader can observe a poll in + // progress. + // + std::lock_guard lock(tcs_info_mtx); + + this->tcs_info.init(); + + this->tcs_info.parse_reqpos( reqpos ); + this->tcs_info.parse_reqstat( reqstat ); + this->tcs_info.parse_weather( weather ); + this->tcs_info.parse_pa( parallactic ); + this->tcs_info.motion = motion; + this->tcs_info.parse_lamps( lamps ); return error; } @@ -414,7 +434,6 @@ namespace TCS { */ long Interface::isopen( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::isopen"; - std::stringstream message; // Help // @@ -1173,14 +1192,16 @@ namespace TCS { return ERROR; } - // parse the reply which stores it in the TcsInfo class + // Parse the reply into a local TcsInfo. get_tcs_info() refreshes the shared + // tcs_info.pa on every poll, so this query must not write to it -- that would + // put a pa into the published record which was sampled at a different time + // than everything else in it. // + TcsInfo info; + 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; - } + oss << info.pa; retstring = oss.str(); if ( !retstring.empty() && !silent ) logwrite( function, retstring ); @@ -1269,7 +1290,6 @@ namespace TCS { */ long Interface::get_motion( const std::string &arg, std::string &retstring ) { std::string function = "TCS::Interface::get_motion"; - std::stringstream message; long error = NO_ERROR; // Help @@ -1285,7 +1305,13 @@ namespace TCS { // continuously (sequencerd, targetcontrol GUI) don't flood the log // with ERROR on every call after a shutdown. // - if ( ! this->tcs_info.isopen ) { + bool isopen; + { + std::lock_guard lock(tcs_info_mtx); + isopen = this->tcs_info.isopen; + } + + if ( ! isopen ) { retstring = "not_connected"; return NO_ERROR; } @@ -1390,8 +1416,6 @@ namespace TCS { */ long Interface::coords( std::string args, std::string &retstring ) { std::string function = "TCS::Interface::coords"; - std::string retcode; - std::stringstream message; // Help // @@ -1425,6 +1449,174 @@ namespace TCS { /***** TCS::Interface::coords ***********************************************/ + /***** TCS::Interface::lamp *************************************************/ + /** + * @brief dome lamp control + * @details TCS-native command is "NPS " where + * is: 0=off, 1=on, 2=state + * is: 1=low, 2=high, 3=He arc, 4=new high + * @param[in] args expect " [ on | off ]" + * @param[out] retstring + * @return ERROR | NO_ERROR | HELP + * + */ + long Interface::lamp( std::string args, std::string &retstring ) { + std::string function = "TCS::Interface::lamp"; + + // Help + // + if ( args == "?" ) { + retstring = TCSD_LAMP; + retstring.append( " [ on | off ]\n" ); + retstring.append( " where is one of { " ); + std::lock_guard lock(tcs_info_mtx); + for (const auto &[name,info] : this->tcs_info.lampinfo) { + retstring.append( name ); + retstring.append( " " ); + } + retstring.append( "} and is case-insensitive.\n" ); + retstring.append( " The optional { on off } will turn the designated lamp on or off.\n" ); + retstring.append( " No argument will return the lamp state.\n" ); + return HELP; + } + + // check arguments. default to status and unspecified lamp. + // + std::vector tokens; + int ntok = Tokenize( args, tokens, " " ); + + std::string state="status"; // optional + std::string which=""; // required + + if ( ntok < 1 || ntok > 2 ) { retstring="invalid_arguments"; return ERROR; } + if ( ntok > 1 ) state = tokens.at(1); + if ( ntok > 0 ) which = tokens.at(0); + + make_uppercase( which ); + + // default req_state reads status and arg can override this + // + int req_state = 2; // status + + if ( caseCompareString( state, "on" ) ) req_state = 1; + else + if ( caseCompareString( state, "off" ) ) req_state = 0; + + // no default lamp, must be specified + // + { + std::lock_guard lock(tcs_info_mtx); + auto lamploc = this->tcs_info.lampinfo.find(which); + if (lamploc==this->tcs_info.lampinfo.end()) { + logwrite(function, "ERROR unknown lamp '"+which+"'"); + retstring = "unknown_lamp"; + return ERROR; + } + } + + long error = NO_ERROR; + + // requesting on|off + // + if (req_state!=2) { + error = set_lamp(which, req_state); + if (error != NO_ERROR) { + logwrite(function, "ERROR setting lamp '"+which+"'"); + retstring="ERR"; + return ERROR; + } + std::this_thread::sleep_for(std::chrono::milliseconds(250)); // wait for it to turn on|off + } + + // whether or not requesting on|off, check state now + // which requires a completely different command + // + error = get_lamp(which, retstring); + + if (error != NO_ERROR) { + logwrite(function, "ERROR reading state of lamp '"+which+"'"); + retstring="ERR"; + } + + return error; + } + /***** TCS::Interface::lamp *************************************************/ + + + /***** TCS::Interface::set_lamp *********************************************/ + /** + * @brief set on|off state of specified lamp + * @param[in] which name of lamp + * @param[in] state 0=off 1=on + * @return ERROR | NO_ERROR + * + */ + long Interface::set_lamp(const std::string &which, int state) { + size_t lampnum = -1; + { + std::lock_guard lock(tcs_info_mtx); + lampnum = this->tcs_info.lampinfo[which].num; + } + + // send lamp power command to TCS + std::ostringstream cmd; + cmd << "NPS " << state << " " << lampnum; + std::string reply; + long error = this->send_command( cmd.str(), reply, TCS::FAST_RESPONSE ); + + return error; + } + /***** TCS::Interface::set_lamp *********************************************/ + + + /***** TCS::Interface::get_lamp *********************************************/ + /** + * @brief get on|off state of specified lamp + * @param[in] which name of lamp + * @param[out] retstring reference to return string for the on|off state + * @return ERROR | NO_ERROR + * + */ + long Interface::get_lamp(const std::string &which, std::string &retstring) { + size_t lampnum = -1; + { + std::lock_guard lock(tcs_info_mtx); + lampnum = this->tcs_info.lampinfo[which].num; + } + + // send lamp query command to TCS + std::ostringstream cmd; + cmd << "LAMPS?"; + std::string reply; + long error = this->send_command( cmd.str(), reply, TCS::FAST_RESPONSE ); + + // tokenize reply which contains comma-delimited list of all lamps + std::vector tokens; + Tokenize( reply, tokens, "," ); + + size_t lampindex = lampnum-1; // vector index into 1-based lampnum + + if (lampindex < 0 || lampindex >= tokens.size()) { + logwrite("TCS::Interface::get_lamp", "ERROR expected n,n,n,n but got '"+reply+"'"); + return ERROR; + } + + try { + int state = std::stoi(tokens.at(lampindex)); + retstring = (state==1 ? "on" : "off"); + std::lock_guard lock(tcs_info_mtx); + this->tcs_info.lampinfo[which].state = state; + } + catch (const std::exception &e) { + logwrite("TCS::Interface::get_lamp", "ERROR "+std::string(e.what())); + return ERROR; + } + + return error; + } + /***** TCS::Interface::get_lamp *********************************************/ + + /***** TCS::Interface::pt_offset ********************************************/ /** * @brief wrapper for the native PT command @@ -1526,8 +1718,6 @@ 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; // Help // @@ -1649,6 +1839,8 @@ namespace TCS { } else // These commands reply with information (not a code)... if ( cmd == "?NAME" || + cmd == "LAMPS?" || + cmd == "NPS" || cmd == "?PARALLACTIC" || cmd == "?WEATHER" || cmd == "RAWDEC" || @@ -1800,6 +1992,11 @@ namespace TCS { /***** TCS::TcsInfo::parse_pa ***********************************************/ + /** + * @brief extract parallactic angle from string and store it in the class + * @param[in] input "PARALLACTIC = xx.xx" + * + */ void TcsInfo::parse_pa( std::string &input ) { const std::string function("TCS::TcsInfo::parse_pa"); @@ -1825,6 +2022,11 @@ namespace TCS { /***** TCS::TcsInfo::parse_weather ******************************************/ + /** + * @brief extract values from "?WEATHER" command and store in the class + * @param[in] input + * + */ void TcsInfo::parse_weather( std::string &input ) { const std::string function("TCS::TcsInfo::parse_weather"); std::stringstream message; @@ -1869,8 +2071,15 @@ namespace TCS { } return; } + /***** TCS::TcsInfo::parse_weather ******************************************/ + /***** TCS::TcsInfo::parse_reqstat ******************************************/ + /** + * @brief extract values from "REQSTAT" command and store in the class + * @param[in] input + * + */ void TcsInfo::parse_reqstat( std::string &input ) { const std::string function("TCS::TcsInfo::parse_weather"); std::stringstream message; @@ -1915,7 +2124,15 @@ namespace TCS { } return; } + /***** TCS::TcsInfo::parse_reqstat ******************************************/ + + /***** TCS::TcsInfo::parse_reqpos *******************************************/ + /** + * @brief extract values from "REQPOS" command and store in the class + * @param[in] input + * + */ void TcsInfo::parse_reqpos( std::string &input ) { const std::string function="TCS::TcsInfo::parse_weather"; std::stringstream message; @@ -1959,5 +2176,49 @@ namespace TCS { } return; } + /***** TCS::TcsInfo::parse_reqpos *******************************************/ + + + /***** TCS::TcsInfo::parse_lamps ********************************************/ + /** + * @brief extract values from "LAMPS?" command and store in TcsInfo class + * @details TCS replies with #,#,#,# where #={0 1} to indicate the state of + * lamps 1,2,3,4 + * @param[in] input + * + */ + void TcsInfo::parse_lamps( std::string &input ) { + const std::string function="TCS::TcsInfo::parse_lamps"; + std::stringstream message; + + // input string is expected to be: #,#,#,# DONE + // Remove "DONE" + // + size_t pos = input.find("DONE"); + if ( pos != std::string::npos ) input.erase( pos, 4 ); + // Tokenize on the comma "," and expect 4 tokens + // + std::vector tokens; + Tokenize( input, tokens, "," ); + + if (tokens.size() != 4) { + logwrite(function, "ERROR expected 4 tokens but got '"+input+"'"); + return; + } + + // lampinfo map indexed by name but TCS response held by tokens is indexed by number. + // This is not particularly efficient but there are only four to loop through. + // + for (auto &[name,info] : this->lampinfo) { + for (size_t tok=0; tok lampinfo; ///< lamp info indexed by a UI key + TcsInfo() - : isopen(false) { this->init(); } + : isopen(false) + { + // these map indices are the lamp names accepted by the UI + // this is the only place they appear + // + lampinfo["LO"] = { Key::Tcsd::LAMP_LO, 1, -1 }; + lampinfo["HI"] = { Key::Tcsd::LAMP_HI, 2, -1 }; + lampinfo["ARC"] = { Key::Tcsd::LAMP_ARC, 3, -1 }; + lampinfo["ULTRA"] = { Key::Tcsd::LAMP_ULTRA, 4, -1 }; + + this->init(); + } /** * @brief initialize all class member variables to "non-values" @@ -96,9 +116,13 @@ namespace TCS { domeazimuth=NAN; airmass=NAN; focus=NAN; + offsetra=NAN; + offsetdec=NAN; offsetrate=NAN; cassangle=NAN; + pa=NAN; domeshutters=-1; + for (auto &[name,info] : lampinfo) { info.state=-1; } } // These functions parse the return string from native TCS commands @@ -108,6 +132,7 @@ namespace TCS { void parse_reqstat( std::string &input ); ///< parse retstring from native REQSTAT void parse_reqpos( std::string &input ); ///< parse retstring from native REQPOS void parse_pa( std::string &input ); ///< parse retstring from native ?PARALLACTIC + void parse_lamps( std::string &input ); ///< parse retstring from native LAMPS? }; /***** TCS::TcsInfo *********************************************************/ @@ -493,6 +518,9 @@ namespace TCS { long get_motion( const std::string &arg, std::string &retstring ); long ringgo( const std::string &arg, std::string &retstring ); long coords( std::string args, std::string &retstring ); + long lamp( std::string args, std::string &retstring ); + long set_lamp( const std::string &which, int state ); + long get_lamp( const std::string &which, std::string &retstring ); long pt_offset( std::string args, std::string &retstring ); long zero_offsets( const std::string args, std::string &retstring ); long ret_offsets( std::string args, std::string &retstring ); diff --git a/tcsd/tcs_server.cpp b/tcsd/tcs_server.cpp index 6501cf95..0a1a4020 100644 --- a/tcsd/tcs_server.cpp +++ b/tcsd/tcs_server.cpp @@ -702,6 +702,11 @@ void doit(TcsIO &tcs_io, const std::string &client_cmd, bool is_slow_command) { } else + if ( cmd==TCSD_LAMP ) { + ret = this->interface.lamp( args, retstring ); + } + else + // list // if ( cmd.compare( TCSD_LIST ) == 0 ) {