API
 
Loading...
Searching...
No Matches
indiCompRuleConfig.hpp
Go to the documentation of this file.
1/** \file indiCompRuleConfig.hpp
2 * \brief Configuration of rules for the MagAO-X stateRuleEngine
3 *
4 * \ingroup stateRuleEngine_files
5 */
6
7#ifndef stateRuleEngine_indiCompRuleConfig_hpp
8#define stateRuleEngine_indiCompRuleConfig_hpp
9
10#include <map>
11
12#include "indiCompRules.hpp"
13
14/// Structure to provide management of the rule and property maps
15/** This owns all pointers in the rule engine, and `delete`s them on destruction.
16 */
18{
19 typedef std::map<std::string, indiCompRule *> ruleMapT;
20 typedef std::map<std::string, pcf::IndiProperty *> propMapT;
21
24
26 {
27 auto rit = rules.begin();
28 while( rit != rules.end() )
29 {
30 delete rit->second;
31 ++rit;
32 }
33
34 auto pit = props.begin();
35 while( pit != props.end() )
36 {
37 delete pit->second;
38 ++pit;
39 }
40 }
41};
42
43/* Structure used to hold ruleVal rule keys aside for final processing
44 ruleVal rules can be created before the rules they link exist, so
45 we hold the keys aside and set the pointers after all rules are created.
46*/
48{
49 std::string rule1;
50 std::string rule2;
51};
52
53/// Extract a property-only reference from a rule configuration.
54/** Reads the property, adding it to the property map if necessary.
55 *
56 * \throws mx::err::invalidconfig if the property is not configured or if the
57 * property already exists in the map but with a different type
58 */
60 pcf::IndiProperty **prop, ///< [out] pointer to the property, newly created or existing, which is in the map.
61 std::string &property, ///< [out] the property name from the configuration
62 indiRuleMaps &maps, ///< [in] contains the property map to which the property is added
63 const std::string &section, ///< [in] name of the section for this rule
64 const std::string &propkey, ///< [in] the key for the property name
65 const pcf::IndiProperty::Type &type, ///< [in] the type of the property
66 mx::app::appConfigurator &config ///< [in] the application configuration structure
67)
68{
69 config.configUnused( property, mx::app::iniFile::makeKey( section, propkey ) );
70 if( property == "" )
71 {
72 throw mx::exception( mx::error_t::invalidconfig, std::format( "{} for rule {} not found", propkey, section ) );
73 }
74
75 if( maps.props.count( property ) > 0 )
76 {
77 if( maps.props[property]->getType() != type )
78 {
79 throw mx::exception( mx::error_t::invalidconfig,
80 "property " + property + " exists but is not correct type" );
81 }
82
83 *prop = maps.props[property];
84 }
85 else
86 {
87 *prop = new pcf::IndiProperty( type );
88 maps.props.insert( std::pair<std::string, pcf::IndiProperty *>( { property, *prop } ) );
89
90 ///\todo have to split device and propertyName
91 }
92}
93
94/// Extract a property from a rule configuration
95/** Reads the property and element, adding the property to the property map if necessary.
96 *
97 * \throws mx::err::invalidconfig if the property is already in the map but of a different type
98 */
100 pcf::IndiProperty **prop, ///< [out] pointer to the property, newly created or existing, which is in the map.
101 std::string &element, ///< [out] the element name from the configuration
102 indiRuleMaps &maps, ///< [in] contains the property map to which the property is added
103 const std::string &section, ///< [in] name of the section for this rule
104 const std::string &propkey, ///< [in] the key for the property name
105 const std::string &elkey, ///< [in] the key for the element name
106 const pcf::IndiProperty::Type &type, ///< [in] the type of the property
107 mx::app::appConfigurator &config ///< [in] the application configuration structure
108)
109{
110 std::string property;
111 extractRuleProperty( prop, property, maps, section, propkey, type, config );
112
113 config.configUnused( element, mx::app::iniFile::makeKey( section, elkey ) );
114}
115
116/// \cond
117// strip leading and trailing whitespace and then opening and closing "". leaves spaces between "".
118inline void stripQuotesWS( std::string &str )
119{
120 if( str.size() == 0 )
121 {
122 return;
123 }
124
125 if( str[0] != '\"' && str[0] != ' ' && str.back() != ' ' ) // get out fast if we can
126 {
127 return;
128 }
129
130 // strip white space at front
131 size_t ns = str.find_first_not_of( " \t\r\n" );
132 if( ns != std::string::npos && ns != 0 )
133 {
134 str.erase( 0, ns );
135
136 if( str.size() == 0 )
137 {
138 return;
139 }
140 }
141 else if( ns == std::string::npos ) // the rare all spaces
142 {
143 str = "";
144 return;
145 }
146
147 // strip white space at back
148 ns = str.find_last_not_of( " \t\r\n" );
149 if( ns != std::string::npos && ns != str.size() - 1 )
150 {
151 str.erase( ns + 1 );
152
153 if( str.size() == 0 )
154 {
155 return;
156 }
157 }
158
159 if( str[0] == '\"' && str.back() == '\"' )
160 {
161 if( str.size() == 1 || str.size() == 2 )
162 {
163 str = "";
164 return;
165 }
166 str.erase( str.size() - 1, 1 );
167 str.erase( 0, 1 );
168 }
169 else if( str[0] == '\"' )
170 {
171 if( str.size() == 1 )
172 {
173 str = "";
174 return;
175 }
176 str.erase( 0, 1 );
177 }
178 else if( str.back() == '\"' )
179 {
180 if( str.size() == 1 || str.size() == 2 )
181 {
182 str = "";
183 return;
184 }
185 str.erase( str.size() - 1, 1 );
186 }
187}
188/// \endcond
189
190/// Load the rule and properties maps for a rule engine from a configuration file
191/** ///\todo check for insertion failure
192 * ///\todo add a constructor that has priority, message, and comparison, to reduce duplication
193 */
194void loadRuleConfig( indiRuleMaps &maps, /**< [out] contains the rule and property maps in
195 which to place the items found in config */
196 std::map<std::string, ruleRuleKeys> &rrkMap, /**< [out] Holds the ruleVal rule keys aside for
197 later post-processing*/
198 mx::app::appConfigurator &config /**< [in] the application configuration structure */
199)
200{
201 std::vector<std::string> sections;
202
203 config.unusedSections( sections );
204
205 if( sections.size() == 0 )
206 {
207 throw mx::exception( mx::error_t::invalidconfig, "no rules found in config" );
208 }
209
210 for( size_t i = 0; i < sections.size(); ++i )
211 {
212 bool ruleTypeSet = config.isSetUnused( mx::app::iniFile::makeKey( sections[i], "ruleType" ) );
213
214 // If there is no ruleType then this isn't a rule
215 if( !ruleTypeSet )
216 {
217 continue;
218 }
219
220 // If the rule already exists this is an error
221 if( maps.rules.count( sections[i] ) != 0 )
222 {
223 throw mx::exception( mx::error_t::invalidconfig, "duplicate rule: " + sections[i] );
224 }
225
226 std::string ruleType;
227 config.configUnused( ruleType, mx::app::iniFile::makeKey( sections[i], "ruleType" ) );
228
229 std::string priostr = "none";
230 config.configUnused( priostr, mx::app::iniFile::makeKey( sections[i], "priority" ) );
231 rulePriority priority = string2priority( priostr );
232
233 std::string message;
234 config.configUnused( message, mx::app::iniFile::makeKey( sections[i], "message" ) );
235 stripQuotesWS( message ); // strips "" and any leading/trailing whitespace
236
237 auto configureRuleBase = [&]( indiCompRule *rule )
238 {
239 std::string compstr = comp2string( rule->defaultComparison() );
240 config.configUnused( compstr, mx::app::iniFile::makeKey( sections[i], "comp" ) );
241
242 rule->priority( priority );
243 rule->message( message );
244 rule->comparison( string2comp( compstr ) );
245 };
246
247 if( ruleType == numValRule::name )
248 {
249 numValRule *nvr = new numValRule;
250 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
251
252 configureRuleBase( nvr );
253
254 pcf::IndiProperty *prop = nullptr;
255 std::string element;
256
258 &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Number, config );
259 nvr->property( prop );
260 nvr->element( element );
261
262 double target = nvr->target();
263 config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
264 nvr->target( target );
265
266 double tol = nvr->tol();
267 config.configUnused( tol, mx::app::iniFile::makeKey( sections[i], "tol" ) );
268 nvr->tol( tol );
269 }
270 else if( ruleType == txtValRule::name )
271 {
272 txtValRule *tvr = new txtValRule;
273 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], tvr } ) );
274
275 configureRuleBase( tvr );
276
277 pcf::IndiProperty *prop = nullptr;
278 std::string element;
279
281 &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Text, config );
282 tvr->property( prop );
283 tvr->element( element );
284
285 std::string target = tvr->target();
286 config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
287 tvr->target( target );
288 }
289 else if( ruleType == swValRule::name )
290 {
291 swValRule *svr = new swValRule;
292 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], svr } ) );
293
294 configureRuleBase( svr );
295
296 pcf::IndiProperty *prop = nullptr;
297 std::string element;
298
300 &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Switch, config );
301 svr->property( prop );
302 svr->element( element );
303
304 std::string target = "On";
305 config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
306 svr->target( target );
307 }
308 else if( ruleType == timeDiffRule::name )
309 {
310 timeDiffRule *nvr = new timeDiffRule;
311 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
312
313 configureRuleBase( nvr );
314
315 pcf::IndiProperty *prop = nullptr;
316 std::string element;
317
319 &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Number, config );
320 nvr->property( prop );
321 nvr->element( element );
322
323 double target = nvr->target();
324 config.configUnused( target, mx::app::iniFile::makeKey( sections[i], "target" ) );
325 nvr->target( target );
326
327 double tol = nvr->tol();
328 config.configUnused( tol, mx::app::iniFile::makeKey( sections[i], "tol" ) );
329 nvr->tol( tol );
330 }
331 else if( ruleType == elCompNumRule::name )
332 {
333 elCompNumRule *nvr = new elCompNumRule;
334 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], nvr } ) );
335
336 configureRuleBase( nvr );
337
338 pcf::IndiProperty *prop1;
339 std::string element1;
340
342 &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Number, config );
343 nvr->property1( prop1 );
344 nvr->element1( element1 );
345
346 pcf::IndiProperty *prop2;
347 std::string element2;
348
350 &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Number, config );
351 nvr->property2( prop2 );
352 nvr->element2( element2 );
353 }
354 else if( ruleType == elCompTxtRule::name )
355 {
356 elCompTxtRule *tvr = new elCompTxtRule;
357 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], tvr } ) );
358
359 configureRuleBase( tvr );
360
361 pcf::IndiProperty *prop1;
362 std::string element1;
363
365 &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Text, config );
366 tvr->property1( prop1 );
367 tvr->element1( element1 );
368
369 pcf::IndiProperty *prop2;
370 std::string element2;
371
373 &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Text, config );
374 tvr->property2( prop2 );
375 tvr->element2( element2 );
376 }
377 else if( ruleType == elCompSwRule::name )
378 {
379 elCompSwRule *svr = new elCompSwRule;
380 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], svr } ) );
381
382 configureRuleBase( svr );
383
384 pcf::IndiProperty *prop1;
385 std::string element1;
386
388 &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Switch, config );
389 svr->property1( prop1 );
390 svr->element1( element1 );
391
392 pcf::IndiProperty *prop2;
393 std::string element2;
394
396 &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Switch, config );
397 svr->property2( prop2 );
398 svr->element2( element2 );
399 }
400 else if( ruleType == ruleCompRule::name )
401 {
402 // Here we have to hold the ruleVal keys separately for later processing after all the rules are created.
403
404 if( rrkMap.count( sections[i] ) > 0 )
405 {
406 // This probably should be impossible, since we already checked maps.rules above...
407 throw mx::exception( mx::error_t::invalidconfig, "duplicate ruleRule: " + sections[i] );
408 }
409
410 ruleCompRule *rcr = new ruleCompRule;
411 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], rcr } ) );
412
413 configureRuleBase( rcr );
414
415 ruleRuleKeys rrk;
416
417 config.configUnused( rrk.rule1, mx::app::iniFile::makeKey( sections[i], "rule1" ) );
418 if( rrk.rule1 == "" )
419 {
420 throw mx::exception( mx::error_t::invalidconfig,
421 "rule1 for ruleVal rule " + sections[i] + " not found" );
422 }
423 if( rrk.rule1 == sections[i] )
424 {
425 throw mx::exception( mx::error_t::invalidconfig,
426 "rule1 for ruleVal rule " + sections[i] + " can't equal rule name" );
427 }
428
429 config.configUnused( rrk.rule2, mx::app::iniFile::makeKey( sections[i], "rule2" ) );
430 if( rrk.rule2 == "" )
431 {
432 throw mx::exception( mx::error_t::invalidconfig,
433 "rule2 for ruleVal rule " + sections[i] + " not found" );
434 }
435 if( rrk.rule2 == sections[i] )
436 {
437 throw mx::exception( mx::error_t::invalidconfig,
438 "rule2 for ruleVal rule " + sections[i] + " can't equal rule name" );
439 }
440
441 rrkMap.insert( std::pair<std::string, ruleRuleKeys>( sections[i], rrk ) );
442 }
443 else if( ruleType == multiSwitchComboRule::name )
444 {
446 maps.rules.insert( std::pair<std::string, indiCompRule *>( { sections[i], mscr } ) );
447
448 configureRuleBase( mscr );
449 mscr->ruleName( sections[i] );
450
451 int numSwitches = 0;
452 config.configUnused( numSwitches, mx::app::iniFile::makeKey( sections[i], "numSwitches" ) );
453 if( numSwitches < 1 )
454 {
455 throw mx::exception(
456 mx::error_t::invalidconfig,
457 std::format( "numSwitches for multiSwitchCombo rule {} must be greater than zero", sections[i] ) );
458 }
459
460 for( int n = 0; n < numSwitches; ++n )
461 {
462 pcf::IndiProperty *prop = nullptr;
463 std::string property;
464
465 std::string propKey = std::format( "property{}", n + 1 );
466 extractRuleProperty( &prop, property, maps, sections[i], propKey, pcf::IndiProperty::Switch, config );
467
468 mscr->property( prop, property );
469 }
470
471 std::string format;
472 config.configUnused( format, mx::app::iniFile::makeKey( sections[i], "format" ) );
473 stripQuotesWS( format );
474 mscr->format( format );
475
476 pcf::IndiProperty *targetProp = nullptr;
477 std::string targetProperty;
479 &targetProp, targetProperty, maps, sections[i], "targetProperty", pcf::IndiProperty::Switch, config );
480 mscr->targetProperty( targetProp );
481 mscr->targetPropertyKey( targetProperty );
482
483 indiCompRule::boolorerr_t rv = mscr->valid();
484 if( mscr->isError( rv ) )
485 {
486 throw mx::exception( mx::error_t::invalidconfig,
487 std::format( "multiSwitchCombo rule {} is invalid: {}",
488 sections[i],
489 std::get<std::string>( rv ) ) );
490 }
491 }
492 else
493 {
494 throw mx::exception( mx::error_t::notimpl,
495 std::format( "unknown rule type {} in {}", ruleType, sections[i] ) );
496 }
497 }
498}
499
500/// Finalize ruleVal rules
501/** ///\todo check for insertion failure
502 * ///\todo add a constructor that has priority, message, and comparison, to reduce duplication
503 */
505 indiRuleMaps &maps, /**< [in/out] contains the rule and property maps with rules ot finalize */
506 std::map<std::string, ruleRuleKeys> &rrkMap ///< [out] Holds the ruleVal rule keys aside for later post-processing
507)
508{
509 // Now set the rule pointers for any ruleVal rules
510 auto it = rrkMap.begin();
511 while( it != rrkMap.end() )
512 {
513 if( maps.rules.count( it->first ) == 0 )
514 {
515 throw mx::exception( mx::error_t::invalidconfig, std::format( "rule parsing error for {}", it->first ) );
516 }
517
518 if( maps.rules.count( it->second.rule1 ) == 0 )
519 {
520 throw mx::exception( mx::error_t::invalidconfig,
521 std::format( "rule1 {} not found "
522 "for ruleVal rule {}",
523 it->second.rule1,
524 it->first ) );
525 }
526
527 if( maps.rules.count( it->second.rule2 ) == 0 )
528 {
529 throw mx::exception( mx::error_t::invalidconfig,
530 std::format( "rule2 {} not found "
531 "for ruleVal rule {}",
532 it->second.rule2,
533 it->first ) );
534 }
535
536 ruleCompRule *rcr = nullptr;
537
538 try
539 {
540 rcr = dynamic_cast<ruleCompRule *>( maps.rules[it->first] );
541 }
542 catch( const std::exception &e )
543 {
544 std::throw_with_nested(
545 mx::exception( mx::error_t::invalidconfig, std::format( "error casting {}", it->first ) ) );
546 }
547
548 if( rcr == nullptr )
549 {
550 throw mx::exception( mx::error_t::invalidconfig,
551 std::format( "{} is not a ruleVal rule but has rules", it->first ) );
552 }
553
554 rcr->rule1( maps.rules[it->second.rule1] );
555 rcr->rule2( maps.rules[it->second.rule2] );
556
557 ++it;
558 }
559}
560
561#endif // stateRuleEngine_indiCompRuleConfig_hpp
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 extractRuleProperty(pcf::IndiProperty **prop, std::string &property, indiRuleMaps &maps, const std::string &section, const std::string &propkey, const pcf::IndiProperty::Type &type, mx::app::appConfigurator &config)
Extract a property-only reference from a rule configuration.
void finalizeRuleValRules(indiRuleMaps &maps, std::map< std::string, ruleRuleKeys > &rrkMap)
Finalize ruleVal rules.
void extractRuleProp(pcf::IndiProperty **prop, std::string &element, indiRuleMaps &maps, const std::string &section, const std::string &propkey, const std::string &elkey, const pcf::IndiProperty::Type &type, mx::app::appConfigurator &config)
Extract a property from a rule configuration.
The rules for the MagAO-X stateRuleEngine.
std::string comp2string(const ruleComparison &comparison)
Get the string representation of a ruleComparison member.
ruleComparison string2comp(const std::string &cstr)
Get the ruleComparison member from a string representation.
rulePriority string2priority(const std::string &pstr)
Get the rulePriority member from a string representation.
rulePriority
Reporting priorities for rules.
Compare two elements based on their numeric values.
static constexpr char name[]
Name of this rule, used by config system.
Compare two elements based on their switch values.
static constexpr char name[]
Name of this rule, used by config system.
Compare two elements based on their text values.
static constexpr char name[]
Name of this rule, used by config system.
Virtual base-class for all rules.
std::variant< bool, std::string > boolorerr_t
In-band error reporting type.
bool isError(boolorerr_t rv)
Check if returned value indicates an error.
Structure to provide management of the rule and property maps.
std::map< std::string, pcf::IndiProperty * > propMapT
std::map< std::string, indiCompRule * > ruleMapT
Build and compare a switch-name combination against a target switch vector.
void targetProperty(pcf::IndiProperty *property)
Set the target switch property.
void property(pcf::IndiProperty *property, const std::string &propertyKey)
Append one source switch property.
virtual boolorerr_t valid()
Check if this rule is valid as configured.
static constexpr char name[]
Name of this rule, used by config system.
void format(const std::string &format)
Set the literal format string for the source switch names.
void targetPropertyKey(const std::string &propertyKey)
Set the target property key used in diagnostics.
void ruleName(const std::string &ruleName)
Set the rule name used in diagnostics.
Compare the value of a number element to a target.
void target(const double &tgt)
Set the target for the comparison.
void tol(const double &t)
Set the tolerance.
static constexpr char name[]
Name of this rule, used by config system.
void property(pcf::IndiProperty *property)
Set the property pointer.
void element(const std::string &el)
Set the element name.
A rule to compare two rules.
void rule2(indiCompRule *r)
Set the pointer to the second rule.
void rule1(indiCompRule *r)
Set the pointer to the first rule.
static constexpr char name[]
Name of this rule, used by config system.
Compare the value of a switch to a target value.
void target(const pcf::IndiElement::SwitchStateType &ss)
Set the target for the comparison.
static constexpr char name[]
Name of this rule, used by config system.
Compare the difference in time between a value and now.
static constexpr char name[]
Name of this rule, used by config system.
void target(const double &tgt)
Set the target for the comparison.
void tol(const double &t)
Set the tolerance.
void property1(pcf::IndiProperty *property)
Set the first property pointer.
void element2(const std::string &el)
Set the second element name.
void element1(const std::string &el)
Set the first element name.
void property2(pcf::IndiProperty *property)
Set the second property pointer.
Compare the value of a text element to a target value.
void target(const std::string &target)
Set the target for the comparison.
static constexpr char name[]
Name of this rule, used by config system.