LCOV - code coverage report
Current view: top level - apps/stateRuleEngine - indiCompRules.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 71.1 % 432 307
Test Date: 2026-04-15 19:34:29 Functions: 92.5 % 67 62

            Line data    Source code
       1              : /** \file indiCompRules.hpp
       2              :  * \brief The rules for the MagAO-X stateRuleEngine
       3              :  *
       4              :  * \ingroup stateRuleEngine_files
       5              :  */
       6              : 
       7              : #ifndef stateRuleEngine_indiCompRules_hpp
       8              : #define stateRuleEngine_indiCompRules_hpp
       9              : 
      10              : #include <variant>
      11              : 
      12              : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
      13              :                                          //Included here for standalone testing of this file
      14              : 
      15              : /// Logical comparisons for the INDI rules
      16              : enum class ruleComparison
      17              : {
      18              :     Eq,   ///< Equal
      19              :     Neq,  ///< Not equal
      20              :     Lt,   ///< Less than
      21              :     Gt,   ///< Greater than
      22              :     LtEq, ///< Less than or equal to
      23              :     GtEq, ///< Greater than or equal to
      24              :     And,  ///< boolean and
      25              :     Nand, ///< boolean nand
      26              :     Or,   ///< boolean or
      27              :     Nor,  ///< boolean nor
      28              :     Imply,
      29              :     Nimply,
      30              :     Xor  = Neq, ///< boolean xor, equivalent to not equal
      31              :     Xnor = Eq   ///< boolean xnor, equivalent to equal
      32              : };
      33              : 
      34              : /// Get the \ref ruleComparison member from a string representation.
      35              : /** Needed for processing configuration files
      36              :  */
      37           28 : ruleComparison string2comp( const std::string &cstr )
      38              : {
      39           28 :     if( cstr == "Eq" )
      40              :     {
      41           21 :         return ruleComparison::Eq;
      42              :     }
      43            7 :     else if( cstr == "Neq" )
      44              :     {
      45            3 :         return ruleComparison::Neq;
      46              :     }
      47            4 :     else if( cstr == "Lt" )
      48              :     {
      49            0 :         return ruleComparison::Lt;
      50              :     }
      51            4 :     else if( cstr == "Gt" )
      52              :     {
      53            0 :         return ruleComparison::Gt;
      54              :     }
      55            4 :     else if( cstr == "LtEq" )
      56              :     {
      57            0 :         return ruleComparison::LtEq;
      58              :     }
      59            4 :     else if( cstr == "GtEq" )
      60              :     {
      61            2 :         return ruleComparison::GtEq;
      62              :     }
      63            2 :     else if( cstr == "And" )
      64              :     {
      65            2 :         return ruleComparison::And;
      66              :     }
      67            0 :     else if( cstr == "Nand" )
      68              :     {
      69            0 :         return ruleComparison::Nand;
      70              :     }
      71            0 :     else if( cstr == "Or" )
      72              :     {
      73            0 :         return ruleComparison::Or;
      74              :     }
      75            0 :     else if( cstr == "Nor" )
      76              :     {
      77            0 :         return ruleComparison::Nor;
      78              :     }
      79            0 :     else if( cstr == "Xor" )
      80              :     {
      81            0 :         return ruleComparison::Xor;
      82              :     }
      83            0 :     else if( cstr == "Xnor" )
      84              :     {
      85            0 :         return ruleComparison::Xnor;
      86              :     }
      87            0 :     else if( cstr == "Imply" )
      88              :     {
      89            0 :         return ruleComparison::Imply;
      90              :     }
      91            0 :     else if( cstr == "Nimply" )
      92              :     {
      93            0 :         return ruleComparison::Nimply;
      94              :     }
      95              :     else
      96              :     {
      97            0 :         throw mx::exception( mx::error_t::invalidarg, cstr + " is not a valid comparison" );
      98              :     }
      99              : }
     100              : 
     101              : /// Reporting priorities for rules
     102              : enum class rulePriority
     103              : {
     104              :     none,    ///< Don't publish
     105              :     info,    ///< For information only
     106              :     caution, ///< Caution -- make sure you know what you're doing
     107              :     warning, ///< Warning -- something is probably wrong, you should check
     108              :     alert    ///< Alert -- something is definitely wrong, you should take action
     109              : };
     110              : 
     111              : /// Get the \ref rulePriority member from a string representation.
     112              : /** Needed for processing configuration files
     113              :  */
     114           28 : rulePriority string2priority( const std::string &pstr )
     115              : {
     116           28 :     if( pstr == "none" )
     117              :     {
     118           23 :         return rulePriority::none;
     119              :     }
     120            5 :     else if( pstr == "info" )
     121              :     {
     122            1 :         return rulePriority::info;
     123              :     }
     124            4 :     else if( pstr == "caution" )
     125              :     {
     126            1 :         return rulePriority::caution;
     127              :     }
     128            3 :     else if( pstr == "warning" )
     129              :     {
     130            2 :         return rulePriority::warning;
     131              :     }
     132            1 :     else if( pstr == "alert" )
     133              :     {
     134            1 :         return rulePriority::alert;
     135              :     }
     136              :     else
     137              :     {
     138            0 :         throw mx::exception( mx::error_t::invalidarg, pstr + " is not a valid priority" );
     139              :     }
     140              : }
     141              : 
     142              : /// Virtual base-class for all rules
     143              : /** Provides error handling and comparison functions.
     144              :  * Derived classes must implement valid() and value().
     145              :  */
     146              : struct indiCompRule
     147              : {
     148              :   public:
     149              :     /// In-band error reporting type
     150              :     typedef std::variant<bool, std::string> boolorerr_t;
     151              : 
     152              :     /// Check if returned value indicates an error
     153          156 :     bool isError( boolorerr_t rv /**< [in] the return value to check*/ )
     154              :     {
     155          156 :         return ( rv.index() > 0 );
     156              :     }
     157              : 
     158              :     static constexpr double default_info_msg_delay    = 0; // Send once
     159              :     static constexpr double default_caution_msg_delay = 60;
     160              :     static constexpr double default_warning_msg_delay = 30;
     161              :     static constexpr double default_alert_msg_delay   = 5;
     162              : 
     163              :   protected:
     164              :     /// The reporting priority for this rule
     165              :     rulePriority m_priority{ rulePriority::none };
     166              : 
     167              :     /// The message used for notifications
     168              :     std::string m_message;
     169              : 
     170              :     timespec m_lastMsg{ 0, 0 }; ///< Time the message was last sent
     171              : 
     172              :     double m_messageDelay{ 0 }; ///< Delay between sending messages
     173              : 
     174              :     int m_messageCount{ 0 }; ///< Number of times the message has been sent
     175              : 
     176              :     /// The comparison for this rule
     177              :     ruleComparison m_comparison{ ruleComparison::Eq };
     178              : 
     179              :   public:
     180              :     /// Virtual destructor
     181           92 :     virtual ~indiCompRule()
     182           92 :     {
     183           92 :     }
     184              : 
     185              :     /// Set priority of this rule
     186              :     /** Also sets the message delay, to default for priority if not set.
     187              :      */
     188           27 :     void priority( const rulePriority &p,         /**< [in] the new priority */
     189              :                    double              delay = -1 /**< [in] [opt] the message delay, if \< 0 the default is used */
     190              :     )
     191              :     {
     192           27 :         m_priority = p;
     193              : 
     194           27 :         if( delay < 0 )
     195              :         {
     196           27 :             switch( m_priority )
     197              :             {
     198            1 :             case rulePriority::info:
     199            1 :                 m_messageDelay = default_info_msg_delay;
     200            1 :                 break;
     201            1 :             case rulePriority::caution:
     202            1 :                 m_messageDelay = default_caution_msg_delay;
     203            1 :                 break;
     204            2 :             case rulePriority::warning:
     205            2 :                 m_messageDelay = default_warning_msg_delay;
     206            2 :                 break;
     207            1 :             case rulePriority::alert:
     208            1 :                 m_messageDelay = default_alert_msg_delay;
     209            1 :                 break;
     210           22 :             default:
     211           22 :                 m_messageDelay = 0;
     212              :             }
     213              :         }
     214              :         else
     215              :         {
     216            0 :             m_messageDelay = delay;
     217              :         }
     218           27 :     }
     219              : 
     220              :     /// Get the rule priority
     221              :     /**
     222              :      * \returns the current rule priority
     223              :      */
     224           12 :     const rulePriority &priority()
     225              :     {
     226           12 :         return m_priority;
     227              :     }
     228              : 
     229              :     /// Set the message
     230           27 :     void message( const std::string &m /**< [in] the new message*/ )
     231              :     {
     232           27 :         m_message = m;
     233           27 :     }
     234              : 
     235              :     /// Get the message
     236              :     /** Optionally sets the message time to now.
     237              :      * \returns the current message
     238              :      */
     239            0 :     const std::string &message( bool settime = false /**< If true m_lastMsg is set to now */ )
     240              :     {
     241            0 :         if( settime )
     242              :         {
     243            0 :             if( clock_gettime( CLOCK_ISIO, &m_lastMsg ) < 0 )
     244              :             {
     245            0 :                 throw mx::exception( mx::errno2error_t( errno ), "getting message time" );
     246              :             }
     247              :         }
     248              : 
     249            0 :         return m_message;
     250              :     }
     251              : 
     252              :     const timespec &lastMsg()
     253              :     {
     254              :         return m_lastMsg;
     255              :     }
     256              : 
     257              :     /// Get the time since the last message
     258            0 :     double sinceLastMsg()
     259              :     {
     260              :         timespec ts;
     261            0 :         if( clock_gettime( CLOCK_ISIO, &ts ) < 0 )
     262              :         {
     263            0 :             throw mx::exception( mx::errno2error_t( errno ), "getting current time" );
     264              :         }
     265              : 
     266            0 :         return ( 1.0 * ts.tv_sec + ts.tv_nsec / 1e9 ) - ( 1.0 * m_lastMsg.tv_sec + m_lastMsg.tv_nsec / 1e9 );
     267              :     }
     268              : 
     269              :     /// Check if it's time to send a message
     270              :     /** If the message delay is \<= 0, this is based on message count (i.e. has it been sent).
     271              :      * Otherwise it's based on the time since last sent
     272              :      */
     273            0 :     bool timeToSend()
     274              :     {
     275            0 :         if( m_messageDelay <= 0 )
     276              :         {
     277            0 :             if( m_messageCount == 0 )
     278              :             {
     279            0 :                 return true;
     280              :             }
     281              :             else
     282              :             {
     283            0 :                 return false;
     284              :             }
     285              :         }
     286              :         else
     287              :         {
     288            0 :             if( m_messageCount == 0 || sinceLastMsg() >= m_messageDelay )
     289              :             {
     290            0 :                 return true;
     291              :             }
     292              :             else
     293              :             {
     294            0 :                 return false;
     295              :             }
     296              :         }
     297              :     }
     298              : 
     299              :     /// Set the message delay
     300              :     void messageDelay( double md /**< [in] the new message delay */ )
     301              :     {
     302              :         m_messageDelay = md;
     303              :     }
     304              : 
     305              :     /// Get the message delay
     306              :     double messageDelay()
     307              :     {
     308              :         return m_messageDelay;
     309              :     }
     310              : 
     311              :     /// Set the message count
     312            0 :     void messageCount( int mc /**< [in] the new message count */ )
     313              :     {
     314            0 :         m_messageCount = mc;
     315            0 :     }
     316              : 
     317              :     /// Increment the message count
     318            0 :     int incMessageCount()
     319              :     {
     320            0 :         ++m_messageCount;
     321            0 :         return m_messageCount;
     322              :     }
     323              : 
     324              :     /// Get the message count
     325              :     int messageCount()
     326              :     {
     327              :         return m_messageCount;
     328              :     }
     329              : 
     330              :     /// Set the comparison for this rule
     331          108 :     void comparison( const ruleComparison &c /**< [in] the new comparison*/ )
     332              :     {
     333          108 :         m_comparison = c;
     334          108 :     }
     335              : 
     336              :     /// Get the rule comparison
     337              :     /**
     338              :      * \returns the current rule comparison
     339              :      *
     340              :      */
     341           12 :     const ruleComparison &comparison()
     342              :     {
     343           12 :         return m_comparison;
     344              :     }
     345              : 
     346              :     /// Report whether the rule is valid as configured
     347              :     /** If not valid, the return value is a std::string with the reason.
     348              :      * If valid, the return value is a bool set to true.
     349              :      */
     350              :     virtual boolorerr_t valid() = 0;
     351              : 
     352              :     /// Get the value of this rule
     353              :     /**
     354              :      * \returns the result of the comparison defined by the rule
     355              :      */
     356              :     virtual bool value() = 0;
     357              : 
     358              :     /// Compare two strings
     359              :     /** String comparison can only be Eq or Neq.
     360              :      *
     361              :      * \returns true if the comparison is true
     362              :      * \returns false if the comparison is false
     363              :      * \returns std::string with error message if the comparison is not valid
     364              :      */
     365           22 :     boolorerr_t compTxt( const std::string &str1, ///< [in] the first string to compare
     366              :                          const std::string &str2  ///< [in] the second string to compare
     367              :     )
     368              :     {
     369           22 :         boolorerr_t rv = false;
     370              : 
     371           22 :         switch( m_comparison )
     372              :         {
     373           18 :         case ruleComparison::Eq:
     374           18 :             if( str1 == str2 )
     375           10 :                 rv = true;
     376           18 :             break;
     377            4 :         case ruleComparison::Neq:
     378            4 :             if( str1 != str2 )
     379            2 :                 rv = true;
     380            4 :             break;
     381            0 :         default:
     382            0 :             rv = "operator not valid for string comparison";
     383              :         }
     384              : 
     385           22 :         return rv;
     386            0 :     }
     387              : 
     388              :     /// Compare two switches
     389              :     /** Switch comparison can only be Eq or Neq.
     390              :      *
     391              :      * \returns true if the comparison is true
     392              :      * \returns false if the comparison is false
     393              :      * \returns std::string with error message if the comparison is not valid
     394              :      */
     395           16 :     boolorerr_t compSw( const pcf::IndiElement::SwitchStateType &sw1, ///< [in] the first switch to compare
     396              :                         const pcf::IndiElement::SwitchStateType &sw2  ///< [in] the first switch to compare
     397              :     )
     398              :     {
     399           16 :         boolorerr_t rv = false;
     400              : 
     401           16 :         switch( m_comparison )
     402              :         {
     403            8 :         case ruleComparison::Eq:
     404            8 :             if( sw1 == sw2 )
     405            4 :                 rv = true;
     406            8 :             break;
     407            8 :         case ruleComparison::Neq:
     408            8 :             if( sw1 != sw2 )
     409            4 :                 rv = true;
     410            8 :             break;
     411            0 :         default:
     412            0 :             rv = "operator not valid for switch comparison";
     413              :         }
     414              : 
     415           16 :         return rv;
     416            0 :     }
     417              : 
     418              :     /// Compare two numbers
     419              :     /** The comparison is (num1 comp num2), e.g. (num1 \< num2).
     420              :      * A tolerance is included for floating point equality.
     421              :      *
     422              :      * \returns true if the comparison is true
     423              :      * \returns false if the comparison is false
     424              :      * \returns std::string with error message if the comparison is not valid
     425              :      */
     426           18 :     boolorerr_t compNum( const double &num1, ///< [in] the first number to compare
     427              :                          const double &num2, ///< [in] the second number to compare
     428              :                          const double &tol   ///< [in] the tolerance for the comparison
     429              :     )
     430              :     {
     431           18 :         boolorerr_t rv = false;
     432              : 
     433           18 :         switch( m_comparison )
     434              :         {
     435            6 :         case ruleComparison::Eq:
     436            6 :             if( fabs( num1 - num2 ) <= tol )
     437            3 :                 rv = true;
     438            6 :             break;
     439            0 :         case ruleComparison::Neq:
     440            0 :             if( fabs( num1 - num2 ) > tol )
     441            0 :                 rv = true;
     442            0 :             break;
     443            2 :         case ruleComparison::Lt:
     444            2 :             if( num1 < num2 )
     445            1 :                 rv = true;
     446            2 :             break;
     447            4 :         case ruleComparison::Gt:
     448            4 :             if( num1 > num2 )
     449            2 :                 rv = true;
     450            4 :             break;
     451            3 :         case ruleComparison::LtEq:
     452            3 :             if( fabs( num1 - num2 ) <= tol )
     453            1 :                 rv = true;
     454            2 :             else if( num1 < num2 )
     455            1 :                 rv = true;
     456            3 :             break;
     457            3 :         case ruleComparison::GtEq:
     458            3 :             if( fabs( num1 - num2 ) <= tol )
     459            1 :                 rv = true;
     460            2 :             else if( num1 > num2 )
     461            1 :                 rv = true;
     462            3 :             break;
     463            0 :         default:
     464            0 :             rv = "operator not valid for compNum";
     465              :         }
     466              : 
     467           18 :         return rv;
     468            0 :     }
     469              : 
     470              :     /// Compare two booleans
     471              :     /**
     472              :      * \returns true if the comparison is true
     473              :      * \returns false if the comparison is false
     474              :      * \returns std::string with error message if the comparison is not valid
     475              :      */
     476            9 :     boolorerr_t compBool( const bool &b1, ///< [in] the first bool to compare
     477              :                           const bool &b2  ///< [in] the second bool to compare
     478              :     )
     479              :     {
     480            9 :         boolorerr_t rv = false;
     481              : 
     482            9 :         switch( m_comparison )
     483              :         {
     484            0 :         case ruleComparison::Eq:
     485            0 :             if( b1 == b2 )
     486            0 :                 rv = true;
     487            0 :             break;
     488            0 :         case ruleComparison::Neq:
     489            0 :             if( b1 != b2 )
     490            0 :                 rv = true;
     491            0 :             break;
     492            5 :         case ruleComparison::And:
     493            5 :             if( b1 && b2 )
     494            2 :                 rv = true;
     495            5 :             break;
     496            0 :         case ruleComparison::Nand:
     497            0 :             if( !( b1 && b2 ) )
     498            0 :                 rv = true;
     499            0 :             break;
     500            4 :         case ruleComparison::Or:
     501            4 :             if( b1 || b2 )
     502            3 :                 rv = true;
     503            4 :             break;
     504            0 :         case ruleComparison::Nor:
     505            0 :             if( !b1 && !b2 )
     506            0 :                 rv = true;
     507            0 :             break;
     508            0 :         case ruleComparison::Imply:
     509              :             // https://en.wikipedia.org/wiki/Material_conditional
     510            0 :             if( !b1 || b2 )
     511            0 :                 rv = true;
     512            0 :             break;
     513            0 :         case ruleComparison::Nimply:
     514              :             // https://en.wikipedia.org/wiki/Material_nonimplication
     515            0 :             if( b1 && !b2 )
     516            0 :                 rv = true;
     517            0 :             break;
     518            0 :         default:
     519            0 :             rv = "operator not valid for ruleCompRule";
     520              :         }
     521              : 
     522            9 :         return rv;
     523            0 :     }
     524              : };
     525              : 
     526              : /// A rule base class for testing an element in one property
     527              : struct onePropRule : public indiCompRule
     528              : {
     529              : 
     530              :   protected:
     531              :     int m_type; ///< The property type, from pcf::IndiProperty::Type
     532              : 
     533              :     pcf::IndiProperty *m_property{ nullptr }; ///< Pointer to the property
     534              : 
     535              :     std::string m_element; ///< The element name within the property
     536              : 
     537              :   public:
     538              :     // Default c'tor is deleted, you must supply the property type
     539              :     onePropRule() = delete;
     540              : 
     541              :     /// Constructor.  You must provide the property type to construct a onePropRule
     542           58 :     explicit onePropRule( int type ) : m_type( type /**< The property type, from pcf::IndiProperty::Type*/ )
     543              :     {
     544           58 :     }
     545              : 
     546              :     /// Set the property pointer
     547              :     /**
     548              :      * \throws mx::err::invalidarg if \p property is nullptr
     549              :      * \throws mx::err::invalidconfig if the supplied property has the wrong type
     550              :      */
     551           58 :     void property( pcf::IndiProperty *property /**< [in] the new property pointer*/ )
     552              :     {
     553           58 :         if( property == nullptr )
     554              :         {
     555            0 :             throw mx::exception( mx::error_t::invalidarg, "property is nullptr" );
     556              :         }
     557              : 
     558           58 :         if( property->getType() != m_type )
     559              :         {
     560            0 :             throw mx::exception( mx::error_t::invalidconfig, "property is not correct type" );
     561              :         }
     562              : 
     563           58 :         m_property = property;
     564           58 :     }
     565              : 
     566              :     /// Get the property pointer
     567              :     /**
     568              :      * \returns the current value of m_property
     569              :      */
     570            8 :     const pcf::IndiProperty *property()
     571              :     {
     572            8 :         return m_property;
     573              :     }
     574              : 
     575              :     /// Set the element name
     576           58 :     void element( const std::string &el /**< [in] the new element name*/ )
     577              :     {
     578           58 :         m_element = el;
     579           58 :     }
     580              : 
     581              :     /// Get the element name
     582              :     /**
     583              :      * \returns the current value of m_element
     584              :      */
     585            8 :     const std::string &element()
     586              :     {
     587            8 :         return m_element;
     588              :     }
     589              : 
     590              :     /// Check if this rule is valid
     591              :     /** The rule is valid if the property pointer is not null, and the element
     592              :      * is contained within the property.
     593              :      *
     594              :      * If not valid, the return value is a std::string with the reason.
     595              :      * If valid, the return value is a bool set to true.
     596              :      */
     597           64 :     virtual boolorerr_t valid()
     598              :     {
     599           64 :         boolorerr_t rv;
     600           64 :         if( m_property == nullptr )
     601              :         {
     602            0 :             rv = "property is null";
     603              :         }
     604           64 :         else if( !m_property->find( m_element ) )
     605              :         {
     606            0 :             rv = "element is not found";
     607              :         }
     608              :         else
     609              :         {
     610           64 :             rv = true;
     611              :         }
     612              : 
     613           64 :         return rv;
     614            0 :     }
     615              : };
     616              : 
     617              : /// A rule base class for testing elements in two properties
     618              : struct twoPropRule : public indiCompRule
     619              : {
     620              : 
     621              :   protected:
     622              :     int m_type; ///< The property type, from pcf::IndiProperty::Type
     623              : 
     624              :     pcf::IndiProperty *m_property1{ nullptr }; ///< Pointer to the first property
     625              : 
     626              :     std::string m_element1; ///< The element name within the first property
     627              : 
     628              :     pcf::IndiProperty *m_property2{ nullptr }; ///< Pointer to the second property
     629              : 
     630              :     std::string m_element2; ///< The element name within the second property
     631              : 
     632              :   public:
     633              :     // Default c'tor is deleted, you must supply the property type
     634              :     twoPropRule() = delete;
     635              : 
     636              :     /// Constructor.  You must provide the property type to construct a twoPropRule
     637           18 :     explicit twoPropRule( int type ) : m_type( type /**< The property type, from pcf::IndiProperty::Type*/ )
     638              :     {
     639           18 :     }
     640              : 
     641              :     /// Set the first property pointer
     642              :     /**
     643              :      * \throws mx::err::invalidarg if \p property is nullptr
     644              :      * \throws mx::err::invalidconfig if the supplied property has the wrong type
     645              :      */
     646           18 :     void property1( pcf::IndiProperty *property /**< [in] the new property pointer*/ )
     647              :     {
     648           18 :         if( property == nullptr )
     649              :         {
     650            0 :             throw mx::exception( mx::error_t::invalidarg, "property is nullptr" );
     651              :         }
     652              : 
     653           18 :         if( property->getType() != m_type )
     654              :         {
     655            0 :             throw mx::exception( mx::error_t::invalidconfig, "property is not correct type" );
     656              :         }
     657              : 
     658           18 :         m_property1 = property;
     659           18 :     }
     660              : 
     661              :     /// Get the first property pointer
     662              :     /**
     663              :      * \returns the current value of m_property1
     664              :      */
     665            3 :     const pcf::IndiProperty *property1()
     666              :     {
     667            3 :         return m_property1;
     668              :     }
     669              : 
     670              :     /// Set the first element name
     671           18 :     void element1( const std::string &el /**< [in] the new element name*/ )
     672              :     {
     673           18 :         m_element1 = el;
     674           18 :     }
     675              : 
     676              :     /// Get the first element name
     677              :     /**
     678              :      * \returns the current value of m_element1
     679              :      */
     680            3 :     const std::string &element1()
     681              :     {
     682            3 :         return m_element1;
     683              :     }
     684              : 
     685              :     /// Set the second property pointer
     686              :     /**
     687              :      * \throws mx::err::invalidarg if \p property is nullptr
     688              :      * \throws mx::err::invalidconfig if the supplied property has the wrong type
     689              :      */
     690           18 :     void property2( pcf::IndiProperty *property /**< [in] the new property pointer*/ )
     691              :     {
     692           18 :         if( property == nullptr )
     693              :         {
     694            0 :             throw mx::exception( mx::error_t::invalidarg, "property is nullptr" );
     695              :         }
     696              : 
     697           18 :         if( property->getType() != m_type )
     698              :         {
     699            0 :             throw mx::exception( mx::error_t::invalidconfig, "property is not correct type" );
     700              :         }
     701              : 
     702           18 :         m_property2 = property;
     703           18 :     }
     704              : 
     705              :     /// Get the second property pointer
     706              :     /**
     707              :      * \returns the current value of m_property2
     708              :      */
     709            3 :     const pcf::IndiProperty *property2()
     710              :     {
     711            3 :         return m_property2;
     712              :     }
     713              : 
     714              :     /// Set the second element name
     715           18 :     void element2( const std::string &el /**< [in] the new element name*/ )
     716              :     {
     717           18 :         m_element2 = el;
     718           18 :     }
     719              : 
     720              :     /// Get the second element name
     721              :     /**
     722              :      * \returns the current value of m_element2
     723              :      */
     724            3 :     const std::string &element2()
     725              :     {
     726            3 :         return m_element2;
     727              :     }
     728              : 
     729              :     /// Check if this rule is valid
     730              :     /** The rule is valid if both property pointers are not null, and the elements
     731              :      * are contained within their respective properties.
     732              :      *
     733              :      * If not valid, the return value is a std::string with the reason.
     734              :      * If valid, the return value is a bool set to true.
     735              :      */
     736           14 :     virtual boolorerr_t valid()
     737              :     {
     738           14 :         boolorerr_t rv;
     739              : 
     740           14 :         if( m_property1 == nullptr )
     741              :         {
     742            0 :             rv = "property1 is null";
     743            0 :             return rv;
     744              :         }
     745              : 
     746           14 :         if( !m_property1->find( m_element1 ) )
     747              :         {
     748            0 :             rv = "element1 is not found";
     749            0 :             return rv;
     750              :         }
     751              : 
     752           14 :         if( m_property2 == nullptr )
     753              :         {
     754            0 :             rv = "property2 is null";
     755            0 :             return rv;
     756              :         }
     757              : 
     758           14 :         if( !m_property2->find( m_element2 ) )
     759              :         {
     760            0 :             rv = "element2 is not found";
     761            0 :             return rv;
     762              :         }
     763              : 
     764           14 :         rv = true;
     765              : 
     766           14 :         return rv;
     767            0 :     }
     768              : };
     769              : 
     770              : /// Compare the value of a number element to a target
     771              : /**
     772              :  */
     773              : struct numValRule : public onePropRule
     774              : {
     775              : 
     776              :   public:
     777              :     /// Name of this rule, used by config system
     778              :     static constexpr char name[] = "numVal";
     779              : 
     780              :   protected:
     781              :     double m_target{ 0 }; ///< The target value for comparison
     782              :     double m_tol{ 1e-6 }; ///< The tolerance for the comparison
     783              : 
     784              :   public:
     785              :     /// Default c'tor.
     786           16 :     numValRule() : onePropRule( pcf::IndiProperty::Number )
     787              :     {
     788           16 :     }
     789              : 
     790              :     /// Set the target for the comparison
     791           16 :     void target( const double &tgt /**< [in] The new target*/ )
     792              :     {
     793           16 :         m_target = tgt;
     794           16 :     }
     795              : 
     796              :     /// Get the target
     797              :     /**
     798              :      * \returns the current value of m_target
     799              :      */
     800            6 :     const double &target()
     801              :     {
     802            6 :         return m_target;
     803              :     }
     804              : 
     805              :     /// Set the tolerance
     806              :     /** This is used for equality comparison to allow for floating point precision
     807              :      * and text conversions in INDI.  Set to 0 for strict comparison.
     808              :      *
     809              :      * \throws mx::err:invalidarg if the new value is negative
     810              :      */
     811            3 :     void tol( const double &t /**< [in] the new tolerance*/ )
     812              :     {
     813            3 :         if( t < 0 )
     814              :         {
     815            0 :             throw mx::exception( mx::error_t::invalidarg, "tolerance can't be negative" );
     816              :         }
     817              : 
     818            3 :         m_tol = t;
     819            3 :     }
     820              : 
     821              :     /// Get the tolerance
     822              :     /**
     823              :      * \returns the current value of m_tol
     824              :      */
     825            6 :     const double &tol()
     826              :     {
     827            6 :         return m_tol;
     828              :     }
     829              : 
     830              :     /// Get the value of this rule
     831              :     /** First checks if the rule is currently valid.  The performs the comparison and returns the result.
     832              :      *
     833              :      * \returns the value of the comparison, true or false
     834              :      *
     835              :      * \throws mx::err::invalidconfig if the rule is not currently valid
     836              :      * \throws mx::err::invalidconfig on an error from the comparison
     837              :      *
     838              :      */
     839           14 :     virtual bool value()
     840              :     {
     841           14 :         boolorerr_t rv = valid();
     842           14 :         if( isError( rv ) )
     843              :         {
     844            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
     845              :         }
     846              : 
     847           14 :         double val = ( *m_property )[m_element].get<double>();
     848              : 
     849           14 :         rv = compNum( val, m_target, m_tol );
     850           14 :         if( isError( rv ) )
     851              :         {
     852            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
     853              :         }
     854              : 
     855           28 :         return std::get<bool>( rv );
     856           14 :     }
     857              : };
     858              : 
     859              : /// Compare the value of a text element to a target value
     860              : /** Can only be Eq or Neq.
     861              :  */
     862              : struct txtValRule : public onePropRule
     863              : {
     864              : 
     865              :   public:
     866              :     /// Name of this rule, used by config system
     867              :     static constexpr char name[] = "txtVal";
     868              : 
     869              :   protected:
     870              :     std::string m_target; ///< The target value for comparison
     871              : 
     872              :   public:
     873              :     /// Default c'tor.
     874           27 :     txtValRule() : onePropRule( pcf::IndiProperty::Text )
     875              :     {
     876           27 :     }
     877              : 
     878              :     /// Set the target for the comparison
     879           27 :     void target( const std::string &target /**< [in] The new target*/ )
     880              :     {
     881           27 :         m_target = target;
     882           27 :     }
     883              : 
     884              :     /// Get the target
     885              :     /**
     886              :      * \returns the current value of m_target
     887              :      */
     888           11 :     const std::string &target()
     889              :     {
     890           11 :         return m_target;
     891              :     }
     892              : 
     893              :     /// Get the value of this rule
     894              :     /** First checks if the rule is currently valid.  The performs the comparison and returns the result.
     895              :      *
     896              :      * \returns the value of the comparison, true or false
     897              :      *
     898              :      * \throws mx::err::invalidconfig if the rule is not currently valid
     899              :      * \throws mx::err::invalidconfig on an error from the comparison
     900              :      *
     901              :      */
     902           18 :     virtual bool value()
     903              :     {
     904           18 :         boolorerr_t rv = valid();
     905           18 :         if( isError( rv ) )
     906              :         {
     907            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
     908              :         }
     909              : 
     910           18 :         rv = compTxt( ( *m_property )[m_element].get(), m_target );
     911           18 :         if( isError( rv ) )
     912              :         {
     913            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
     914              :         }
     915              : 
     916           36 :         return std::get<bool>( rv );
     917           18 :     }
     918              : };
     919              : 
     920              : /// Compare the value of a switch to a target value
     921              : /** Can only be Eq or Neq to On or Off.
     922              :  */
     923              : struct swValRule : public onePropRule
     924              : {
     925              : 
     926              :   public:
     927              :     /// Name of this rule, used by config system
     928              :     static constexpr char name[] = "swVal";
     929              : 
     930              :   protected:
     931              :     pcf::IndiElement::SwitchStateType m_target{
     932              :         pcf::IndiElement::UnknownSwitchState }; ///< The target value for comparison
     933              : 
     934              :   public:
     935              :     /// Default c'tor.
     936           11 :     swValRule() : onePropRule( pcf::IndiProperty::Switch )
     937              :     {
     938           11 :     }
     939              : 
     940              :     /// Set the target for the comparison
     941              :     void target( const pcf::IndiElement::SwitchStateType &ss /**< [in] The new target*/ )
     942              :     {
     943              :         m_target = ss;
     944              :     }
     945              : 
     946              :     /// Set the target for the comparison
     947              :     /** This version provided for config file processing.
     948              :      *
     949              :      * \throws mx::err::invalidarg if switchState is something other than "On" or Off
     950              :      */
     951           11 :     void target( const std::string &switchState /**< [in] The new target*/ )
     952              :     {
     953           11 :         if( switchState == "On" )
     954              :         {
     955            6 :             m_target = pcf::IndiElement::On;
     956              :         }
     957            5 :         else if( switchState == "Off" )
     958              :         {
     959            5 :             m_target = pcf::IndiElement::Off;
     960              :         }
     961              :         else
     962              :         {
     963            0 :             throw mx::exception( mx::error_t::invalidarg, "invalid switch state" );
     964              :         }
     965           11 :     }
     966              : 
     967              :     /// Get the target
     968              :     /**
     969              :      * \returns the current value of m_target
     970              :      */
     971            2 :     const pcf::IndiElement::SwitchStateType &target()
     972              :     {
     973            2 :         return m_target;
     974              :     }
     975              : 
     976              :     /// Get the value of this rule
     977              :     /** First checks if the rule is currently valid.  The performs the comparison and returns the result.
     978              :      *
     979              :      * \returns the value of the comparison, true or false
     980              :      *
     981              :      * \throws mx::err::invalidconfig if the rule is not currently valid
     982              :      * \throws mx::err::invalidconfig on an error from the comparison
     983              :      *
     984              :      */
     985            8 :     virtual bool value()
     986              :     {
     987            8 :         boolorerr_t rv = valid();
     988            8 :         if( isError( rv ) )
     989              :         {
     990            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
     991              :         }
     992              : 
     993            8 :         rv = compSw( ( *m_property )[m_element].getSwitchState(), m_target );
     994            8 :         if( isError( rv ) )
     995              :         {
     996            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
     997              :         }
     998              : 
     999           16 :         return std::get<bool>( rv );
    1000            8 :     }
    1001              : };
    1002              : 
    1003              : /// Compare the difference in time between a value and now
    1004              : /** Now is the time of evaluation of the rule
    1005              :  */
    1006              : struct timeDiffRule : public onePropRule
    1007              : {
    1008              : 
    1009              :   public:
    1010              :     /// Name of this rule, used by config system
    1011              :     static constexpr char name[] = "timeDiff";
    1012              : 
    1013              :   protected:
    1014              :     double m_target{ 0 }; ///< The target value for comparison
    1015              :     double m_tol{ 1e-6 }; ///< The tolerance for the comparison
    1016              : 
    1017              :   public:
    1018              :     /// Default c'tor.
    1019            4 :     timeDiffRule() : onePropRule( pcf::IndiProperty::Number )
    1020              :     {
    1021            4 :     }
    1022              : 
    1023              :     /// Set the target for the comparison
    1024            4 :     void target( const double &tgt /**< [in] The new target*/ )
    1025              :     {
    1026            4 :         m_target = tgt;
    1027            4 :     }
    1028              : 
    1029              :     /// Get the target
    1030              :     /**
    1031              :      * \returns the current value of m_target
    1032              :      */
    1033            2 :     const double &target()
    1034              :     {
    1035            2 :         return m_target;
    1036              :     }
    1037              : 
    1038              :     /// Set the tolerance
    1039              :     /** This is used for equality comparison to allow for floating point precision
    1040              :      * and text conversions in INDI.  Set to 0 for strict comparison.
    1041              :      *
    1042              :      * \throws mx::err:invalidarg if the new value is negative
    1043              :      */
    1044            2 :     void tol( const double &t /**< [in] the new tolerance*/ )
    1045              :     {
    1046            2 :         if( t < 0 )
    1047              :         {
    1048            0 :             throw mx::exception( mx::error_t::invalidarg, "tolerance can't be negative" );
    1049              :         }
    1050              : 
    1051            2 :         m_tol = t;
    1052            2 :     }
    1053              : 
    1054              :     /// Get the tolerance
    1055              :     /**
    1056              :      * \returns the current value of m_tol
    1057              :      */
    1058            2 :     const double &tol()
    1059              :     {
    1060            2 :         return m_tol;
    1061              :     }
    1062              : 
    1063              :     /// Get the value of this rule
    1064              :     /** First checks if the rule is currently valid.  The performs the comparison and returns the result.
    1065              :      *
    1066              :      * \returns the value of the comparison, true or false
    1067              :      *
    1068              :      * \throws mx::err::invalidconfig if the rule is not currently valid
    1069              :      * \throws mx::err::invalidconfig on an error from the comparison
    1070              :      *
    1071              :      */
    1072            2 :     virtual bool value()
    1073              :     {
    1074            2 :         boolorerr_t rv = valid();
    1075            2 :         if( isError( rv ) )
    1076              :         {
    1077            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1078              :         }
    1079              : 
    1080              :         timespec now;
    1081            2 :         clock_gettime( CLOCK_ISIO, &now );
    1082              : 
    1083            2 :         double val = ( 1.0 * now.tv_sec + now.tv_nsec / 1e9 ) - ( *m_property )[m_element].get<double>();
    1084              : 
    1085            2 :         rv = compNum( val, m_target, m_tol );
    1086            2 :         if( isError( rv ) )
    1087              :         {
    1088            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1089              :         }
    1090              : 
    1091            4 :         return std::get<bool>( rv );
    1092            2 :     }
    1093              : };
    1094              : 
    1095              : /// Compare two elements based on their numeric values
    1096              : struct elCompNumRule : public twoPropRule
    1097              : {
    1098              : 
    1099              :   public:
    1100              :     /// Name of this rule, used by config system
    1101              :     static constexpr char name[] = "elCompNum";
    1102              : 
    1103              :   protected:
    1104              :     double m_tol{ 1e-6 }; ///< The tolerance for the comparison
    1105              : 
    1106              :   public:
    1107              :     /// Default c'tor.
    1108            3 :     elCompNumRule() : twoPropRule( pcf::IndiProperty::Number )
    1109              :     {
    1110            3 :     }
    1111              : 
    1112              :     /// Set the tolerance
    1113              :     /** This is used for equality comparison to allow for floating point precision
    1114              :      * and text conversions in INDI.  Set to 0 for strict comparison.
    1115              :      *
    1116              :      * \throws mx::err:invalidarg if the new value is negative
    1117              :      */
    1118              :     void tol( const double &t /**< [in] the new tolerance*/ )
    1119              :     {
    1120              :         if( t < 0 )
    1121              :         {
    1122              :             throw mx::exception( mx::error_t::invalidarg, "tolerance can't be negative" );
    1123              :         }
    1124              : 
    1125              :         m_tol = t;
    1126              :     }
    1127              : 
    1128              :     /// Get the tolerance
    1129              :     /**
    1130              :      * \returns the current value of m_tol
    1131              :      */
    1132              :     const double &tol()
    1133              :     {
    1134              :         return m_tol;
    1135              :     }
    1136              : 
    1137              :     /// Get the value of this rule
    1138              :     /** First checks if the rule is currently valid.  The performs the comparison and returns the result.
    1139              :      *
    1140              :      * \returns the value of the comparison, true or false
    1141              :      *
    1142              :      * \throws mx::err::invalidconfig if the rule is not currently valid
    1143              :      * \throws mx::err::invalidconfig on an error from the comparison
    1144              :      *
    1145              :      */
    1146            2 :     virtual bool value()
    1147              :     {
    1148            2 :         boolorerr_t rv = valid();
    1149            2 :         if( isError( rv ) )
    1150              :         {
    1151            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1152              :         }
    1153              : 
    1154            2 :         rv = compNum( ( *m_property1 )[m_element1].get<double>(), ( *m_property2 )[m_element2].get<double>(), m_tol );
    1155            2 :         if( isError( rv ) )
    1156              :         {
    1157            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1158              :         }
    1159              : 
    1160            4 :         return std::get<bool>( rv );
    1161            2 :     }
    1162              : };
    1163              : 
    1164              : /// Compare two elements based on their text values
    1165              : struct elCompTxtRule : public twoPropRule
    1166              : {
    1167              :   public:
    1168              :     /// Name of this rule, used by config system
    1169              :     static constexpr char name[] = "elCompTxt";
    1170              : 
    1171              :     /// Default c'tor.
    1172            5 :     elCompTxtRule() : twoPropRule( pcf::IndiProperty::Text )
    1173              :     {
    1174            5 :     }
    1175              : 
    1176              :     /// Get the value of this rule
    1177              :     /** First checks if the rule is currently valid.  The performs the comparison and returns the result.
    1178              :      *
    1179              :      * \returns the value of the comparison, true or false
    1180              :      *
    1181              :      * \throws mx::err::invalidconfig if the rule is not currently valid
    1182              :      * \throws mx::err::invalidconfig on an error from the comparison
    1183              :      *
    1184              :      */
    1185            4 :     virtual bool value()
    1186              :     {
    1187            4 :         boolorerr_t rv = valid();
    1188            4 :         if( isError( rv ) )
    1189              :         {
    1190            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1191              :         }
    1192              : 
    1193            4 :         rv = compTxt( ( *m_property1 )[m_element1].get(), ( *m_property2 )[m_element2].get() );
    1194            4 :         if( isError( rv ) )
    1195              :         {
    1196            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1197              :         }
    1198              : 
    1199            8 :         return std::get<bool>( rv );
    1200            4 :     }
    1201              : };
    1202              : 
    1203              : /// Compare two elements based on their switch values
    1204              : struct elCompSwRule : public twoPropRule
    1205              : {
    1206              : 
    1207              :   public:
    1208              :     /// Name of this rule, used by config system
    1209              :     static constexpr char name[] = "elCompSw";
    1210              : 
    1211              :     /// Default c'tor.
    1212           10 :     elCompSwRule() : twoPropRule( pcf::IndiProperty::Switch )
    1213              :     {
    1214           10 :     }
    1215              : 
    1216              :     /// Get the value of this rule
    1217              :     /** First checks if the rule is currently valid.  The performs the comparison and returns the result.
    1218              :      *
    1219              :      * \returns the value of the comparison, true or false
    1220              :      *
    1221              :      * \throws mx::err::invalidconfig if the rule is not currently valid
    1222              :      * \throws mx::err::invalidconfig on an error from the comparison
    1223              :      *
    1224              :      */
    1225            8 :     virtual bool value()
    1226              :     {
    1227            8 :         boolorerr_t rv = valid();
    1228            8 :         if( isError( rv ) )
    1229              :         {
    1230            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1231              :         }
    1232              : 
    1233            8 :         rv = compSw( ( *m_property1 )[m_element1].getSwitchState(), ( *m_property2 )[m_element2].getSwitchState() );
    1234            8 :         if( isError( rv ) )
    1235              :         {
    1236            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1237              :         }
    1238              : 
    1239           16 :         return std::get<bool>( rv );
    1240            8 :     }
    1241              : };
    1242              : 
    1243              : /// A rule to compare two rules
    1244              : /**
    1245              :  *
    1246              :  */
    1247              : struct ruleCompRule : public indiCompRule
    1248              : {
    1249              : 
    1250              :   public:
    1251              :     /// Name of this rule, used by config system
    1252              :     static constexpr char name[] = "ruleComp";
    1253              : 
    1254              :   protected:
    1255              :     indiCompRule *m_rule1{ nullptr }; ///< rule one
    1256              :     indiCompRule *m_rule2{ nullptr }; ///< rule two
    1257              : 
    1258              :   public:
    1259              :     /// Default c'tor
    1260              :     /** Changes default comparison to And for ruleCompRule
    1261              :      */
    1262           16 :     ruleCompRule()
    1263           16 :     {
    1264           16 :         comparison( ruleComparison::And );
    1265           16 :     }
    1266              : 
    1267              :     /// Set the pointer to the first rule
    1268           12 :     void rule1( indiCompRule *r /**< [in] the new pointer to rule1*/ )
    1269              :     {
    1270           12 :         m_rule1 = r;
    1271           12 :     }
    1272              : 
    1273              :     /// Get the pointer to the first rule
    1274              :     /**
    1275              :      * \returns the current value of m_rule1
    1276              :      */
    1277            2 :     const indiCompRule *rule1()
    1278              :     {
    1279            2 :         return m_rule1;
    1280              :     }
    1281              : 
    1282              :     /// Set the pointer to the second rule
    1283           12 :     void rule2( indiCompRule *r /**< [in] the new pointer to rule2*/ )
    1284              :     {
    1285           12 :         m_rule2 = r;
    1286           12 :     }
    1287              : 
    1288              :     /// Get the pointer to the first rule
    1289              :     /**
    1290              :      * \returns the current value of m_rule2
    1291              :      */
    1292            2 :     const indiCompRule *rule2()
    1293              :     {
    1294            2 :         return m_rule2;
    1295              :     }
    1296              : 
    1297              :     /// Check if this rule is valid
    1298              :     /** The rule is valid if the rule pointers are not nullptr, and if each rule is itself valid.
    1299              :      *
    1300              :      * If not valid, the return value is a std::string with the reason.
    1301              :      * If valid, the return value is a bool set to true.
    1302              :      */
    1303           13 :     virtual boolorerr_t valid()
    1304              :     {
    1305           13 :         boolorerr_t rv;
    1306           13 :         if( m_rule1 == nullptr )
    1307              :         {
    1308            0 :             rv = "rule1 is nullptr";
    1309              :         }
    1310           13 :         else if( m_rule2 == nullptr )
    1311              :         {
    1312            0 :             rv = "rule2 is nullptr";
    1313              :         }
    1314              :         else
    1315              :         {
    1316           13 :             rv = m_rule1->valid();
    1317           13 :             if( isError( rv ) )
    1318              :             {
    1319            0 :                 return rv;
    1320              :             }
    1321              : 
    1322           13 :             rv = m_rule2->valid();
    1323           13 :             if( isError( rv ) )
    1324              :             {
    1325            0 :                 return rv;
    1326              :             }
    1327              : 
    1328           13 :             rv = true;
    1329              :         }
    1330              : 
    1331           13 :         return rv;
    1332            0 :     }
    1333              : 
    1334              :     /// Get the value of this rule
    1335              :     /** First checks if the rule is currently valid.  The performs the comparison and returns the result.
    1336              :      *
    1337              :      * \returns the value of the comparison, true or false
    1338              :      *
    1339              :      * \throws mx::err::invalidconfig if the rule is not currently valid
    1340              :      * \throws mx::err::invalidconfig on an error from the comparison
    1341              :      *
    1342              :      */
    1343            9 :     virtual bool value()
    1344              :     {
    1345            9 :         boolorerr_t rv = valid();
    1346            9 :         if( isError( rv ) )
    1347              :         {
    1348            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1349              :         }
    1350              : 
    1351            9 :         rv = compBool( m_rule1->value(), m_rule2->value() );
    1352            9 :         if( isError( rv ) )
    1353              :         {
    1354            0 :             throw mx::exception( mx::error_t::invalidconfig, std::get<std::string>( rv ) );
    1355              :         }
    1356              : 
    1357           18 :         return std::get<bool>( rv );
    1358            9 :     }
    1359              : };
    1360              : 
    1361              : #endif // stateRuleEngine_indiCompRules_hpp
        

Generated by: LCOV version 2.0-1