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
|