LCOV - code coverage report
Current view: top level - apps/stateRuleEngine - stateRuleEngine.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 3.1 % 159 5
Test Date: 2026-04-15 19:34:29 Functions: 20.0 % 10 2

            Line data    Source code
       1              : /** \file stateRuleEngine.hpp
       2              :  * \brief The MagAO-X stateRuleEngine application header file
       3              :  *
       4              :  * \ingroup stateRuleEngine_files
       5              :  */
       6              : 
       7              : #ifndef stateRuleEngine_hpp
       8              : #define stateRuleEngine_hpp
       9              : 
      10              : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
      11              : #include "../../magaox_git_version.h"
      12              : 
      13              : #include "indiCompRuleConfig.hpp"
      14              : 
      15              : /** \defgroup stateRuleEngine
      16              :  * \brief The MagAO-X stateRuleEngine application
      17              :  *
      18              :  * <a href="../handbook/operating/software/apps/stateRuleEngine.html">Application Documentation</a>
      19              :  *
      20              :  * \ingroup apps
      21              :  *
      22              :  */
      23              : 
      24              : /** \defgroup stateRuleEngine_files
      25              :  * \ingroup stateRuleEngine
      26              :  */
      27              : 
      28              : namespace MagAOX
      29              : {
      30              : namespace app
      31              : {
      32              : 
      33              : /// The MagAO-X stateRuleEngine
      34              : /**
      35              :  * \ingroup stateRuleEngine
      36              :  */
      37              : class stateRuleEngine : public MagAOXApp<true>
      38              : {
      39              : 
      40              :     // Give the test harness access.
      41              :     friend class stateRuleEngine_test;
      42              : 
      43              :   protected:
      44              :     /** \name Configurable Parameters
      45              :      *@{
      46              :      */
      47              : 
      48              :     std::string m_ruleDir; /**< Directory containing config files containing rules to load. Relative to config
      49              :                               directory.  If this is set, then rules in the device config file are ignored*/
      50              : 
      51              :     indiRuleMaps m_ruleMaps;
      52              : 
      53              :     ///@}
      54              : 
      55              :   public:
      56              :     /// Default c'tor.
      57              :     stateRuleEngine();
      58              : 
      59              :     /// D'tor, declared and defined for noexcept.
      60            1 :     ~stateRuleEngine() noexcept
      61            1 :     {
      62            1 :     }
      63              : 
      64              :     virtual void setupConfig();
      65              : 
      66              :     /// Implementation of loadConfig logic, separated for testing.
      67              :     /** This is called by loadConfig().
      68              :      */
      69              :     int loadConfigImpl(
      70              :         mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
      71              : 
      72              :     virtual void loadConfig();
      73              : 
      74              :     /// Startup function
      75              :     /**
      76              :      *
      77              :      */
      78              :     virtual int appStartup();
      79              : 
      80              :     /// Implementation of the FSM for stateRuleEngine.
      81              :     /**
      82              :      * \returns 0 on no critical error
      83              :      * \returns -1 on an error requiring shutdown
      84              :      */
      85              :     virtual int appLogic();
      86              : 
      87              :     /// Shutdown the app.
      88              :     /**
      89              :      *
      90              :      */
      91              :     virtual int appShutdown();
      92              : 
      93              :     /// The static callback function to be registered for rule properties
      94              :     /**
      95              :      *
      96              :      * \returns 0 on success.
      97              :      * \returns -1 on error.
      98              :      */
      99              :     static int st_newCallBack_ruleProp(
     100              :         void                    *app,   ///< [in] a pointer to this, will be static_cast-ed to derivedT.
     101              :         const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
     102              :     );
     103              : 
     104              :     /// Callback to process a NEW preset position request
     105              :     /**
     106              :      * \returns 0 on success.
     107              :      * \returns -1 on error.
     108              :      */
     109              :     int newCallBack_ruleProp(
     110              :         const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/ );
     111              : 
     112              :     pcf::IndiProperty m_indiP_info;
     113              :     pcf::IndiProperty m_indiP_caution;
     114              :     pcf::IndiProperty m_indiP_warning;
     115              :     pcf::IndiProperty m_indiP_alert;
     116              : };
     117              : 
     118            3 : stateRuleEngine::stateRuleEngine() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
     119              : {
     120            1 :     return;
     121            0 : }
     122              : 
     123            0 : void stateRuleEngine::setupConfig()
     124              : {
     125            0 :     config.add( "rules.dir",
     126              :                 "",
     127              :                 "rules.dir",
     128              :                 argType::Required,
     129              :                 "rules",
     130              :                 "dir",
     131              :                 false,
     132              :                 "string",
     133              :                 "Directory containing config files containing rules to load. Relative to config directory.  If this is "
     134              :                 "set, then rules in the device config file are ignored" );
     135            0 : }
     136              : 
     137            0 : int stateRuleEngine::loadConfigImpl( mx::app::appConfigurator &_config )
     138              : {
     139            0 :     _config( m_ruleDir, "rules.dir" );
     140              : 
     141            0 :     std::map<std::string, ruleRuleKeys> rrkMap;
     142              : 
     143            0 :     if( m_ruleDir == "" )
     144              :     {
     145              :         try
     146              :         {
     147            0 :             loadRuleConfig( m_ruleMaps, rrkMap, _config );
     148            0 :             finalizeRuleValRules( m_ruleMaps, rrkMap );
     149              :         }
     150            0 :         catch( const std::exception &e )
     151              :         {
     152            0 :             return log<software_critical, -1>( std::format( "Rule config exception caught:\n{}", e.what() ) );
     153            0 :         }
     154              :     }
     155              :     else
     156              :     {
     157            0 :         std::vector<std::string> conffiles;
     158            0 :         if( mx::ioutils::getFileNames( conffiles, m_configDir + '/' + m_ruleDir, "", "", ".conf" ) !=
     159              :             mx::error_t::noerror )
     160              :         {
     161            0 :             return log<software_critical, -1>( "Error reading rules" );
     162              :         }
     163              : 
     164            0 :         for( auto &cnf : conffiles )
     165              :         {
     166              :             // Create a configurator and set it up to log
     167            0 :             mx::app::appConfigurator fcfg;
     168              : 
     169            0 :             fcfg.m_sources = true;
     170            0 :             fcfg.configLog = configLog;
     171              : 
     172              :             // now process the config file
     173            0 :             if( fcfg.readConfig( cnf ) < 0 )
     174              :             {
     175            0 :                 return log<software_critical, -1>( std::format( "error reading rule config file: {}", cnf ) );
     176              :             }
     177              : 
     178              :             try
     179              :             {
     180              :                 // and finally add to our rule map
     181            0 :                 loadRuleConfig( m_ruleMaps, rrkMap, fcfg );
     182              :             }
     183            0 :             catch( const std::exception &e )
     184              :             {
     185            0 :                 return log<software_critical, -1>(
     186            0 :                     std::format( "Rule config exception caught from {}:\n{}", cnf, e.what() ) );
     187            0 :             }
     188            0 :         }
     189              : 
     190              :         try
     191              :         {
     192            0 :             finalizeRuleValRules( m_ruleMaps, rrkMap );
     193              :         }
     194            0 :         catch( const std::exception &e )
     195              :         {
     196            0 :             return log<software_critical, -1>( std::format( "Error finalizing rules:\n{}", e.what() ) );
     197            0 :         }
     198            0 :     }
     199              : 
     200            0 :     return 0;
     201            0 : }
     202              : 
     203            0 : void stateRuleEngine::loadConfig()
     204              : {
     205            0 :     if( loadConfigImpl( config ) < 0 )
     206              :     {
     207            0 :         log<software_critical>( "error in configuration" );
     208            0 :         m_shutdown = true;
     209              :     }
     210            0 : }
     211              : 
     212            0 : int stateRuleEngine::appStartup()
     213              : {
     214            0 :     for( auto it = m_ruleMaps.rules.begin(); it != m_ruleMaps.rules.end(); ++it )
     215              :     {
     216            0 :         if( it->second->priority() == rulePriority::info )
     217              :         {
     218            0 :             if( m_indiP_info.getDevice() != m_configName )
     219              :             {
     220            0 :                 if( registerIndiPropertyNew( m_indiP_info,
     221              :                                              "info",
     222            0 :                                              pcf::IndiProperty::Switch,
     223            0 :                                              pcf::IndiProperty::ReadOnly,
     224            0 :                                              pcf::IndiProperty::Idle,
     225            0 :                                              pcf::IndiProperty::AnyOfMany,
     226            0 :                                              nullptr ) < 0 )
     227              :                 {
     228            0 :                     return log<software_critical, -1>();
     229              :                 }
     230              :             }
     231              : 
     232            0 :             m_indiP_info.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
     233            0 :             m_indiP_info[it->first].setLabel( it->second->message() );
     234              :         }
     235              : 
     236            0 :         if( it->second->priority() == rulePriority::caution )
     237              :         {
     238            0 :             if( m_indiP_caution.getDevice() != m_configName )
     239              :             {
     240            0 :                 if( registerIndiPropertyNew( m_indiP_caution,
     241              :                                              "caution",
     242            0 :                                              pcf::IndiProperty::Switch,
     243            0 :                                              pcf::IndiProperty::ReadOnly,
     244            0 :                                              pcf::IndiProperty::Idle,
     245            0 :                                              pcf::IndiProperty::AnyOfMany,
     246            0 :                                              nullptr ) < 0 )
     247              :                 {
     248            0 :                     return log<software_critical, -1>( { __FILE__, __LINE__ } );
     249              :                 }
     250              :             }
     251              : 
     252            0 :             m_indiP_caution.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
     253            0 :             m_indiP_caution[it->first].setLabel( it->second->message() );
     254              :         }
     255              : 
     256            0 :         if( it->second->priority() == rulePriority::warning )
     257              :         {
     258            0 :             if( m_indiP_warning.getDevice() != m_configName )
     259              :             {
     260            0 :                 if( registerIndiPropertyNew( m_indiP_warning,
     261              :                                              "warning",
     262            0 :                                              pcf::IndiProperty::Switch,
     263            0 :                                              pcf::IndiProperty::ReadOnly,
     264            0 :                                              pcf::IndiProperty::Idle,
     265            0 :                                              pcf::IndiProperty::AnyOfMany,
     266            0 :                                              nullptr ) < 0 )
     267              :                 {
     268            0 :                     return log<software_critical, -1>( { __FILE__, __LINE__ } );
     269              :                 }
     270              :             }
     271              : 
     272            0 :             m_indiP_warning.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
     273            0 :             m_indiP_warning[it->first].setLabel( it->second->message() );
     274              :         }
     275              : 
     276            0 :         if( it->second->priority() == rulePriority::alert )
     277              :         {
     278            0 :             if( m_indiP_alert.getDevice() != m_configName )
     279              :             {
     280            0 :                 if( registerIndiPropertyNew( m_indiP_alert,
     281              :                                              "alert",
     282            0 :                                              pcf::IndiProperty::Switch,
     283            0 :                                              pcf::IndiProperty::ReadOnly,
     284            0 :                                              pcf::IndiProperty::Idle,
     285            0 :                                              pcf::IndiProperty::AnyOfMany,
     286            0 :                                              nullptr ) < 0 )
     287              :                 {
     288            0 :                     return log<software_critical, -1>( { __FILE__, __LINE__ } );
     289              :                 }
     290              :             }
     291              : 
     292            0 :             m_indiP_alert.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
     293            0 :             m_indiP_alert[it->first].setLabel( it->second->message() );
     294              :         }
     295              :     }
     296              : 
     297            0 :     for( auto it = m_ruleMaps.props.begin(); it != m_ruleMaps.props.end(); ++it )
     298              :     {
     299            0 :         if( it->second == nullptr )
     300            0 :             continue;
     301              : 
     302            0 :         std::string devName, propName;
     303              : 
     304            0 :         int rv = indi::parseIndiKey( devName, propName, it->first );
     305            0 :         if( rv != 0 )
     306              :         {
     307            0 :             log<software_error>( { __FILE__, __LINE__, 0, rv, "error parsing INDI key: " + it->first } );
     308            0 :             return -1;
     309              :         }
     310              : 
     311            0 :         registerIndiPropertySet( *it->second, devName, propName, st_newCallBack_ruleProp );
     312            0 :     }
     313              : 
     314            0 :     state( stateCodes::READY );
     315              : 
     316            0 :     return 0;
     317              : }
     318              : 
     319            0 : int stateRuleEngine::appLogic()
     320              : {
     321            0 :     for( auto it = m_ruleMaps.rules.begin(); it != m_ruleMaps.rules.end(); ++it )
     322              :     {
     323            0 :         if( it->second->priority() != rulePriority::none )
     324              :         {
     325              :             try
     326              :             {
     327            0 :                 bool val = it->second->value();
     328            0 :                 pcf::IndiElement::SwitchStateType onoff = pcf::IndiElement::Off;
     329              : 
     330            0 :                 if( val )
     331              :                 {
     332            0 :                     onoff = pcf::IndiElement::On;
     333              :                 }
     334              : 
     335            0 :                 if( it->second->priority() == rulePriority::info )
     336              :                 {
     337            0 :                     updateSwitchIfChanged( m_indiP_info, it->first, onoff );
     338              :                 }
     339            0 :                 else if( it->second->priority() == rulePriority::caution )
     340              :                 {
     341            0 :                     updateSwitchIfChanged( m_indiP_caution, it->first, onoff );
     342              :                 }
     343            0 :                 else if( it->second->priority() == rulePriority::warning )
     344              :                 {
     345            0 :                     updateSwitchIfChanged( m_indiP_warning, it->first, onoff );
     346              :                 }
     347              :                 else
     348              :                 {
     349            0 :                     updateSwitchIfChanged( m_indiP_alert, it->first, onoff );
     350              :                 }
     351              : 
     352            0 :                 if(val && it->second->timeToSend())
     353              :                 {
     354            0 :                     std::string prio;
     355              : 
     356            0 :                     if( it->second->priority() == rulePriority::info )
     357              :                     {
     358            0 :                         prio = "INFO";
     359              :                     }
     360            0 :                     else if( it->second->priority() == rulePriority::caution )
     361              :                     {
     362            0 :                         prio = "CAUTION";
     363              :                     }
     364            0 :                     else if( it->second->priority() == rulePriority::warning )
     365              :                     {
     366            0 :                         prio = "WARNING";
     367              :                     }
     368              :                     else
     369              :                     {
     370            0 :                         prio = "ALERT";
     371              :                     }
     372              : 
     373            0 :                     pcf::IndiProperty ip;
     374            0 :                     ip.setDevice( m_configName );
     375            0 :                     std::string msg;
     376              : 
     377            0 :                     if(it->second->message(true) == "") //Set the time no matter what
     378              :                     {
     379            0 :                         msg = std::format("{}: {}", prio, it->first);
     380              :                     }
     381              :                     else 
     382              :                     {
     383            0 :                         msg = std::format("{}: {}", prio, it->second->message());
     384              :                     }
     385              : 
     386            0 :                     it->second->incMessageCount();
     387              :                 
     388            0 :                     ip.setMessage( msg );
     389              :                     try
     390              :                     {
     391            0 :                         m_indiDriver->sendMessage( ip );
     392              :                     }
     393            0 :                     catch( const std::exception &e )
     394              :                     {
     395            0 :                         log<software_error>( std::format( "exception caught from sendMessage: {}", e.what() ) );
     396            0 :                     }
     397            0 :                 }
     398            0 :                 else if(!val)
     399              :                 {
     400            0 :                     it->second->messageCount(0); //resets so that next time will get sent
     401              :                 }
     402              :             }
     403            0 :             catch( const std::exception &e )
     404              :             {
     405              :                 ///\todo how to handle startup vs misconfiguration
     406              : 
     407              :                 /*
     408              :                 if(it->second->priority() == rulePriority::none)
     409              :                 {
     410              :                     updateSwitchIfChanged(m_indiP_info, it->first, pcf::IndiElement::Off);
     411              :                 }*/
     412            0 :             }
     413              :         }
     414              :     }
     415              : 
     416            0 :     return 0;
     417              : }
     418              : 
     419            0 : int stateRuleEngine::appShutdown()
     420              : {
     421            0 :     return 0;
     422              : }
     423              : 
     424            0 : int stateRuleEngine::st_newCallBack_ruleProp( void *app, const pcf::IndiProperty &ipRecv )
     425              : {
     426            0 :     stateRuleEngine *sre = static_cast<stateRuleEngine *>( app );
     427              : 
     428            0 :     sre->newCallBack_ruleProp( ipRecv );
     429              : 
     430            0 :     return 0;
     431              : }
     432              : 
     433            0 : int stateRuleEngine::newCallBack_ruleProp( const pcf::IndiProperty &ipRecv )
     434              : {
     435            0 :     std::string key = ipRecv.createUniqueKey();
     436              : 
     437            0 :     if( m_ruleMaps.props.count( key ) == 0 )
     438              :     {
     439            0 :         return 0;
     440              :     }
     441              : 
     442            0 :     if( m_ruleMaps.props[key] == nullptr ) //
     443              :     {
     444            0 :         return 0;
     445              :     }
     446              : 
     447            0 :     *m_ruleMaps.props[key] = ipRecv;
     448              : 
     449            0 :     return 0;
     450            0 : }
     451              : 
     452              : } // namespace app
     453              : } // namespace MagAOX
     454              : 
     455              : #endif // stateRuleEngine_hpp
        

Generated by: LCOV version 2.0-1