API
 
Loading...
Searching...
No Matches
stateRuleEngine.hpp
Go to the documentation of this file.
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
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
28namespace MagAOX
29{
30namespace app
31{
32
33/// The MagAO-X stateRuleEngine
34/**
35 * \ingroup stateRuleEngine
36 */
37class stateRuleEngine : public MagAOXApp<true>
38{
39
40 // Give the test harness access.
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 /// Owns the configured rules and subscribed INDI property objects.
53
54 ///@}
55
56 /// Get the published rule-state property for a reporting priority.
57 pcf::IndiProperty *
58 ruleStateProperty( const rulePriority &priority /**< [in] the reporting priority for the rule */ );
59
60 /// Get the notification label for a reporting priority.
61 static std::string
62 notificationLabel( const rulePriority &priority /**< [in] the reporting priority for the rule */ );
63
64 /// Report whether a published rule element is currently `On`.
65 static bool ruleIsOn( pcf::IndiProperty &property, /**< [in] the published property to inspect */
66 const std::string &ruleName /**< [in] the rule element name to inspect */ );
67
68 /// Format a notification message for a rule.
69 static std::string notificationMessage(
70 const std::string &ruleName, /**< [in] the rule name used as fallback text */
71 indiCompRule &rule, /**< [in] the rule whose message text is used */
72 const std::string &label, /**< [in] the label prefix, e.g. `INFO` */
73 bool cleared = false, /**< [in] true when formatting a clear notification */
74 bool settime = false /**< [in] true when reading the rule message should set its send time */ );
75
76 /// Send one formatted notification through the INDI driver.
77 virtual int sendNotification( const std::string &message /**< [in] the fully formatted notification message */ );
78
79 public:
80 /// Default c'tor.
82
83 /// D'tor, declared and defined for noexcept.
87
88 virtual void setupConfig();
89
90 /// Implementation of loadConfig logic, separated for testing.
91 /** This is called by loadConfig().
92 */
94 mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
95
96 virtual void loadConfig();
97
98 /// Startup function
99 /**
100 *
101 */
102 virtual int appStartup();
103
104 /// Implementation of the FSM for stateRuleEngine.
105 /**
106 * \returns 0 on no critical error
107 * \returns -1 on an error requiring shutdown
108 */
109 virtual int appLogic();
110
111 /// Shutdown the app.
112 /**
113 *
114 */
115 virtual int appShutdown();
116
117 /// The static callback function to be registered for rule properties
118 /**
119 *
120 * \returns 0 on success.
121 * \returns -1 on error.
122 */
123 static int st_newCallBack_ruleProp(
124 void *app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
125 const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
126 );
127
128 /// Callback to process a NEW preset position request
129 /**
130 * \returns 0 on success.
131 * \returns -1 on error.
132 */
134 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/ );
135
136 /// Published `info`-priority rule states.
137 pcf::IndiProperty m_indiP_info;
138
139 /// Published `caution`-priority rule states.
140 pcf::IndiProperty m_indiP_caution;
141
142 /// Published `warning`-priority rule states.
143 pcf::IndiProperty m_indiP_warning;
144
145 /// Published `alert`-priority rule states.
146 pcf::IndiProperty m_indiP_alert;
147};
148
149stateRuleEngine::stateRuleEngine() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
150{
151 return;
152}
153
154pcf::IndiProperty *stateRuleEngine::ruleStateProperty( const rulePriority &priority )
155{
156 if( priority == rulePriority::info )
157 {
158 return &m_indiP_info;
159 }
160
161 if( priority == rulePriority::caution )
162 {
163 return &m_indiP_caution;
164 }
165
166 if( priority == rulePriority::warning )
167 {
168 return &m_indiP_warning;
169 }
170
171 if( priority == rulePriority::alert )
172 {
173 return &m_indiP_alert;
174 }
175
176 return nullptr;
177}
178
180{
181 if( priority == rulePriority::info )
182 {
183 return "INFO";
184 }
185
186 if( priority == rulePriority::caution )
187 {
188 return "CAUTION";
189 }
190
191 if( priority == rulePriority::warning )
192 {
193 return "WARNING";
194 }
195
196 if( priority == rulePriority::alert )
197 {
198 return "ALERT";
199 }
200
201 return "INFO";
202}
203
204bool stateRuleEngine::ruleIsOn( pcf::IndiProperty &property, const std::string &ruleName )
205{
206 if( !property.find( ruleName ) )
207 {
208 return false;
209 }
210
211 return property[ruleName].getSwitchState() == pcf::IndiElement::On;
212}
213
215 const std::string &ruleName, indiCompRule &rule, const std::string &label, bool cleared, bool settime )
216{
217 std::string detail;
218 if( settime )
219 {
220 detail = rule.message( true );
221 }
222 else
223 {
224 detail = rule.message();
225 }
226
227 if( detail == "" )
228 {
229 detail = ruleName;
230 }
231
232 if( cleared )
233 {
234 detail = std::format( "Cleared: {}", detail );
235 }
236
237 return std::format( "{}: {}", label, detail );
238}
239
240int stateRuleEngine::sendNotification( const std::string &message )
241{
242 if( m_indiDriver == nullptr )
243 {
244 return 0;
245 }
246
247 pcf::IndiProperty ip;
248 ip.setDevice( m_configName );
249 ip.setMessage( message );
250
251 try
252 {
253 m_indiDriver->sendMessage( ip );
254 }
255 catch( const std::exception &e )
256 {
257 return log<software_error, -1>( std::format( "exception caught from sendMessage: {}", e.what() ) );
258 }
259
260 return 0;
261}
262
264{
265 config.add( "rules.dir",
266 "",
267 "rules.dir",
268 argType::Required,
269 "rules",
270 "dir",
271 false,
272 "string",
273 "Directory containing config files containing rules to load. Relative to config directory. If this is "
274 "set, then rules in the device config file are ignored" );
275}
276
277int stateRuleEngine::loadConfigImpl( mx::app::appConfigurator &_config )
278{
279 _config( m_ruleDir, "rules.dir" );
280
281 std::map<std::string, ruleRuleKeys> rrkMap;
282
283 if( m_ruleDir == "" )
284 {
285 try
286 {
289 }
290 catch( const std::exception &e )
291 {
292 return log<software_critical, -1>( std::format( "Rule config exception caught:\n{}", e.what() ) );
293 }
294 }
295 else
296 {
297 std::vector<std::string> conffiles;
298 if( mx::ioutils::getFileNames( conffiles, m_configDir + '/' + m_ruleDir, "", "", ".conf" ) !=
299 mx::error_t::noerror )
300 {
301 return log<software_critical, -1>( "Error reading rules" );
302 }
303
304 for( auto &cnf : conffiles )
305 {
306 // Create a configurator and set it up to log
307 mx::app::appConfigurator fcfg;
308
309 fcfg.m_sources = true;
310 fcfg.configLog = configLog;
311
312 // now process the config file
313 if( fcfg.readConfig( cnf ) < 0 )
314 {
315 return log<software_critical, -1>( std::format( "error reading rule config file: {}", cnf ) );
316 }
317
318 try
319 {
320 // and finally add to our rule map
322 }
323 catch( const std::exception &e )
324 {
325 return log<software_critical, -1>(
326 std::format( "Rule config exception caught from {}:\n{}", cnf, e.what() ) );
327 }
328 }
329
330 try
331 {
333 }
334 catch( const std::exception &e )
335 {
336 return log<software_critical, -1>( std::format( "Error finalizing rules:\n{}", e.what() ) );
337 }
338 }
339
340 return 0;
341}
342
344{
345 if( loadConfigImpl( config ) < 0 )
346 {
347 log<software_critical>( "error in configuration" );
348 m_shutdown = true;
349 }
350}
351
353{
354 for( auto it = m_ruleMaps.rules.begin(); it != m_ruleMaps.rules.end(); ++it )
355 {
356 if( it->second->priority() == rulePriority::info )
357 {
358 if( m_indiP_info.getDevice() != m_configName )
359 {
361 "info",
362 pcf::IndiProperty::Switch,
363 pcf::IndiProperty::ReadOnly,
364 pcf::IndiProperty::Idle,
365 pcf::IndiProperty::AnyOfMany,
366 nullptr ) < 0 )
367 {
368 return log<software_critical, -1>();
369 }
370 }
371
372 m_indiP_info.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
373 m_indiP_info[it->first].setLabel( it->second->message() );
374 }
375
376 if( it->second->priority() == rulePriority::caution )
377 {
378 if( m_indiP_caution.getDevice() != m_configName )
379 {
381 "caution",
382 pcf::IndiProperty::Switch,
383 pcf::IndiProperty::ReadOnly,
384 pcf::IndiProperty::Idle,
385 pcf::IndiProperty::AnyOfMany,
386 nullptr ) < 0 )
387 {
388 return log<software_critical, -1>( { __FILE__, __LINE__ } );
389 }
390 }
391
392 m_indiP_caution.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
393 m_indiP_caution[it->first].setLabel( it->second->message() );
394 }
395
396 if( it->second->priority() == rulePriority::warning )
397 {
398 if( m_indiP_warning.getDevice() != m_configName )
399 {
401 "warning",
402 pcf::IndiProperty::Switch,
403 pcf::IndiProperty::ReadOnly,
404 pcf::IndiProperty::Idle,
405 pcf::IndiProperty::AnyOfMany,
406 nullptr ) < 0 )
407 {
408 return log<software_critical, -1>( { __FILE__, __LINE__ } );
409 }
410 }
411
412 m_indiP_warning.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
413 m_indiP_warning[it->first].setLabel( it->second->message() );
414 }
415
416 if( it->second->priority() == rulePriority::alert )
417 {
418 if( m_indiP_alert.getDevice() != m_configName )
419 {
421 "alert",
422 pcf::IndiProperty::Switch,
423 pcf::IndiProperty::ReadOnly,
424 pcf::IndiProperty::Idle,
425 pcf::IndiProperty::AnyOfMany,
426 nullptr ) < 0 )
427 {
428 return log<software_critical, -1>( { __FILE__, __LINE__ } );
429 }
430 }
431
432 m_indiP_alert.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
433 m_indiP_alert[it->first].setLabel( it->second->message() );
434 }
435 }
436
437 for( auto it = m_ruleMaps.props.begin(); it != m_ruleMaps.props.end(); ++it )
438 {
439 if( it->second == nullptr )
440 continue;
441
442 std::string devName, propName;
443
444 int rv = indi::parseIndiKey( devName, propName, it->first );
445 if( rv != 0 )
446 {
447 log<software_error>( { __FILE__, __LINE__, 0, rv, "error parsing INDI key: " + it->first } );
448 return -1;
449 }
450
452 }
453
455
456 return 0;
457}
458
460{
461 for( auto it = m_ruleMaps.rules.begin(); it != m_ruleMaps.rules.end(); ++it )
462 {
463 if( it->second->priority() != rulePriority::none )
464 {
465 try
466 {
467 bool val = it->second->value();
468 std::string diagnostic;
469 while( it->second->popRuntimeDiagnostic( diagnostic ) )
470 {
472 }
473
474 pcf::IndiProperty *ruleState = ruleStateProperty( it->second->priority() );
475 if( ruleState == nullptr )
476 {
477 continue;
478 }
479
480 bool wasOn = ruleIsOn( *ruleState, it->first );
481
482 pcf::IndiElement::SwitchStateType onoff = pcf::IndiElement::Off;
483
484 if( val )
485 {
486 onoff = pcf::IndiElement::On;
487 }
488
490
491 if( val && it->second->timeToSend() )
492 {
493 std::string msg = notificationMessage(
494 it->first, *( it->second ), notificationLabel( it->second->priority() ), false, true );
495
496 it->second->incMessageCount();
497
498 if( sendNotification( msg ) < 0 )
499 {
500 return -1;
501 }
502 }
503 else if( !val )
504 {
505 if( wasOn )
506 {
507 std::string msg = notificationMessage(
508 it->first, *( it->second ), notificationLabel( rulePriority::info ), true );
509
510 if( sendNotification( msg ) < 0 )
511 {
512 return -1;
513 }
514 }
515
516 it->second->messageCount( 0 ); // resets so that next time will get sent
517 }
518 }
519 catch( const std::exception &e )
520 {
521 std::string diagnostic;
522 while( it->second->popRuntimeDiagnostic( diagnostic ) )
523 {
525 }
526
527 ///\todo how to handle startup vs misconfiguration
528
529 /*
530 if(it->second->priority() == rulePriority::none)
531 {
532 updateSwitchIfChanged(m_indiP_info, it->first, pcf::IndiElement::Off);
533 }*/
534 }
535 }
536 }
537
538 return 0;
539}
540
542{
543 return 0;
544}
545
546int stateRuleEngine::st_newCallBack_ruleProp( void *app, const pcf::IndiProperty &ipRecv )
547{
548 stateRuleEngine *sre = static_cast<stateRuleEngine *>( app );
549
551
552 return 0;
553}
554
555int stateRuleEngine::newCallBack_ruleProp( const pcf::IndiProperty &ipRecv )
556{
557 std::string key = ipRecv.createUniqueKey();
558
559 if( m_ruleMaps.props.count( key ) == 0 )
560 {
561 return 0;
562 }
563
564 if( m_ruleMaps.props[key] == nullptr ) //
565 {
566 return 0;
567 }
568
569 *m_ruleMaps.props[key] = ipRecv;
570
571 return 0;
572}
573
574} // namespace app
575} // namespace MagAOX
576
577#endif // stateRuleEngine_hpp
The base-class for XWCTk applications.
std::string m_configName
The name of the configuration file (minus .conf).
stateCodes::stateCodeT state()
Get the current state code.
int registerIndiPropertyNew(pcf::IndiProperty &prop, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for.
static void configLog(const std::string &name, const int &code, const std::string &value, const std::string &source)
Callback for config system logging.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI switch element value if it has changed.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
std::string m_configDir
The path to configuration files for MagAOX.
int registerIndiPropertySet(pcf::IndiProperty &prop, const std::string &devName, const std::string &propName, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is monitored for updates from others.
The MagAO-X stateRuleEngine.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
pcf::IndiProperty m_indiP_warning
Published warning-priority rule states.
virtual int appStartup()
Startup function.
pcf::IndiProperty m_indiP_caution
Published caution-priority rule states.
static bool ruleIsOn(pcf::IndiProperty &property, const std::string &ruleName)
Report whether a published rule element is currently On.
static std::string notificationMessage(const std::string &ruleName, indiCompRule &rule, const std::string &label, bool cleared=false, bool settime=false)
Format a notification message for a rule.
~stateRuleEngine() noexcept
D'tor, declared and defined for noexcept.
virtual int sendNotification(const std::string &message)
Send one formatted notification through the INDI driver.
indiRuleMaps m_ruleMaps
Owns the configured rules and subscribed INDI property objects.
virtual int appShutdown()
Shutdown the app.
pcf::IndiProperty m_indiP_info
Published info-priority rule states.
static int st_newCallBack_ruleProp(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for rule properties.
pcf::IndiProperty m_indiP_alert
Published alert-priority rule states.
static std::string notificationLabel(const rulePriority &priority)
Get the notification label for a reporting priority.
pcf::IndiProperty * ruleStateProperty(const rulePriority &priority)
Get the published rule-state property for a reporting priority.
int newCallBack_ruleProp(const pcf::IndiProperty &ipRecv)
Callback to process a NEW preset position request.
virtual int appLogic()
Implementation of the FSM for stateRuleEngine.
Configuration of rules for the MagAO-X stateRuleEngine.
void loadRuleConfig(indiRuleMaps &maps, std::map< std::string, ruleRuleKeys > &rrkMap, mx::app::appConfigurator &config)
Load the rule and properties maps for a rule engine from a configuration file.
void finalizeRuleValRules(indiRuleMaps &maps, std::map< std::string, ruleRuleKeys > &rrkMap)
Finalize ruleVal rules.
rulePriority
Reporting priorities for rules.
@ none
Don't publish.
@ caution
Caution – make sure you know what you're doing.
@ warning
Warning – something is probably wrong, you should check.
@ alert
Alert – something is definitely wrong, you should take action.
@ info
For information only.
int parseIndiKey(std::string &devName, std::string &propName, const std::string &key)
Parse an INDI key into the device and property names.
std::stringstream msg
const pcf::IndiProperty & ipRecv
Definition dm.hpp:19
@ READY
The device is ready for operation, but is not operating.
Software CRITICAL log entry.
Software ERR log entry.
Virtual base-class for all rules.
Structure to provide management of the rule and property maps.