LCOV - code coverage report
Current view: top level - libMagAOX/app/dev - telemeter.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 100.0 % 53 53
Test Date: 2026-01-03 21:03:39 Functions: 100.0 % 11 11

            Line data    Source code
       1              : /** \file telemeter.hpp
       2              :  * \author Jared R. Males
       3              :  * \brief Configuration and control of a telemetry logger
       4              :  *
       5              :  * \ingroup app_files
       6              :  *
       7              :  */
       8              : 
       9              : #ifndef app_telemeter_hpp
      10              : #define app_telemeter_hpp
      11              : 
      12              : namespace MagAOX
      13              : {
      14              : namespace app
      15              : {
      16              : namespace dev
      17              : {
      18              : 
      19              : #ifdef XWCTEST_NAMESPACE
      20              : namespace XWCTEST_NAMESPACE
      21              : {
      22              : #endif
      23              : 
      24              : 
      25              : /// A device base class which saves telemetry.
      26              : /**
      27              :   * CRTP class `derivedT` has the following requirements:
      28              :   * - Must be a MagAOXApp
      29              :   * - Must include the following friend declaration:
      30              :   *   \code
      31              :   *       friend class dev::telemeter<DERIVEDNAME>; //replace DERIVEDNAME with derivedT class name
      32              :   *   \endcode
      33              :   * - Must include the following typedef:
      34              :   *   \code
      35              :   *       typedef dev::telemeter<DERIVEDNAME> telemeterT; //replace DERIVEDNAME with derivedT class name
      36              :   *   \endcode
      37              :   * - Must implement the following interface:
      38              :   *   \code
      39              :   *       int checkRecordTimes()
      40              :   *       {
      41              :   *            // Must call this variadic template function with each relevant telemetry type exactly like this
      42              :   *            return telemeterT::checkRecordTimes( telem_type1(), telem_type2(), ..., telem_typeN());
      43              :   *       }
      44              :   *   \endcode
      45              :   *   where there is one constructor-call argument for each telemetry log type recorded by this device.  The resultant
      46              :   *   objects are not used, rather the types are just used for variadic template resolution.
      47              :   *
      48              :   * - Must provide one overload of the following function for each telemetry type:
      49              :   *   \code
      50              :   *       int recordTelem( const telem_type1 * )
      51              :   *       {
      52              :   *          //DO NOT USE telem_type1
      53              :   *          return m_tel<telem_type1>( { message entered here } );
      54              :   *       }
      55              :   *   \endcode
      56              :   *   You MUST NOT use the pointer argument, it is for type resolution only -- you
      57              :   *   should fill in the telemetry log message using internal values. Note that calls to this function should result
      58              :   *   in a telemetry log entry every time -- it is called when the minimum interval has elapsed since the last entry.
      59              :   *
      60              :   * - Must call this class's setupConfig(), loadConfig(), appStartup(), appLogic(), and appShutdown()
      61              :   *   in the corresponding function of `derivedT`, with error checking.
      62              :   *   For convenience the following macros are defined to provide error checking:
      63              :   *   \code
      64              :   *       TELEMETER_SETUP_CONFIG( cfig )
      65              :   *       TELEMETER_LOAD_CONFIG( cfig )
      66              :   *       TELEMETER_APP_STARTUP
      67              :   *       TELEMETER_APP_LOGIC
      68              :   *       TELEMETER_APP_SHUTDOWN
      69              :   *   \endcode
      70              :   *
      71              :   * \ingroup appdev
      72              :   */
      73              : template <class derivedT>
      74              : struct telemeter
      75              : {
      76              :     typedef XWC_DEFAULT_VERBOSITY verboseT;
      77              : 
      78              :     /// The log manager type.
      79              :     typedef logger::logManager<derivedT, logFileRaw<verboseT>> logManagerT;
      80              : 
      81              :     logManagerT m_tel;
      82              : 
      83              :     double m_maxInterval{10.0}; ///< The maximum interval, in seconds, between telemetry records. Default is 10.0 seconds.
      84              : 
      85              :     telemeter();
      86              : 
      87              :     /// Make a telemetry recording
      88              :     /** Wrapper for logManager::log, which updates telT::lastRecord.
      89              :      *
      90              :      * \tparam logT the log entry type
      91              :      * \tparam retval the value returned by this method.
      92              :      *
      93              :      */
      94              :     template <typename telT>
      95              :     int telem(const typename telT::messageT &msg /**< [in] the data to log */);
      96              : 
      97              :     // Make a telemetry recording, for an empty record
      98              :     /* Wrapper for logManager::log, which updates telT::lastRecord.
      99              :      *
     100              :      * \tparam logT the log entry type
     101              :      * \tparam retval the value returned by this method.
     102              :      *
     103              :      */
     104              :     //template <typename telT>
     105              :     //int telem(); I think this shouldn't be defined, because empty telem makes no sense.  Delete after 11/27/2025
     106              : 
     107              :     /// Setup an application configurator for the device section
     108              :     /**
     109              :      * \returns 0 on success.
     110              :      * \returns -1 on error (nothing implemented yet)
     111              :      */
     112              :     int setupConfig(appConfigurator &config /**< [in] an application configuration to setup */);
     113              : 
     114              :     /// Load the device section from an application configurator
     115              :     /**
     116              :      *
     117              :      * \returns 0 on success
     118              :      * \returns -1 on error (nothing implemented yet)
     119              :      */
     120              :     int loadConfig(appConfigurator &config /**< [in] an application configuration from which to load values */);
     121              : 
     122              :     /// Starts the telemetry log thread.
     123              :     /**
     124              :      * This should be called from `derivedT::appStartup`
     125              :      *
     126              :      * \returns 0 on success
     127              :      * \returns -1 on error
     128              :      */
     129              :     int appStartup();
     130              : 
     131              :     /// Perform `telemeter` application logic
     132              :     /** This calls `derivedT::checkRecordTimes()`, and should be called from `derivedT::appLogic`, but only
     133              :      * when the FSM is in states where telemetry logging makes sense.
     134              :      *
     135              :      * \returns 0 on success
     136              :      * \returns -1 on error
     137              :      */
     138              :     int appLogic();
     139              : 
     140              :     /// Perform `telemeter` application shutdown
     141              :     /** This currently does nothing.
     142              :      *
     143              :      * \returns 0 on success
     144              :      * \returns -1 on error
     145              :      */
     146              :     int appShutdown();
     147              : 
     148              :     /// Check the time of the last record for each telemetry type and make an entry if needed
     149              :     /** This must be called from `derivedT::checkRecordTimes()`, with one template parameter
     150              :      * for ach telemetry log type being  recorded.
     151              :      *
     152              :      * \returns 0 on succcess
     153              :      * \returns -1 on error
     154              :      */
     155              :     template <class telT, class... telTs>
     156              :     int checkRecordTimes(const telT &tel, ///< [in] [unused] object of the telemetry type to record
     157              :                          telTs... tels    ///< [in] [unused] objects of the additional telemetry types to record
     158              :     );
     159              : 
     160              :     /// Worker function to actually perform the record time checking logic
     161              :     /** Recursively calls itself until the variadic template list is exhausted.
     162              :      *
     163              :      * \returns 0 on succcess
     164              :      * \returns -1 on error
     165              :      */
     166              :     template <class telT, class... telTs>
     167              :     int checkRecordTimes(timespec &ts,    ///<[in] [unused] the timestamp that records are compared to
     168              :                          const telT &tel, ///< [in] [unused] objects of the telemetry type to record
     169              :                          telTs... tels    ///< [in] [unused] objects of the additional telemetry types to record
     170              :     );
     171              : 
     172              :     /// Empty function called at the end of the template list
     173              :     /**
     174              :      * \returns 0 on succcess
     175              :      * \returns -1 on error
     176              :      */
     177              :     int checkRecordTimes(timespec &ts /**<[in] [unused] the timestamp that records are compared to */);
     178              : 
     179              : private:
     180              :     /// Access the derived class.
     181           20 :     derivedT &derived()
     182              :     {
     183           20 :         return *static_cast<derivedT *>(this);
     184              :     }
     185              : };
     186              : 
     187              : template <class derivedT>
     188          271 : telemeter<derivedT>::telemeter()
     189              : {
     190          271 : }
     191              : 
     192              : template <class derivedT>
     193              : template <typename telT>
     194           14 : int telemeter<derivedT>::telem(const typename telT::messageT &msg)
     195              : {
     196              : 
     197           14 :     m_tel.template log<telT>(msg, logPrio::LOG_TELEM);
     198              : 
     199              :     // Set timestamp
     200           14 :     clock_gettime(CLOCK_REALTIME, &telT::lastRecord);
     201              : 
     202           14 :     return 0;
     203              : }
     204              : 
     205              : /* I think this shouldn't be defined.  Delete after 11/27/2025
     206              : template <class derivedT>
     207              : template <typename telT>
     208              : int telemeter<derivedT>::telem()
     209              : {
     210              : 
     211              :     m_tel.template log<telT>(logPrio::LOG_TELEM);
     212              : 
     213              :     // Set timestamp
     214              :     clock_gettime(CLOCK_REALTIME, &telT::lastRecord);
     215              : 
     216              :     return 0;
     217              : }*/
     218              : 
     219              : template <class derivedT>
     220            5 : int telemeter<derivedT>::setupConfig(mx::app::appConfigurator &config)
     221              : {
     222            5 :     m_tel.m_configSection = "telemeter";
     223              : 
     224            5 :     m_tel.setupConfig(config);
     225              : 
     226           65 :     config.add("telemeter.maxInterval", "", "telemeter.maxInterval", argType::Required, "telemeter", "maxInterval", false, "double", "The maximum interval, in seconds, between telemetry records. Default is 10.0 seconds.");
     227              : 
     228            5 :     return 0;
     229              : }
     230              : 
     231              : template <class derivedT>
     232            5 : int telemeter<derivedT>::loadConfig(mx::app::appConfigurator &config)
     233              : {
     234            5 :     m_tel.m_logLevel = logPrio::LOG_TELEM;
     235              : 
     236              :     // Setup default log path
     237            5 :     std::string tmpstr = mx::sys::getEnv( MAGAOX_env_telem );
     238            5 :     if( tmpstr == "" )
     239              :     {
     240            5 :         tmpstr = MAGAOX_telRelPath;
     241              :     }
     242            5 :     m_tel.logPath(std::string(derived().basePath()) + "/" + tmpstr);
     243              : 
     244           10 :     m_tel.logExt("bintel");
     245              : 
     246            5 :     m_tel.logName(derived().m_configName);
     247              : 
     248            5 :     m_tel.loadConfig(config);
     249              : 
     250            5 :     config(m_maxInterval, "telemeter.maxInterval");
     251              : 
     252            5 :     return 0;
     253            5 : }
     254              : 
     255              : template <class derivedT>
     256            3 : int telemeter<derivedT>::appStartup()
     257              : {
     258              :     //----------------------------------------//
     259              :     //        Begin the telemetry system
     260              :     //----------------------------------------//
     261              : 
     262            3 :     m_tel.logThreadStart();
     263              : 
     264              :     // clang-format off
     265              :     #ifdef XWCTEST_TELEMETER_LOGSTART
     266              :     m_tel.logShutdown(true); // LCOV_EXCL_LINE
     267              :     sleep(2); // LCOV_EXCL_LINE
     268              :     #endif // clang-format on
     269              : 
     270              :     // Give up to 2 secs to make sure log thread has time to get started and try to open a file.
     271            3 :     int w = 0;
     272           25 :     while (m_tel.logThreadRunning() == false && w < 20)
     273              :     {
     274              :         // Sleep for 100 msec
     275           22 :         std::this_thread::sleep_for(std::chrono::duration<unsigned long, std::nano>(100000000));
     276           22 :         ++w;
     277              :     }
     278              : 
     279            3 :     if (m_tel.logThreadRunning() == false)
     280              :     {
     281            1 :         derivedT::template log<software_critical>({__FILE__, __LINE__, "telemetry thread not running.  exiting."});
     282            1 :         return -1;
     283              :     }
     284              : 
     285            2 :     return 0;
     286              : }
     287              : 
     288              : template <class derivedT>
     289            3 : int telemeter<derivedT>::appLogic()
     290              : {
     291            3 :     if( m_tel.logThreadRunning() == false )
     292              :     {
     293            1 :         derived().state( stateCodes::FAILURE );
     294              : 
     295              :         // Directly ouput the error b/c all other outputs are via the log thread
     296            1 :         std::cerr << "\nCRITICAL: telemetry thread not running.  Exiting.\n\n";
     297              : 
     298            1 :         derived().m_shutdown = 1;
     299              : 
     300            1 :         return -1;
     301              :     }
     302              : 
     303            2 :     return derived().checkRecordTimes();
     304              : }
     305              : 
     306              : template <class derivedT>
     307            2 : int telemeter<derivedT>::appShutdown()
     308              : {
     309            2 :     return 0;
     310              : }
     311              : 
     312              : template <class derivedT>
     313              : template <class telT, class... telTs>
     314            2 : int telemeter<derivedT>::checkRecordTimes(const telT &tel, telTs... tels)
     315              : {
     316              :     timespec ts;
     317              : 
     318            2 :     clock_gettime(CLOCK_REALTIME, &ts);
     319            4 :     return checkRecordTimes(ts, tel, tels...);
     320              : }
     321              : 
     322              : template <class derivedT>
     323              : template <class telT, class... telTs>
     324            4 : int telemeter<derivedT>::checkRecordTimes(timespec &ts, const telT &tel, telTs... tels)
     325              : {
     326              :     // Check if it's been more than maxInterval seconds since the last record.  This is corrected for the pause of the main loop.
     327            4 :     if (((double)ts.tv_sec - ((double)ts.tv_nsec) / 1e9) - ((double)telT::lastRecord.tv_sec - ((double)telT::lastRecord.tv_nsec) / 1e9) > m_maxInterval - ((double)derived().m_loopPause) / 1e9)
     328              :     {
     329            2 :         derived().recordTelem(&tel);
     330              :     }
     331              : 
     332            4 :     return checkRecordTimes(ts, tels...);
     333              : }
     334              : 
     335              : template <class derivedT>
     336            2 : int telemeter<derivedT>::checkRecordTimes(timespec &ts)
     337              : {
     338              :     static_cast<void>(ts); // be unused
     339              : 
     340            2 :     return 0;
     341              : }
     342              : 
     343              : /// Call telemeter::setupConfig with error checking
     344              : /**
     345              :   * \param cfig the application configurator
     346              :   */
     347              : #define TELEMETER_SETUP_CONFIG( cfig )                                                   \
     348              :     if (telemeterT::setupConfig( cfig) < 0)                                              \
     349              :     {                                                                                    \
     350              :         log<software_error>({__FILE__, __LINE__, "Error from telemeterT::setupConfig"}); \
     351              :         m_shutdown = true;                                                               \
     352              :     }
     353              : 
     354              : /// Call telemeter::loadConfig with error checking
     355              : /** This must be inside a function that returns int, e.g. the standard loadConfigImpl.
     356              :   * \param cfig the application configurator
     357              :   */
     358              : #define TELEMETER_LOAD_CONFIG( cfig )                                                              \
     359              :     if (telemeterT::loadConfig(cfig) < 0)                                                          \
     360              :     {                                                                                              \
     361              :         return log<software_error, -1>({__FILE__, __LINE__, "Error from telemeterT::loadConfig"}); \
     362              :     }
     363              : 
     364              : /// Call telemeter::appStartup with error checking
     365              : #define TELEMETER_APP_STARTUP                                 \
     366              :     if (telemeterT::appStartup() < 0)                         \
     367              :     {                                                         \
     368              :         return log<software_error, -1>({__FILE__, __LINE__}); \
     369              :     }
     370              : 
     371              : /// Call telemeter::appLogic with error checking
     372              : #define TELEMETER_APP_LOGIC                                   \
     373              :     if (telemeterT::appLogic() < 0)                           \
     374              :     {                                                         \
     375              :         return log<software_error, -1>({__FILE__, __LINE__}); \
     376              :     }
     377              : 
     378              : /// Call telemeter::appShutdown with error checking
     379              : #define TELEMETER_APP_SHUTDOWN                                                           \
     380              :     if (telemeterT::appShutdown() < 0)                                                   \
     381              :     {                                                                                    \
     382              :         log<software_error>({__FILE__, __LINE__, "error from telemeterT::appShutdown"}); \
     383              :     }
     384              : 
     385              : 
     386              : #ifdef XWCTEST_NAMESPACE
     387              : } // namespace XWCTEST_NAMESPACE
     388              : #endif
     389              : 
     390              : 
     391              : } // namespace dev
     392              : } // namespace tty
     393              : } // namespace MagAOX
     394              : 
     395              : #endif // tty_telemeter_hpp
        

Generated by: LCOV version 2.0-1