Line data Source code
1 : /** \file stdMotionNode.hpp
2 : * \brief The MagAO-X Instrument Graph stdMotionNode header file
3 : *
4 : * \ingroup instGraph_files
5 : */
6 :
7 : #ifndef stdMotionNode_hpp
8 : #define stdMotionNode_hpp
9 :
10 : #include "fsmNode.hpp"
11 :
12 : ///
13 : /**
14 : *
15 : * The key assumption of this node is that it should be in a valid, not-`none`, preset position
16 : * for its ioputs to be `on`. It also supports triggering an alternate `on` state, which is used for
17 : * stages which have a continuous tracking mode (k-mirror and ADC).
18 : *
19 : * The preset is specified by an INDI property with signature `<device>.<presetPrefix>Name` where device
20 : * and presetPrefix are part of the configuration. This INDI property is a switch vector.
21 : *
22 : * The device and prefix can only be set once.
23 : */
24 : class stdMotionNode : public fsmNode
25 : {
26 :
27 : protected:
28 : /// The prefix for preset naes. Usually either "preset" or "filter", to which "Name" is appended.
29 : std::string m_presetPrefix;
30 :
31 : /// The INDI key (device.property) for the presets. This is, say, `fwpupil.filterName`. It is set automatically.
32 : std::string m_presetKey;
33 :
34 : /// The current value of the preset property. Corresponds to the element name of the selected preset.
35 : std::string m_curVal;
36 :
37 : /// The current value of the put label.
38 : std::string m_curLabel;
39 :
40 : std::vector<std::string> m_presetPutName{ "out" };
41 :
42 : /// This sets whether the multi-put selector is on the input or the output (default)
43 : /** If this is a multi-put node (m_presetPutName.size() > 1) then the value of the preset switch
44 : * controls which input or output is on, with the others off.
45 : */
46 : ingr::ioDir m_presetDir{ ingr::ioDir::output };
47 :
48 : /// Contains the names of any puts which are always on if any are on.
49 : std::set<std::string> m_alwaysOn;
50 :
51 : /// Contains the names of any puts which are not automatically turned on if they are off.
52 : std::set<std::string> m_noAutoOn;
53 :
54 : /// The INDI key (device.property) for the switch denoting that this stage should be or should not be tracking
55 : std::string m_trackingReqKey;
56 :
57 : /// The element of the INDI property denoted by m_trackingReqKey to follow.
58 : std::string m_trackingReqElement;
59 :
60 : /// The INDI key (device.property) for the switch denoting that this stage is tracking
61 : std::string m_trackerKey;
62 :
63 : /// The element of the INDI property denoted by m_trackerKey to follow.
64 : std::string m_trackerElement;
65 :
66 : /// Flag indicating if the stage should be (true) or should not be (false, default) tracking.
67 : bool m_trackingReq{ false };
68 :
69 : /// Flag indicating whether or not the stage is currently tracking (default false).
70 : bool m_tracking{ false };
71 :
72 : public:
73 : /// Only c'tor. Must be constructed with node name and a parent graph.
74 : stdMotionNode( const std::string &name, /** [in] the name of this node*/
75 : ingr::instGraphXML *parentGraph /** [in] the graph which this node belongs to*/ );
76 :
77 : /// Set the device name. This can only be done once.
78 : /**
79 : * \throws
80 : */
81 : virtual void device( const std::string &dev /**< [in] */ );
82 :
83 : using fsmNode::device;
84 :
85 : virtual void presetPrefix( const std::string &pp /**< [in] */ );
86 :
87 : const std::string &presetPrefix();
88 :
89 : /// Get the current label text
90 : /**
91 : * \returns the current value of m_curLabel.
92 : */
93 : const std::string &curLabel();
94 :
95 : void presetPutName( const std::vector<std::string> &ppp /**< [in] */ );
96 :
97 : const std::vector<std::string> &presetPutName();
98 :
99 : void presetDir( const ingr::ioDir &dir /**< [in] */ );
100 :
101 : const ingr::ioDir &presetDir();
102 :
103 : void trackingReqKey( const std::string &tk /**< [in] */ );
104 :
105 : const std::string &trackingReqKey();
106 :
107 : void trackingReqElement( const std::string &te /**< [in] */ );
108 :
109 : const std::string &trackingReqElement();
110 :
111 : void trackerKey( const std::string &tk /**< [in] */ );
112 :
113 : const std::string &trackerKey();
114 :
115 : void trackerElement( const std::string &te /**< [in] */ );
116 :
117 : const std::string &trackerElement();
118 :
119 : /// INDI SetProperty callback
120 : virtual int handleSetProperty( const pcf::IndiProperty &ipRecv /**< [in] the received INDI property to handle*/ );
121 :
122 : virtual void togglePutsOn();
123 :
124 : virtual void togglePutsOff();
125 :
126 : void loadConfig(
127 : mx::app::appConfigurator &config /**< [in] the application configurator loaded with this node's options*/ );
128 : };
129 :
130 19 : inline stdMotionNode::stdMotionNode( const std::string &name, ingr::instGraphXML *parentGraph )
131 55 : : fsmNode( name, parentGraph )
132 : {
133 18 : }
134 :
135 7 : inline void stdMotionNode::device( const std::string &dev )
136 : {
137 : // This will enforce the one-time only rule
138 7 : fsmNode::device( dev );
139 :
140 : // If presetPrefix is set, then we can make the key
141 6 : if( m_presetPrefix != "" )
142 : {
143 1 : m_presetKey = m_device + "." + m_presetPrefix + "Name";
144 1 : key( m_presetKey );
145 : }
146 6 : }
147 :
148 6 : inline void stdMotionNode::presetPrefix( const std::string &pp )
149 : {
150 : // Set it one time only
151 6 : if( m_presetPrefix != "" && pp != m_presetPrefix )
152 : {
153 : std::string msg =
154 1 : "stdMotionNode::presetPrefix: attempt to change preset prefix from " + m_presetPrefix + " to " + pp;
155 1 : msg += " at ";
156 1 : msg += __FILE__;
157 1 : msg += " " + std::to_string( __LINE__ );
158 1 : throw std::runtime_error( msg );
159 1 : }
160 :
161 5 : m_presetPrefix = pp;
162 :
163 : // If device has been set then we can create the key
164 5 : if( m_device != "" )
165 : {
166 4 : m_presetKey = m_device + "." + m_presetPrefix + "Name";
167 4 : key( m_presetKey );
168 : }
169 5 : }
170 :
171 2 : inline const std::string &stdMotionNode::presetPrefix()
172 : {
173 2 : return m_presetPrefix;
174 : }
175 :
176 11 : inline const std::string &stdMotionNode::curLabel()
177 : {
178 11 : return m_curLabel;
179 : }
180 :
181 3 : inline void stdMotionNode::presetPutName( const std::vector<std::string> &ppp )
182 : {
183 3 : m_presetPutName = ppp;
184 3 : }
185 :
186 5 : inline const std::vector<std::string> &stdMotionNode::presetPutName()
187 : {
188 5 : return m_presetPutName;
189 : }
190 :
191 12 : inline void stdMotionNode::presetDir( const ingr::ioDir &dir )
192 : {
193 12 : m_presetDir = dir;
194 12 : }
195 :
196 2 : inline const ingr::ioDir &stdMotionNode::presetDir()
197 : {
198 2 : return m_presetDir;
199 : }
200 :
201 4 : inline void stdMotionNode::trackingReqKey( const std::string &tk )
202 : {
203 4 : m_trackingReqKey = tk;
204 :
205 4 : if( m_trackingReqKey != "" )
206 : {
207 2 : key( m_trackingReqKey );
208 : }
209 4 : }
210 :
211 1 : inline const std::string &stdMotionNode::trackingReqKey()
212 : {
213 1 : return m_trackingReqKey;
214 : }
215 :
216 4 : inline void stdMotionNode::trackingReqElement( const std::string &te )
217 : {
218 4 : m_trackingReqElement = te;
219 4 : }
220 :
221 1 : inline const std::string &stdMotionNode::trackingReqElement()
222 : {
223 1 : return m_trackingReqElement;
224 : }
225 :
226 4 : inline void stdMotionNode::trackerKey( const std::string &tk )
227 : {
228 4 : m_trackerKey = tk;
229 :
230 4 : if( m_trackerKey != "" )
231 : {
232 2 : key( m_trackerKey );
233 : }
234 4 : }
235 :
236 2 : inline const std::string &stdMotionNode::trackerKey()
237 : {
238 2 : return m_trackerKey;
239 : }
240 :
241 4 : inline void stdMotionNode::trackerElement( const std::string &te )
242 : {
243 4 : m_trackerElement = te;
244 4 : }
245 :
246 2 : inline const std::string &stdMotionNode::trackerElement()
247 : {
248 2 : return m_trackerElement;
249 : }
250 :
251 14 : inline int stdMotionNode::handleSetProperty( const pcf::IndiProperty &ipRecv )
252 : {
253 14 : int rv = fsmNode::handleSetProperty( ipRecv );
254 :
255 14 : if( rv < 0 )
256 : {
257 0 : return rv;
258 : }
259 :
260 14 : if( ipRecv.createUniqueKey() == m_trackingReqKey )
261 : {
262 3 : if( ipRecv.find( m_trackingReqElement ) )
263 : {
264 3 : if( ipRecv[m_trackingReqElement].getSwitchState() == pcf::IndiElement::On )
265 : {
266 1 : if( !m_trackingReq )
267 : {
268 1 : ++m_changes;
269 : }
270 :
271 1 : m_trackingReq = true;
272 : }
273 : else
274 : {
275 2 : if( m_trackingReq )
276 : {
277 1 : ++m_changes;
278 : }
279 2 : m_trackingReq = false;
280 : }
281 : }
282 : }
283 11 : else if( ipRecv.createUniqueKey() == m_trackerKey )
284 : {
285 3 : if( ipRecv.find( m_trackerElement ) )
286 : {
287 3 : if( ipRecv[m_trackerElement].getSwitchState() == pcf::IndiElement::On )
288 : {
289 1 : if( !m_tracking )
290 : {
291 1 : ++m_changes;
292 : }
293 :
294 1 : m_tracking = true;
295 : }
296 : else
297 : {
298 2 : if( m_tracking )
299 : {
300 1 : ++m_changes;
301 : }
302 2 : m_tracking = false;
303 : }
304 : }
305 : }
306 8 : else if( ipRecv.createUniqueKey() == m_presetKey )
307 : {
308 5 : if( m_node != nullptr )
309 : {
310 5 : bool nothingIsOn = true;
311 15 : for( auto &&it : ipRecv.getElements() )
312 : {
313 10 : if( it.second.getSwitchState() == pcf::IndiElement::On )
314 : {
315 4 : if( m_curVal != it.second.getName() && !m_tracking ) // we only update if not tracking
316 : {
317 4 : ++m_changes;
318 : }
319 :
320 4 : m_curVal = it.second.getName();
321 4 : nothingIsOn = false;
322 : }
323 : }
324 :
325 5 : if( nothingIsOn )
326 : {
327 1 : if( m_curVal != "" && !m_tracking ) // we only update if not tracking
328 : {
329 1 : ++m_changes;
330 : }
331 1 : m_curVal = "";
332 : }
333 : }
334 : }
335 :
336 14 : if( m_changes > 0 )
337 : {
338 12 : m_changes = 0;
339 :
340 12 : if( m_trackingReq )
341 : {
342 3 : if( m_tracking &&
343 2 : ( m_state == MagAOX::app::stateCodes::READY || m_state == MagAOX::app::stateCodes::OPERATING ) )
344 : {
345 2 : togglePutsOn();
346 : }
347 : else
348 : { // Either we aren't tracking or we aren't READY || OPERATING
349 1 : togglePutsOff();
350 : }
351 : }
352 : else
353 : {
354 9 : if( m_state != MagAOX::app::stateCodes::READY || m_tracking || m_curVal == "none" || m_curVal == "" )
355 : {
356 6 : togglePutsOff();
357 : }
358 : else
359 : {
360 3 : togglePutsOn();
361 : }
362 : }
363 : }
364 :
365 14 : return 0;
366 : }
367 :
368 5 : inline void stdMotionNode::togglePutsOn()
369 : {
370 5 : if( m_node == nullptr || !m_parentGraph || !m_node->auxDataValid() )
371 : {
372 0 : return;
373 : }
374 :
375 5 : if( m_trackingReq )
376 : {
377 2 : if( m_tracking )
378 : {
379 2 : m_curLabel = "tracking";
380 4 : m_parentGraph->valuePut( name(), m_presetPutName[0], m_presetDir, "tracking" );
381 8 : m_parentGraph->valueExtra( m_node->name(), "state", "tracking" );
382 2 : xigNode::togglePutsOn();
383 : }
384 : else
385 : {
386 0 : m_curLabel = "not tracking";
387 0 : m_parentGraph->valuePut( name(), m_presetPutName[0], m_presetDir, "not tracking" );
388 0 : m_parentGraph->valueExtra( m_node->name(), "state", "not tracking" );
389 0 : m_parentGraph->stateChange();
390 : }
391 : }
392 3 : else if( m_state == MagAOX::app::stateCodes::READY )
393 : {
394 3 : m_curLabel = m_curVal;
395 :
396 6 : m_parentGraph->valueExtra( m_node->name(), "state", m_curLabel );
397 :
398 3 : if( m_presetPutName.size() == 1 ) // There's only one put, it's just on or off with a value
399 : {
400 3 : m_parentGraph->valuePut( name(), m_presetPutName[0], m_presetDir, m_curVal );
401 3 : xigNode::togglePutsOn();
402 : }
403 : else // There is more than one put, and which one is on is selected by the value of the switch
404 : {
405 0 : if( m_presetDir == ingr::ioDir::output )
406 : {
407 : ingr::instIOPut *pptr; // We get this pointer using the node accessors
408 : // which throw if there's a nullptr
409 :
410 : // first deal with the single input
411 : try
412 : {
413 0 : pptr = m_node->inputs().begin()->second;
414 : }
415 0 : catch( ... )
416 : {
417 0 : return;
418 0 : }
419 :
420 : // the single node is always on if any are on
421 0 : pptr->enabled( true );
422 0 : pptr->state( ingr::putState::on );
423 :
424 0 : ingr::putState inst = pptr->state();
425 0 : if( inst != ingr::putState::on )
426 : {
427 0 : inst = ingr::putState::waiting;
428 : }
429 :
430 : // Now deal with the many
431 0 : for( auto s : m_presetPutName )
432 : {
433 : try
434 : {
435 0 : pptr = m_node->output( s );
436 : }
437 0 : catch( ... )
438 : {
439 0 : return;
440 0 : }
441 :
442 0 : if( s == m_curVal || m_alwaysOn.count( s ) == 1 )
443 : {
444 0 : pptr->enabled( true );
445 0 : pptr->state( inst );
446 : }
447 : else
448 : {
449 0 : pptr->state( ingr::putState::off );
450 :
451 0 : if( m_noAutoOn.count( s ) == 1 ) // if we turn it off, we disable it
452 : {
453 0 : pptr->enabled( false );
454 : }
455 : }
456 0 : }
457 : }
458 : else // m_presetDir == ingr::ioDir::input )
459 : {
460 : ingr::instIOPut *pptr; // We get this pointer using the node accessors
461 : // which throw if there's a nullptr
462 :
463 : // first deal with the single input
464 : try
465 : {
466 0 : pptr = m_node->outputs().begin()->second;
467 : }
468 0 : catch( ... )
469 : {
470 0 : return;
471 0 : }
472 :
473 : // the single node is always on if any are on
474 0 : pptr->enabled( true );
475 0 : pptr->state( ingr::putState::on );
476 :
477 : // Now deal with the many
478 0 : for( auto s : m_presetPutName )
479 : {
480 : try
481 : {
482 0 : pptr = m_node->input( s );
483 : }
484 0 : catch( ... )
485 : {
486 0 : return;
487 0 : }
488 :
489 0 : if( s == m_curVal || m_alwaysOn.count( s ) == 1 )
490 : {
491 0 : pptr->enabled( true );
492 0 : pptr->state( ingr::putState::on );
493 : }
494 : else
495 : {
496 0 : pptr->state( ingr::putState::off );
497 :
498 0 : if( m_noAutoOn.count( s ) == 1 ) // if we turn it off, we disable it
499 : {
500 0 : pptr->enabled( false );
501 : }
502 : }
503 0 : }
504 : }
505 : }
506 3 : m_parentGraph->stateChange();
507 : }
508 :
509 5 : return;
510 : }
511 :
512 7 : inline void stdMotionNode::togglePutsOff()
513 : {
514 7 : if( m_node == nullptr || !m_parentGraph || !m_node->auxDataValid() )
515 : {
516 0 : return;
517 : }
518 :
519 7 : if( m_tracking ) // regardless of whether required, if tracking this is our state
520 : {
521 1 : m_curLabel = "tracking";
522 2 : m_parentGraph->valuePut( name(), m_presetPutName[0], m_presetDir, "tracking" );
523 5 : m_parentGraph->valueExtra( m_node->name(), "state", "tracking" );
524 : }
525 6 : else if( m_trackingReq ) // we can only be "not tracking" if tracking is required
526 : {
527 1 : m_curLabel = "not tracking";
528 2 : m_parentGraph->valuePut( name(), m_presetPutName[0], m_presetDir, "not tracking" );
529 5 : m_parentGraph->valueExtra( m_node->name(), "state", "not tracking" );
530 : }
531 5 : else if( m_presetPutName.size() == 1 ) // otherwise, if we have a single node it's off
532 : {
533 5 : m_curLabel = "off";
534 10 : m_parentGraph->valuePut( name(), m_presetPutName[0], m_presetDir, "off" );
535 25 : m_parentGraph->valueExtra( m_node->name(), "state", "---" );
536 : }
537 : else
538 : {
539 : // We don't change labels if m_presetPutName.size() > 1
540 0 : m_parentGraph->valueExtra( m_node->name(), "state", "---" );
541 : }
542 :
543 : // replace xigNode::togglePutsOff() so we can check for always-on
544 :
545 7 : for( auto &&iput : m_node->inputs() )
546 : {
547 0 : if( m_alwaysOn.count( iput.second->name() ) > 0 )
548 : {
549 0 : continue;
550 : }
551 :
552 : // iput.second->enabled(false);
553 0 : iput.second->state( ingr::putState::off );
554 : }
555 :
556 7 : for( auto &&oput : m_node->outputs() )
557 : {
558 0 : if( m_alwaysOn.count( oput.second->name() ) > 0 )
559 : {
560 0 : continue;
561 : }
562 : // oput.second->enabled(false);
563 0 : oput.second->state( ingr::putState::off );
564 :
565 0 : if( m_noAutoOn.count( oput.second->name() ) == 1 ) // if we turn it off, we disable it
566 : {
567 0 : oput.second->enabled( false );
568 : }
569 : }
570 : }
571 :
572 17 : inline void stdMotionNode::loadConfig( mx::app::appConfigurator &config )
573 : {
574 17 : if( !m_parentGraph )
575 : {
576 5 : std::string msg = XIGN_EXCEPTION( "stdMotionNode::loadConfig", "parent graph is null" );
577 1 : throw std::runtime_error( msg );
578 1 : }
579 :
580 16 : std::string type;
581 16 : config.configUnused( type, mx::app::iniFile::makeKey( name(), "type" ) );
582 :
583 16 : if( type != "stdMotion" )
584 : {
585 15 : std::string msg = XIGN_EXCEPTION( "stdMotionNode::loadConfig", "node type is not stdMotion" );
586 3 : throw std::runtime_error( msg );
587 3 : }
588 :
589 13 : std::string dev = name();
590 26 : config.configUnused( dev, mx::app::iniFile::makeKey( name(), "device" ) );
591 :
592 26 : std::string prePrefix = "preset";
593 26 : config.configUnused( prePrefix, mx::app::iniFile::makeKey( name(), "presetPrefix" ) );
594 :
595 26 : std::string preDir = "output";
596 13 : config.configUnused( preDir, mx::app::iniFile::makeKey( name(), "presetDir" ) );
597 :
598 13 : if( preDir == "input" )
599 : {
600 1 : presetDir( ingr::ioDir::input );
601 : }
602 12 : else if( preDir == "output" )
603 : {
604 11 : presetDir( ingr::ioDir::output );
605 : }
606 : else
607 : {
608 5 : std::string msg = XIGN_EXCEPTION( "stdMotionNode::loadConfig", "invalid presetDir (must be input or output)" );
609 1 : throw std::runtime_error( msg );
610 1 : }
611 :
612 36 : std::vector<std::string> prePutName( { "out" } );
613 12 : config.configUnused( prePutName, mx::app::iniFile::makeKey( name(), "presetPutName" ) );
614 12 : if( prePutName.size() == 0 )
615 : {
616 5 : std::string msg = XIGN_EXCEPTION( "stdMotionNode::loadConfig", "presetPutName can't be empty" );
617 1 : throw std::runtime_error( msg );
618 1 : }
619 :
620 11 : std::vector<std::string> alwaysOn;
621 11 : config.configUnused( alwaysOn, mx::app::iniFile::makeKey( name(), "alwaysOn" ) );
622 : try
623 : {
624 11 : for( auto &ao : alwaysOn )
625 : {
626 0 : m_alwaysOn.insert( ao );
627 : }
628 : }
629 0 : catch( const std::exception &e )
630 : {
631 0 : std::string msg = XIGN_EXCEPTION( "stdMotionNode::loadConfig", "exception from insert in m_alwaysOn" );
632 0 : msg += ":";
633 0 : msg += e.what();
634 0 : throw std::runtime_error( msg );
635 0 : }
636 :
637 11 : std::vector<std::string> noAutoOn;
638 11 : config.configUnused( noAutoOn, mx::app::iniFile::makeKey( name(), "noAutoOn" ) );
639 : try
640 : {
641 11 : for( auto &ao : noAutoOn )
642 : {
643 0 : m_noAutoOn.insert( ao );
644 : }
645 : }
646 0 : catch( const std::exception &e )
647 : {
648 0 : std::string msg = XIGN_EXCEPTION( "stdMotionNode::loadConfig", "exception from insert in m_noAutoOn" );
649 0 : msg += ":";
650 0 : msg += e.what();
651 0 : throw std::runtime_error( msg );
652 0 : }
653 :
654 11 : std::string trackReqKey;
655 11 : config.configUnused( trackReqKey, mx::app::iniFile::makeKey( name(), "trackingReqKey" ) );
656 :
657 11 : std::string trackReqEl;
658 11 : config.configUnused( trackReqEl, mx::app::iniFile::makeKey( name(), "trackingReqElement" ) );
659 :
660 : // Check if both are set
661 11 : if( ( trackReqKey == "" && trackReqEl != "" ) || ( trackReqKey != "" && trackReqEl == "" ) )
662 : {
663 10 : std::string msg = XIGN_EXCEPTION( "stdMotionNode::loadConfig",
664 : "trackingReqKey and trackingReqElement must both be provided" );
665 2 : throw std::runtime_error( msg );
666 2 : }
667 :
668 9 : std::string trackKey;
669 9 : config.configUnused( trackKey, mx::app::iniFile::makeKey( name(), "trackerKey" ) );
670 :
671 9 : std::string trackEl;
672 9 : config.configUnused( trackEl, mx::app::iniFile::makeKey( name(), "trackerElement" ) );
673 :
674 : // Check if both are set
675 9 : if( ( trackKey == "" && trackEl != "" ) || ( trackKey != "" && trackEl == "" ) )
676 : {
677 : std::string msg =
678 10 : XIGN_EXCEPTION( "stdMotionNode::loadConfig", "trackingKey and trackingElement must both be provided" );
679 2 : throw std::runtime_error( msg );
680 2 : }
681 :
682 : // This will catch the case where one or the other pair was set, but not both
683 7 : if( ( trackKey == "" && trackReqKey != "" ) || ( trackKey != "" && trackReqKey == "" ) )
684 : {
685 : std::string msg =
686 10 : XIGN_EXCEPTION( "stdMotionNode::loadConfig", "trackingReqKey and trackerKey must both be provided" );
687 2 : throw std::runtime_error( msg );
688 2 : }
689 :
690 5 : device( dev );
691 4 : presetPrefix( prePrefix );
692 3 : presetPutName( prePutName );
693 3 : trackingReqKey( trackReqKey );
694 3 : trackingReqElement( trackReqEl );
695 3 : trackerKey( trackKey );
696 3 : trackerElement( trackEl );
697 135 : }
698 :
699 : #endif // stdMotionNode_hpp
|