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 :
11 : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
12 : #include "../../magaox_git_version.h"
13 :
14 : #include "indiCompRuleConfig.hpp"
15 :
16 : /** \defgroup stateRuleEngine
17 : * \brief The MagAO-X stateRuleEngine application
18 : *
19 : * <a href="../handbook/operating/software/apps/stateRuleEngine.html">Application Documentation</a>
20 : *
21 : * \ingroup apps
22 : *
23 : */
24 :
25 : /** \defgroup stateRuleEngine_files
26 : * \ingroup stateRuleEngine
27 : */
28 :
29 : namespace MagAOX
30 : {
31 : namespace app
32 : {
33 :
34 : /// The MagAO-X stateRuleEngine
35 : /**
36 : * \ingroup stateRuleEngine
37 : */
38 : class stateRuleEngine : public MagAOXApp<true>
39 : {
40 :
41 : //Give the test harness access.
42 : friend class stateRuleEngine_test;
43 :
44 : protected:
45 :
46 : /** \name Configurable Parameters
47 : *@{
48 : */
49 :
50 : std::string m_ruleDir; /**< Directory containing config files containing rules to load. Relative to config directory. If this is
51 : set, then rules in the device config file are ignored*/
52 :
53 : indiRuleMaps m_ruleMaps;
54 :
55 : ///@}
56 :
57 : public:
58 : /// Default c'tor.
59 : stateRuleEngine();
60 :
61 : /// D'tor, declared and defined for noexcept.
62 0 : ~stateRuleEngine() noexcept
63 0 : {}
64 :
65 : virtual void setupConfig();
66 :
67 : /// Implementation of loadConfig logic, separated for testing.
68 : /** This is called by loadConfig().
69 : */
70 : int loadConfigImpl( 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 :
94 : /// The static callback function to be registered for rule properties
95 : /**
96 : *
97 : * \returns 0 on success.
98 : * \returns -1 on error.
99 : */
100 : static int st_newCallBack_ruleProp( 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( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
110 :
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 :
119 0 : stateRuleEngine::stateRuleEngine() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
120 : {
121 0 : return;
122 0 : }
123 :
124 0 : void stateRuleEngine::setupConfig()
125 : {
126 0 : config.add( "rules.dir",
127 : "",
128 : "rules.dir",
129 : argType::Required,
130 : "rules",
131 : "dir",
132 : false,
133 : "string",
134 : "Directory containing config files containing rules to load. Relative to config directory. If this is "
135 : "set, then rules in the device config file are ignored" );
136 0 : }
137 :
138 0 : int stateRuleEngine::loadConfigImpl( mx::app::appConfigurator & _config )
139 : {
140 0 : _config(m_ruleDir, "rules.dir");
141 :
142 0 : std::map<std::string, ruleRuleKeys> rrkMap;
143 :
144 0 : if(m_ruleDir == "")
145 : {
146 : try
147 : {
148 0 : loadRuleConfig(m_ruleMaps, rrkMap, _config);
149 0 : finalizeRuleValRules(m_ruleMaps, rrkMap);
150 : }
151 0 : catch(mx::err::mxException & e)
152 : {
153 0 : return log<software_critical,-1>({__FILE__,__LINE__, std::string("Rule config exception caught:\n") + e.what()});
154 0 : }
155 : }
156 : else
157 : {
158 0 : std::vector<std::string> conffiles;
159 0 : if(mx::ioutils::getFileNames(conffiles, m_configDir + "/" + m_ruleDir, "", "", ".conf") != mx::error_t::noerror)
160 : {
161 0 : return log<software_critical,-1>({__FILE__,__LINE__, "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>({__FILE__,__LINE__, "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(mx::err::mxException & e)
184 : {
185 0 : return log<software_critical,-1>({__FILE__,__LINE__, std::string("Rule config exception caught from ") + cnf + ":\n" + e.what()});
186 0 : }
187 0 : }
188 :
189 : try
190 : {
191 0 : finalizeRuleValRules(m_ruleMaps, rrkMap);
192 : }
193 0 : catch(const std::exception& e)
194 : {
195 0 : return log<software_critical,-1>({__FILE__,__LINE__, std::string("Error finalizing rules:\n") + e.what()});
196 0 : }
197 :
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>({__FILE__,__LINE__,"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, "info", pcf::IndiProperty::Switch, pcf::IndiProperty::ReadOnly,
221 0 : pcf::IndiProperty::Idle, pcf::IndiProperty::AnyOfMany, nullptr) < 0)
222 : {
223 0 : return log<software_critical,-1>({__FILE__, __LINE__});
224 : }
225 : }
226 :
227 0 : m_indiP_info.add(pcf::IndiElement(it->first, pcf::IndiElement::Off));
228 0 : m_indiP_info[it->first].setLabel(it->second->message());
229 : }
230 :
231 0 : if(it->second->priority() == rulePriority::caution)
232 : {
233 0 : if(m_indiP_caution.getDevice() != m_configName)
234 : {
235 0 : if(registerIndiPropertyNew( m_indiP_caution, "caution", pcf::IndiProperty::Switch, pcf::IndiProperty::ReadOnly,
236 0 : pcf::IndiProperty::Idle, pcf::IndiProperty::AnyOfMany, nullptr) < 0)
237 : {
238 0 : return log<software_critical,-1>({__FILE__, __LINE__});
239 : }
240 : }
241 :
242 0 : m_indiP_caution.add(pcf::IndiElement(it->first, pcf::IndiElement::Off));
243 0 : m_indiP_caution[it->first].setLabel(it->second->message());
244 : }
245 :
246 0 : if(it->second->priority() == rulePriority::warning)
247 : {
248 0 : if(m_indiP_warning.getDevice() != m_configName)
249 : {
250 0 : if(registerIndiPropertyNew( m_indiP_warning, "warning", pcf::IndiProperty::Switch, pcf::IndiProperty::ReadOnly,
251 0 : pcf::IndiProperty::Idle, pcf::IndiProperty::AnyOfMany, nullptr) < 0)
252 : {
253 0 : return log<software_critical,-1>({__FILE__, __LINE__});
254 : }
255 : }
256 :
257 0 : m_indiP_warning.add(pcf::IndiElement(it->first, pcf::IndiElement::Off));
258 0 : m_indiP_warning[it->first].setLabel(it->second->message());
259 : }
260 :
261 0 : if(it->second->priority() == rulePriority::alert)
262 : {
263 0 : if(m_indiP_alert.getDevice() != m_configName)
264 : {
265 0 : if(registerIndiPropertyNew( m_indiP_alert, "alert", pcf::IndiProperty::Switch, pcf::IndiProperty::ReadOnly,
266 0 : pcf::IndiProperty::Idle, pcf::IndiProperty::AnyOfMany, nullptr) < 0)
267 : {
268 0 : return log<software_critical,-1>({__FILE__, __LINE__});
269 : }
270 : }
271 :
272 0 : m_indiP_alert.add(pcf::IndiElement(it->first, pcf::IndiElement::Off));
273 0 : m_indiP_alert[it->first].setLabel(it->second->message());
274 :
275 : }
276 : }
277 :
278 0 : for(auto it = m_ruleMaps.props.begin(); it != m_ruleMaps.props.end(); ++it)
279 : {
280 0 : if(it->second == nullptr) continue;
281 :
282 0 : std::string devName, propName;
283 :
284 0 : int rv = indi::parseIndiKey(devName, propName, it->first);
285 0 : if(rv != 0)
286 : {
287 0 : log<software_error>({__FILE__, __LINE__, 0, rv, "error parsing INDI key: " + it->first});
288 0 : return -1;
289 : }
290 :
291 0 : registerIndiPropertySet( *it->second, devName, propName, st_newCallBack_ruleProp);
292 0 : }
293 :
294 0 : state(stateCodes::READY);
295 :
296 0 : return 0;
297 : }
298 :
299 0 : int stateRuleEngine::appLogic()
300 : {
301 0 : for(auto it = m_ruleMaps.rules.begin(); it != m_ruleMaps.rules.end(); ++it)
302 : {
303 : #if 0
304 : try
305 : {
306 : bool val = it->second->value();
307 : std::cerr << it->first << " " << val << "\n";
308 : }
309 : catch(...){}
310 : #endif
311 :
312 0 : if(it->second->priority() != rulePriority::none)
313 : {
314 : try
315 : {
316 0 : bool val = it->second->value();
317 :
318 0 : pcf::IndiElement::SwitchStateType onoff = pcf::IndiElement::Off;
319 0 : if(val) onoff = pcf::IndiElement::On;
320 :
321 0 : if(it->second->priority() == rulePriority::info)
322 : {
323 0 : updateSwitchIfChanged(m_indiP_info, it->first, onoff);
324 : }
325 0 : else if(it->second->priority() == rulePriority::caution)
326 : {
327 0 : updateSwitchIfChanged(m_indiP_caution, it->first, onoff);
328 : }
329 0 : else if(it->second->priority() == rulePriority::warning)
330 : {
331 0 : updateSwitchIfChanged(m_indiP_warning, it->first, onoff);
332 : }
333 : else
334 : {
335 0 : updateSwitchIfChanged(m_indiP_alert, it->first, onoff);
336 : }
337 :
338 : }
339 0 : catch(const std::exception & e)
340 : {
341 : ///\todo how to handle startup vs misconfiguration
342 :
343 : /*
344 : if(it->second->priority() == rulePriority::none)
345 : {
346 : updateSwitchIfChanged(m_indiP_info, it->first, pcf::IndiElement::Off);
347 : }*/
348 0 : }
349 : }
350 : }
351 :
352 :
353 0 : return 0;
354 : }
355 :
356 0 : int stateRuleEngine::appShutdown()
357 : {
358 0 : return 0;
359 : }
360 :
361 0 : int stateRuleEngine::st_newCallBack_ruleProp( void * app,
362 : const pcf::IndiProperty &ipRecv
363 : )
364 : {
365 0 : stateRuleEngine * sre = static_cast<stateRuleEngine *>(app);
366 :
367 0 : sre->newCallBack_ruleProp(ipRecv);
368 :
369 0 : return 0;
370 : }
371 :
372 0 : int stateRuleEngine::newCallBack_ruleProp( const pcf::IndiProperty &ipRecv)
373 : {
374 0 : std::string key = ipRecv.createUniqueKey();
375 :
376 0 : if(m_ruleMaps.props.count(key) == 0)
377 : {
378 0 : return 0;
379 : }
380 :
381 0 : if(m_ruleMaps.props[key] == nullptr) //
382 : {
383 0 : return 0;
384 : }
385 :
386 0 : *m_ruleMaps.props[key] = ipRecv;
387 :
388 0 : return 0;
389 0 : }
390 :
391 : } //namespace app
392 : } //namespace MagAOX
393 :
394 : #endif //stateRuleEngine_hpp
|