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

            Line data    Source code
       1              : /** \file flowRPM.hpp
       2              :  * \brief The MagAO-X flowRPM application header.
       3              :  *
       4              :  * \ingroup flowRPM_files
       5              :  */
       6              : 
       7              : #ifndef flowRPM_hpp
       8              : #define flowRPM_hpp
       9              : 
      10              : #include <cerrno>
      11              : #include <cmath>
      12              : #include <fstream>
      13              : #include <sstream>
      14              : 
      15              : #include "../../libMagAOX/libMagAOX.hpp" // Note this is included on command line to trigger pch
      16              : #include "../../magaox_git_version.h"
      17              : 
      18              : /** \defgroup flowRPM
      19              :  * \brief The MagAO-X application to convert a file-based fan reading into flow telemetry.
      20              :  *
      21              :  * <a href="../handbook/operating/software/apps/flowRPM.html">Application Documentation</a>
      22              :  *
      23              :  * \ingroup apps
      24              :  *
      25              :  */
      26              : 
      27              : /** \defgroup flowRPM_files
      28              :  * \ingroup flowRPM
      29              :  */
      30              : 
      31              : namespace MagAOX
      32              : {
      33              : namespace app
      34              : {
      35              : 
      36              : /// The MagAO-X flow-from-RPM monitor.
      37              : /**
      38              :  * \ingroup flowRPM
      39              :  */
      40              : class flowRPM : public MagAOXApp<true>, public dev::telemeter<flowRPM>
      41              : {
      42              :     // Give the test harness access.
      43              :     friend class flowRPM_test;
      44              : 
      45              :     friend class dev::telemeter<flowRPM>;
      46              : 
      47              :   public:
      48              :     /// Status returned by the file parser.
      49              :     enum class parseStatus
      50              :     {
      51              :         success,
      52              :         fileReadError,
      53              :         missingTimestamp,
      54              :         malformedTimestamp,
      55              :         missingRecord,
      56              :         malformedRecord,
      57              :         wrongDescriptor,
      58              :         wrongUnits,
      59              :         badStatus,
      60              :         badValue,
      61              :         staleReading
      62              :     };
      63              : 
      64              :     /// Parsed result of the current file contents.
      65              :     struct parseResult
      66              :     {
      67              :         parseStatus m_status{ parseStatus::fileReadError }; ///< Outcome of the parse attempt.
      68              :         double      m_flowRate{ -999.0 };                   ///< Displayed flow rate in LPM or the bad-value sentinel.
      69              :         double      m_age{ -999.0 };                        ///< Displayed age in seconds or the bad-value sentinel.
      70              :         timespec    m_sourceTs{ 0, 0 };                     ///< Parsed source timestamp when available.
      71              :     };
      72              : 
      73              :     /// The telemeter base type.
      74              :     typedef dev::telemeter<flowRPM> telemeterT;
      75              : 
      76              :   protected:
      77              :     /** \name Configurable Parameters - Data
      78              :      *
      79              :      * @{
      80              :      */
      81              : 
      82              :     /// Path to the file written by the systemd producer.
      83              :     std::string m_inputPath{ "/tmp/fac_flow.txt" };
      84              : 
      85              :     /// Maximum allowed source age in seconds before the reading is treated as stale.
      86              :     double m_maxAge{ 60.0 };
      87              : 
      88              :     /// Expected fan descriptor in the pipe-delimited record.
      89              :     std::string m_fanDescriptor{ "CHA_FAN1" };
      90              : 
      91              :     /// Bad-value sentinel published on parse or availability failures.
      92              :     double m_badValue{ -999.0 };
      93              : 
      94              :     /// Minimum interval between repeated logs for the same persistent error.
      95              :     double m_errorLogInterval{ 60.0 };
      96              : 
      97              :     ///@}
      98              : 
      99              :     /** \name Runtime State - Data
     100              :      *
     101              :      * @{
     102              :      */
     103              : 
     104              :     /// Current published flow rate in LPM or the bad-value sentinel.
     105              :     double m_flowRate{ -999.0 };
     106              : 
     107              :     /// Current published age in seconds or the bad-value sentinel.
     108              :     double m_age{ -999.0 };
     109              : 
     110              :     /// Timestamp parsed from the input file for the currently displayed value.
     111              :     timespec m_sourceTs{ 0, 0 };
     112              : 
     113              :     /// Whether the current displayed value is valid.
     114              :     bool m_haveValidReading{ false };
     115              : 
     116              :     /// Last flow value written to telemetry.
     117              :     double m_lastTelemFlowRate{ std::numeric_limits<double>::quiet_NaN() };
     118              : 
     119              :     /// Last validity state written to telemetry.
     120              :     bool m_lastTelemValid{ false };
     121              : 
     122              :     /// Time of the most recent error log emission.
     123              :     timespec m_lastErrorLogTs{ 0, 0 };
     124              : 
     125              :     /// Key for the most recently logged error class.
     126              :     std::string m_lastErrorKey;
     127              : 
     128              :     /// Read-only status property exposing flow rate and age.
     129              :     pcf::IndiProperty m_indiP_status;
     130              : 
     131              :     ///@}
     132              : 
     133              :   public:
     134              :     /// Default c'tor.
     135              :     flowRPM();
     136              : 
     137              :     /// D'tor, declared and defined for noexcept.
     138              :     ~flowRPM() noexcept;
     139              : 
     140              :     /// Set up the application configuration.
     141              :     virtual void setupConfig();
     142              : 
     143              :     /// Implementation of loadConfig logic, separated for testing.
     144              :     virtual int
     145              :     loadConfigImpl( mx::app::appConfigurator &_config /**< [in] application configuration from which to load */ );
     146              : 
     147              :     /// Load the application configuration.
     148              :     virtual void loadConfig();
     149              : 
     150              :     /// Perform application startup.
     151              :     virtual int appStartup();
     152              : 
     153              :     /// Implementation of the FSM for flowRPM.
     154              :     virtual int appLogic();
     155              : 
     156              :     /// Shut the application down.
     157              :     virtual int appShutdown();
     158              : 
     159              :     /** \name Parser Helpers
     160              :      *
     161              :      * @{
     162              :      */
     163              : 
     164              :     /// Parse a source timestamp line into a timespec.
     165              :     int parseTimestamp( timespec          &ts,  /**< [out] parsed timestamp */
     166              :                         const std::string &line /**< [in] source timestamp line */
     167              :     ) const;
     168              : 
     169              :     /// Parse the sensor record line into a flow rate in LPM.
     170              :     parseStatus parseRecordLine( double            &flowRate, /**< [out] parsed flow rate in LPM */
     171              :                                  const std::string &line      /**< [in] sensor record line */
     172              :     ) const;
     173              : 
     174              :     /// Parse the two-line file contents using a supplied current time.
     175              :     int parseFileContents( parseResult       &result,   /**< [out] parse result */
     176              :                            const std::string &contents, /**< [in] raw file contents */
     177              :                            const timespec    &now       /**< [in] current time for age calculation */
     178              :     ) const;
     179              : 
     180              :     /// Read the configured file and parse its current contents.
     181              :     virtual int readAndParse( parseResult    &result, /**< [out] parse result */
     182              :                               const timespec &now     /**< [in] current time for age calculation */
     183              :     ) const;
     184              : 
     185              :     /// Get the string key used for a parse status in log rate limiting.
     186              :     static std::string statusKey( parseStatus status /**< [in] parse status to stringify */ );
     187              : 
     188              :     /// Get the configured input path.
     189              :     const std::string &inputPath() const;
     190              : 
     191              :     /// Get the configured maximum reading age in seconds.
     192              :     double maxAge() const;
     193              : 
     194              :     /// Get the configured fan descriptor.
     195              :     const std::string &fanDescriptor() const;
     196              : 
     197              :     /// Get the configured bad-value sentinel.
     198              :     double badValue() const;
     199              : 
     200              :     /// Get the configured repeated-error log interval.
     201              :     double errorLogInterval() const;
     202              : 
     203              :     /// Get the currently published flow rate.
     204              :     double flowRate() const;
     205              : 
     206              :     /// Get the currently published age.
     207              :     double age() const;
     208              : 
     209              :     /// Get whether the current published value is valid.
     210              :     bool haveValidReading() const;
     211              : 
     212              :     ///@}
     213              : 
     214              :     /** \name Telemeter
     215              :      *
     216              :      * @{
     217              :      */
     218              : 
     219              :     /// Check whether telemetry records need to be forced.
     220              :     int checkRecordTimes();
     221              : 
     222              :     /// Record telemetry when requested by the telemeter helper.
     223              :     int recordTelem( const logger::telem_flowrpm * /**< [in] telemetry tag used for overload resolution */ );
     224              : 
     225              :     /// Record the currently displayed flow state to telemetry.
     226              :     virtual int recordFlow( bool force = false /**< [in] force a telemetry record even if unchanged */ );
     227              : 
     228              :     /// Reconcile a newly parsed result against the currently displayed state.
     229              :     parseResult reconcileResult( const parseResult &result, /**< [in] newly parsed file result */
     230              :                                  const timespec    &now     /**< [in] current time */
     231              :     ) const;
     232              : 
     233              :     /// Publish a parse result to INDI and runtime state.
     234              :     virtual int publishResult( const parseResult &result /**< [in] parse result to publish */ );
     235              : 
     236              :     /// Determine whether a repeated error should be logged now.
     237              :     bool shouldLogError( const std::string &key, /**< [in] error key under consideration */
     238              :                          const timespec    &now  /**< [in] current time */
     239              :     );
     240              : 
     241              :     ///@}
     242              : };
     243              : 
     244              : namespace flowRPMDetail
     245              : {
     246              : 
     247              : /// Trim leading and trailing ASCII whitespace from a token.
     248          132 : inline std::string trimToken( const std::string &token )
     249              : {
     250          132 :     const std::string::size_type first = token.find_first_not_of( " \t\r" );
     251              : 
     252          132 :     if( first == std::string::npos )
     253              :     {
     254           10 :         return "";
     255              :     }
     256              : 
     257          127 :     const std::string::size_type last = token.find_last_not_of( " \t\r" );
     258          127 :     return token.substr( first, last - first + 1 );
     259              : }
     260              : 
     261              : /// Convert a timespec to fractional seconds.
     262           32 : inline double timespecToDouble( const timespec &ts )
     263              : {
     264           32 :     return static_cast<double>( ts.tv_sec ) + 1e-9 * static_cast<double>( ts.tv_nsec );
     265              : }
     266              : 
     267              : /// Measure elapsed seconds between two timestamps.
     268           16 : inline double elapsedSeconds( const timespec &start, const timespec &end )
     269              : {
     270           16 :     return timespecToDouble( end ) - timespecToDouble( start );
     271              : }
     272              : 
     273              : /// Split a pipe-delimited sensor line into trimmed fields.
     274           16 : inline std::vector<std::string> splitPipeDelimited( const std::string &line )
     275              : {
     276           16 :     std::vector<std::string> fields;
     277           16 :     std::stringstream        ss( line );
     278           16 :     std::string              field;
     279              : 
     280          111 :     while( std::getline( ss, field, '|' ) )
     281              :     {
     282           95 :         fields.push_back( trimToken( field ) );
     283              :     }
     284              : 
     285           32 :     return fields;
     286           16 : }
     287              : 
     288              : /// Split file contents into non-empty logical lines.
     289           18 : inline std::vector<std::string> splitLogicalLines( const std::string &contents )
     290              : {
     291           18 :     std::vector<std::string> lines;
     292           18 :     std::stringstream        ss( contents );
     293           18 :     std::string              line;
     294              : 
     295           53 :     while( std::getline( ss, line ) )
     296              :     {
     297           35 :         if( !line.empty() && line.back() == '\r' )
     298              :         {
     299            6 :             line.pop_back();
     300              :         }
     301              : 
     302           35 :         if( trimToken( line ).empty() )
     303              :         {
     304            4 :             continue;
     305              :         }
     306              : 
     307           31 :         lines.push_back( line );
     308              :     }
     309              : 
     310           36 :     return lines;
     311           18 : }
     312              : 
     313              : } // namespace flowRPMDetail
     314              : 
     315          287 : inline flowRPM::flowRPM() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
     316              : {
     317           41 :     return;
     318            0 : }
     319              : 
     320           41 : inline flowRPM::~flowRPM() noexcept
     321              : {
     322           41 : }
     323              : 
     324            3 : inline void flowRPM::setupConfig()
     325              : {
     326           42 :     config.add( "input.path",
     327              :                 "",
     328              :                 "input.path",
     329              :                 argType::Required,
     330              :                 "input",
     331              :                 "path",
     332              :                 false,
     333              :                 "string",
     334              :                 "Path to the file containing the two-line flow RPM record." );
     335           42 :     config.add( "input.maxAge",
     336              :                 "",
     337              :                 "input.maxAge",
     338              :                 argType::Required,
     339              :                 "input",
     340              :                 "maxAge",
     341              :                 false,
     342              :                 "double",
     343              :                 "Maximum source age in seconds before the reading is treated as stale. Default is 60." );
     344           42 :     config.add( "input.fanDescriptor",
     345              :                 "",
     346              :                 "input.fanDescriptor",
     347              :                 argType::Required,
     348              :                 "input",
     349              :                 "fanDescriptor",
     350              :                 false,
     351              :                 "string",
     352              :                 "Expected descriptor token in the source file. Default is CHA_FAN1." );
     353           42 :     config.add( "input.badValue",
     354              :                 "",
     355              :                 "input.badValue",
     356              :                 argType::Required,
     357              :                 "input",
     358              :                 "badValue",
     359              :                 false,
     360              :                 "double",
     361              :                 "Bad-value sentinel published when the file can not be read or parsed. Default is -999." );
     362           42 :     config.add( "input.errorLogInterval",
     363              :                 "",
     364              :                 "input.errorLogInterval",
     365              :                 argType::Required,
     366              :                 "input",
     367              :                 "errorLogInterval",
     368              :                 false,
     369              :                 "double",
     370              :                 "Minimum interval in seconds between repeated logs of the same persistent error. Default is 60." );
     371              : 
     372            3 :     TELEMETER_SETUP_CONFIG( config );
     373            3 : }
     374              : 
     375            2 : inline int flowRPM::loadConfigImpl( mx::app::appConfigurator &_config )
     376              : {
     377            4 :     _config( m_inputPath, "input.path" );
     378            4 :     _config( m_maxAge, "input.maxAge" );
     379            4 :     _config( m_fanDescriptor, "input.fanDescriptor" );
     380            4 :     _config( m_badValue, "input.badValue" );
     381            2 :     _config( m_errorLogInterval, "input.errorLogInterval" );
     382              : 
     383            2 :     TELEMETER_LOAD_CONFIG( _config );
     384              : 
     385            2 :     return 0;
     386              : }
     387              : 
     388            3 : inline void flowRPM::loadConfig()
     389              : {
     390            3 :     if( loadConfigImpl( config ) < 0 )
     391              :     {
     392            1 :         m_shutdown = 1;
     393              :     }
     394            3 : }
     395              : 
     396            9 : inline int flowRPM::appStartup()
     397              : {
     398           63 :     createROIndiNumber( m_indiP_status, "status", "Flow Status" );
     399           54 :     indi::addNumberElement<double>( m_indiP_status, "flow_rate", -1e9, 1e9, 0.0, "%0.3f", "Flow Rate [LPM]" );
     400           45 :     indi::addNumberElement<double>( m_indiP_status, "age", -1e9, 1e9, 0.0, "%0.3f", "Age [s]" );
     401           18 :     m_indiP_status["flow_rate"] = m_badValue;
     402           18 :     m_indiP_status["age"]       = m_badValue;
     403            9 :     registerIndiPropertyReadOnly( m_indiP_status );
     404              : 
     405            9 :     TELEMETER_APP_STARTUP;
     406              : 
     407            9 :     state( stateCodes::READY );
     408              : 
     409            9 :     return 0;
     410              : }
     411              : 
     412            9 : inline int flowRPM::appLogic()
     413              : {
     414              :     timespec    now;
     415            9 :     parseResult result;
     416            9 :     parseResult displayResult;
     417              : 
     418            9 :     clock_gettime( CLOCK_REALTIME, &now );
     419              : 
     420            9 :     if( readAndParse( result, now ) < 0 )
     421              :     {
     422            2 :         return log<software_error, -1>( { __FILE__, __LINE__, "unexpected failure while reading flow file" } );
     423              :     }
     424              : 
     425            8 :     displayResult = reconcileResult( result, now );
     426              : 
     427            8 :     const bool wasValid = m_haveValidReading;
     428            8 :     const bool isValid  = ( displayResult.m_status == parseStatus::success );
     429              : 
     430            8 :     if( publishResult( displayResult ) < 0 )
     431              :     {
     432            2 :         return log<software_error, -1>( { __FILE__, __LINE__, "failed to publish flow result" } );
     433              :     }
     434              : 
     435            7 :     if( isValid )
     436              :     {
     437            5 :         if( !wasValid )
     438              :         {
     439            4 :             log<text_log>( "Recovered valid flow reading from " + m_inputPath + ".", logPrio::LOG_NOTICE );
     440              :         }
     441              : 
     442            5 :         m_lastErrorKey.clear();
     443              :     }
     444              :     else
     445              :     {
     446            2 :         const std::string key = statusKey( result.m_status );
     447              : 
     448            2 :         if( shouldLogError( key, now ) )
     449              :         {
     450            2 :             log<software_error>( { __FILE__, __LINE__, "flowRPM " + key + " for " + m_inputPath } );
     451              :         }
     452            2 :     }
     453              : 
     454            7 :     if( recordFlow() < 0 )
     455              :     {
     456            2 :         return log<software_error, -1>( { __FILE__, __LINE__, "error recording flow telemetry" } );
     457              :     }
     458              : 
     459            6 :     TELEMETER_APP_LOGIC;
     460              : 
     461            6 :     return 0;
     462              : }
     463              : 
     464            1 : inline int flowRPM::appShutdown()
     465              : {
     466            1 :     TELEMETER_APP_SHUTDOWN;
     467              : 
     468            1 :     return 0;
     469              : }
     470              : 
     471           19 : inline int flowRPM::parseTimestamp( timespec &ts, const std::string &line ) const
     472              : {
     473           19 :     std::stringstream ss( line );
     474           19 :     long long         sec  = 0;
     475           19 :     long long         nsec = 0;
     476              : 
     477           19 :     if( !( ss >> sec >> nsec ) )
     478              :     {
     479            1 :         return -1;
     480              :     }
     481              : 
     482           18 :     char trailing = '\0';
     483           18 :     if( ss >> trailing )
     484              :     {
     485            2 :         return -1;
     486              :     }
     487              : 
     488           16 :     if( nsec < 0 || nsec >= 1000000000LL )
     489              :     {
     490            2 :         return -1;
     491              :     }
     492              : 
     493           14 :     ts.tv_sec  = static_cast<time_t>( sec );
     494           14 :     ts.tv_nsec = static_cast<long>( nsec );
     495              : 
     496           14 :     return 0;
     497           19 : }
     498              : 
     499           16 : inline flowRPM::parseStatus flowRPM::parseRecordLine( double &flowRate, const std::string &line ) const
     500              : {
     501           16 :     const std::vector<std::string> fields = flowRPMDetail::splitPipeDelimited( line );
     502              : 
     503           16 :     if( fields.size() != 6 )
     504              :     {
     505            1 :         return parseStatus::malformedRecord;
     506              :     }
     507              : 
     508           15 :     if( fields[1] != m_fanDescriptor )
     509              :     {
     510            2 :         return parseStatus::wrongDescriptor;
     511              :     }
     512              : 
     513           13 :     if( fields[4] != "RPM" )
     514              :     {
     515            2 :         return parseStatus::wrongUnits;
     516              :     }
     517              : 
     518           11 :     if( fields[5] != "'OK'" )
     519              :     {
     520            2 :         return parseStatus::badStatus;
     521              :     }
     522              : 
     523            9 :     char *end    = nullptr;
     524            9 :     errno        = 0;
     525            9 :     double value = std::strtod( fields[3].c_str(), &end );
     526              : 
     527            9 :     if( errno != 0 || end == fields[3].c_str() || *end != '\0' )
     528              :     {
     529            2 :         return parseStatus::badValue;
     530              :     }
     531              : 
     532            7 :     flowRate = value / 1000.0;
     533              : 
     534            7 :     return parseStatus::success;
     535           16 : }
     536              : 
     537           17 : inline int flowRPM::parseFileContents( parseResult &result, const std::string &contents, const timespec &now ) const
     538              : {
     539           17 :     result.m_status   = parseStatus::fileReadError;
     540           17 :     result.m_flowRate = m_badValue;
     541           17 :     result.m_age      = m_badValue;
     542           17 :     result.m_sourceTs = { 0, 0 };
     543              : 
     544           17 :     const std::vector<std::string> lines = flowRPMDetail::splitLogicalLines( contents );
     545              : 
     546           17 :     if( lines.empty() )
     547              :     {
     548            2 :         result.m_status = parseStatus::missingTimestamp;
     549            2 :         return 0;
     550              :     }
     551              : 
     552           15 :     if( parseTimestamp( result.m_sourceTs, lines[0] ) < 0 )
     553              :     {
     554            2 :         result.m_status = parseStatus::malformedTimestamp;
     555            2 :         return 0;
     556              :     }
     557              : 
     558           13 :     if( lines.size() < 2 )
     559              :     {
     560            2 :         result.m_status = parseStatus::missingRecord;
     561            2 :         return 0;
     562              :     }
     563              : 
     564           11 :     if( lines.size() != 2 )
     565              :     {
     566            1 :         result.m_status = parseStatus::malformedRecord;
     567            1 :         result.m_age    = std::max( 0.0, flowRPMDetail::elapsedSeconds( result.m_sourceTs, now ) );
     568            1 :         return 0;
     569              :     }
     570              : 
     571           10 :     double parsedFlowRate = m_badValue;
     572              : 
     573           10 :     result.m_status = parseRecordLine( parsedFlowRate, lines[1] );
     574           10 :     result.m_age    = std::max( 0.0, flowRPMDetail::elapsedSeconds( result.m_sourceTs, now ) );
     575              : 
     576           10 :     if( result.m_status != parseStatus::success )
     577              :     {
     578            4 :         return 0;
     579              :     }
     580              : 
     581            6 :     if( result.m_age > m_maxAge )
     582              :     {
     583            1 :         result.m_status = parseStatus::staleReading;
     584            1 :         return 0;
     585              :     }
     586              : 
     587            5 :     result.m_flowRate = parsedFlowRate;
     588            5 :     return 0;
     589           17 : }
     590              : 
     591            8 : inline int flowRPM::readAndParse( parseResult &result, const timespec &now ) const
     592              : {
     593            8 :     std::ifstream ifs( m_inputPath.c_str() );
     594              : 
     595            8 :     result.m_status   = parseStatus::fileReadError;
     596            8 :     result.m_flowRate = m_badValue;
     597            8 :     result.m_age      = m_badValue;
     598            8 :     result.m_sourceTs = { 0, 0 };
     599              : 
     600            8 :     if( !ifs )
     601              :     {
     602            3 :         return 0;
     603              :     }
     604              : 
     605            5 :     std::stringstream buffer;
     606            5 :     buffer << ifs.rdbuf();
     607              : 
     608            5 :     return parseFileContents( result, buffer.str(), now );
     609            8 : }
     610              : 
     611           14 : inline std::string flowRPM::statusKey( parseStatus status )
     612              : {
     613           14 :     switch( status )
     614              :     {
     615            1 :     case parseStatus::success:
     616            2 :         return "success";
     617            3 :     case parseStatus::fileReadError:
     618            6 :         return "open_failed";
     619            1 :     case parseStatus::missingTimestamp:
     620            2 :         return "missing_timestamp";
     621            1 :     case parseStatus::malformedTimestamp:
     622            2 :         return "timestamp_parse_failed";
     623            1 :     case parseStatus::missingRecord:
     624            2 :         return "missing_record";
     625            1 :     case parseStatus::malformedRecord:
     626            2 :         return "record_parse_failed";
     627            1 :     case parseStatus::wrongDescriptor:
     628            2 :         return "wrong_descriptor";
     629            1 :     case parseStatus::wrongUnits:
     630            2 :         return "wrong_units";
     631            1 :     case parseStatus::badStatus:
     632            2 :         return "bad_status";
     633            1 :     case parseStatus::badValue:
     634            2 :         return "bad_value";
     635            1 :     case parseStatus::staleReading:
     636            2 :         return "stale";
     637            1 :     default:
     638            2 :         return "unknown";
     639              :     }
     640              : }
     641              : 
     642            2 : inline const std::string &flowRPM::inputPath() const
     643              : {
     644            2 :     return m_inputPath;
     645              : }
     646              : 
     647            4 : inline double flowRPM::maxAge() const
     648              : {
     649            4 :     return m_maxAge;
     650              : }
     651              : 
     652            2 : inline const std::string &flowRPM::fanDescriptor() const
     653              : {
     654            2 :     return m_fanDescriptor;
     655              : }
     656              : 
     657           26 : inline double flowRPM::badValue() const
     658              : {
     659           26 :     return m_badValue;
     660              : }
     661              : 
     662            2 : inline double flowRPM::errorLogInterval() const
     663              : {
     664            2 :     return m_errorLogInterval;
     665              : }
     666              : 
     667            7 : inline double flowRPM::flowRate() const
     668              : {
     669            7 :     return m_flowRate;
     670              : }
     671              : 
     672            6 : inline double flowRPM::age() const
     673              : {
     674            6 :     return m_age;
     675              : }
     676              : 
     677            7 : inline bool flowRPM::haveValidReading() const
     678              : {
     679            7 :     return m_haveValidReading;
     680              : }
     681              : 
     682            6 : inline int flowRPM::checkRecordTimes()
     683              : {
     684            6 :     return telemeterT::checkRecordTimes( logger::telem_flowrpm() );
     685              : }
     686              : 
     687            1 : inline int flowRPM::recordTelem( const logger::telem_flowrpm * )
     688              : {
     689            1 :     return recordFlow( true );
     690              : }
     691              : 
     692            7 : inline int flowRPM::recordFlow( bool force )
     693              : {
     694            7 :     if( force || m_flowRate != m_lastTelemFlowRate || m_haveValidReading != m_lastTelemValid )
     695              :     {
     696            4 :         telem<logger::telem_flowrpm>( logger::telem_flowrpm::messageT( m_flowRate, m_age, m_haveValidReading ) );
     697              : 
     698            4 :         m_lastTelemFlowRate = m_flowRate;
     699            4 :         m_lastTelemValid    = m_haveValidReading;
     700              :     }
     701              : 
     702            7 :     return 0;
     703              : }
     704              : 
     705           10 : inline flowRPM::parseResult flowRPM::reconcileResult( const parseResult &result, const timespec &now ) const
     706              : {
     707           10 :     parseResult displayResult = result;
     708              : 
     709           10 :     if( result.m_status == parseStatus::success )
     710              :     {
     711            5 :         return displayResult;
     712              :     }
     713              : 
     714            5 :     if( !m_haveValidReading )
     715              :     {
     716            2 :         return displayResult;
     717              :     }
     718              : 
     719            3 :     const double lastGoodAge = std::max( 0.0, flowRPMDetail::elapsedSeconds( m_sourceTs, now ) );
     720              : 
     721            3 :     if( lastGoodAge > m_maxAge )
     722              :     {
     723            1 :         displayResult.m_status   = parseStatus::staleReading;
     724            1 :         displayResult.m_flowRate = m_badValue;
     725            1 :         displayResult.m_age      = lastGoodAge;
     726            1 :         displayResult.m_sourceTs = m_sourceTs;
     727            1 :         return displayResult;
     728              :     }
     729              : 
     730            2 :     displayResult.m_status   = parseStatus::success;
     731            2 :     displayResult.m_flowRate = m_flowRate;
     732            2 :     displayResult.m_age      = lastGoodAge;
     733            2 :     displayResult.m_sourceTs = m_sourceTs;
     734              : 
     735            2 :     return displayResult;
     736              : }
     737              : 
     738           11 : inline int flowRPM::publishResult( const parseResult &result )
     739              : {
     740           11 :     m_flowRate         = result.m_flowRate;
     741           11 :     m_age              = result.m_age;
     742           11 :     m_sourceTs         = result.m_sourceTs;
     743           11 :     m_haveValidReading = ( result.m_status == parseStatus::success );
     744              : 
     745           22 :     updateIfChanged( m_indiP_status,
     746           33 :                      std::vector<std::string>( { "flow_rate", "age" } ),
     747           22 :                      std::vector<double>( { m_flowRate, m_age } ),
     748           11 :                      m_haveValidReading ? INDI_OK : INDI_ALERT );
     749              : 
     750           11 :     return 0;
     751           33 : }
     752              : 
     753            6 : inline bool flowRPM::shouldLogError( const std::string &key, const timespec &now )
     754              : {
     755            6 :     if( key != m_lastErrorKey )
     756              :     {
     757            4 :         m_lastErrorKey   = key;
     758            4 :         m_lastErrorLogTs = now;
     759            4 :         return true;
     760              :     }
     761              : 
     762            2 :     if( flowRPMDetail::elapsedSeconds( m_lastErrorLogTs, now ) >= m_errorLogInterval )
     763              :     {
     764            1 :         m_lastErrorLogTs = now;
     765            1 :         return true;
     766              :     }
     767              : 
     768            1 :     return false;
     769              : }
     770              : 
     771              : } // namespace app
     772              : } // namespace MagAOX
     773              : 
     774              : #endif // flowRPM_hpp
        

Generated by: LCOV version 2.0-1