LCOV - code coverage report
Current view: top level - apps/adcTracker - adcTracker.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 96.9 % 384 372
Test Date: 2026-04-15 19:34:29 Functions: 100.0 % 32 32

            Line data    Source code
       1              : /** \file adcTracker.hpp
       2              :  * \brief The MagAO-X ADC Tracker header file
       3              :  * \author Jared R. Males (jaredmales@gmail.com)
       4              :  *
       5              :  * \ingroup adcTracker_files
       6              :  */
       7              : 
       8              : #ifndef adcTracker_hpp
       9              : #define adcTracker_hpp
      10              : 
      11              : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
      12              : #include "../../magaox_git_version.h"
      13              : 
      14              : #include <cmath>
      15              : 
      16              : #include <mx/math/gslInterpolator.hpp>
      17              : #include <mx/ioutils/readColumns.hpp>
      18              : 
      19              : /** \defgroup adcTracker
      20              :  * \brief The MagAO-X application to track sky rotation with the atmospheric dispersion corrector.
      21              :  *
      22              :  * <a href="../handbook/operating/software/apps/adcTracker.html">Application Documentation</a>
      23              :  *
      24              :  * \ingroup apps
      25              :  *
      26              :  */
      27              : 
      28              : /** \defgroup adcTracker_files
      29              :  * \ingroup adcTracker
      30              :  */
      31              : 
      32              : namespace MagAOX
      33              : {
      34              : namespace app
      35              : {
      36              : 
      37              : /// The MagAO-X ADC Tracker
      38              : /**
      39              :  * \ingroup adcTracker
      40              :  */
      41              : class adcTracker : public MagAOXApp<true>, public dev::telemeter<adcTracker>
      42              : {
      43              : 
      44              :     // Give the test harness access.
      45              :     friend class adcTracker_test;
      46              : 
      47              :     friend class dev::telemeter<adcTracker>;
      48              : 
      49              :     typedef dev::telemeter<adcTracker> telemeterT;
      50              : 
      51              :   protected:
      52              :     /** \name Configurable Parameters
      53              :      *@{
      54              :      */
      55              : 
      56              :     // here add parameters which will be config-able at runtime
      57              :     std::string m_lookupFile{ "adc_lookup_table.txt" }; ///< The name of the file, in the calib directory, containing
      58              :                                                         ///< the ADC lookup table.  Default is 'adc_lookup_table.txt'.
      59              : 
      60              :     float m_adc1zero{ 0 }; ///< The starting point for ADC 1. Default is 0.
      61              : 
      62              :     int m_adc1lupsign{ 1 }; ///< The sign to apply to the lookup table value for ADC 1.
      63              : 
      64              :     float m_adc2zero{ 0 }; ///< The starting point for ADC 2. Default is 0.
      65              : 
      66              :     int m_adc2lupsign{ 1 }; ///< The sign to apply to the lookup table value for ADC 2.
      67              : 
      68              :     float m_deltaAngle{ 0 }; ///< The offset angle to apply to the looked-up values, applied to both.  Default is 0.
      69              : 
      70              :     float m_adc1delta{ 0 }; ///< The offset angle to apply to the looked-up value for ADC 1, applied in addition to
      71              :                             ///< deltaAngle.  Default is 0.
      72              : 
      73              :     float m_adc2delta{ 0 }; ///< The offset angle to apply to the looked-up value for ADC 2, applied in addition to
      74              :                             ///< deltaAngle.  Default is 0.
      75              : 
      76              :     float m_minZD{ 5.1 }; ///< The minimum zenith distance at which to interpolate and move the ADCs.  Default is 5.1.
      77              : 
      78              :     std::string m_adc1DevName{ "stageadc1" }; ///< The device name of the ADC 1 stage.  Default is 'stageadc1'.
      79              :     std::string m_adc2DevName{ "stageadc2" }; ///< The device name of the ADC 2 stage.  Default is 'stageadc2'.
      80              : 
      81              :     std::string m_tcsDevName{
      82              :         "tcsi" }; ///< The device name of the TCS interface providing 'teldata.zd'.  Default is 'tcsi'.
      83              : 
      84              :     float m_updateInterval{ 10 }; ///< The interval at which to update positions, in seconds.  Default is 10 secs.
      85              : 
      86              :     ///@}
      87              : 
      88              :     float m_maxZD{ 0 }; ///< The maximum zenith distance represented by the loaded lookup table.
      89              : 
      90              :     std::vector<double> m_lupZD;   ///< Lookup-table zenith-distance samples.
      91              :     std::vector<double> m_lupADC1; ///< Lookup-table ADC 1 offsets corresponding to m_lupZD.
      92              :     std::vector<double> m_lupADC2; ///< Lookup-table ADC 2 offsets corresponding to m_lupZD.
      93              : 
      94              :     mx::math::gslInterpolator<mx::math::gsl_interp_linear<double>>
      95              :         m_terpADC1; ///< ADC 1 interpolator built from the lookup table.
      96              :     mx::math::gslInterpolator<mx::math::gsl_interp_linear<double>>
      97              :         m_terpADC2; ///< ADC 2 interpolator built from the lookup table.
      98              : 
      99              :     bool m_lookupReady{ false }; ///< True once the ADC lookup table has been validated and the interpolators are ready.
     100              : 
     101              :     bool m_tracking{ false }; ///< True when automatic ADC updates are enabled.
     102              : 
     103              :     float m_zd{ 0 }; ///< The most recent finite zenith distance received from the TCS interface.
     104              : 
     105              :     bool m_haveZD{ false }; ///< True once at least one valid zenith distance has been received.
     106              : 
     107              :     double m_lastUpdate{ 0 }; ///< Timestamp of the last ADC command dispatched by the tracker.
     108              : 
     109              :     pcf::IndiProperty m_indiP_belowMinZD; ///< Status switch indicating that the current ZD is below minZD.
     110              :     pcf::IndiProperty m_indiP_aboveMaxZD; ///< Status switch indicating that the current ZD is above maxZD.
     111              : 
     112              :     enum class zdLimitState
     113              :     {
     114              :         unknown,  ///< No valid range status is currently available.
     115              :         inRange,  ///< The current ZD is within the usable lookup-table range.
     116              :         belowMin, ///< The current ZD is below minZD.
     117              :         aboveMax  ///< The current ZD is above maxZD.
     118              :     };
     119              : 
     120              :     zdLimitState m_zdLimitState{
     121              :         zdLimitState::unknown }; ///< Current ZD limit state reflected in the status properties.
     122              :     zdLimitState m_lastLoggedZDLimitState{
     123              :         zdLimitState::unknown }; ///< Last ZD limit state already announced by appLogic threshold-crossing warnings.
     124              : 
     125              :   public:
     126              :     /// Default c'tor.
     127              :     adcTracker();
     128              : 
     129              :     /// D'tor, declared and defined for noexcept.
     130           44 :     ~adcTracker() noexcept
     131           44 :     {
     132           44 :     }
     133              : 
     134              :     /// Set up configuration entries.
     135              :     virtual void setupConfig();
     136              : 
     137              :     /// Implementation of loadConfig logic, separated for testing.
     138              :     /** This is called by loadConfig().
     139              :      */
     140              :     int loadConfigImpl(
     141              :         mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
     142              : 
     143              :     /// Load configuration values.
     144              :     virtual void loadConfig();
     145              : 
     146              :     /// Startup function
     147              :     /**
     148              :      *
     149              :      */
     150              :     virtual int appStartup();
     151              : 
     152              :     /// Implementation of the FSM for adcTracker.
     153              :     /**
     154              :      * \returns 0 on no critical error
     155              :      * \returns -1 on an error requiring shutdown
     156              :      */
     157              :     virtual int appLogic();
     158              : 
     159              :     /// Shutdown the app.
     160              :     /**
     161              :      *
     162              :      */
     163              :     virtual int appShutdown();
     164              : 
     165              :   protected:
     166              :     /// Send the ADC 1 target command.
     167              :     virtual int sendADC1Position( float adc1 /**< [in] the ADC 1 target position */ );
     168              : 
     169              :     /// Send the ADC 2 target command.
     170              :     virtual int sendADC2Position( float adc2 /**< [in] the ADC 2 target position */ );
     171              : 
     172              :     /// Initialize the ADC lookup interpolators from the loaded lookup-table data.
     173              :     virtual void setupInterpolators();
     174              : 
     175              :     /// Interpolate the ADC 1 lookup-table value for a zenith distance.
     176              :     virtual float interpolateADC1( float zd /**< [in] the zenith distance to interpolate */ );
     177              : 
     178              :     /// Interpolate the ADC 2 lookup-table value for a zenith distance.
     179              :     virtual float interpolateADC2( float zd /**< [in] the zenith distance to interpolate */ );
     180              : 
     181              :     /// Extract the zenith distance from the incoming TCS INDI property.
     182              :     virtual float extractZD( const pcf::IndiProperty &ipRecv /**< [in] the incoming teldata property */ );
     183              : 
     184              :     /// Update a local status switch and publish it if INDI is active.
     185              :     void updateStatusSwitch( pcf::IndiProperty &prop, /**< [in/out] the status property to update */
     186              :                              bool               on /**< [in] true to set the switch on */ );
     187              : 
     188              :     /// Update the min/max ZD status properties and return the resulting ZD limit state.
     189              :     zdLimitState updateZDLimitState( bool  haveZD, /**< [in] true when a valid ZD is available */
     190              :                                      float zd,     /**< [in] the current zenith distance */
     191              :                                      float minZD,  /**< [in] the active minimum ZD threshold */
     192              :                                      float maxZD /**< [in] the active maximum ZD threshold */ );
     193              : 
     194              :     /// Emit a one-time threshold-crossing warning when appLogic enters a new out-of-range state.
     195              :     void logZDLimitCrossing( zdLimitState state, /**< [in] the current ZD limit state */
     196              :                              float        zd,    /**< [in] the current zenith distance */
     197              :                              float        minZD, /**< [in] the active minimum ZD threshold */
     198              :                              float        maxZD /**< [in] the active maximum ZD threshold */ );
     199              : 
     200              :     /** @name INDI
     201              :      *
     202              :      * @{
     203              :      */
     204              :   protected:
     205              :     pcf::IndiProperty m_indiP_tracking; ///< The INDI toggle used to enable or disable tracking.
     206              : 
     207              :     pcf::IndiProperty m_indiP_deltaAngle; ///< The shared user offset applied to both ADC targets.
     208              :     pcf::IndiProperty m_indiP_deltaADC1;  ///< The user offset applied only to ADC 1.
     209              :     pcf::IndiProperty m_indiP_deltaADC2;  ///< The user offset applied only to ADC 2.
     210              : 
     211              :     pcf::IndiProperty m_indiP_minZD; ///< The user-configurable minimum zenith distance for interpolation.
     212              : 
     213              :     pcf::IndiProperty m_indiP_teldata; ///< The subscribed TCS property providing zenith distance updates.
     214              : 
     215              :     pcf::IndiProperty m_indiP_adc1pos; ///< The outbound ADC 1 stage position command property.
     216              :     pcf::IndiProperty m_indiP_adc2pos; ///< The outbound ADC 2 stage position command property.
     217              : 
     218              :   public:
     219              :     /// Handle new tracking toggle requests.
     220            1 :     INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_tracking );
     221              : 
     222              :     /// Handle new shared delta-angle requests.
     223            1 :     INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_deltaAngle );
     224              :     /// Handle new ADC 1 delta-angle requests.
     225            1 :     INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_deltaADC1 );
     226              :     /// Handle new ADC 2 delta-angle requests.
     227            1 :     INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_deltaADC2 );
     228              : 
     229              :     /// Handle new minimum-zenith-distance requests.
     230            1 :     INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_minZD );
     231              : 
     232              :     /// Handle incoming telescope data updates.
     233            1 :     INDI_SETCALLBACK_DECL( adcTracker, m_indiP_teldata );
     234              : 
     235              :     ///@}
     236              : 
     237              :     /** \name Telemeter Interface
     238              :      *
     239              :      * @{
     240              :      */
     241              :     int checkRecordTimes();
     242              : 
     243              :     int recordTelem( const telem_adctrack * );
     244              : 
     245              :     int recordADCTrack( bool force = false );
     246              : 
     247              :     ///@}
     248              : };
     249              : 
     250          484 : adcTracker::adcTracker() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
     251              : {
     252              : 
     253           44 :     return;
     254            0 : }
     255              : 
     256            2 : void adcTracker::setupConfig()
     257              : {
     258           28 :     config.add( "adcs.lookupFile",
     259              :                 "",
     260              :                 "adcs.lookupFile",
     261              :                 argType::Required,
     262              :                 "adcs",
     263              :                 "lookupFile",
     264              :                 false,
     265              :                 "string",
     266              :                 "The name of the file, in the calib directory, containing the adc lookup table.  Default is "
     267              :                 "'adc_lookup_table.txt'." );
     268              : 
     269           28 :     config.add( "adcs.adc1zero",
     270              :                 "",
     271              :                 "adcs.adc1zero",
     272              :                 argType::Required,
     273              :                 "adcs",
     274              :                 "adc1zero",
     275              :                 false,
     276              :                 "float",
     277              :                 "The starting point for ADC 1. Default is 0." );
     278              : 
     279           28 :     config.add( "adcs.adc1lupsign",
     280              :                 "",
     281              :                 "adcs.adc1lupsign",
     282              :                 argType::Required,
     283              :                 "adcs",
     284              :                 "adc1lupsign",
     285              :                 false,
     286              :                 "int",
     287              :                 "The sign to apply for the LUP values for ADC 1. Default is +1." );
     288              : 
     289           28 :     config.add( "adcs.adc2zero",
     290              :                 "",
     291              :                 "adcs.adc2zero",
     292              :                 argType::Required,
     293              :                 "adcs",
     294              :                 "adc2zero",
     295              :                 false,
     296              :                 "float",
     297              :                 "The starting point for ADC 2. Default is 0." );
     298              : 
     299           28 :     config.add( "adcs.adc2lupsign",
     300              :                 "",
     301              :                 "adcs.adc2lupsign",
     302              :                 argType::Required,
     303              :                 "adcs",
     304              :                 "adc2lupsign",
     305              :                 false,
     306              :                 "int",
     307              :                 "The sign to apply for the LUP values for ADC 2. Default is +1." );
     308              : 
     309           28 :     config.add( "adcs.deltaAngle",
     310              :                 "",
     311              :                 "adcs.deltaAngle",
     312              :                 argType::Required,
     313              :                 "adcs",
     314              :                 "deltaAngle",
     315              :                 false,
     316              :                 "float",
     317              :                 "The offset angle to apply to the looked-up values, applied to both.  Default is 0." );
     318              : 
     319           28 :     config.add( "adcs.adc1delta",
     320              :                 "",
     321              :                 "adcs.adc1delta",
     322              :                 argType::Required,
     323              :                 "adcs",
     324              :                 "adc1delta",
     325              :                 false,
     326              :                 "float",
     327              :                 "The offset angle to apply to the looked-up value for ADC 1, applied in addition to deltaAngle.  "
     328              :                 "Default is 0." );
     329              : 
     330           28 :     config.add( "adcs.adc2delta",
     331              :                 "",
     332              :                 "adcs.adc2delta",
     333              :                 argType::Required,
     334              :                 "adcs",
     335              :                 "adc2delta",
     336              :                 false,
     337              :                 "float",
     338              :                 "The offset angle to apply to the looked-up value for ADC 2, applied in addition to deltaAngle.  "
     339              :                 "Default is 0." );
     340              : 
     341           28 :     config.add( "adcs.minZD",
     342              :                 "",
     343              :                 "adcs.minZD",
     344              :                 argType::Required,
     345              :                 "adcs",
     346              :                 "minZD",
     347              :                 false,
     348              :                 "float",
     349              :                 "The minimum zenith distance at which to interpolate and move the ADCs.  Default is 5.1" );
     350              : 
     351           28 :     config.add( "adcs.adc1DevName",
     352              :                 "",
     353              :                 "adcs.adc1devName",
     354              :                 argType::Required,
     355              :                 "adcs",
     356              :                 "adc1DevName",
     357              :                 false,
     358              :                 "string",
     359              :                 "The device name of the ADC 1 stage.  Default is 'stageadc1'" );
     360              : 
     361           28 :     config.add( "adcs.adc2DevName",
     362              :                 "",
     363              :                 "adcs.adc2devName",
     364              :                 argType::Required,
     365              :                 "adcs",
     366              :                 "adc2DevName",
     367              :                 false,
     368              :                 "string",
     369              :                 "The device name of the ADC 2 stage.  Default is 'stageadc2'" );
     370              : 
     371           28 :     config.add( "tcs.devName",
     372              :                 "",
     373              :                 "tcs.devName",
     374              :                 argType::Required,
     375              :                 "tcs",
     376              :                 "devName",
     377              :                 false,
     378              :                 "string",
     379              :                 "The device name of the TCS Interface providing 'teldata.zd'.  Default is 'tcsi'" );
     380              : 
     381           28 :     config.add( "tracking.updateInterval",
     382              :                 "",
     383              :                 "tracking.updateInterval",
     384              :                 argType::Required,
     385              :                 "tracking",
     386              :                 "updateInterval",
     387              :                 false,
     388              :                 "float",
     389              :                 "The interval at which to update positions, in seconds.  Default is 10 secs." );
     390              : 
     391            2 :     TELEMETER_SETUP_CONFIG( config );
     392            2 : }
     393              : 
     394            2 : int adcTracker::loadConfigImpl( mx::app::appConfigurator &_config )
     395              : {
     396            4 :     _config( m_lookupFile, "adcs.lookupFile" );
     397            4 :     _config( m_adc1zero, "adcs.adc1zero" );
     398            4 :     _config( m_adc1lupsign, "adcs.adc1lupsign" );
     399            4 :     _config( m_adc2zero, "adcs.adc2zero" );
     400            4 :     _config( m_adc2lupsign, "adcs.adc2lupsign" );
     401            4 :     _config( m_deltaAngle, "adcs.deltaAngle" );
     402            4 :     _config( m_adc1delta, "adcs.adc1delta" );
     403            4 :     _config( m_adc2delta, "adcs.adc2delta" );
     404            4 :     _config( m_minZD, "adcs.minZD" );
     405            4 :     _config( m_adc1DevName, "adcs.adc1DevName" );
     406            4 :     _config( m_adc2DevName, "adcs.adc2DevName" );
     407              : 
     408            4 :     _config( m_tcsDevName, "tcs.devName" );
     409              : 
     410            2 :     _config( m_updateInterval, "tracking.updateInterval" );
     411              : 
     412            2 :     TELEMETER_LOAD_CONFIG( _config );
     413              : 
     414            2 :     return 0;
     415              : }
     416              : 
     417            2 : void adcTracker::loadConfig()
     418              : {
     419            2 :     loadConfigImpl( config );
     420            2 : }
     421              : 
     422           20 : int adcTracker::appStartup()
     423              : {
     424           20 :     TELEMETER_APP_STARTUP;
     425              : 
     426           20 :     std::string luppath = m_calibDir + "/" + m_lookupFile;
     427              : 
     428           20 :     if( mx::ioutils::readColumns<mx::ioutils::readColCommaDelim>( luppath, m_lupZD, m_lupADC1, m_lupADC2 ) !=
     429              :         mx::error_t::noerror )
     430              :     {
     431            1 :         return log<software_critical, -1>( "error reading lookup table from " + luppath );
     432              :     }
     433              : 
     434           19 :     if( m_lupZD.size() != m_lupADC1.size() || m_lupZD.size() != m_lupADC2.size() )
     435              :     {
     436            1 :         return log<software_critical, -1>( "inconsistent sizes in " + luppath );
     437              :     }
     438              : 
     439           18 :     if( m_lupZD.size() < 2 )
     440              :     {
     441            1 :         return log<software_critical, -1>( "lookup table must contain at least two rows in " + luppath );
     442              :     }
     443              : 
     444           67 :     for( size_t n = 0; n < m_lupZD.size(); ++n )
     445              :     {
     446           51 :         if( !std::isfinite( m_lupZD[n] ) || !std::isfinite( m_lupADC1[n] ) || !std::isfinite( m_lupADC2[n] ) )
     447              :         {
     448            0 :             return log<software_critical, -1>( "non-finite lookup table value at row " + std::to_string( n ) + " in " +
     449            0 :                                                luppath );
     450              :         }
     451              : 
     452           51 :         if( n > 0 && m_lupZD[n] <= m_lupZD[n - 1] )
     453              :         {
     454            2 :             return log<software_critical, -1>( "lookup table zenith distances must be strictly increasing in " +
     455            1 :                                                luppath );
     456              :         }
     457              :     }
     458              : 
     459           48 :     log<text_log>( "Read ADC lookup table " + luppath + " with " + std::to_string( m_lupZD.size() ) +
     460           64 :                    " entries spanning ZD 0 to " + std::to_string( m_lupZD.back() ) );
     461              : 
     462              :     try
     463              :     {
     464           16 :         setupInterpolators();
     465              :     }
     466            2 :     catch( const std::exception &e )
     467              :     {
     468            4 :         return log<software_critical, -1>( std::string( "exception setting up ADC interpolators from " ) + luppath +
     469            5 :                                            ": " + e.what() );
     470            1 :     }
     471            1 :     catch( ... )
     472              :     {
     473            1 :         return log<software_critical, -1>( "unknown exception setting up ADC interpolators from " + luppath );
     474            1 :     }
     475              : 
     476           14 :     m_maxZD       = static_cast<float>( m_lupZD.back() );
     477           14 :     m_lookupReady = true;
     478              : 
     479           84 :     createStandardIndiToggleSw( m_indiP_tracking, "tracking" );
     480           14 :     registerIndiPropertyNew( m_indiP_tracking, INDI_NEWCALLBACK( m_indiP_tracking ) );
     481              : 
     482          112 :     createStandardIndiNumber<float>( m_indiP_deltaAngle, "deltaAngle", -180.0, 180.0, 0, "%0.2f" );
     483           28 :     m_indiP_deltaAngle["target"].set( m_deltaAngle );
     484           28 :     m_indiP_deltaAngle["current"].set( m_deltaAngle );
     485           14 :     registerIndiPropertyNew( m_indiP_deltaAngle, INDI_NEWCALLBACK( m_indiP_deltaAngle ) );
     486              : 
     487          112 :     createStandardIndiNumber<float>( m_indiP_deltaADC1, "deltaADC1", -180.0, 180.0, 0, "%0.2f" );
     488           28 :     m_indiP_deltaADC1["target"].set( m_adc1delta );
     489           28 :     m_indiP_deltaADC1["current"].set( m_adc1delta );
     490           14 :     registerIndiPropertyNew( m_indiP_deltaADC1, INDI_NEWCALLBACK( m_indiP_deltaADC1 ) );
     491              : 
     492          112 :     createStandardIndiNumber<float>( m_indiP_deltaADC2, "deltaADC2", -180.0, 180.0, 0, "%0.2f" );
     493           28 :     m_indiP_deltaADC2["target"].set( m_adc2delta );
     494           28 :     m_indiP_deltaADC2["current"].set( m_adc2delta );
     495           14 :     registerIndiPropertyNew( m_indiP_deltaADC2, INDI_NEWCALLBACK( m_indiP_deltaADC2 ) );
     496              : 
     497          112 :     createStandardIndiNumber<float>( m_indiP_minZD, "minZD", 0.0, 90.0, 0, "%0.2f" );
     498           28 :     m_indiP_minZD["target"].set( m_minZD );
     499           28 :     m_indiP_minZD["current"].set( m_minZD );
     500           14 :     registerIndiPropertyNew( m_indiP_minZD, INDI_NEWCALLBACK( m_indiP_minZD ) );
     501              : 
     502           14 :     m_indiP_belowMinZD = pcf::IndiProperty( pcf::IndiProperty::Switch );
     503           14 :     m_indiP_belowMinZD.setDevice( configName() );
     504           28 :     m_indiP_belowMinZD.setName( "belowMinZD" );
     505           14 :     m_indiP_belowMinZD.setPerm( pcf::IndiProperty::ReadOnly );
     506           14 :     m_indiP_belowMinZD.setRule( pcf::IndiProperty::AtMostOne );
     507           14 :     m_indiP_belowMinZD.setState( INDI_IDLE );
     508           28 :     m_indiP_belowMinZD.setLabel( "Below minZD" );
     509           28 :     m_indiP_belowMinZD.setGroup( "status" );
     510           28 :     m_indiP_belowMinZD.add( pcf::IndiElement( "state" ) );
     511           28 :     m_indiP_belowMinZD["state"].setSwitchState( pcf::IndiElement::Off );
     512           14 :     m_indiP_belowMinZD.setPerm( pcf::IndiProperty::ReadOnly );
     513           14 :     registerIndiPropertyNew( m_indiP_belowMinZD, nullptr );
     514              : 
     515           14 :     m_indiP_aboveMaxZD = pcf::IndiProperty( pcf::IndiProperty::Switch );
     516           14 :     m_indiP_aboveMaxZD.setDevice( configName() );
     517           28 :     m_indiP_aboveMaxZD.setName( "aboveMaxZD" );
     518           14 :     m_indiP_aboveMaxZD.setPerm( pcf::IndiProperty::ReadOnly );
     519           14 :     m_indiP_aboveMaxZD.setRule( pcf::IndiProperty::AtMostOne );
     520           14 :     m_indiP_aboveMaxZD.setState( INDI_IDLE );
     521           28 :     m_indiP_aboveMaxZD.setLabel( "Above maxZD" );
     522           28 :     m_indiP_aboveMaxZD.setGroup( "status" );
     523           28 :     m_indiP_aboveMaxZD.add( pcf::IndiElement( "state" ) );
     524           28 :     m_indiP_aboveMaxZD["state"].setSwitchState( pcf::IndiElement::Off );
     525           14 :     m_indiP_aboveMaxZD.setPerm( pcf::IndiProperty::ReadOnly );
     526           14 :     registerIndiPropertyNew( m_indiP_aboveMaxZD, nullptr );
     527              : 
     528           42 :     REG_INDI_SETPROP( m_indiP_teldata, m_tcsDevName, "teldata" );
     529              : 
     530           14 :     m_indiP_adc1pos = pcf::IndiProperty( pcf::IndiProperty::Number );
     531           14 :     m_indiP_adc1pos.setDevice( m_adc1DevName );
     532           28 :     m_indiP_adc1pos.setName( "position" );
     533           28 :     m_indiP_adc1pos.add( pcf::IndiElement( "target" ) );
     534              : 
     535           14 :     m_indiP_adc2pos = pcf::IndiProperty( pcf::IndiProperty::Number );
     536           14 :     m_indiP_adc2pos.setDevice( m_adc2DevName );
     537           28 :     m_indiP_adc2pos.setName( "position" );
     538           28 :     m_indiP_adc2pos.add( pcf::IndiElement( "target" ) );
     539              : 
     540           14 :     recordADCTrack( true );
     541           14 :     updateZDLimitState( false, 0.0f, m_minZD, m_maxZD );
     542              : 
     543           14 :     state( stateCodes::READY );
     544              : 
     545           14 :     return 0;
     546           20 : }
     547              : 
     548           10 : int adcTracker::appLogic()
     549              : {
     550           10 :     TELEMETER_APP_LOGIC;
     551              : 
     552           10 :     const double now = mx::sys::get_curr_time();
     553              : 
     554           10 :     bool  tracking    = false;
     555           10 :     bool  lookupReady = false;
     556           10 :     bool  haveZD      = false;
     557           10 :     float zd          = 0;
     558           10 :     float minZD       = 0;
     559           10 :     float deltaAngle  = 0;
     560           10 :     float adc1delta   = 0;
     561           10 :     float adc2delta   = 0;
     562           10 :     float adc1zero    = 0;
     563           10 :     float adc2zero    = 0;
     564           10 :     float maxZD       = 0;
     565           10 :     float lastUpdate  = 0;
     566           10 :     int   adc1lupsign = 1;
     567           10 :     int   adc2lupsign = 1;
     568              : 
     569              :     { // mutex scope
     570           10 :         std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
     571              : 
     572           10 :         if( !lock.owns_lock() )
     573              :         {
     574            1 :             return 0;
     575              :         }
     576              : 
     577            9 :         tracking    = m_tracking;
     578            9 :         lookupReady = m_lookupReady;
     579            9 :         haveZD      = m_haveZD;
     580            9 :         zd          = m_zd;
     581            9 :         minZD       = m_minZD;
     582            9 :         deltaAngle  = m_deltaAngle;
     583            9 :         adc1delta   = m_adc1delta;
     584            9 :         adc2delta   = m_adc2delta;
     585            9 :         adc1zero    = m_adc1zero;
     586            9 :         adc2zero    = m_adc2zero;
     587            9 :         adc1lupsign = m_adc1lupsign;
     588            9 :         adc2lupsign = m_adc2lupsign;
     589            9 :         maxZD       = m_maxZD;
     590            9 :         lastUpdate  = m_lastUpdate;
     591              : 
     592            9 :         if( !tracking )
     593              :         {
     594            1 :             m_lastUpdate = 0;
     595              :         }
     596           10 :     } // mutex scope
     597              : 
     598            9 :     zdLimitState limitState = updateZDLimitState( lookupReady && haveZD, zd, minZD, maxZD );
     599              : 
     600            9 :     if( !tracking )
     601              :     {
     602            1 :         m_lastLoggedZDLimitState = zdLimitState::unknown;
     603            1 :         return 0;
     604              :     }
     605              : 
     606            8 :     logZDLimitCrossing( limitState, zd, minZD, maxZD );
     607              : 
     608            8 :     if( !lookupReady || !haveZD || now - lastUpdate <= m_updateInterval )
     609              :     {
     610            1 :         return 0;
     611              :     }
     612              : 
     613              :     { // mutex scope
     614            7 :         std::lock_guard<std::mutex> guard( m_indiMutex );
     615            7 :         m_lastUpdate = now;
     616            7 :     } // mutex scope
     617              : 
     618            7 :     if( !std::isfinite( zd ) )
     619              :     {
     620            0 :         log<software_error>( "received non-finite zenith distance in ADC tracker" );
     621            0 :         return 0;
     622              :     }
     623              : 
     624            7 :     float dadc1 = 0.0;
     625            7 :     float dadc2 = 0.0;
     626              : 
     627            7 :     if( zd > maxZD )
     628              :     {
     629            1 :         dadc1 = static_cast<float>( m_lupADC1.back() );
     630            1 :         dadc2 = static_cast<float>( m_lupADC2.back() );
     631              :     }
     632            6 :     else if( zd >= minZD )
     633              :     {
     634              :         try
     635              :         {
     636            5 :             dadc1 = interpolateADC1( zd );
     637            3 :             dadc2 = interpolateADC2( zd );
     638              :         }
     639            2 :         catch( const std::exception &e )
     640              :         {
     641            3 :             log<software_error>( std::string( "exception interpolating ADC targets: " ) + e.what() );
     642            1 :             return 0;
     643            1 :         }
     644            1 :         catch( ... )
     645              :         {
     646            1 :             log<software_error>( "unknown exception interpolating ADC targets" );
     647            1 :             return 0;
     648            1 :         }
     649              :     }
     650              :     else
     651              :     {
     652              :     }
     653              : 
     654            5 :     float adc1 = adc1zero + adc1lupsign * ( dadc1 + adc1delta + deltaAngle );
     655            5 :     float adc2 = adc2zero + adc2lupsign * ( dadc2 + adc2delta + deltaAngle );
     656              : 
     657            5 :     if( !std::isfinite( adc1 ) || !std::isfinite( adc2 ) )
     658              :     {
     659            0 :         log<software_error>( "computed non-finite ADC target" );
     660            0 :         return 0;
     661              :     }
     662              : 
     663            5 :     if( sendADC1Position( adc1 ) < 0 || sendADC2Position( adc2 ) < 0 )
     664              :     {
     665            2 :         log<software_error>( "failed to send ADC target positions" );
     666              :     }
     667              : 
     668            5 :     return 0;
     669              : }
     670              : 
     671            1 : int adcTracker::appShutdown()
     672              : {
     673            1 :     TELEMETER_APP_SHUTDOWN;
     674              : 
     675            1 :     return 0;
     676              : }
     677              : 
     678            1 : int adcTracker::sendADC1Position( float adc1 )
     679              : {
     680            3 :     if( sendNewProperty( m_indiP_adc1pos, "target", adc1 ) < 0 )
     681              :     {
     682            1 :         return log<software_error, -1>( "failed to send ADC 1 target" );
     683              :     }
     684              : 
     685            0 :     return 0;
     686              : }
     687              : 
     688            1 : int adcTracker::sendADC2Position( float adc2 )
     689              : {
     690            3 :     if( sendNewProperty( m_indiP_adc2pos, "target", adc2 ) < 0 )
     691              :     {
     692            1 :         return log<software_error, -1>( "failed to send ADC 2 target" );
     693              :     }
     694              : 
     695            0 :     return 0;
     696              : }
     697              : 
     698           14 : void adcTracker::setupInterpolators()
     699              : {
     700           14 :     m_terpADC1.setup( m_lupZD, m_lupADC1 );
     701           14 :     m_terpADC2.setup( m_lupZD, m_lupADC2 );
     702           14 : }
     703              : 
     704            3 : float adcTracker::interpolateADC1( float zd )
     705              : {
     706            3 :     return static_cast<float>( std::fabs( m_terpADC1( zd ) ) );
     707              : }
     708              : 
     709            3 : float adcTracker::interpolateADC2( float zd )
     710              : {
     711            3 :     return static_cast<float>( std::fabs( m_terpADC2( zd ) ) );
     712              : }
     713              : 
     714            3 : float adcTracker::extractZD( const pcf::IndiProperty &ipRecv )
     715              : {
     716            6 :     return ipRecv["zd"].get<float>();
     717              : }
     718              : 
     719           60 : void adcTracker::updateStatusSwitch( pcf::IndiProperty &prop, bool on )
     720              : {
     721          120 :     if( !prop.find( "state" ) )
     722              :     {
     723           58 :         return;
     724              :     }
     725              : 
     726           46 :     pcf::IndiElement::SwitchStateType    newVal = on ? pcf::IndiElement::On : pcf::IndiElement::Off;
     727           46 :     pcf::IndiProperty::PropertyStateType state  = on ? INDI_OK : INDI_IDLE;
     728              : 
     729          138 :     if( prop["state"].getSwitchState() == newVal && prop.getState() == state )
     730              :     {
     731           44 :         return;
     732              :     }
     733              : 
     734            2 :     prop["state"].setSwitchState( newVal );
     735            2 :     prop.setState( state );
     736            2 :     prop.setTimeStamp( pcf::TimeStamp() );
     737              : 
     738            2 :     if( m_indiDriver )
     739              :     {
     740            0 :         m_indiDriver->sendSetProperty( prop );
     741              :     }
     742              : }
     743              : 
     744           30 : adcTracker::zdLimitState adcTracker::updateZDLimitState( bool haveZD, float zd, float minZD, float maxZD )
     745              : {
     746           30 :     zdLimitState nextState = zdLimitState::unknown;
     747              : 
     748           30 :     if( haveZD && std::isfinite( zd ) )
     749              :     {
     750            8 :         if( zd < minZD )
     751              :         {
     752            1 :             nextState = zdLimitState::belowMin;
     753              :         }
     754            7 :         else if( zd > maxZD )
     755              :         {
     756            1 :             nextState = zdLimitState::aboveMax;
     757              :         }
     758              :         else
     759              :         {
     760            6 :             nextState = zdLimitState::inRange;
     761              :         }
     762              :     }
     763              : 
     764           30 :     updateStatusSwitch( m_indiP_belowMinZD, nextState == zdLimitState::belowMin );
     765           30 :     updateStatusSwitch( m_indiP_aboveMaxZD, nextState == zdLimitState::aboveMax );
     766              : 
     767           30 :     m_zdLimitState = nextState;
     768              : 
     769           30 :     return nextState;
     770              : }
     771              : 
     772            8 : void adcTracker::logZDLimitCrossing( zdLimitState state, float zd, float minZD, float maxZD )
     773              : {
     774            8 :     if( state == m_lastLoggedZDLimitState )
     775              :     {
     776            1 :         return;
     777              :     }
     778              : 
     779            7 :     if( state == zdLimitState::belowMin )
     780              :     {
     781            1 :         log<text_log>( "ADC tracker below minZD: zd=" + std::to_string( zd ) + " minZD=" + std::to_string( minZD ),
     782              :                        logPrio::LOG_WARNING );
     783              :     }
     784            6 :     else if( state == zdLimitState::aboveMax )
     785              :     {
     786            1 :         log<text_log>( "ADC tracker above maxZD: zd=" + std::to_string( zd ) + " maxZD=" + std::to_string( maxZD ),
     787              :                        logPrio::LOG_WARNING );
     788              :     }
     789              : 
     790            7 :     m_lastLoggedZDLimitState = state;
     791              : }
     792              : 
     793            5 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_tracking )( const pcf::IndiProperty &ipRecv )
     794              : {
     795            5 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tracking, ipRecv );
     796              : 
     797            8 :     if( !ipRecv.find( "toggle" ) )
     798            1 :         return 0;
     799              : 
     800            6 :     if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
     801              :     {
     802            4 :         updateSwitchIfChanged( m_indiP_tracking, "toggle", pcf::IndiElement::On, INDI_IDLE );
     803              : 
     804              :         { // mutex scope
     805            2 :             std::lock_guard<std::mutex> guard( m_indiMutex );
     806            2 :             m_tracking   = true;
     807            2 :             m_lastUpdate = 0;
     808            2 :         }
     809              : 
     810            2 :         log<text_log>( "started ADC rotation tracking" );
     811              :     }
     812              :     else
     813              :     {
     814            2 :         updateSwitchIfChanged( m_indiP_tracking, "toggle", pcf::IndiElement::Off, INDI_IDLE );
     815              : 
     816              :         { // mutex scope
     817            1 :             std::lock_guard<std::mutex> guard( m_indiMutex );
     818            1 :             m_tracking               = false;
     819            1 :             m_lastUpdate             = 0;
     820            1 :             m_lastLoggedZDLimitState = zdLimitState::unknown;
     821            1 :         }
     822              : 
     823            1 :         log<text_log>( "stopped ADC rotation tracking" );
     824              :     }
     825              : 
     826            3 :     recordADCTrack();
     827              : 
     828            3 :     return 0;
     829              : }
     830              : 
     831            4 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaAngle )( const pcf::IndiProperty &ipRecv )
     832              : {
     833            4 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaAngle, ipRecv );
     834              : 
     835              :     float target;
     836              : 
     837            3 :     if( indiTargetUpdate( m_indiP_deltaAngle, target, ipRecv ) < 0 )
     838              :     {
     839            1 :         return log<software_error, -1>();
     840              :     }
     841              : 
     842              :     { // mutex scope
     843            2 :         std::lock_guard<std::mutex> guard( m_indiMutex );
     844              : 
     845            2 :         m_deltaAngle = target;
     846            4 :         updateIfChanged( m_indiP_deltaAngle, "current", m_deltaAngle );
     847            2 :     } // mutex scope
     848              : 
     849            2 :     log<text_log>( "set deltaAngle to " + std::to_string( m_deltaAngle ) );
     850              : 
     851            2 :     recordADCTrack();
     852              : 
     853            2 :     return 0;
     854              : }
     855              : 
     856            4 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaADC1 )( const pcf::IndiProperty &ipRecv )
     857              : {
     858            4 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaADC1, ipRecv );
     859              : 
     860              :     float target;
     861              : 
     862            4 :     if( indiTargetUpdate( m_indiP_deltaADC1, target, ipRecv ) < 0 )
     863              :     {
     864            1 :         return log<software_error, -1>();
     865              :     }
     866              : 
     867              :     { // mutex scope
     868            3 :         std::lock_guard<std::mutex> guard( m_indiMutex );
     869              : 
     870            3 :         m_adc1delta = target;
     871            6 :         updateIfChanged( m_indiP_deltaADC1, "current", m_adc1delta );
     872            3 :     } // mutex scope
     873              : 
     874            3 :     log<text_log>( "set deltaADC1 to " + std::to_string( m_adc1delta ) );
     875              : 
     876            3 :     recordADCTrack();
     877              : 
     878            3 :     return 0;
     879              : }
     880              : 
     881            4 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaADC2 )( const pcf::IndiProperty &ipRecv )
     882              : {
     883            4 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaADC2, ipRecv );
     884              : 
     885              :     float target;
     886              : 
     887            4 :     if( indiTargetUpdate( m_indiP_deltaADC2, target, ipRecv ) < 0 )
     888              :     {
     889            1 :         return log<software_error, -1>();
     890              :     }
     891              : 
     892              :     { // mutex scope
     893            3 :         std::lock_guard<std::mutex> guard( m_indiMutex );
     894              : 
     895            3 :         m_adc2delta = target;
     896            6 :         updateIfChanged( m_indiP_deltaADC2, "current", m_adc2delta );
     897            3 :     } // mutex scope
     898              : 
     899            3 :     log<text_log>( "set deltaADC2 to " + std::to_string( m_adc2delta ) );
     900              : 
     901            3 :     recordADCTrack();
     902              : 
     903            3 :     return 0;
     904              : }
     905              : 
     906            4 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_minZD )( const pcf::IndiProperty &ipRecv )
     907              : {
     908            4 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_minZD, ipRecv );
     909              : 
     910              :     float target;
     911            4 :     float zd     = 0;
     912            4 :     float maxZD  = 0;
     913            4 :     bool  haveZD = false;
     914              : 
     915            4 :     if( indiTargetUpdate( m_indiP_minZD, target, ipRecv ) < 0 )
     916              :     {
     917            1 :         return log<software_error, -1>();
     918              :     }
     919              : 
     920              :     { // mutex scope
     921            3 :         std::lock_guard<std::mutex> guard( m_indiMutex );
     922              : 
     923            3 :         m_minZD = target;
     924            6 :         updateIfChanged( m_indiP_minZD, "current", m_minZD );
     925            3 :         zd     = m_zd;
     926            3 :         maxZD  = m_maxZD;
     927            3 :         haveZD = m_lookupReady && m_haveZD;
     928            3 :     } // mutex scope
     929              : 
     930            3 :     log<text_log>( "set minZD to " + std::to_string( m_minZD ) );
     931              : 
     932            3 :     recordADCTrack();
     933            3 :     updateZDLimitState( haveZD, zd, target, maxZD );
     934              : 
     935            3 :     return 0;
     936              : }
     937              : 
     938            8 : INDI_SETCALLBACK_DEFN( adcTracker, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
     939              : {
     940            8 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_teldata, ipRecv );
     941              : 
     942           14 :     if( !ipRecv.find( "zd" ) )
     943            1 :         return 0;
     944              : 
     945            6 :     float zd          = 0;
     946            6 :     bool  lookupReady = false;
     947            6 :     float minZD       = 0;
     948            6 :     float maxZD       = 0;
     949              : 
     950              :     try
     951              :     {
     952            6 :         zd = extractZD( ipRecv );
     953              :     }
     954            2 :     catch( const std::exception &e )
     955              :     {
     956            3 :         log<software_error>( std::string( "exception reading teldata.zd: " ) + e.what() );
     957            1 :         return 0;
     958            1 :     }
     959            1 :     catch( ... )
     960              :     {
     961            1 :         log<software_error>( "unknown exception reading teldata.zd" );
     962            1 :         return 0;
     963            1 :     }
     964              : 
     965            4 :     if( !std::isfinite( zd ) )
     966              :     {
     967            0 :         log<software_error>( "received non-finite teldata.zd" );
     968            0 :         return 0;
     969              :     }
     970              : 
     971              :     { // mutex scope
     972            4 :         std::lock_guard<std::mutex> guard( m_indiMutex );
     973            4 :         m_zd        = zd;
     974            4 :         m_haveZD    = true;
     975            4 :         lookupReady = m_lookupReady;
     976            4 :         minZD       = m_minZD;
     977            4 :         maxZD       = m_maxZD;
     978            4 :     } // mutex scope
     979              : 
     980            4 :     updateZDLimitState( lookupReady, zd, minZD, maxZD );
     981              : 
     982            4 :     return 0;
     983              : }
     984              : 
     985           10 : int adcTracker::checkRecordTimes()
     986              : {
     987           10 :     return telemeterT::checkRecordTimes( telem_adctrack() );
     988              : }
     989              : 
     990            1 : int adcTracker::recordTelem( const telem_adctrack * )
     991              : {
     992            1 :     return recordADCTrack( true );
     993              : }
     994              : 
     995           29 : int adcTracker::recordADCTrack( bool force )
     996              : {
     997              :     static bool  tracking   = false;
     998              :     static float deltaAngle = 0;
     999              :     static float adc1delta  = 0;
    1000              :     static float adc2delta  = 0;
    1001              :     static float minZD      = 0;
    1002              : 
    1003           29 :     bool  nextTracking   = false;
    1004           29 :     float nextDeltaAngle = 0;
    1005           29 :     float nextADC1delta  = 0;
    1006           29 :     float nextADC2delta  = 0;
    1007           29 :     float nextMinZD      = 0;
    1008              : 
    1009              :     { // mutex scope
    1010           29 :         std::lock_guard<std::mutex> guard( m_indiMutex );
    1011              : 
    1012           29 :         nextTracking   = m_tracking;
    1013           29 :         nextDeltaAngle = m_deltaAngle;
    1014           29 :         nextADC1delta  = m_adc1delta;
    1015           29 :         nextADC2delta  = m_adc2delta;
    1016           29 :         nextMinZD      = m_minZD;
    1017           29 :     } // mutex scope
    1018              : 
    1019           29 :     if( nextTracking != tracking || nextDeltaAngle != deltaAngle || nextADC1delta != adc1delta ||
    1020           16 :         nextADC2delta != adc2delta || nextMinZD != minZD || force )
    1021              :     {
    1022           29 :         telem<telem_adctrack>( { nextTracking, nextDeltaAngle, nextADC1delta, nextADC2delta, nextMinZD } );
    1023              : 
    1024           29 :         tracking   = nextTracking;
    1025           29 :         deltaAngle = nextDeltaAngle;
    1026           29 :         adc1delta  = nextADC1delta;
    1027           29 :         adc2delta  = nextADC2delta;
    1028           29 :         minZD      = nextMinZD;
    1029              :     }
    1030              : 
    1031           29 :     return 0;
    1032              : }
    1033              : 
    1034              : } // namespace app
    1035              : } // namespace MagAOX
    1036              : 
    1037              : #endif // adcTracker_hpp
        

Generated by: LCOV version 2.0-1