API
 
Loading...
Searching...
No Matches
closedLoopIndi.hpp
Go to the documentation of this file.
1/** \file closedLoopIndi.hpp
2 * \brief The MagAO-X INDI Closed Loop header file
3 *
4 * \ingroup closedLoopIndi_files
5 */
6
7#ifndef closedLoopIndi_hpp
8#define closedLoopIndi_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/** \defgroup closedLoopIndi
14 * \brief The MagAO-X application to do closed-loop control using INDI properties
15 *
16 * <a href="../handbook/operating/software/apps/closedLoopIndi.html">Application Documentation</a>
17 *
18 * \ingroup apps
19 *
20 */
21
22/** \defgroup closedLoopIndi_files
23 * \ingroup closedLoopIndi
24 */
25
26namespace MagAOX
27{
28namespace app
29{
30
31/// The MagAO-X application to do closed-loop control using INDI properties
32/**
33 * \ingroup closedLoopIndi
34 */
35class closedLoopIndi : public MagAOXApp<true>
36{
37 // Give the test harness access.
38 friend class closedLoopIndi_test;
39
40 protected:
41 /** \name Configurable Parameters
42 *@{
43 */
44
45 std::string m_inputDevice; ///< The device with the input disturbances and frame counter.
46 std::string m_inputProperty; ///< The property with the input disturbances and frame counter.
47 std::vector<std::string> m_inputElements{
48 "x", "y" }; ///< The elements with the input disturbances. Must be two, defaults are "x" and "y".
50 "counter" }; ///< The element with the frame counter, a monotonically increasing integer. Default is "counter".
51
52 std::vector<std::string>
53 m_ctrlDevices; ///< Device names of the controller(s). If only one, it's used for both properties. Max two.
54 std::vector<std::string>
55 m_ctrlProperties; ///< Properties of the ctrl device(s) to which to give the commands. Must specify two.
56 std::vector<std::string> m_ctrlCurrents{ "current",
57 "current" }; ///< current elements of the properties on which to base the
58 ///< commands. Must specify 0 or 2. Default is 'current'.
59 std::vector<std::string> m_ctrlTargets{ "target",
60 "target" }; ///< target elements of the properties to which to send the
61 ///< commands. Must specify 0 or 2. Default is 'target'.
62
63 mx::improc::eigenImage<float> m_references; ///< The reference values of the disturbances
64
65 bool m_operatingOK{ false }; ///< If true then it's ok for the ctrl device to be OPERATING when a command is sent
66
67 std::unordered_map<std::string, std::string> m_fsmStates; ///< The FSM states of the control devices.
68
69 std::vector<float> m_currents; ///< The current commands
70
71 mx::improc::eigenImage<float> m_intMat; ///< The interaction matrix. Default is [1 0][0 1].
72
73 std::vector<float> m_defaultGains; ///< The default gains, per-axis
74
75 std::string m_upstreamDevice; ///< The upstream device to monitor to automatically open this loop if it's loop opens
76 std::string m_upstreamProperty{ "loop_state" }; ///< The name of the toggle switch to monitor
77 bool m_upstreamFollowClosed{ false }; ///< If true, this loop also closes when the upstream loop closes.
78
79 ///@}
80
81 int64_t m_counter = -1; ///< The latest value of the loop counter
82 mx::improc::eigenImage<float> m_measurements; ///< The latest value of the measurements
83 float m_delta0{ 0 }; ///< Latest first-axis residual.
84 float m_delta1{ 0 }; ///< Latest second-axis residual.
85
86 mx::improc::eigenImage<float> m_commands; ///< The latest commands
87
88 float m_ggain{ 0 }; ///< The global gain
89
90 std::vector<float> m_gains; ///< The axis gains
91
92 bool m_loopClosed{ false }; ///< Whether or not the loop is closed
93
94 public:
95 /// Default c'tor.
97
98 /// D'tor, declared and defined for noexcept.
102
103 /// Set up the app configuration interface.
104 virtual void setupConfig();
105
106 /// Implementation of loadConfig logic, separated for testing.
107 /** This is called by loadConfig().
108 */
109 int loadConfigImpl(
110 mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
111
112 /// Load the application configuration.
113 virtual void loadConfig();
114
115 /// Startup function
116 /**
117 *
118 */
119 virtual int appStartup();
120
121 /// Implementation of the FSM for closedLoopIndi.
122 /**
123 * \returns 0 on no critical error
124 * \returns -1 on an error requiring shutdown
125 */
126 virtual int appLogic();
127
128 /// Shutdown the app.
129 /**
130 *
131 */
132 virtual int appShutdown();
133
134 /// Change the loop state
135 int toggleLoop( bool onoff /**< [in] true to close the loop and false to open it */ );
136
137 /// Update the loop with a new command
138 int updateLoop();
139
140 /// Send commands to the control devices
141 int sendCommands( std::vector<float> &commands /**< [in] command values to transmit */ );
142
143 // INDI
144
145 pcf::IndiProperty m_indiP_deltas; ///< Published loop residual values.
146
147 pcf::IndiProperty m_indiP_reference0; ///< INDI property exposing the first reference value.
149
150 pcf::IndiProperty m_indiP_reference1; ///< INDI property exposing the second reference value.
152
153 pcf::IndiProperty m_indiP_inputs; ///< INDI property monitored for incoming disturbances.
155
156 pcf::IndiProperty m_indiP_ggain; ///< INDI property exposing the global loop gain.
158
159 pcf::IndiProperty m_indiP_ctrlEnabled; ///< INDI property exposing the loop open/closed state.
161
162 pcf::IndiProperty m_indiP_counterReset; ///< INDI property used to request a counter reset.
164
165 pcf::IndiProperty m_indiP_ctrl0_fsm; ///< The INDI property for fsm state of axis 0
167
168 pcf::IndiProperty m_indiP_ctrl0; ///< The INDI property used for control of axis 0
170
171 pcf::IndiProperty m_indiP_ctrl1_fsm; ///< The INDI property for fsm state of axis 1
173
174 pcf::IndiProperty m_indiP_ctrl1; ///< The INDI property used for control of axis 1
176
177 pcf::IndiProperty m_indiP_upstream; ///< Property used to monitor the upstream loop state.
179};
180
181closedLoopIndi::closedLoopIndi() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
182{
183 return;
184}
185
187{
188 config.add( "input.device",
189 "",
190 "input.device",
191 argType::Required,
192 "input",
193 "device",
194 false,
195 "string",
196 "The device with the input disturbances and frame counter." );
197 config.add( "input.property",
198 "",
199 "input.property",
200 argType::Required,
201 "input",
202 "property",
203 false,
204 "string",
205 "The property with the input disturbances and counter." );
206 config.add( "input.elements",
207 "",
208 "input.elements",
209 argType::Required,
210 "input",
211 "elements",
212 false,
213 "vector<string>",
214 " The elements with the input disturbances. Must be two, defaults are 'x' and 'y'." );
215 config.add( "input.counterElement",
216 "",
217 "input.counterElement",
218 argType::Required,
219 "input",
220 "counterElement",
221 false,
222 "string",
223 "The element with the frame counter, a monotonically increasing integer. Default is 'counter'." );
224
225 config.add( "input.references",
226 "",
227 "input.references",
228 argType::Required,
229 "input",
230 "references",
231 false,
232 "vector<float>",
233 "The reference values for the input disturbances." );
234
235 config.add( "ctrl.devices",
236 "",
237 "ctrl.devices",
238 argType::Required,
239 "ctrl",
240 "devices",
241 false,
242 "string",
243 "Device names of the controller(s). If only one, it's used for both properties. Max two." );
244 config.add( "ctrl.properties",
245 "",
246 "ctrl.properties",
247 argType::Required,
248 "ctrl",
249 "properties",
250 false,
251 "string",
252 "Properties of the ctrl device(s) to which to give the commands. Must specify two." );
253 config.add(
254 "ctrl.currents",
255 "",
256 "ctrl.currents",
257 argType::Required,
258 "ctrl",
259 "currents",
260 false,
261 "vector<string>",
262 "current elements of the properties on which to base the commands. Must specify 0 or 2. Default is 'current'" );
263 config.add(
264 "ctrl.targets",
265 "",
266 "ctrl.targets",
267 argType::Required,
268 "ctrl",
269 "targets",
270 false,
271 "vector<string>",
272 "target elements of the properties to which to send the commands. Must specify 0 or 2. Default is 'target'." );
273 config.add( "ctrl.operatingOK",
274 "",
275 "ctrl.operatingOK",
276 argType::Required,
277 "ctrl",
278 "operatingOK",
279 false,
280 "bool",
281 "If true then it's ok for the ctrl device to be OPERATING when a command is sent. Default is false." );
282
283 config.add( "loop.intMat00",
284 "",
285 "loop.intMat00",
286 argType::Required,
287 "loop",
288 "intMat00",
289 false,
290 "float",
291 "element (0,0) of the interaction matrix. Default is 1." );
292 config.add( "loop.intMat01",
293 "",
294 "loop.intMat01",
295 argType::Required,
296 "loop",
297 "intMat01",
298 false,
299 "float",
300 "element (0,1) of the interaction matrix. Default is 0." );
301 config.add( "loop.intMat10",
302 "",
303 "loop.intMat10",
304 argType::Required,
305 "loop",
306 "intMat10",
307 false,
308 "float",
309 "element (1,0) of the interaction matrix. Default is 0." );
310 config.add( "loop.intMat11",
311 "",
312 "loop.intMat11",
313 argType::Required,
314 "loop",
315 "intMat11",
316 false,
317 "float",
318 "element (1,1) of the interaction matrix. Default is 1." );
319
320 config.add(
321 "loop.gain", "", "loop.gain", argType::Required, "loop", "gain", false, "float", "default global loop gain." );
322 config.add( "loop.gains",
323 "",
324 "loop.gains",
325 argType::Required,
326 "loop",
327 "gains",
328 false,
329 "vector<float>",
330 "default loop gains. If single number, it is applied to all axes." );
331 config.add( "loop.upstream",
332 "",
333 "loop.upstream",
334 argType::Required,
335 "loop",
336 "upstream",
337 false,
338 "string",
339 "Upstream loop device name. This loop will open, and optionally close, with the upstream loop. "
340 "Default none." );
341 config.add( "loop.upstreamProperty",
342 "",
343 "loop.upstreamProperty",
344 argType::Required,
345 "loop",
346 "upstreamProperty",
347 false,
348 "string",
349 "Property of upstream loop device to follow. Must be a toggle. Default is loop_state." );
350 config.add( "loop.upstreamFollowClosed",
351 "",
352 "loop.upstreamFollowClosed",
353 argType::Required,
354 "loop",
355 "upstreamFollowClosed",
356 false,
357 "bool",
358 "If true, this loop also closes when the upstream loop closes. Default is false." );
359}
360
361int closedLoopIndi::loadConfigImpl( mx::app::appConfigurator &_config )
362{
363 _config( m_inputDevice, "input.device" );
364 _config( m_inputProperty, "input.property" );
365 _config( m_inputElements, "input.elements" );
366 _config( m_inputCounterElement, "input.counterElement" );
367
368 if( m_inputDevice == "" )
369 {
370 m_shutdown = 1;
371 return log<software_error, -1>( { __FILE__, __LINE__, "no input device specified" } );
372 }
373
374 if( m_inputProperty == "" )
375 {
376 m_shutdown = 1;
377 return log<software_error, -1>( { __FILE__, __LINE__, "no input property specified" } );
378 }
379
380 if( m_inputElements.size() != 2 )
381 {
382 m_shutdown = 1;
383 return log<software_error, -1>( { __FILE__, __LINE__, "must specify only two input.elements" } );
384 }
385
386 std::vector<float> refs( { 0, 0 } );
387 _config( refs, "input.references" );
388 if( refs.size() != 2 )
389 {
390 m_shutdown = 1;
391 return log<software_error, -1>( { __FILE__, __LINE__, "input.references must have 2 elements" } );
392 }
393
394 m_references.resize( 2, 1 );
395 m_references( 0, 0 ) = refs[0];
396 m_references( 1, 0 ) = refs[1];
397
398 _config( m_ctrlDevices, "ctrl.devices" );
399 _config( m_ctrlProperties, "ctrl.properties" );
400 _config( m_ctrlCurrents, "ctrl.currents" );
401 _config( m_ctrlTargets, "ctrl.targets" );
402 _config( m_operatingOK, "ctrl.operatingOK" );
403
404 if( m_ctrlDevices.size() == 1 )
405 {
406 m_ctrlDevices.push_back( m_ctrlDevices[0] );
407 }
408 else if( m_ctrlDevices.size() != 2 )
409 {
410 m_shutdown = 1;
411 return log<software_error, -1>( { __FILE__, __LINE__, "must specify two ctrl.devices" } );
412 }
413
414 if( m_ctrlProperties.size() != 2 )
415 {
416 m_shutdown = 1;
417 return log<software_error, -1>( { __FILE__, __LINE__, "must specify two ctrl.properties" } );
418 }
419
420 if( m_ctrlTargets.size() != 2 )
421 {
422 m_shutdown = 1;
423 return log<software_error, -1>( { __FILE__, __LINE__, "must specify two ctrl.targets" } );
424 }
425
426 if( m_ctrlCurrents.size() != 2 )
427 {
428 m_shutdown = 1;
429 return log<software_error, -1>( { __FILE__, __LINE__, "must specify two ctrl.currents" } );
430 }
431
432 if( m_ctrlTargets.size() != 2 )
433 {
434 m_shutdown = 1;
435 return log<software_error, -1>( { __FILE__, __LINE__, "must specify two ctrl.targets" } );
436 }
437
438 float im00 = 1;
439 float im01 = 0;
440 float im10 = 0;
441 float im11 = 1;
442
443 _config( im00, "loop.intMat00" );
444 _config( im01, "loop.intMat01" );
445 _config( im10, "loop.intMat10" );
446 _config( im11, "loop.intMat11" );
447
448 m_intMat.resize( 2, 2 );
449 m_intMat( 0, 0 ) = im00;
450 m_intMat( 0, 1 ) = im01;
451 m_intMat( 1, 0 ) = im10;
452 m_intMat( 1, 1 ) = im11;
453
454 _config( m_ggain, "loop.gain" );
455
456 _config( m_defaultGains, "loop.gains" );
457 _config( m_upstreamDevice, "loop.upstream" );
458 _config( m_upstreamProperty, "loop.upstreamProperty" );
459 _config( m_upstreamFollowClosed, "loop.upstreamFollowClosed" );
460
461 return 0;
462}
463
465{
466 loadConfigImpl( config );
467}
468
470{
472
473 CREATE_REG_INDI_RO_NUMBER( m_indiP_deltas, "deltas", "Deltas", "Deltas" );
474 m_indiP_deltas.add( pcf::IndiElement( "delta0" ) );
475 m_indiP_deltas["delta0"] = 0;
476 m_indiP_deltas.add( pcf::IndiElement( "delta1" ) );
477 m_indiP_deltas["delta1"] = 0;
478
479 CREATE_REG_INDI_NEW_NUMBERF( m_indiP_reference0, "reference0", -1e15, 1e15, 1, "%g", "reference0", "references" );
480 m_indiP_reference0["current"] = m_references( 0, 0 );
481 m_indiP_reference0["target"] = m_references( 0, 0 );
482
483 CREATE_REG_INDI_NEW_NUMBERF( m_indiP_reference1, "reference1", -1e15, 1e15, 1, "%g", "reference1", "references" );
484 m_indiP_reference1["current"] = m_references( 1, 0 );
485 m_indiP_reference1["target"] = m_references( 1, 0 );
486
487 if( m_ctrlTargets.size() != m_defaultGains.size() )
488 {
489 if( m_defaultGains.size() == 1 )
490 {
491 m_defaultGains.push_back( m_defaultGains[0] );
492 log<text_log>( "Setting loop.gains gains to be same size as ctrl.Targets", logPrio::LOG_NOTICE );
493 }
494 else
495 {
496 return log<software_error, -1>(
497 { __FILE__, __LINE__, "ctrl.Targets and loop.gains are not the same size" } );
498 }
499 }
500
501 m_gains.resize( m_defaultGains.size() );
502 for( size_t n = 0; n < m_defaultGains.size(); ++n )
504
505 CREATE_REG_INDI_NEW_NUMBERU( m_indiP_ggain, "loop_gain", 0, 1, 0, "%0.2f", "gain", "loop" );
506 m_indiP_ggain["current"] = m_ggain;
507 m_indiP_ggain["target"] = m_ggain;
508
510
512
513 m_measurements.resize( 2, 1 );
514 m_measurements.setZero();
515
516 m_currents.resize( m_ctrlDevices.size(), -1e15 );
517
519
521
522 if( m_ctrlDevices[1] != m_ctrlDevices[0] )
523 {
525 }
526
528
529 m_commands.resize( 2, 2 );
530 m_commands.setZero();
531
532 // Get the loop state for managing offloading
533 if( m_upstreamDevice != "" )
534 {
536 }
537
538 return 0;
539}
540
542{
543 if( m_loopClosed )
544 {
546 }
547 else
548 {
550 }
551
552 return 0;
553}
554
556{
557 return 0;
558}
559
561{
562 if( !m_loopClosed && onoff ) // not enabled so change
563 {
564 m_loopClosed = true;
566 updateSwitchIfChanged( m_indiP_ctrlEnabled, "toggle", pcf::IndiElement::On, INDI_OK );
567 return 0;
568 }
569
570 if( m_loopClosed && !onoff )
571 {
572 m_loopClosed = false;
574 updateSwitchIfChanged( m_indiP_ctrlEnabled, "toggle", pcf::IndiElement::Off, INDI_IDLE );
575
576 return 0;
577 }
578
579 return 0;
580}
581
583{
584 bool ready = false;
585
586 // This should only give ready == true if all devices exist and are ready
587 for( size_t n = 0; n < m_ctrlDevices.size(); ++n )
588 {
589 if( m_fsmStates.count( m_ctrlDevices[n] ) > 0 )
590 {
591 if( m_fsmStates[m_ctrlDevices[n]] == "READY" ||
592 ( m_operatingOK && m_fsmStates[m_ctrlDevices[n]] == "OPERATING" ) )
593 {
594 ready = true;
595 }
596 else
597 {
598 ready = false;
599 break;
600 }
601 }
602 else
603 {
604 ready = false;
605 break;
606 }
607 }
608
609 if( ready != true )
610 {
611 return 0;
612 }
613
614 m_delta0 = m_measurements( 0, 0 ) - m_references( 0, 0 );
615 m_delta1 = m_measurements( 1, 0 ) - m_references( 1, 0 );
616
617 m_commands.matrix() = m_intMat.matrix() * ( m_measurements - m_references ).matrix();
618
619 std::vector<float> commands;
620 commands.resize( m_measurements.rows() );
621
622 for( int cc = 0; cc < m_measurements.rows(); ++cc )
623 {
625 }
626
627 // And send commands.
628 int rv;
629 if( m_loopClosed )
630 {
632 }
633 else
634 {
635 rv = 0;
636 }
637
639 std::vector<std::string>( { "delta0", "delta1" } ),
640 std::vector<float>( { m_delta0, m_delta1 } ) );
641 return rv;
642}
643
644inline int closedLoopIndi::sendCommands( std::vector<float> &commands )
645{
646 for( size_t n = 0; n < m_ctrlDevices.size(); ++n )
647 {
648 pcf::IndiProperty ip( pcf::IndiProperty::Number );
649
650 ip.setDevice( m_ctrlDevices[n] );
651 ip.setName( m_ctrlProperties[n] );
652 ip.add( pcf::IndiElement( m_ctrlTargets[n] ) );
654
656 }
657
658 return 0;
659}
660
661INDI_NEWCALLBACK_DEFN( closedLoopIndi, m_indiP_reference0 )( const pcf::IndiProperty &ipRecv )
662{
663 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_reference0 );
664
665 float target;
666
667 if( indiTargetUpdate( m_indiP_reference0, target, ipRecv, true ) < 0 )
668 {
669 return log<software_error, -1>( { __FILE__, __LINE__ } );
670 }
671
672 m_references( 0, 0 ) = target;
673
674 updateIfChanged( m_indiP_reference0,
675 std::vector<std::string>( { "current", "target" } ),
676 std::vector<float>( { m_references( 0, 0 ), m_references( 0, 0 ) } ) );
677
678 log<text_log>( "set reference0 to " + std::to_string( m_references( 0, 0 ) ), logPrio::LOG_NOTICE );
679
680 return 0;
681}
682
683INDI_NEWCALLBACK_DEFN( closedLoopIndi, m_indiP_reference1 )( const pcf::IndiProperty &ipRecv )
684{
685 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_reference1 );
686
687 float target;
688
689 if( indiTargetUpdate( m_indiP_reference1, target, ipRecv, true ) < 0 )
690 {
691 return log<software_error, -1>( { __FILE__, __LINE__ } );
692 }
693
694 m_references( 1, 0 ) = target;
695
696 updateIfChanged( m_indiP_reference1,
697 std::vector<std::string>( { "current", "target" } ),
698 std::vector<float>( { m_references( 1, 0 ), m_references( 1, 0 ) } ) );
699
700 log<text_log>( "set reference1 to " + std::to_string( m_references( 1, 0 ) ), logPrio::LOG_NOTICE );
701
702 return 0;
703}
704
705INDI_SETCALLBACK_DEFN( closedLoopIndi, m_indiP_inputs )( const pcf::IndiProperty &ipRecv )
706{
707 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_inputs )
708
709 if( !ipRecv.find( m_inputElements[0] ) )
710 return -1;
711 if( !ipRecv.find( m_inputElements[1] ) )
712 return -1;
713 if( !ipRecv.find( m_inputCounterElement ) )
714 return -1;
715
716 int counter = ipRecv[m_inputCounterElement].get<int>();
717
718 if( counter != m_counter )
719 {
720 m_counter = counter;
721 m_measurements( 0, 0 ) = ipRecv[m_inputElements[0]].get<float>();
722 m_measurements( 1, 0 ) = ipRecv[m_inputElements[1]].get<float>();
723
724 return updateLoop();
725 }
726
727 return 0;
728}
729
730INDI_NEWCALLBACK_DEFN( closedLoopIndi, m_indiP_ggain )( const pcf::IndiProperty &ipRecv )
731{
732 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_ggain );
733
734 float target;
735
736 if( indiTargetUpdate( m_indiP_ggain, target, ipRecv, true ) < 0 )
737 {
738 log<software_error>( { __FILE__, __LINE__ } );
739 return -1;
740 }
741
742 m_ggain = target;
743
744 updateIfChanged( m_indiP_ggain, "current", m_ggain );
745 updateIfChanged( m_indiP_ggain, "target", m_ggain );
746
747 log<text_log>( "set global gain to " + std::to_string( m_ggain ), logPrio::LOG_NOTICE );
748
749 return 0;
750}
751
752INDI_NEWCALLBACK_DEFN( closedLoopIndi, m_indiP_ctrlEnabled )( const pcf::IndiProperty &ipRecv )
753{
754 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_ctrlEnabled );
755
756 // switch is toggled to on
757 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
758 {
759 return toggleLoop( true );
760 }
761
762 // switch is toggle to off
763 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
764 {
765 return toggleLoop( false );
766 }
767
768 return 0;
769}
770
771INDI_NEWCALLBACK_DEFN( closedLoopIndi, m_indiP_counterReset )( const pcf::IndiProperty &ipRecv )
772{
773 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_counterReset );
774
775 // switch is toggled to on
776 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
777 {
778 m_counter = -1;
779 }
780
781 return 0;
782}
783
784INDI_SETCALLBACK_DEFN( closedLoopIndi, m_indiP_ctrl0_fsm )( const pcf::IndiProperty &ipRecv )
785{
786 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_ctrl0_fsm );
787
788 if( ipRecv.find( "state" ) )
789 {
790 m_fsmStates[ipRecv.getDevice()] = ipRecv["state"].get();
791 }
792
793 return 0;
794}
795
796INDI_SETCALLBACK_DEFN( closedLoopIndi, m_indiP_ctrl0 )( const pcf::IndiProperty &ipRecv )
797{
798 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_ctrl0 );
799
800 if( ipRecv.find( m_ctrlCurrents[0] ) )
801 {
802 m_currents[0] = ipRecv[m_ctrlCurrents[0]].get<float>();
803 }
804
805 return 0;
806}
807
808INDI_SETCALLBACK_DEFN( closedLoopIndi, m_indiP_ctrl1_fsm )( const pcf::IndiProperty &ipRecv )
809{
810 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_ctrl1_fsm );
811
812 if( ipRecv.find( "state" ) )
813 {
814 m_fsmStates[ipRecv.getDevice()] = ipRecv["state"].get();
815 }
816
817 return 0;
818}
819
820INDI_SETCALLBACK_DEFN( closedLoopIndi, m_indiP_ctrl1 )( const pcf::IndiProperty &ipRecv )
821{
822 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_ctrl1 );
823
824 if( ipRecv.find( m_ctrlCurrents[1] ) )
825 {
826 m_currents[1] = ipRecv[m_ctrlCurrents[1]].get<float>();
827 }
828
829 return 0;
830}
831
832INDI_SETCALLBACK_DEFN( closedLoopIndi, m_indiP_upstream )( const pcf::IndiProperty &ipRecv )
833{
834 INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_upstream );
835
836 if( !ipRecv.find( "toggle" ) )
837 return 0;
838
839 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On && m_upstreamFollowClosed )
840 {
841 std::cerr << "upstream on\n";
842 return toggleLoop( true );
843 }
844 else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
845 {
846 std::cerr << "upstream off\n";
847 return toggleLoop( false );
848 }
849
850 return 0;
851}
852
853} // namespace app
854} // namespace MagAOX
855
856#endif // closedLoopIndi_hpp
The base-class for XWCTk applications.
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
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.
int sendNewProperty(const pcf::IndiProperty &ipSend, const std::string &el, const T &newVal)
Send a newProperty command to another device (using the INDI Client interface)
The MagAO-X application to do closed-loop control using INDI properties.
int toggleLoop(bool onoff)
Change the loop state.
~closedLoopIndi() noexcept
D'tor, declared and defined for noexcept.
pcf::IndiProperty m_indiP_upstream
Property used to monitor the upstream loop state.
virtual void setupConfig()
Set up the app configuration interface.
float m_ggain
The global gain.
pcf::IndiProperty m_indiP_inputs
INDI property monitored for incoming disturbances.
INDI_SETCALLBACK_DECL(closedLoopIndi, m_indiP_upstream)
INDI_SETCALLBACK_DECL(closedLoopIndi, m_indiP_ctrl0)
INDI_NEWCALLBACK_DECL(closedLoopIndi, m_indiP_reference1)
pcf::IndiProperty m_indiP_ctrlEnabled
INDI property exposing the loop open/closed state.
virtual int appShutdown()
Shutdown the app.
INDI_SETCALLBACK_DECL(closedLoopIndi, m_indiP_inputs)
std::vector< float > m_gains
The axis gains.
std::string m_inputCounterElement
The element with the frame counter, a monotonically increasing integer. Default is "counter".
INDI_NEWCALLBACK_DECL(closedLoopIndi, m_indiP_ctrlEnabled)
pcf::IndiProperty m_indiP_reference1
INDI property exposing the second reference value.
mx::improc::eigenImage< float > m_commands
The latest commands.
int64_t m_counter
The latest value of the loop counter.
std::string m_inputProperty
The property with the input disturbances and frame counter.
INDI_NEWCALLBACK_DECL(closedLoopIndi, m_indiP_counterReset)
virtual int appStartup()
Startup function.
float m_delta0
Latest first-axis residual.
bool m_operatingOK
If true then it's ok for the ctrl device to be OPERATING when a command is sent.
bool m_loopClosed
Whether or not the loop is closed.
pcf::IndiProperty m_indiP_deltas
Published loop residual values.
pcf::IndiProperty m_indiP_ctrl1_fsm
The INDI property for fsm state of axis 1.
pcf::IndiProperty m_indiP_reference0
INDI property exposing the first reference value.
virtual void loadConfig()
Load the application configuration.
mx::improc::eigenImage< float > m_measurements
The latest value of the measurements.
std::string m_upstreamProperty
The name of the toggle switch to monitor.
pcf::IndiProperty m_indiP_ctrl1
The INDI property used for control of axis 1.
std::vector< std::string > m_ctrlTargets
std::vector< std::string > m_inputElements
The elements with the input disturbances. Must be two, defaults are "x" and "y".
pcf::IndiProperty m_indiP_counterReset
INDI property used to request a counter reset.
std::string m_upstreamDevice
The upstream device to monitor to automatically open this loop if it's loop opens.
std::vector< std::string > m_ctrlProperties
Properties of the ctrl device(s) to which to give the commands. Must specify two.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
int sendCommands(std::vector< float > &commands)
Send commands to the control devices.
std::vector< float > m_defaultGains
The default gains, per-axis.
INDI_SETCALLBACK_DECL(closedLoopIndi, m_indiP_ctrl0_fsm)
bool m_upstreamFollowClosed
If true, this loop also closes when the upstream loop closes.
std::string m_inputDevice
The device with the input disturbances and frame counter.
INDI_SETCALLBACK_DECL(closedLoopIndi, m_indiP_ctrl1_fsm)
int updateLoop()
Update the loop with a new command.
pcf::IndiProperty m_indiP_ctrl0_fsm
The INDI property for fsm state of axis 0.
INDI_NEWCALLBACK_DECL(closedLoopIndi, m_indiP_ggain)
mx::improc::eigenImage< float > m_intMat
The interaction matrix. Default is [1 0][0 1].
std::vector< std::string > m_ctrlCurrents
std::vector< float > m_currents
The current commands.
virtual int appLogic()
Implementation of the FSM for closedLoopIndi.
INDI_NEWCALLBACK_DECL(closedLoopIndi, m_indiP_reference0)
float m_delta1
Latest second-axis residual.
mx::improc::eigenImage< float > m_references
The reference values of the disturbances.
std::unordered_map< std::string, std::string > m_fsmStates
The FSM states of the control devices.
pcf::IndiProperty m_indiP_ctrl0
The INDI property used for control of axis 0.
pcf::IndiProperty m_indiP_ggain
INDI property exposing the global loop gain.
std::vector< std::string > m_ctrlDevices
Device names of the controller(s). If only one, it's used for both properties. Max two.
INDI_SETCALLBACK_DECL(closedLoopIndi, m_indiP_ctrl1)
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define CREATE_REG_INDI_NEW_NUMBERU(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as unsigned int, using the standard call...
#define CREATE_REG_INDI_NEW_TOGGLESWITCH(prop, name)
Create and register a NEW INDI property as a standard toggle switch, using the standard callback name...
#define CREATE_REG_INDI_NEW_REQUESTSWITCH(prop, name)
Create and register a NEW INDI property as a standard request switch, using the standard callback nam...
#define CREATE_REG_INDI_NEW_NUMBERF(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as float, using the standard callback na...
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
#define CREATE_REG_INDI_RO_NUMBER(prop, name, label, group)
Create and register a RO INDI property as a number, using the standard callback name.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
#define INDI_IDLE
Definition indiUtils.hpp:27
#define INDI_OK
Definition indiUtils.hpp:28
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
Definition dm.hpp:19
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
@ OPERATING
The device is operating, other than homing.
@ READY
The device is ready for operation, but is not operating.
Software ERR log entry.