diff --git a/Config/flexured.cfg.in b/Config/flexured.cfg.in index f6aaebde..c7cb09ef 100644 --- a/Config/flexured.cfg.in +++ b/Config/flexured.cfg.in @@ -4,7 +4,6 @@ LOGPATH=/data/logs/ # where to write logs BLKPORT=@FLEXURED_BLK_PORT@ # slitd server blocking port NBPORT=@FLEXURED_NB_PORT@ # slitd server non-blocking port -DAEMON=no # run as daemon? ASYNCPORT=@MESSAGEPORT@ # asynchronous message port ASYNCGROUP=239.1.1.234 # asynchronous message broadcast group @@ -19,6 +18,31 @@ SUB_ENDPOINT="tcp://127.0.0.1:@MESSAGE_BROKER_PUB_PORT@" # EMULATOR_PORT=@FLEXURED_EMULATOR@ +# COLLIMATOR_POSITION +# +# +POSITION_COEFFICIENTS=(U X 20.962529 -0.037965 1.016758) +POSITION_COEFFICIENTS=(U Y 0.110335 19.866173 -0.209023) +POSITION_COEFFICIENTS=(G X 20.092616 0.202743 -0.028325) +POSITION_COEFFICIENTS=(G Y 0.697003 -21.372195 -1.078488) +POSITION_COEFFICIENTS=(R X -19.342549 -0.139683 0.497744) +POSITION_COEFFICIENTS=(R Y 0.173243 20.487537 0.611772) +POSITION_COEFFICIENTS=(I X -21.177322 0.205141 -0.492189) +POSITION_COEFFICIENTS=(I Y -0.049052 -22.605129 0.141980) + +# FLEXURE_POLYNOMIALS +# < ... > (expected 20 values) +# +FLEXURE_POLYNOMIALS=( I X -0.010050 -0.453673 3.922206 -5.996148 3.412802 0.005490 -3.205597 -0.720826 -0.054873 -0.071945 1.848058 -10.817125 25.420519 -25.960362 9.732404 -0.012039 -0.126167 0.450766 -0.302394 0.000000 ) +FLEXURE_POLYNOMIALS=( I Y 0.002736 0.336990 -6.849322 13.060501 -9.703479 0.056297 -1.168824 2.917605 -7.729839 4.986293 2.292288 -2.011299 0.629802 0.000000 0.000000 -0.138345 0.195320 -0.032475 0.000000 0.000000 ) +FLEXURE_POLYNOMIALS=( R X -0.009290 -0.538726 0.119516 0.000000 0.000000 0.031857 1.486486 7.743371 -14.374896 9.870450 2.157921 -7.554184 15.041910 -11.824859 2.810760 -0.028081 0.116145 1.275392 -4.032078 2.742630 ) +FLEXURE_POLYNOMIALS=( R Y 0.051850 -4.060014 24.799761 -43.279609 27.854001 0.069700 -0.253810 9.387602 -17.881394 11.001366 1.091908 3.470392 -22.509011 39.129797 -21.486402 0.063973 -1.368978 7.417551 -14.718536 9.557388 ) +FLEXURE_POLYNOMIALS=( G X 0.029701 1.331934 -8.949185 10.074266 -3.794059 0.111452 -1.386185 7.325222 -4.590153 -0.796477 1.074222 3.459916 -6.013151 3.162186 0.000000 0.115437 -0.885630 0.680245 0.000000 0.000000 ) +FLEXURE_POLYNOMIALS=( G Y 0.023484 0.327449 -6.305071 6.915979 -3.185265 0.043076 -0.308662 8.331641 -9.886829 3.006096 4.718045 -27.106506 74.046080 -84.281823 34.303818 0.004524 -1.747392 9.720284 -19.590870 12.067846 ) +FLEXURE_POLYNOMIALS=( U X 0.029929 -2.366930 2.843202 0.000000 0.000000 0.021643 0.446831 11.575399 -13.130549 5.162279 0.712767 -0.519063 0.220622 0.000000 0.000000 0.017301 2.682137 -10.738551 15.776922 -7.607582 ) +FLEXURE_POLYNOMIALS=( U Y 0.003026 -0.103969 3.620296 -6.585365 4.795205 0.010984 -3.756635 0.463073 -0.858354 0.000000 0.977025 -11.541444 30.074095 -32.763652 12.876655 -0.020034 0.126004 0.000000 0.000000 0.000000 ) + + # For each actuator's controller specify: # MOTOR_CONTROLLER=" " # name of controller @@ -42,12 +66,12 @@ MOTOR_CONTROLLER="I @IP_FLEXURE_I@ 50000 1 3" # 3 = Y = spatial # MOTOR_AXIS="U 1 25 225 0 na 100.0" -MOTOR_AXIS="U 2 -1000 1000 0 na 0.0" -MOTOR_AXIS="U 3 -1000 1000 0 na 0.0" +MOTOR_AXIS="U 2 -1000 1000 0 na 0.0" +MOTOR_AXIS="U 3 -1000 1000 0 na 0.0" MOTOR_AXIS="G 1 25 225 0 na 100.0" -MOTOR_AXIS="G 2 -1000 1000 0 na 0.0" -MOTOR_AXIS="G 3 -1000 1000 0 na 0.0" +MOTOR_AXIS="G 2 -1000 1000 0 na 0.0" +MOTOR_AXIS="G 3 -1000 1000 0 na 0.0" MOTOR_AXIS="R 1 25 225 0 na 100.0" MOTOR_AXIS="R 2 -1000 1000 0 na 0.0" diff --git a/acamd/acam_interface.cpp b/acamd/acam_interface.cpp index 591d4100..f3a0daf1 100644 --- a/acamd/acam_interface.cpp +++ b/acamd/acam_interface.cpp @@ -1621,17 +1621,17 @@ namespace Acam { // extract and store values in the class // - 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::TCSNAME, tcsdata.tcsname ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::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::TELRA_H, tcsdata.ra_scope_h ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::TELDEC_D, tcsdata.dec_scope_d ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::RAOFFSET, tcsdata.offsetra ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::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::TELFOCUS, tcsdata.telfocus ); Common::extract_telemetry_value( jmessage, Key::Tcsd::AIRMASS, tcsdata.airmass ); // save them to the database diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index de6e6eb7..39db994d 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -2805,6 +2805,10 @@ namespace AstroCam { Common::Header telem; + // Helper function adds all keys from a table. + // The tables are defined in fits_header_defs.h and contain extension + // channel name (as appropriate), keyword, value, comment, and type. + // auto add = [&]( const std::string &topic, const auto &keytable ) { auto it = this->live_telemetry.find( topic ); if ( it != this->live_telemetry.end() ) add_cached_telem( telem, it->second, keytable ); diff --git a/common/common.h b/common/common.h index 4e9b2905..28072586 100644 --- a/common/common.h +++ b/common/common.h @@ -7,7 +7,9 @@ #pragma once +#include #include +#include #include #include #include @@ -233,6 +235,13 @@ namespace Common { iface.subscriber_topics.push_back(topic); } + // check subscriber initialization (this would be a programming error) + // + if (!iface.subscriber) { + logwrite(function, "ERROR subscriber object is not initialized"); + return ERROR; + } + try { // connect to the message broker and wait for connection to establish // diff --git a/common/fits_header_defs.h b/common/fits_header_defs.h index 9d1f5352..e62fc210 100644 --- a/common/fits_header_defs.h +++ b/common/fits_header_defs.h @@ -45,18 +45,18 @@ namespace FitsHeaderKeys { }; 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", "" } + { "I", Key::Flexured::FLEX_X_I.c_str(), "FLEX_X", "I flexure axis 2 (X) in um", "" }, + { "I", Key::Flexured::FLEX_Y_I.c_str(), "FLEX_Y", "I flexure axis 3 (Y) in um", "" }, + { "I", Key::Flexured::FLEX_Z_I.c_str(), "FLEX_Z", "I flexure axis 1 (Z) in um", "" }, + { "R", Key::Flexured::FLEX_X_R.c_str(), "FLEX_X", "R flexure axis 2 (X) in um", "" }, + { "R", Key::Flexured::FLEX_Y_R.c_str(), "FLEX_Y", "R flexure axis 3 (Y) in um", "" }, + { "R", Key::Flexured::FLEX_Z_R.c_str(), "FLEX_Z", "R flexure axis 1 (Z) in um", "" }, + { "G", Key::Flexured::FLEX_X_G.c_str(), "FLEX_X", "G flexure axis 2 (X) in um", "" }, + { "G", Key::Flexured::FLEX_Y_G.c_str(), "FLEX_Y", "G flexure axis 3 (Y) in um", "" }, + { "G", Key::Flexured::FLEX_Z_G.c_str(), "FLEX_Z", "G flexure axis 1 (Z) in um", "" }, + { "U", Key::Flexured::FLEX_X_U.c_str(), "FLEX_X", "U flexure axis 2 (X) in um", "" }, + { "U", Key::Flexured::FLEX_Y_U.c_str(), "FLEX_Y", "U flexure axis 3 (Y) in um", "" }, + { "U", Key::Flexured::FLEX_Z_U.c_str(), "FLEX_Z", "U flexure axis 1 (Z) in um", "" } }; const Extension FocusInfoKeys[] = { @@ -92,12 +92,15 @@ namespace FitsHeaderKeys { }; const Primary TcsInfoKeys[] = { + { Key::Tcsd::PA.c_str(), "", "position angle in deg" }, { 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::TELRA_H.c_str(), "", "TCS reported RA in decimal hours", "DOUBLE" }, + { Key::Tcsd::TELDEC_D.c_str(), "", "TCS reported DEC in decimal deg", "DOUBLE" }, { 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" }, diff --git a/common/message_keys.h b/common/message_keys.h index 6ff0fb38..4fa79450 100644 --- a/common/message_keys.h +++ b/common/message_keys.h @@ -121,12 +121,17 @@ namespace Key { namespace Tcsd { inline const std::string TELRA = "TELRA"; inline const std::string TELDEC = "TELDEC"; + inline const std::string TELRA_H = "TELRA_H"; + inline const std::string TELDEC_D = "TELDEC_D"; 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 MOTION = "MOTION"; + inline const std::string ISOPEN = "ISOPEN"; + inline const std::string TCSNAME = "TCSNAME"; inline const std::string HA = "HA"; + inline const std::string PA = "PA"; inline const std::string RAOFFSET = "RAOFFSET"; inline const std::string DECLOFFS = "DECLOFFS"; inline const std::string ZENANGLE = "ZENANGLE"; @@ -151,18 +156,22 @@ namespace Key { } 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"; + inline const std::string AxisX = "FLEX_X_"; + inline const std::string AxisY = "FLEX_Y_"; + inline const std::string AxisZ = "FLEX_Z_"; + + inline const std::string FLEX_X_U = "FLEX_X_U"; + inline const std::string FLEX_Y_U = "FLEX_Y_U"; + inline const std::string FLEX_Z_U = "FLEX_Z_U"; + inline const std::string FLEX_X_G = "FLEX_X_G"; + inline const std::string FLEX_Y_G = "FLEX_Y_G"; + inline const std::string FLEX_Z_G = "FLEX_Z_G"; + inline const std::string FLEX_X_R = "FLEX_X_R"; + inline const std::string FLEX_Y_R = "FLEX_Y_R"; + inline const std::string FLEX_Z_R = "FLEX_Z_R"; + inline const std::string FLEX_X_I = "FLEX_X_I"; + inline const std::string FLEX_Y_I = "FLEX_Y_I"; + inline const std::string FLEX_Z_I = "FLEX_Z_I"; } namespace Focusd { diff --git a/common/tcs_constants.h b/common/tcs_constants.h index 8efd05f4..2c758ec0 100644 --- a/common/tcs_constants.h +++ b/common/tcs_constants.h @@ -30,7 +30,7 @@ inline static const char TCS_LAMPNAME_ULTRA[] = "ULTRA"; const int TCS_LAMPNUM_U inline static const char TCS_SUCCESS_STR[] = "success"; inline static const char TCS_UNRECOGNIZED_COMMAND_STR[] = "tcs_unrecognized_command"; inline static const char TCS_INVALID_PARAMETER_STR[] = "tcs_bad_param"; -inline static const char TCS_UNABLE_TO_EXECUTE_STR[] = "tcs_already_moving"; +inline static const char TCS_UNABLE_TO_EXECUTE_STR[] = "tcs_unable_to_execute"; inline static const char TCS_HOST_UNAVAILABLE_STR[] = "tcs_unavailable"; inline static const char TCS_UNDEFINED_STR[] = "tcs_undefined"; diff --git a/flexured/CMakeLists.txt b/flexured/CMakeLists.txt index 8522b400..9665cb9d 100644 --- a/flexured/CMakeLists.txt +++ b/flexured/CMakeLists.txt @@ -4,6 +4,8 @@ # @author David Hale # ---------------------------------------------------------------------------- +# add_compile_options( -O0 -g -march=native -DNDEBUG) + cmake_minimum_required( VERSION 3.12 ) set( FLEXURED_DIR ${PROJECT_BASE_DIR}/flexured ) @@ -16,10 +18,16 @@ include_directories( ${PROJECT_BASE_DIR}/common ) link_directories( ${PROJECT_BASE_DIR}/lib ) +# ZeroMQ +# +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 ${FLEXURED_DIR}/flexure_interface.cpp + ${FLEXURED_DIR}/flexure_compensator.cpp ) target_link_libraries(flexured @@ -29,6 +37,8 @@ target_link_libraries(flexured logentry utilities ${CMAKE_THREAD_LIBS_INIT} + ${ZMQPP_LIB} + ${ZMQ_LIB} ) # -- Port Definitions ---------------------------------------------------------- diff --git a/flexured/flexure_compensator.cpp b/flexured/flexure_compensator.cpp new file mode 100644 index 00000000..2adcb58c --- /dev/null +++ b/flexured/flexure_compensator.cpp @@ -0,0 +1,280 @@ +/** + * @file flexure_compensator.cpp + * @brief this contains the flexure compensator code + * @author David Hale & Matt + * Algorithms by Matt Matuszewski + * + */ + +#include "tcs_info.h" +#include "flexure_compensator.h" + +namespace Flexure { + + /***** Flexure::Compensator::Compensator ************************************/ + /** + * @brief class constructor + * @param[in] info constructed with a reference to the TcsInfo object + * owned by Interface. + * + */ + Compensator::Compensator(TcsInfo &info) : tcs_info(info) { + // position_coefficients and flexure_polynomials are maps + // indexed by a pair. This initializes their indices. + // Values will be loaded by Compensator::load_position_coefficients(). + // + for (const auto &chan : { "U", "G", "R", "I" }) { + for (const auto &axis : { X, Y }) { + position_coefficients[{chan,axis}] = std::vector(); + flexure_polynomials[{chan,axis}] = std::vector(); + } + } + + // trig function used for each channel's calculate_shift() + // + this->trigfunction["U"] = TrigFunction::Cosine; + this->trigfunction["G"] = TrigFunction::Cosine; + this->trigfunction["R"] = TrigFunction::Cosine; + this->trigfunction["I"] = TrigFunction::Cosine; + + } + /***** Flexure::Compensator::Compensator ************************************/ + + + /***** Flexure::Compensator::load_vector_from_config ************************/ + /** + * @brief loads position coefficients from configuration file + * @details This parses a configuration file row given the specified VectorType + * and loads the class map vector specified by VectorType. This will be + * either a vector of POSITION_COEFFICIENTS or FLEXURE_POLYNOMIALS, + * which are vectors assigned to a map indexed by pair { chan, axis }. + * @param[in] config configuration line + * @param[in] type one of VectorType enum to specify which vector map to load + * @return ERROR|NO_ERROR + * + */ + long Compensator::load_vector_from_config(std::string &config, VectorType type) { + const std::string function("Flexure::Compensator::load_vector_from_config"); + std::vector tokens; + Tokenize(config, tokens, " "); + + size_t vecsize; + vector_map_t* vecmap; + + // assign the vecmap pointer to the appropriate map based on VectorType, + // which also has a fixed vector size + if (type==VectorType::POSITION_COEFFICIENTS) { + vecmap = &this->position_coefficients; + vecsize = 3; + } + else + if (type==VectorType::FLEXURE_POLYNOMIALS) { + vecmap = &this->flexure_polynomials; + vecsize = 20; + } + else { + logwrite(function, "ERROR invalid vector type"); + return ERROR; + } + + // expect ... + if (tokens.size() != vecsize+2 ) { + std::ostringstream oss; + oss << "ERROR got \"" << config << "\" but expected ... (" << vecsize << " values)"; + logwrite(function, oss.str()); + return ERROR; + } + + // the vector is in a map indexed by pair { chan, axis } + std::string chan = tokens[0]; + std::string axis = tokens[1]; + + if (vecmap->find({chan,axis}) == vecmap->end()) { + logwrite(function, "ERROR invalid chan,axis: "+chan+","+axis); + return ERROR; + } + + // erase the vector and load it with the values provided by the configuration row + try { + (*vecmap)[{chan,axis}].clear(); + for (int tok=2; tokflexure_polynomials[{chan,axis}] are a,b,c,d,e then + * return a + bx + cx^2 + dx^3 + ex^4. + * @param[in] which pair { channel, axis } + * @param[in] inputvar independent input variable for the polynomial fit + * @param[in] offset offset in vector to start reading coefficients + * @return double (a + bx + cx^2 + dx^3 + ex^4) + * @throws std::out_of_range + * + */ + double Compensator::flexure_polynomial_fit(const std::pair &which, + double inputvar, size_t offset) { + + const auto it = this->flexure_polynomials.find(which); + + if (it == this->flexure_polynomials.end()) { + throw std::out_of_range("invalid chan,axis '"+which.first+","+which.second+"'"); + } + + const auto &p = it->second; + + if (offset+5 > p.size()) { + throw std::out_of_range("not enough flexure polynomial coefficients"); + } + + // coefficients are in units of sine, so use sin(inputvar) here + // + const double x = std::sin(inputvar); + + // a + bx + cx^2 + dx^3 + ex^4, where x=sin(inputvar) + // + return p[offset] + x*( p[offset+1] + x*( p[offset+2] + x*( p[offset+3] + x*p[offset+4] ) ) ); + } + /***** Flexure::Compensator::flexure_polynomial_fit *************************/ + + + /***** Flexure::Compensator::calculate_shift ********************************/ + /** + * @brief calculates the shift(chan,axis) of the spectrum on the detector + * @details C + A1 * sin(cass-theta) + A2 * sin(2*(cass-theta)) or + * C + A1 * cos(cass-theta) + A2 * cos(2*(cass-theta)) + * Input coefficients are a function of (chan,axis) so the output + * shift will also be a function of (chan,axis). The zenangle + * limits are applied here. + * @param[in] which pair { channel, axis } + * @return double (calculated shift) + * @throws std::exception + * + */ + double Compensator::calculate_shift(const std::pair &which) { + const std::string function("Flexure::Compensator::calculate_shift"); + + if (this->flexure_polynomials.find(which)==this->flexure_polynomials.end()) { + throw std::out_of_range("invaid chan,axis '"+which.first+","+which.second+"'"); + } + + double zenangle = this->tcs_info.get_zenangle(); + + zenangle = (zenangle>ZENMAX ? ZENMAX : zenangle); // cap zenangle at max value + + if (zenangle < ZENMIN) return 0.; // no correction below minimum zenangle + + double zenangle_rad = zenangle * DEGTORAD; + double equivalentcass_rad = this->tcs_info.get_equivalentcass() * DEGTORAD; + + try { + double c = this->flexure_polynomial_fit(which, zenangle_rad, 0); + double a1 = this->flexure_polynomial_fit(which, zenangle_rad, 5); + double theta = this->flexure_polynomial_fit(which, zenangle_rad, 10); + double a2 = this->flexure_polynomial_fit(which, zenangle_rad, 15); + + auto [ chan, axis ] = which; + + if (this->trigfunction[chan] == TrigFunction::Sine) { + return c + a1 * std::sin( (equivalentcass_rad - theta)) + + a2 * std::sin(2*(equivalentcass_rad - theta)); + } + else + if (this->trigfunction[chan] == TrigFunction::Cosine) { + return c + a1 * std::cos( (equivalentcass_rad - theta)) + + a2 * std::cos(2*(equivalentcass_rad - theta)); + } + else { + logwrite(function, "ERROR undefined trig function for channel "+chan); + return NAN; + } + } + catch (const std::exception &e) { + logwrite(function, "ERROR: "+std::string(e.what())); + throw; + } + } + /***** Flexure::Compensator::calculate_shift ********************************/ + + + /***** Flexure::Compensator::compensate_shift_to_delta *********************/ + /** + * @brief calculates the tip-tilt adjustment needed to compensate for shift + * @details Given the spectral shift for a specific channel, this calculates + * the adjustment needed to compensate for that shift in pixel units. + * The shift is applied with the opposite sign in order to correct + * for it, so the coefficients are evaluated at { -sx, -sy }. Note + * that the constant term [2] is not negated. + * @param[in] channel string channel name + * @param[in] shift pair { sx, sy } representing shift in X, Y + * @param[out] delta reference to pair { dx, dy } of pix adjustments to X, Y + * + */ + void Compensator::compensate_shift_to_delta(const std::string &channel, + const std::pair &shift, std::pair &delta) { + + const auto &cx = this->position_coefficients.at({channel, X}); + const auto &cy = this->position_coefficients.at({channel, Y}); + + // shift is applied in opposite sign in order to compensate for it + // + const double x = -shift.first; + const double y = -shift.second; + + // delta.first is delta-X = [0]*x + [1]*y + [2] + delta.first = cx[0]*x + cx[1]*y + cx[2]; + + // delta.second is delta-Y = [0]*x + [1]*y + [2] + delta.second = cy[0]*x + cy[1]*y + cy[2]; + } + /***** Flexure::Compensator::compensate_shift_to_delta *********************/ + + + /***** Flexure::Compensator::calculate_compensation ************************/ + /** + * @brief calculates the adjustments needed to compensate for a shift + * @details input is output of calculate_shift() in pixel units, + * output is correction to apply to flexure actuator position + * in collimator position units + * @param[in] channel string channel name + * @param[out] delta ref to pair { dx, dy } of collimator adjustments to X, Y + * + */ + void Compensator::calculate_compensation(const std::string &channel, std::pair &delta) { + + try { + // calculate shift of spectrum on detector + // + double shift_x = this->calculate_shift({channel, X}); + double shift_y = this->calculate_shift({channel, Y}); + + std::pair shift = { shift_x, shift_y }; + + // calculate tip-tilt adjustment needed to compenstate for shift + // + this->compensate_shift_to_delta(channel, shift, delta); + } + catch (const std::exception &e) { + logwrite("Flexure::Compensator::calculate_compensation", "ERROR: "+std::string(e.what())); + delta = { NAN, NAN }; + throw; + } + } + /***** Flexure::Compensator::calculate_compensation ************************/ + +} diff --git a/flexured/flexure_compensator.h b/flexured/flexure_compensator.h new file mode 100644 index 00000000..dde7b212 --- /dev/null +++ b/flexured/flexure_compensator.h @@ -0,0 +1,78 @@ +/** --------------------------------------------------------------------------- + * @file flexure_interface.h + * @brief flexure interface include + * @details defines the classes used by the flexure hardware interface + * @author David Hale + * + */ + +#pragma once + +#include "common.h" +#include "tcs_info.h" + +namespace Flexure { + + constexpr double PI = 3.14159265358979323846; + constexpr double DEGTORAD = PI/180.0; + + constexpr double ZENMIN = 5.0; ///< no compensation below this minimum zenangle + constexpr double ZENMAX = 60.0; ///< cap zenangle at this maximum value + + // PI actuator axis numbers + // + constexpr int AXIS_Z = 1; ///< piston + constexpr int AXIS_X = 2; ///< spectral + constexpr int AXIS_Y = 3; ///< spatial + + constexpr const char* X = "X"; + constexpr const char* Y = "Y"; + + namespace Channel { + constexpr const char* U = "U"; + constexpr const char* G = "G"; + constexpr const char* R = "R"; + constexpr const char* I = "I"; + } + + enum class TrigFunction { + Sine, + Cosine + }; + + enum VectorType : size_t { + POSITION_COEFFICIENTS, + FLEXURE_POLYNOMIALS + }; + + /***** Flexure::Compensator *************************************************/ + /** + * @brief contains functions and data for calculating compensation + * @details this does not compensate anything, just informs how to compensate + * + */ + class Compensator { + private: + using vector_map_t = std::map, std::vector>; + + TcsInfo &tcs_info; ///< reference to Interface's TcsInfo + + vector_map_t position_coefficients; + vector_map_t flexure_polynomials; + + std::map trigfunction; + + double flexure_polynomial_fit(const std::pair &which, double inputvar, size_t offset); + void compensate_shift_to_delta(const std::string &channel, + const std::pair &shift, std::pair &delta); + + public: + Compensator(TcsInfo &info); + + long load_vector_from_config(std::string &config, VectorType type); + double calculate_shift(const std::pair &which); + void calculate_compensation(const std::string &channel, std::pair &delta); + }; + /***** Flexure::Compensator *************************************************/ + +} diff --git a/flexured/flexure_interface.cpp b/flexured/flexure_interface.cpp index e44c036f..176feabb 100644 --- a/flexured/flexure_interface.cpp +++ b/flexured/flexure_interface.cpp @@ -32,7 +32,7 @@ namespace Flexure { // I don't want to prevent the system from working with a subset of controllers, // but the user should be warned, in case it wasn't intentional. // - if ( this->numdev != 2 ) { + if ( this->numdev != 4 ) { message.str(""); message << "WARNING: " << this->numdev << " PI motor controller" << ( this->numdev == 1 ? "" : "s" ) << " defined! (expected 4)"; logwrite( function, message.str() ); @@ -43,6 +43,80 @@ namespace Flexure { /***** Flexure::Interface::initialize_class *********************************/ + /***** Flexure::Interface::handletopic_tcsd *********************************/ + /** + * @brief handler when subscriber receives a message with topic "tcsd" + * @details This loads the tcs_info class with values published by tcsd. + * @param[in] jmessage subscribed-received JSON message + * + */ + void Interface::handletopic_tcsd(const nlohmann::json &jmessage) { + { + std::lock_guard lock(snapshot_mutex); + this->tcs_snapshot_status = true; + } + // extract and store values in the class + double zenangle, casangle, parallactic; + Common::extract_telemetry_value(jmessage, Key::Tcsd::CASANGLE, casangle); + Common::extract_telemetry_value(jmessage, Key::Tcsd::ZENANGLE, zenangle); + Common::extract_telemetry_value(jmessage, Key::Tcsd::PA, parallactic); + this->tcs_info.store(zenangle, casangle, parallactic); + } + /***** Flexure::Interface::handletopic_tcsd *********************************/ + + + /***** Flexure::Interface::request_tcs_snapshot *****************************/ + /** + * @brief requests tcsd to publish a snapshot of its telemetry + * @details This publishes a Topic::SNAPSHOT message naming tcsd, which + * will cause tcsd to publish its snapshot. + * @throws std::exception + * + */ + void Interface::request_tcs_snapshot() { + nlohmann::json jmessage; + { + std::lock_guard lock(snapshot_mutex); + this->tcs_snapshot_status = false; + jmessage[Topic::TCSD] = false; + } + try { + this->publisher->publish(jmessage, Topic::SNAPSHOT); + } + catch (const std::exception &e) { + throw; + } + } + /***** Flexure::Interface::request_tcs_snapshot *****************************/ + + + /***** Flexure::Interface::wait_for_tcs_snapshot ****************************/ + /** + * @brief wait for tcs snapshot data to be published + * @details This is used after request_tcs_snapshot to wait for that to be received. + * @return true when subscriber receives snapshot data + * @throws std::runtime_error on timeout + * + */ + bool Interface::wait_for_tcs_snapshot() { + auto start_time = std::chrono::steady_clock::now(); + auto timeout = std::chrono::seconds(3); + + while (true) { + { + std::lock_guard lock(snapshot_mutex); + if (this->tcs_snapshot_status) return true; + } + + if (std::chrono::steady_clock::now() - start_time > timeout) { + throw std::runtime_error("timeout waiting for TCS telemetry"); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + /***** Flexure::Interface::wait_for_tcs_snapshot ****************************/ + + /***** Flexure::Interface::open *********************************************/ /** * @brief opens the PI socket connection @@ -311,26 +385,112 @@ namespace Flexure { */ long Interface::compensate( std::string args, std::string &retstring ) { const std::string function="Flexure::Interface::compensate"; - std::stringstream message; if ( args == "?" || args == "help" ) { retstring = FLEXURED_COMPENSATE; - retstring.append( " [ dryrun ]\n" ); - retstring.append( " Performs the flexure compensation. If the optional dryrun argument\n" ); - retstring.append( " is supplied then perform the calcuations and show the actions that\n" ); - retstring.append( " would be taken without actually moving anything.\n" ); + retstring.append( " [ ...] [ --dryrun ]\n" ); + retstring.append( " Performs the flexure compensation on one or more specified channels.\n" ); + retstring.append( " If the optional --dryrun argument is supplied then perform the calcuations\n" ); + retstring.append( " and show the actions that would be taken without actually moving anything,\n" ); + retstring.append( " returning the delta X,Y that would be applied.\n" ); return HELP; } - // perform the calculations + // check for --dryrun + // + bool is_dryrun = false; + auto pos = args.find("--dryrun"); + if (pos != std::string::npos) { // if switch in the args + args.erase(pos, 8); // then remove it + is_dryrun = true; // and set is_dryrun + retstring="dryrun:"; + } + + // args can contain a space-delimited list of channels + // + std::vector channels; + Tokenize(args, channels, " "); + if (channels.empty()) { + logwrite(function, "ERROR no channel specified"); + return ERROR; + } + + std::pair delta; + auto motormap = this->motorinterface.get_motormap(); + + // loop through list of specified channel(s) // - retstring="not_yet_implemented"; + for (const auto &chan : channels) { + try { + if (motormap.find(chan) == motormap.end()) throw std::runtime_error("unrecognized channel: "+chan); + this->validate_tcs_telemetry(); + this->compensator.calculate_compensation(chan, delta); + this->offset_tiptilt(chan, delta, is_dryrun); + if (is_dryrun) { + std::ostringstream oss; + oss << " " << chan << " " << delta.first << "," << delta.second; + retstring += oss.str(); + } + } + catch (const std::exception &e) { + logwrite(function, "ERROR: "+std::string(e.what())); + return ERROR; + } + } - return ERROR; + return NO_ERROR; } /***** Flexure::Interface::compensate ***************************************/ + /***** Flexure::Interface::offset_tiptilt ***********************************/ + /** + * @brief offsets the X, Y axes for the specified channel by specified delta + * @details The compensation offsets (delta) must be calculated first, with + * a call to this->compensator.calculate_compensation() for a given + * channel. That returns a delta pair which this function adds to + * the nominal positions and then sends the modified position to the + * actuators. + * @param[in] chan string channel + * @param[in] delta double pair { X, Y } of calculated compensation offsets + * @param[in] is_dryrun if true then no motion is commanded + * @throws std::runtime_error + * + */ + void Interface::offset_tiptilt(const std::string &chan, const std::pair &delta, bool is_dryrun) { + + // nominal positions + auto motormap=this->motorinterface.get_motormap()[chan]; + float nominal_x=motormap.axes[AXIS_X].defpos; + float nominal_y=motormap.axes[AXIS_Y].defpos; + + // new positions + float newposition_x = nominal_x + delta.first; + float newposition_y = nominal_y + delta.second; + + // dryrun only logs what it would have done + if ( is_dryrun ) { + std::ostringstream oss; + oss << "dry run: would move chan " << chan << " by " << delta.first << ", " << delta.second + << " to X=" << newposition_x << " Y=" << newposition_y; + logwrite("Flexure::Interface::offset_tiptilt", oss.str()); + return; + } + + // move X-axis + std::string retstring; + if (this->motorinterface.moveto( chan, AXIS_X, newposition_x, retstring ) != NO_ERROR) { + throw std::runtime_error("moving X axis for channel "+chan); + } + + // move Y-axis + if (this->motorinterface.moveto( chan, AXIS_Y, newposition_y, retstring ) != NO_ERROR) { + throw std::runtime_error("moving Y axis for channel "+chan); + } + } + /***** Flexure::Interface::offset_tiptilt ***********************************/ + + /***** Flexure::Interface::stop *********************************************/ /** * @brief send the stop-all-motion command to all controllers @@ -410,10 +570,11 @@ namespace Flexure { this->motorinterface.get_pos( chan, axis.second.axisnum, addr, position, posname ); } switch ( axis.second.axisnum ) { - case 1 : key = "FLXPIS_" + chan; break; - case 2 : key = "FLXSPE_" + chan; break; - case 3 : key = "FLXSPA_" + chan; break; - default: message.str(""); message << "ERROR unknown axis " << axis.second.axisnum; + case AXIS_Z: key = Key::Flexured::AxisZ + chan; break; + case AXIS_X: key = Key::Flexured::AxisX + chan; break; + case AXIS_Y: key = Key::Flexured::AxisY + chan; break; + default: key = "error"; + message.str(""); message << "ERROR unknown axis " << axis.second.axisnum; logwrite( function, message.str() ); continue; } @@ -493,7 +654,11 @@ namespace Flexure { * * Valid test names are: * motormap - * posmap + * shift + * comp + * tcsinfo + * ishift + * icomp * */ long Interface::test( std::string args, std::string &retstring ) { @@ -502,7 +667,7 @@ namespace Flexure { std::vector tokens; long error = NO_ERROR; - auto _motormap = this->motorinterface.get_motormap(); + auto motormap = this->motorinterface.get_motormap(); Tokenize( args, tokens, " " ); @@ -516,7 +681,11 @@ namespace Flexure { if ( testname == "?" || testname == "help" ) { retstring.clear(); retstring.append( " motormap return definition of motormap\n" ); - retstring.append( " posmap return definition of posmap\n" ); + retstring.append( " shift calculate shift(chan,axis) of spectrum on detector using live TCS info\n" ); + retstring.append( " comp calculates adjustments needed to compensate for shift using live TCS info\n" ); + retstring.append( " tcsinfo print current tcsinfo\n" ); + retstring.append( " ishift calculate shift for \n" ); + retstring.append( " icomp calculate adjustments for \n" ); return HELP; } else @@ -525,7 +694,7 @@ namespace Flexure { // if ( testname == "motormap" ) { retstring="name host:port addr naxes \n axisnum min max reftype defpos"; - for ( const auto &mot : _motormap ) { + for ( const auto &mot : motormap ) { retstring.append("\n"); message.str(""); message << mot.first << " " << mot.second.host << ":" @@ -540,6 +709,123 @@ namespace Flexure { } retstring.append("\n"); } + else + + // shift + // calculate shift of spectrum on detector + // + if (testname == "shift") { + if (tokens.size() != 3) { + retstring="expected "; + logwrite(function, "ERROR "+retstring); + return ERROR; + } + message.str(""); + try { + message << this->compensator.calculate_shift({tokens[1], tokens[2]}); + retstring = message.str(); + } + catch (const std::exception &e) { + retstring=std::string(e.what()); + logwrite(function, "ERROR: "+retstring); + return ERROR; + } + } + else + + // comp + // calculate adjustments needed to compensate for shift + // + if (testname == "comp") { + if (tokens.size() != 2) { + retstring="expected "; + logwrite(function, "ERROR "+retstring); + return ERROR; + } + + try { + std::pair delta; + this->compensator.calculate_compensation(tokens[1], delta); + message.str(""); message << "delta X=" << delta.first << " Y=" << delta.second; + logwrite(function, message.str()); + this->offset_tiptilt(tokens[1], delta, true); // true = dry run + } + catch (const std::exception &e) { + logwrite(function, std::string(e.what())); + return ERROR; + } + } + else + + // get tcsinfo + // + if (testname=="tcsinfo") { + message.str(""); + message << "zenangle = " << this->tcs_info.get_zenangle() << "\n" + << "casangle = " << this->tcs_info.get_casangle() << "\n" + << "parallactic = " << this->tcs_info.get_parallactic() << "\n" + << "equivalent_cass = " << this->tcs_info.get_equivalentcass() << "\n"; + retstring=message.str(); + } + else + + // interactive shift + // calculate shift for specified tcsinfo + // + if (testname=="ishift") { + if (tokens.size()!=6) { + retstring="expected "; + logwrite(function, "ERROR "+retstring); + return ERROR; + } + try { + double zenangle = std::stod(tokens[1]); + double casangle = std::stod(tokens[2]); + double parallactic = std::stod(tokens[3]); + { + std::lock_guard lock(snapshot_mutex); + this->tcs_info.store(zenangle, casangle, parallactic); + message.str(""); + message << this->compensator.calculate_shift({tokens[4], tokens[5]}); + } + retstring=message.str(); + } + catch (const std::exception &e) { + logwrite(function, "ERROR: "+std::string(e.what())); + return ERROR; + } + } + else + + // interactive comp + // calculate compensation adjustments for specified tcsinfo + // + if (testname=="icomp") { + if (tokens.size()!=5) { + retstring="expected "; + logwrite(function, "ERROR "+retstring); + return ERROR; + } + try { + double zenangle = std::stod(tokens[1]); + double casangle = std::stod(tokens[2]); + double parallactic = std::stod(tokens[3]); + std::pair delta; + { + std::lock_guard lock(snapshot_mutex); + this->tcs_info.store(zenangle, casangle, parallactic); + this->compensator.calculate_compensation(tokens[4], delta); + message.str(""); message << "delta X=" << delta.first << " Y=" << delta.second; + } + retstring=message.str(); + this->offset_tiptilt(tokens[4], delta, true); // true = dry run + } + catch (const std::exception &e) { + retstring=std::string(e.what()); + logwrite(function, "ERROR: "+retstring); + return ERROR; + } + } else { message.str(""); message << "ERROR: test " << testname << " unknown";; diff --git a/flexured/flexure_interface.h b/flexured/flexure_interface.h index 0b72a76c..16553a4e 100644 --- a/flexured/flexure_interface.h +++ b/flexured/flexure_interface.h @@ -12,7 +12,9 @@ #include "pi.h" #include "logentry.h" #include "common.h" +#include "tcs_info.h" #include "flexured_commands.h" +#include "flexure_compensator.h" #include #include #include @@ -49,7 +51,7 @@ namespace Flexure { /** * @struct Status - * @brief published flexure state: actuator position (um) by FLX_ key; NaN if unavailable + * @brief published flexure actuator position (um); NaN if unavailable */ struct Status { std::map positions; @@ -70,20 +72,29 @@ namespace Flexure { std::mutex publish_mutex; ///< serializes publish-on-change; held over get_status() — @TODO revisit public: - - Interface() - : context(), - numdev(-1), - is_subscriber_thread_running(false), - should_subscriber_thread_run(false), - motorinterface( FLEXURE_MOVE_TIMEOUT, 0, FLEXURE_POSNAME_TOLERANCE ) + Interface() : + context(), + numdev(-1), + motorinterface(FLEXURE_MOVE_TIMEOUT, 0, FLEXURE_POSNAME_TOLERANCE), + subscriber(std::make_unique(context, Common::PubSub::Mode::SUB)), + is_subscriber_thread_running(false), + should_subscriber_thread_run(false), + tcs_snapshot_status(false), + tcs_info(), + compensator(tcs_info) { topic_handlers = { { Topic::SNAPSHOT, std::function( - [this](const nlohmann::json &msg) { handletopic_snapshot(msg); } ) } + [this](const nlohmann::json &msg) { handletopic_snapshot(msg); } ) }, + { Topic::TCSD, std::function( + [this](const nlohmann::json &msg) { handletopic_tcsd(msg); } ) } }; } + // PI Interface class for the Piezo type + // + Physik_Instrumente::Interface motorinterface; + std::unique_ptr publisher; ///< publisher object std::string publisher_address; ///< publish socket endpoint std::string publisher_topic; ///< my default topic for publishing @@ -96,24 +107,30 @@ namespace Flexure { std::function> topic_handlers; ///< maps a handler function to each topic + bool tcs_snapshot_status; + std::mutex snapshot_mutex; + + // publish/subscribe functions + // long init_pubsub(const std::initializer_list &topics={}) { - if (!subscriber) { - subscriber = std::make_unique(context, Common::PubSub::Mode::SUB); - } return Common::PubSubHandler::init_pubsub(context, *this, topics); } void start_subscriber_thread() { Common::PubSubHandler::start_subscriber_thread(*this); } void stop_subscriber_thread() { Common::PubSubHandler::stop_subscriber_thread(*this); } void handletopic_snapshot( const nlohmann::json &jmessage ); ///< respond to a snapshot request + void handletopic_tcsd( const nlohmann::json &jmessage ); ///< store TCS telemetry published by tcsd void get_status(); ///< refresh status from hardware void publish_status( bool force=false ); ///< publish flexure state on change (or force) + void request_tcs_snapshot(); ///< ask tcsd to publish a snapshot of its telemetry + bool wait_for_tcs_snapshot(); ///< wait for that snapshot to be received + Common::Queue async; - // PI Interface class for the Piezo type - // - Physik_Instrumente::Interface motorinterface; + TcsInfo tcs_info; ///< defined in tcs_info.h + + Compensator compensator; long initialize_class(); long open(); ///< opens the PI socket connection @@ -123,6 +140,7 @@ namespace Flexure { long set( std::string args, std::string &retstring ); ///< set the slit width and offset long get( std::string args, std::string &retstring ); ///< get the current width and offset long compensate( std::string args, std::string &retstring ); ///< perform flexure compensation + void offset_tiptilt(const std::string &chan, const std::pair &delta, bool is_dryrun); long stop(); ///< send the stop-all-motion command to all controllers long send_command( const std::string &name, std::string cmd ); ///< writes the raw command as received to the master controller, no reply @@ -136,6 +154,18 @@ namespace Flexure { std::mutex wait_mtx; ///< mutex object for waiting for threads std::condition_variable cv; ///< condition variable for waiting for threads + + void validate_tcs_telemetry() { + // if TCS telemetry is old then ask it to publish + // + if (this->tcs_info.is_older_than(std::chrono::seconds(10))) { + try { + this->request_tcs_snapshot(); + this->wait_for_tcs_snapshot(); + } + catch (const std::exception &e) { throw; } + } + } }; /***** Flexure::Interface ***************************************************/ diff --git a/flexured/flexure_server.cpp b/flexured/flexure_server.cpp index dcb4b6ad..87fa3d8a 100644 --- a/flexured/flexure_server.cpp +++ b/flexured/flexure_server.cpp @@ -9,6 +9,7 @@ namespace Flexure { + Server* Server::instance = nullptr; /***** Flexure::Server::exit_cleanly ****************************************/ /** @@ -27,6 +28,41 @@ namespace Flexure { /***** Flexure::Server::exit_cleanly ****************************************/ + /***** Flexure::Server::handle_signal ***************************************/ + /** + * @brief handles ctrl-C and other signals + * @param[in] signo + * + */ + void Server::handle_signal(int signo) { + const std::string function("Flexure::Server::handle_signal"); + std::ostringstream message; + + switch (signo) { + case SIGTERM: + case SIGINT: + logwrite(function, "received termination signal"); + message << "NOTICE:" << Flexure::DAEMON_NAME << " exit"; + Server::instance->interface.async.enqueue( message.str() ); + Server::instance->exit_cleanly(); // shutdown the daemon + break; + case SIGHUP: + logwrite(function, "ignored SIGHUP"); + break; + case SIGPIPE: + logwrite(function, "ignored SIGPIPE"); + break; + default: + message << "received unknown signal " << strsignal(signo); + logwrite( function, message.str() ); + message.str(""); message << "NOTICE:" << Flexure::DAEMON_NAME << " exit"; + Server::instance->interface.async.enqueue( message.str() ); + break; + } + } + /***** Flexure::Server::handle_signal ***************************************/ + + /***** Flexure::Server::configure_flexured **********************************/ /** * @brief read and apply the configuration file for the flexure daemon @@ -36,7 +72,7 @@ namespace Flexure { long Server::configure_flexured() { std::string function = "Flexure::Server::configure_flexured"; std::stringstream message; - int applied=0; + int numapplied=0, lastapplied=0; long error; // Clear the motormap map before loading new information from the config file @@ -47,6 +83,8 @@ namespace Flexure { // for (int entry=0; entry < this->config.n_entries; entry++) { + lastapplied=numapplied; + // NBPORT -- nonblocking listening port for the flexure daemon // if ( config.param[entry].find( "NBPORT" ) == 0 ) { @@ -63,10 +101,9 @@ namespace Flexure { return(ERROR); } this->nbport = port; - message.str(""); message << "FLEXURED:config:" << config.param[entry] << "=" << config.arg[entry]; - this->interface.async.enqueue_and_log( function, message.str() ); - applied++; + numapplied++; } + else // BLKPORT -- blocking listening port for the flexure daemon // @@ -84,10 +121,9 @@ namespace Flexure { return(ERROR); } this->blkport = port; - message.str(""); message << "FLEXURED:config:" << config.param[entry] << "=" << config.arg[entry]; - this->interface.async.enqueue_and_log( function, message.str() ); - applied++; + numapplied++; } + else // ASYNCPORT -- asynchronous broadcast message port for the flexure daemon // @@ -105,19 +141,27 @@ namespace Flexure { return(ERROR); } this->asyncport = port; - message.str(""); message << "FLEXURED:config:" << config.param[entry] << "=" << config.arg[entry]; - this->interface.async.enqueue_and_log( function, message.str() ); - applied++; + numapplied++; } + else // ASYNCGROUP -- asynchronous broadcast group for the flexure daemon // if ( config.param[entry].find( "ASYNCGROUP" ) == 0 ) { this->asyncgroup = config.arg[entry]; - message.str(""); message << "FLEXURED:config:" << config.param[entry] << "=" << config.arg[entry]; - this->interface.async.enqueue_and_log( function, message.str() ); - applied++; + numapplied++; } + else + + // MOTOR_CONTROLLER -- address and name of each PI motor controller in daisy-chain + // Each CONTROLLER is stored in an STL map indexed by motorname + // + if ( config.param[entry].find( "MOTOR_CONTROLLER" ) == 0 ) { + if ( this->interface.motorinterface.load_controller_config( config.arg[entry] ) == NO_ERROR ) { + numapplied++; + } + } + else // PUB_ENDPOINT -- my ZeroMQ socket endpoint for publishing telemetry // SUB_ENDPOINT -- the broker endpoint I subscribe to (for snapshot requests) @@ -128,28 +172,16 @@ namespace Flexure { // if ( config.param[entry] == "PUB_ENDPOINT" ) { this->interface.publisher_address = config.arg[entry]; - this->interface.publisher_topic = DAEMON_NAME; // default publish topic is my name - message.str(""); message << "FLEXURED:config:" << config.param[entry] << "=" << config.arg[entry]; - this->interface.async.enqueue_and_log( function, message.str() ); - applied++; + this->interface.publisher_topic = DAEMON_NAME; // default publish topic is my name + numapplied++; } + else + // SUB_ENDPOINT + // if ( config.param[entry] == "SUB_ENDPOINT" ) { this->interface.subscriber_address = config.arg[entry]; - message.str(""); message << "FLEXURED:config:" << config.param[entry] << "=" << config.arg[entry]; - this->interface.async.enqueue_and_log( function, message.str() ); - applied++; - } - - // MOTOR_CONTROLLER -- address and name of each PI motor controller in daisy-chain - // Each CONTROLLER is stored in an STL map indexed by motorname - // - if ( config.param[entry].find( "MOTOR_CONTROLLER" ) == 0 ) { - if ( this->interface.motorinterface.load_controller_config( config.arg[entry] ) == NO_ERROR ) { - message.str(""); message << "FLEXURED:config:" << config.param[entry] << "=" << config.arg[entry]; - this->interface.async.enqueue_and_log( function, message.str() ); - applied++; - } + numapplied++; } // MOTOR_AXIS -- axis info for specified MOTOR_CONTROLLER @@ -170,9 +202,7 @@ namespace Flexure { if ( loc != _motormap.end() ) { this->interface.motorinterface.add_axis( AXIS ); - message.str(""); message << "FLEXURED:config:" << config.param[entry] << "=" << config.arg[entry]; - this->interface.async.enqueue_and_log( function, message.str() ); - applied++; + numapplied++; } else { message.str(""); message << "ERROR motor name \"" << AXIS.motorname << "\" " @@ -185,18 +215,47 @@ namespace Flexure { break; } } + else + + // POSITION_COEFFICIENTS + // + if ( config.param[entry] == "POSITION_COEFFICIENTS" ) { + error=interface.compensator.load_vector_from_config(config.arg[entry], VectorType::POSITION_COEFFICIENTS); + if (error==NO_ERROR) { + numapplied++; + } + else return ERROR; + } + else + // FLEXURE_POLYNOMIALS + // + if ( config.param[entry] == "FLEXURE_POLYNOMIALS" ) { + error=interface.compensator.load_vector_from_config(config.arg[entry], VectorType::FLEXURE_POLYNOMIALS); + if (error==NO_ERROR) { + numapplied++; + } + else return ERROR; + } + + if (numapplied>lastapplied) { + message.str(""); message << "config:" << config.param[entry] << "=" << config.arg[entry]; + this->interface.async.enqueue_and_log( to_uppercase(DAEMON_NAME), function, message.str() ); + } } // end loop through the entries in the configuration file +//logwrite(function, "will start subscriber thread"); +// this->interface.start_subscriber_thread(); + message.str(""); - if (applied==0) { + if (numapplied==0) { message << "ERROR: "; error = ERROR; } else { error = NO_ERROR; } - message << "applied " << applied << " configuration lines to flexured"; + message << "applied " << numapplied << " configuration lines to flexured"; logwrite(function, message.str()); if ( error == NO_ERROR ) error = this->interface.initialize_class(); @@ -570,7 +629,7 @@ namespace Flexure { // Don't append anything nor log the reply if the command was just requesting help. // if (ret != NOTHING) { - if ( ! retstring.empty() ) retstring.append( " " ); + if ( ! retstring.empty() && ret != HELP ) retstring.append( " " ); if ( ret != HELP && ret != JSON ) retstring.append( ret == NO_ERROR ? "DONE" : "ERROR" ); if ( ret == JSON ) { diff --git a/flexured/flexure_server.h b/flexured/flexure_server.h index 5b8faec7..42f6249c 100644 --- a/flexured/flexure_server.h +++ b/flexured/flexure_server.h @@ -46,11 +46,20 @@ namespace Flexure { class Server { private: public: + static Server* instance; + /***** Flexure::Server::Server ******************************************/ /** * @brief class constructor */ - Server() : nbport(-1), blkport(-1), asyncport(-1), cmd_num(0), threads_active(0), id_pool(Flexure::N_THREADS) { } + Server() : nbport(-1), blkport(-1), asyncport(-1), cmd_num(0), threads_active(0), id_pool(Flexure::N_THREADS) { + instance=this; + // register these signals + // + signal(SIGINT, this->signal_handler); + signal(SIGPIPE, this->signal_handler); + signal(SIGHUP, this->signal_handler); + } /***** Flexure::Server::~Server *****************************************/ /** @@ -103,6 +112,8 @@ namespace Flexure { long configure_flexured(); ///< read and apply the configuration file void doit(Network::TcpSocket &sock); ///< the workhorse of each thread connetion + void handle_signal(int signo); + static inline void signal_handler(int signo) { if (instance) { instance->handle_signal(signo); } } }; /***** Flexure::Server ******************************************************/ diff --git a/flexured/flexured.cpp b/flexured/flexured.cpp index 8ee6fd6a..2344c8e1 100644 --- a/flexured/flexured.cpp +++ b/flexured/flexured.cpp @@ -17,18 +17,25 @@ * */ int main(int argc, char **argv) { - std::string function = "Flexure::main"; + const std::string function("main"); std::stringstream message; - std::string logpath; - long ret=NO_ERROR; - std::string daemon_in; // daemon setting read from config file - bool start_daemon = false; // don't start as daemon unless specifically requested - // capture these signals + // Unless specifically requested to run in foreground, + // immediately daemonize. // - signal(SIGINT, signal_handler); - signal(SIGPIPE, signal_handler); - signal(SIGHUP, signal_handler); + if ( !cmdOptionExists( argv, argv+argc, "--foreground" ) ) { + logwrite(function, "starting daemon"); + Daemon::daemonize( Flexure::DAEMON_NAME, "/tmp", "/dev/null", "/tmp/flexured.stderr", "", false ); + std::cerr << get_timestamp() << " (" << function << ") daemonized. child process running" << std::endl; + } + else logwrite(function, "starting"); + + // the child process instantiates a Server object + // + Flexure::Server flexured; + + std::string daemon_in; // daemon setting read from config file + bool start_daemon = false; // don't start as daemon unless specifically requested // check for "-f " command line option to specify config file // @@ -56,6 +63,8 @@ int main(int argc, char **argv) { flexured.exit_cleanly(); } + std::string logpath; + for (int entry=0; entry < flexured.config.n_entries; entry++) { if (flexured.config.param[entry] == "LOGPATH") logpath = flexured.config.arg[entry]; if (flexured.config.param[entry] == "DAEMON") daemon_in = flexured.config.arg[entry]; @@ -78,31 +87,11 @@ int main(int argc, char **argv) { flexured.exit_cleanly(); } - if ( !daemon_in.empty() && daemon_in == "yes" ) start_daemon = true; - else - if ( !daemon_in.empty() && daemon_in == "no" ) start_daemon = false; - else { - message.str(""); message << "ERROR: unrecognized argument DAEMON=" << daemon_in << ", expected { yes | no }"; - logwrite( function, message.str() ); - flexured.exit_cleanly(); - } - - // check for "-d" command line option last so that the command line - // can override the config file to start as daemon - // - if ( cmdOptionExists( argv, argv+argc, "-d" ) ) { - start_daemon = true; - } - - if ( start_daemon ) { - logwrite( function, "starting daemon" ); - Daemon::daemonize( Flexure::DAEMON_NAME, "/tmp", "", "", "" ); - } - if ( ( init_log( logpath, Flexure::DAEMON_NAME ) != 0 ) ) { // initialize the logging system logwrite(function, "ERROR: unable to initialize logging system"); flexured.exit_cleanly(); } + logwrite(function, "world"); message << "this version built " << BUILD_DATE << " " << BUILD_TIME; logwrite(function, message.str()); @@ -110,7 +99,7 @@ int main(int argc, char **argv) { message.str(""); message << flexured.config.n_entries << " lines read from " << flexured.config.filename; logwrite(function, message.str()); - if (ret==NO_ERROR) ret=flexured.configure_flexured(); // get needed values out of read-in configuration file for the daemon + long ret=flexured.configure_flexured(); // get needed values out of read-in configuration file for the daemon if (ret != NO_ERROR) { logwrite(function, "ERROR: unable to configure system"); @@ -124,7 +113,7 @@ int main(int argc, char **argv) { // initialize the pub/sub handler // - if ( flexured.interface.init_pubsub() == ERROR ) { + if ( flexured.interface.init_pubsub({Topic::TCSD}) == ERROR ) { logwrite(function, "ERROR initializing publisher-subscriber handler"); flexured.exit_cleanly(); } @@ -134,6 +123,18 @@ int main(int argc, char **argv) { // flexured.interface.publish_status( true ); + // Ask tcsd for a current snapshot. On a restart the broker replays tcsd's + // last cached message, which tcs_info time-stamps on receipt, so without + // this request an old cached reading would look fresh for 10 seconds. + // + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + try { + flexured.interface.request_tcs_snapshot(); + } + catch ( const std::exception &e ) { + logwrite( function, "ERROR requesting TCS snapshot: "+std::string(e.what()) ); + } + // 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. // Each thread gets a socket object. All of the socket objects are stored in a vector container. @@ -217,40 +218,3 @@ int main(int argc, char **argv) { return 0; } /***** main *******************************************************************/ - - -/***** signal_handler *********************************************************/ -/** - * @brief handles ctrl-C - * @param[in] signo - * - */ -void signal_handler(int signo) { - std::string function = "Flexure::signal_handler"; - std::stringstream message; - - switch (signo) { - case SIGTERM: - case SIGINT: - logwrite(function, "received termination signal"); - message << "NOTICE:" << Flexure::DAEMON_NAME << " exit"; - flexured.interface.async.enqueue( message.str() ); - flexured.exit_cleanly(); // shutdown the daemon - break; - case SIGHUP: - logwrite(function, "caught SIGHUP"); - break; - case SIGPIPE: - logwrite(function, "caught SIGPIPE"); - break; - default: - message << "received unknown signal " << strsignal(signo); - logwrite( function, message.str() ); - message.str(""); message << "NOTICE:" << Flexure::DAEMON_NAME << " exit"; - flexured.interface.async.enqueue( message.str() ); - flexured.exit_cleanly(); // shutdown the daemon - break; - } - return; -} -/***** signal_handler *********************************************************/ diff --git a/flexured/flexured.h b/flexured/flexured.h index 7fdafecc..afd040f0 100644 --- a/flexured/flexured.h +++ b/flexured/flexured.h @@ -29,7 +29,5 @@ #define CONN_TIMEOUT 3000 ///< incoming (non-blocking) connection timeout in milliseconds -Flexure::Server flexured; ///< global Flexure::Server object so that the main daemon can access the namespace - void signal_handler(int signo); ///< handles ctrl-C int main(int argc, char **argv); ///< the main function diff --git a/flexured/tcs_info.h b/flexured/tcs_info.h new file mode 100644 index 00000000..b7c41ba6 --- /dev/null +++ b/flexured/tcs_info.h @@ -0,0 +1,96 @@ +/** --------------------------------------------------------------------------- + * @file tcs_info.h + * @brief defines a class in Flexure for holding TCS info + * @author David Hale + * + */ + +#pragma once + +#include +#include + +namespace Flexure { + + /***** Flexure::TcsInfo *****************************************************/ + /** + * @brief contains TCS telemetry + * + */ + class TcsInfo { + private: + double zenangle; + double casangle; + double parallactic; + double equivalentcass; + + bool is_timeset; + std::chrono::system_clock::time_point telemetry_time; + + public: + + TcsInfo() + : zenangle(0), casangle(0), parallactic(0), equivalentcass(0), is_timeset(false) { } + + double get_zenangle() { return this->zenangle; } + double get_casangle() { return this->casangle; } + double get_parallactic() { return this->parallactic; } + double get_equivalentcass() { return this->equivalentcass; } + + /***** Flexure::TcsInfo:is_older_than ***********************************/ + /** + * @brief is the TCS telemetry more than seconds old? + * @details When the telemetry is stored in this class it is time-stamped. + * This returns true if that time stamp is more than + * seconds ago from now. + * @param[in] sec number of seconds + * @return true|false + */ + bool is_older_than(std::chrono::seconds sec) { + if (!this->is_timeset) return true; + if (std::chrono::system_clock::now() > this->telemetry_time+sec) { + return true; + } + return false; + } + /***** Flexure::TcsInfo:is_older_than ***********************************/ + + /***** Flexure::TcsInfo:set *********************************************/ + /** + * @brief timestamp telemetry + * @details Call this function when + * This returns true if that time stamp is more than + * + */ + void store(const double &_zenangle, + const double &_casangle, + const double &_parallactic, + const std::optional &_equivalentcass=std::nullopt) { + + // store the values received + this->zenangle = _zenangle; + this->casangle = _casangle; + this->parallactic = _parallactic; + + // if equivalentcass provided then use it + if (_equivalentcass) { + this->equivalentcass = *_equivalentcass; + } + // otherwise calculate it + else { + double angle = this->casangle - this->parallactic; + if (angle < -180.0) angle += 360.0; + if (angle > 180.0) angle -= 360.0; + this->equivalentcass = angle; + } + + // record the time that telemetry was received + this->telemetry_time = std::chrono::system_clock::now(); + this->is_timeset = true; + } + /***** Flexure::TcsInfo:set *********************************************/ + + }; + /***** Flexure::TcsInfo *****************************************************/ + +} diff --git a/pygui/zmq_status_service.py b/pygui/zmq_status_service.py index f54f778f..829cb15e 100644 --- a/pygui/zmq_status_service.py +++ b/pygui/zmq_status_service.py @@ -251,10 +251,10 @@ def update_tcs_info(self, data): airmass = data.get('AIRMASS', None) alt = data.get('ALT', None) az = data.get('AZ', None) - dec = data.get('DEC', None) + dec = data.get('TELDEC_D', None) domeaz = data.get('DOMEAZ', None) domeshut = data.get('DOMESHUT', None) - ra = data.get('RA', None) + ra = data.get('TELRA_H', None) raoffset = data.get('RAOFFSET', None) zenangle = data.get('ZENANGLE', None) diff --git a/sequencerd/CMakeLists.txt b/sequencerd/CMakeLists.txt index dda2f133..7d87ff56 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,13 @@ include_directories( ${PROJECT_BASE_DIR}/acamd ) link_directories( ${PROJECT_BASE_DIR}/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} ) +# C-Python API Development libraries and headers +# +find_path(PYTHON_DEV "Python.h" PATHS ${PYTHON_INCLUDE_DIR} NO_DEFAULT_PATH) +find_library(PYTHON_LIB ${PYTHON_VERSION} PATHS ${PYTHON_LIB_DIR} NO_DEFAULT_PATH) +# ZMQ +# find_library( ZMQPP_LIB zmqpp NAMES libzmqpp PATHS /usr/local/lib ) find_library( ZMQ_LIB zmq NAMES libzmq PATHS /usr/local/lib ) @@ -39,15 +42,16 @@ add_executable(sequencerd ${SEQUENCER_DIR}/sequencer_interface.cpp ${SEQUENCER_DIR}/sequence_acquisition.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/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index e15d7e28..8113affb 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -2359,17 +2359,35 @@ namespace Sequencer { /***** Sequencer::Sequence::flexure_set *************************************/ /** - * @brief set the flexure + * @brief compensate for flexure * @return NO_ERROR - * @todo flexure not yet implemented * */ long Sequence::flexure_set() { const std::string function("Sequencer::Sequence::flexure_set"); ScopedState thr_state( thread_state_manager, Sequencer::THR_FLEXURE_SET ); + ScopedState wait_state( wait_state_manager, Sequencer::SEQ_WAIT_FLEXURE ); + + // build up list of activate chans + std::ostringstream activechans; + const std::string calname = std::string(this->target.iscal ? this->target.name : "SCIENCE"); + const auto &calinfo = this->caltarget.get_info(calname); + for (const auto &[chan,active] : calinfo.channel_active) { + if (active) activechans << " " << chan; + } + + std::ostringstream comp; + comp << FLEXURED_COMPENSATE << activechans.str(); + + if ( !this->cancel_flag.load() && + this->flexured.command( comp.str() ) != NO_ERROR ) { + this->broadcast.error( function, "setting flexure compensator" ); + this->thread_error_manager.set( THR_FLEXURE_SET ); + throw std::runtime_error("setting flexure compensator"); + } - logwrite( function, "flexure not yet implemented." ); + this->broadcast.notice( function, "set flexure compensator for "+activechans.str()); return NO_ERROR; } @@ -2431,9 +2449,9 @@ namespace Sequencer { // set the dome lamps // - for ( const auto &[lamp,state] : calinfo.domelamp ) { + for ( const auto &[lampname,state] : calinfo.domelamp ) { if ( this->cancel_flag.load() ) break; - cmd.str(""); cmd << TCSD_LAMP << lamp << " " << (state==1?"on":"off"); + cmd.str(""); cmd << TCSD_LAMP << " " << lampname << " " << (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()); diff --git a/sequencerd/sequencer_interface.cpp b/sequencerd/sequencer_interface.cpp index 78152e5b..a838d282 100644 --- a/sequencerd/sequencer_interface.cpp +++ b/sequencerd/sequencer_interface.cpp @@ -998,7 +998,7 @@ namespace Sequencer { // 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)); + info.domelamp[domelampnames.at(i)] = on_off(tokens.at(11+i)); } // tokens 15-20 -- modulator numbers are {1:6} diff --git a/sequencerd/sequencer_interface.h b/sequencerd/sequencer_interface.h index 1690be96..732dc8e7 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 { "LO", "HI", "ARC", "ULTRA" } { } // this is how they + domelampnames { "LO", "HI", "ARC", "ULTRA" } { } // this is how they are ordered in the TCS ///< 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=LO, 2=HI, 3=ARC, 4=ULTRA], true=on + std::map domelamp; // true=on std::map lampmod; // true=on } calinfo_t; diff --git a/slicecamd/slicecam_interface.cpp b/slicecamd/slicecam_interface.cpp index f323d438..0db1a62c 100644 --- a/slicecamd/slicecam_interface.cpp +++ b/slicecamd/slicecam_interface.cpp @@ -983,17 +983,17 @@ namespace Slicecam { } // 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::TCSNAME, telem.tcsname ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::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::TELRA_H, telem.ra_scope_h ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::TELDEC_D, telem.dec_scope_d ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::RAOFFSET, telem.offsetra ); + Common::extract_telemetry_value( jmessage, Key::Tcsd::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::TELFOCUS, telem.telfocus ); 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 36f26d6a..a58a0441 100644 --- a/tcsd/tcs_interface.cpp +++ b/tcsd/tcs_interface.cpp @@ -46,24 +46,24 @@ namespace TCS { 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; + jmessage_out[Key::Tcsd::ISOPEN] = this->tcs_info.isopen; + jmessage_out[Key::Tcsd::TCSNAME] = this->tcs_info.tcsname; - jmessage_out["PA"] = this->tcs_info.pa; // double + jmessage_out[Key::Tcsd::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::HA] = this->tcs_info.ha; // string + jmessage_out[Key::Tcsd::RAOFFSET] = this->tcs_info.offsetra; // double + jmessage_out[Key::Tcsd::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::TELRA_H] = radec_to_decimal( this->tcs_info.ra_hms ); + jmessage_out[Key::Tcsd::TELDEC_D] = 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::ZENANGLE] = this->tcs_info.zenithangle; + jmessage_out[Key::Tcsd::DOMEAZ] = this->tcs_info.domeazimuth; + jmessage_out[Key::Tcsd::DOMESHUT] = this->tcs_info.domeshutters==1?"open":"closed"; + jmessage_out[Key::Tcsd::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 @@ -81,8 +81,6 @@ namespace TCS { } } - // for backwards compatibility - jmessage_out["messagetype"] = "tcsinfo"; retstring=jmessage_out.dump(); retstring.append(JEOF);