API
 
Loading...
Searching...
No Matches
adcTracker.hpp
Go to the documentation of this file.
1/** \file adcTracker.hpp
2 * \brief The MagAO-X ADC Tracker header file
3 * \author Jared R. Males (jaredmales@gmail.com)
4 *
5 * \ingroup adcTracker_files
6 */
7
8#ifndef adcTracker_hpp
9#define adcTracker_hpp
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 <cmath>
15
16#include <mx/math/gslInterpolator.hpp>
17#include <mx/ioutils/readColumns.hpp>
18
19/** \defgroup adcTracker
20 * \brief The MagAO-X application to track sky rotation with the atmospheric dispersion corrector.
21 *
22 * <a href="../handbook/operating/software/apps/adcTracker.html">Application Documentation</a>
23 *
24 * \ingroup apps
25 *
26 */
27
28/** \defgroup adcTracker_files
29 * \ingroup adcTracker
30 */
31
32namespace MagAOX
33{
34namespace app
35{
36
37/// The MagAO-X ADC Tracker
38/**
39 * \ingroup adcTracker
40 */
41class adcTracker : public MagAOXApp<true>, public dev::telemeter<adcTracker>
42{
43
44 // Give the test harness access.
45 friend class adcTracker_test;
46
47 friend class dev::telemeter<adcTracker>;
48
50
51 protected:
52 /** \name Configurable Parameters
53 *@{
54 */
55
56 // here add parameters which will be config-able at runtime
57 std::string m_lookupFile{ "adc_lookup_table.txt" }; ///< The name of the file, in the calib directory, containing
58 ///< the ADC lookup table. Default is 'adc_lookup_table.txt'.
59
60 float m_adc1zero{ 0 }; ///< The starting point for ADC 1. Default is 0.
61
62 int m_adc1lupsign{ 1 }; ///< The sign to apply to the lookup table value for ADC 1.
63
64 float m_adc2zero{ 0 }; ///< The starting point for ADC 2. Default is 0.
65
66 int m_adc2lupsign{ 1 }; ///< The sign to apply to the lookup table value for ADC 2.
67
68 float m_deltaAngle{ 0 }; ///< The offset angle to apply to the looked-up values, applied to both. Default is 0.
69
70 float m_adc1delta{ 0 }; ///< The offset angle to apply to the looked-up value for ADC 1, applied in addition to
71 ///< deltaAngle. Default is 0.
72
73 float m_adc2delta{ 0 }; ///< The offset angle to apply to the looked-up value for ADC 2, applied in addition to
74 ///< deltaAngle. Default is 0.
75
76 float m_minZD{ 5.1 }; ///< The minimum zenith distance at which to interpolate and move the ADCs. Default is 5.1.
77
78 std::string m_adc1DevName{ "stageadc1" }; ///< The device name of the ADC 1 stage. Default is 'stageadc1'.
79 std::string m_adc2DevName{ "stageadc2" }; ///< The device name of the ADC 2 stage. Default is 'stageadc2'.
80
81 std::string m_tcsDevName{
82 "tcsi" }; ///< The device name of the TCS interface providing 'teldata.zd'. Default is 'tcsi'.
83
84 float m_updateInterval{ 10 }; ///< The interval at which to update positions, in seconds. Default is 10 secs.
85
86 ///@}
87
88 float m_maxZD{ 0 }; ///< The maximum zenith distance represented by the loaded lookup table.
89
90 std::vector<double> m_lupZD; ///< Lookup-table zenith-distance samples.
91 std::vector<double> m_lupADC1; ///< Lookup-table ADC 1 offsets corresponding to m_lupZD.
92 std::vector<double> m_lupADC2; ///< Lookup-table ADC 2 offsets corresponding to m_lupZD.
93
94 mx::math::gslInterpolator<mx::math::gsl_interp_linear<double>>
95 m_terpADC1; ///< ADC 1 interpolator built from the lookup table.
96 mx::math::gslInterpolator<mx::math::gsl_interp_linear<double>>
97 m_terpADC2; ///< ADC 2 interpolator built from the lookup table.
98
99 bool m_lookupReady{ false }; ///< True once the ADC lookup table has been validated and the interpolators are ready.
100
101 bool m_tracking{ false }; ///< True when automatic ADC updates are enabled.
102
103 float m_zd{ 0 }; ///< The most recent finite zenith distance received from the TCS interface.
104
105 bool m_haveZD{ false }; ///< True once at least one valid zenith distance has been received.
106
107 double m_lastUpdate{ 0 }; ///< Timestamp of the last ADC command dispatched by the tracker.
108
109 pcf::IndiProperty m_indiP_belowMinZD; ///< Status switch indicating that the current ZD is below minZD.
110 pcf::IndiProperty m_indiP_aboveMaxZD; ///< Status switch indicating that the current ZD is above maxZD.
111
112 enum class zdLimitState
113 {
114 unknown, ///< No valid range status is currently available.
115 inRange, ///< The current ZD is within the usable lookup-table range.
116 belowMin, ///< The current ZD is below minZD.
117 aboveMax ///< The current ZD is above maxZD.
118 };
119
121 zdLimitState::unknown }; ///< Current ZD limit state reflected in the status properties.
123 zdLimitState::unknown }; ///< Last ZD limit state already announced by appLogic threshold-crossing warnings.
124
125 public:
126 /// Default c'tor.
127 adcTracker();
128
129 /// D'tor, declared and defined for noexcept.
131 {
132 }
133
134 /// Set up configuration entries.
135 virtual void setupConfig();
136
137 /// Implementation of loadConfig logic, separated for testing.
138 /** This is called by loadConfig().
139 */
140 int loadConfigImpl(
141 mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
142
143 /// Load configuration values.
144 virtual void loadConfig();
145
146 /// Startup function
147 /**
148 *
149 */
150 virtual int appStartup();
151
152 /// Implementation of the FSM for adcTracker.
153 /**
154 * \returns 0 on no critical error
155 * \returns -1 on an error requiring shutdown
156 */
157 virtual int appLogic();
158
159 /// Shutdown the app.
160 /**
161 *
162 */
163 virtual int appShutdown();
164
165 protected:
166 /// Send the ADC 1 target command.
167 virtual int sendADC1Position( float adc1 /**< [in] the ADC 1 target position */ );
168
169 /// Send the ADC 2 target command.
170 virtual int sendADC2Position( float adc2 /**< [in] the ADC 2 target position */ );
171
172 /// Initialize the ADC lookup interpolators from the loaded lookup-table data.
173 virtual void setupInterpolators();
174
175 /// Interpolate the ADC 1 lookup-table value for a zenith distance.
176 virtual float interpolateADC1( float zd /**< [in] the zenith distance to interpolate */ );
177
178 /// Interpolate the ADC 2 lookup-table value for a zenith distance.
179 virtual float interpolateADC2( float zd /**< [in] the zenith distance to interpolate */ );
180
181 /// Extract the zenith distance from the incoming TCS INDI property.
182 virtual float extractZD( const pcf::IndiProperty &ipRecv /**< [in] the incoming teldata property */ );
183
184 /// Update a local status switch and publish it if INDI is active.
185 void updateStatusSwitch( pcf::IndiProperty &prop, /**< [in/out] the status property to update */
186 bool on /**< [in] true to set the switch on */ );
187
188 /// Update the min/max ZD status properties and return the resulting ZD limit state.
189 zdLimitState updateZDLimitState( bool haveZD, /**< [in] true when a valid ZD is available */
190 float zd, /**< [in] the current zenith distance */
191 float minZD, /**< [in] the active minimum ZD threshold */
192 float maxZD /**< [in] the active maximum ZD threshold */ );
193
194 /// Emit a one-time threshold-crossing warning when appLogic enters a new out-of-range state.
195 void logZDLimitCrossing( zdLimitState state, /**< [in] the current ZD limit state */
196 float zd, /**< [in] the current zenith distance */
197 float minZD, /**< [in] the active minimum ZD threshold */
198 float maxZD /**< [in] the active maximum ZD threshold */ );
199
200 /** @name INDI
201 *
202 * @{
203 */
204 protected:
205 pcf::IndiProperty m_indiP_tracking; ///< The INDI toggle used to enable or disable tracking.
206
207 pcf::IndiProperty m_indiP_deltaAngle; ///< The shared user offset applied to both ADC targets.
208 pcf::IndiProperty m_indiP_deltaADC1; ///< The user offset applied only to ADC 1.
209 pcf::IndiProperty m_indiP_deltaADC2; ///< The user offset applied only to ADC 2.
210
211 pcf::IndiProperty m_indiP_minZD; ///< The user-configurable minimum zenith distance for interpolation.
212
213 pcf::IndiProperty m_indiP_teldata; ///< The subscribed TCS property providing zenith distance updates.
214
215 pcf::IndiProperty m_indiP_adc1pos; ///< The outbound ADC 1 stage position command property.
216 pcf::IndiProperty m_indiP_adc2pos; ///< The outbound ADC 2 stage position command property.
217
218 public:
219 /// Handle new tracking toggle requests.
221
222 /// Handle new shared delta-angle requests.
224 /// Handle new ADC 1 delta-angle requests.
226 /// Handle new ADC 2 delta-angle requests.
228
229 /// Handle new minimum-zenith-distance requests.
231
232 /// Handle incoming telescope data updates.
234
235 ///@}
236
237 /** \name Telemeter Interface
238 *
239 * @{
240 */
241 int checkRecordTimes();
242
243 int recordTelem( const telem_adctrack * );
244
245 int recordADCTrack( bool force = false );
246
247 ///@}
248};
249
250adcTracker::adcTracker() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
251{
252
253 return;
254}
255
257{
258 config.add( "adcs.lookupFile",
259 "",
260 "adcs.lookupFile",
261 argType::Required,
262 "adcs",
263 "lookupFile",
264 false,
265 "string",
266 "The name of the file, in the calib directory, containing the adc lookup table. Default is "
267 "'adc_lookup_table.txt'." );
268
269 config.add( "adcs.adc1zero",
270 "",
271 "adcs.adc1zero",
272 argType::Required,
273 "adcs",
274 "adc1zero",
275 false,
276 "float",
277 "The starting point for ADC 1. Default is 0." );
278
279 config.add( "adcs.adc1lupsign",
280 "",
281 "adcs.adc1lupsign",
282 argType::Required,
283 "adcs",
284 "adc1lupsign",
285 false,
286 "int",
287 "The sign to apply for the LUP values for ADC 1. Default is +1." );
288
289 config.add( "adcs.adc2zero",
290 "",
291 "adcs.adc2zero",
292 argType::Required,
293 "adcs",
294 "adc2zero",
295 false,
296 "float",
297 "The starting point for ADC 2. Default is 0." );
298
299 config.add( "adcs.adc2lupsign",
300 "",
301 "adcs.adc2lupsign",
302 argType::Required,
303 "adcs",
304 "adc2lupsign",
305 false,
306 "int",
307 "The sign to apply for the LUP values for ADC 2. Default is +1." );
308
309 config.add( "adcs.deltaAngle",
310 "",
311 "adcs.deltaAngle",
312 argType::Required,
313 "adcs",
314 "deltaAngle",
315 false,
316 "float",
317 "The offset angle to apply to the looked-up values, applied to both. Default is 0." );
318
319 config.add( "adcs.adc1delta",
320 "",
321 "adcs.adc1delta",
322 argType::Required,
323 "adcs",
324 "adc1delta",
325 false,
326 "float",
327 "The offset angle to apply to the looked-up value for ADC 1, applied in addition to deltaAngle. "
328 "Default is 0." );
329
330 config.add( "adcs.adc2delta",
331 "",
332 "adcs.adc2delta",
333 argType::Required,
334 "adcs",
335 "adc2delta",
336 false,
337 "float",
338 "The offset angle to apply to the looked-up value for ADC 2, applied in addition to deltaAngle. "
339 "Default is 0." );
340
341 config.add( "adcs.minZD",
342 "",
343 "adcs.minZD",
344 argType::Required,
345 "adcs",
346 "minZD",
347 false,
348 "float",
349 "The minimum zenith distance at which to interpolate and move the ADCs. Default is 5.1" );
350
351 config.add( "adcs.adc1DevName",
352 "",
353 "adcs.adc1devName",
354 argType::Required,
355 "adcs",
356 "adc1DevName",
357 false,
358 "string",
359 "The device name of the ADC 1 stage. Default is 'stageadc1'" );
360
361 config.add( "adcs.adc2DevName",
362 "",
363 "adcs.adc2devName",
364 argType::Required,
365 "adcs",
366 "adc2DevName",
367 false,
368 "string",
369 "The device name of the ADC 2 stage. Default is 'stageadc2'" );
370
371 config.add( "tcs.devName",
372 "",
373 "tcs.devName",
374 argType::Required,
375 "tcs",
376 "devName",
377 false,
378 "string",
379 "The device name of the TCS Interface providing 'teldata.zd'. Default is 'tcsi'" );
380
381 config.add( "tracking.updateInterval",
382 "",
383 "tracking.updateInterval",
384 argType::Required,
385 "tracking",
386 "updateInterval",
387 false,
388 "float",
389 "The interval at which to update positions, in seconds. Default is 10 secs." );
390
391 TELEMETER_SETUP_CONFIG( config );
392}
393
394int adcTracker::loadConfigImpl( mx::app::appConfigurator &_config )
395{
396 _config( m_lookupFile, "adcs.lookupFile" );
397 _config( m_adc1zero, "adcs.adc1zero" );
398 _config( m_adc1lupsign, "adcs.adc1lupsign" );
399 _config( m_adc2zero, "adcs.adc2zero" );
400 _config( m_adc2lupsign, "adcs.adc2lupsign" );
401 _config( m_deltaAngle, "adcs.deltaAngle" );
402 _config( m_adc1delta, "adcs.adc1delta" );
403 _config( m_adc2delta, "adcs.adc2delta" );
404 _config( m_minZD, "adcs.minZD" );
405 _config( m_adc1DevName, "adcs.adc1DevName" );
406 _config( m_adc2DevName, "adcs.adc2DevName" );
407
408 _config( m_tcsDevName, "tcs.devName" );
409
410 _config( m_updateInterval, "tracking.updateInterval" );
411
413
414 return 0;
415}
416
418{
419 loadConfigImpl( config );
420}
421
423{
425
426 std::string luppath = m_calibDir + "/" + m_lookupFile;
427
428 if( mx::ioutils::readColumns<mx::ioutils::readColCommaDelim>( luppath, m_lupZD, m_lupADC1, m_lupADC2 ) !=
429 mx::error_t::noerror )
430 {
431 return log<software_critical, -1>( "error reading lookup table from " + luppath );
432 }
433
434 if( m_lupZD.size() != m_lupADC1.size() || m_lupZD.size() != m_lupADC2.size() )
435 {
436 return log<software_critical, -1>( "inconsistent sizes in " + luppath );
437 }
438
439 if( m_lupZD.size() < 2 )
440 {
441 return log<software_critical, -1>( "lookup table must contain at least two rows in " + luppath );
442 }
443
444 for( size_t n = 0; n < m_lupZD.size(); ++n )
445 {
446 if( !std::isfinite( m_lupZD[n] ) || !std::isfinite( m_lupADC1[n] ) || !std::isfinite( m_lupADC2[n] ) )
447 {
448 return log<software_critical, -1>( "non-finite lookup table value at row " + std::to_string( n ) + " in " +
449 luppath );
450 }
451
452 if( n > 0 && m_lupZD[n] <= m_lupZD[n - 1] )
453 {
454 return log<software_critical, -1>( "lookup table zenith distances must be strictly increasing in " +
455 luppath );
456 }
457 }
458
459 log<text_log>( "Read ADC lookup table " + luppath + " with " + std::to_string( m_lupZD.size() ) +
460 " entries spanning ZD 0 to " + std::to_string( m_lupZD.back() ) );
461
462 try
463 {
465 }
466 catch( const std::exception &e )
467 {
468 return log<software_critical, -1>( std::string( "exception setting up ADC interpolators from " ) + luppath +
469 ": " + e.what() );
470 }
471 catch( ... )
472 {
473 return log<software_critical, -1>( "unknown exception setting up ADC interpolators from " + luppath );
474 }
475
476 m_maxZD = static_cast<float>( m_lupZD.back() );
477 m_lookupReady = true;
478
481
482 createStandardIndiNumber<float>( m_indiP_deltaAngle, "deltaAngle", -180.0, 180.0, 0, "%0.2f" );
483 m_indiP_deltaAngle["target"].set( m_deltaAngle );
484 m_indiP_deltaAngle["current"].set( m_deltaAngle );
486
487 createStandardIndiNumber<float>( m_indiP_deltaADC1, "deltaADC1", -180.0, 180.0, 0, "%0.2f" );
488 m_indiP_deltaADC1["target"].set( m_adc1delta );
489 m_indiP_deltaADC1["current"].set( m_adc1delta );
491
492 createStandardIndiNumber<float>( m_indiP_deltaADC2, "deltaADC2", -180.0, 180.0, 0, "%0.2f" );
493 m_indiP_deltaADC2["target"].set( m_adc2delta );
494 m_indiP_deltaADC2["current"].set( m_adc2delta );
496
497 createStandardIndiNumber<float>( m_indiP_minZD, "minZD", 0.0, 90.0, 0, "%0.2f" );
498 m_indiP_minZD["target"].set( m_minZD );
499 m_indiP_minZD["current"].set( m_minZD );
501
502 m_indiP_belowMinZD = pcf::IndiProperty( pcf::IndiProperty::Switch );
503 m_indiP_belowMinZD.setDevice( configName() );
504 m_indiP_belowMinZD.setName( "belowMinZD" );
505 m_indiP_belowMinZD.setPerm( pcf::IndiProperty::ReadOnly );
506 m_indiP_belowMinZD.setRule( pcf::IndiProperty::AtMostOne );
507 m_indiP_belowMinZD.setState( INDI_IDLE );
508 m_indiP_belowMinZD.setLabel( "Below minZD" );
509 m_indiP_belowMinZD.setGroup( "status" );
510 m_indiP_belowMinZD.add( pcf::IndiElement( "state" ) );
511 m_indiP_belowMinZD["state"].setSwitchState( pcf::IndiElement::Off );
512 m_indiP_belowMinZD.setPerm( pcf::IndiProperty::ReadOnly );
514
515 m_indiP_aboveMaxZD = pcf::IndiProperty( pcf::IndiProperty::Switch );
516 m_indiP_aboveMaxZD.setDevice( configName() );
517 m_indiP_aboveMaxZD.setName( "aboveMaxZD" );
518 m_indiP_aboveMaxZD.setPerm( pcf::IndiProperty::ReadOnly );
519 m_indiP_aboveMaxZD.setRule( pcf::IndiProperty::AtMostOne );
520 m_indiP_aboveMaxZD.setState( INDI_IDLE );
521 m_indiP_aboveMaxZD.setLabel( "Above maxZD" );
522 m_indiP_aboveMaxZD.setGroup( "status" );
523 m_indiP_aboveMaxZD.add( pcf::IndiElement( "state" ) );
524 m_indiP_aboveMaxZD["state"].setSwitchState( pcf::IndiElement::Off );
525 m_indiP_aboveMaxZD.setPerm( pcf::IndiProperty::ReadOnly );
527
529
530 m_indiP_adc1pos = pcf::IndiProperty( pcf::IndiProperty::Number );
531 m_indiP_adc1pos.setDevice( m_adc1DevName );
532 m_indiP_adc1pos.setName( "position" );
533 m_indiP_adc1pos.add( pcf::IndiElement( "target" ) );
534
535 m_indiP_adc2pos = pcf::IndiProperty( pcf::IndiProperty::Number );
536 m_indiP_adc2pos.setDevice( m_adc2DevName );
537 m_indiP_adc2pos.setName( "position" );
538 m_indiP_adc2pos.add( pcf::IndiElement( "target" ) );
539
540 recordADCTrack( true );
541 updateZDLimitState( false, 0.0f, m_minZD, m_maxZD );
542
544
545 return 0;
546}
547
549{
551
552 const double now = mx::sys::get_curr_time();
553
554 bool tracking = false;
555 bool lookupReady = false;
556 bool haveZD = false;
557 float zd = 0;
558 float minZD = 0;
559 float deltaAngle = 0;
560 float adc1delta = 0;
561 float adc2delta = 0;
562 float adc1zero = 0;
563 float adc2zero = 0;
564 float maxZD = 0;
565 float lastUpdate = 0;
566 int adc1lupsign = 1;
567 int adc2lupsign = 1;
568
569 { // mutex scope
570 std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
571
572 if( !lock.owns_lock() )
573 {
574 return 0;
575 }
576
577 tracking = m_tracking;
580 zd = m_zd;
581 minZD = m_minZD;
589 maxZD = m_maxZD;
591
592 if( !tracking )
593 {
594 m_lastUpdate = 0;
595 }
596 } // mutex scope
597
599
600 if( !tracking )
601 {
603 return 0;
604 }
605
607
609 {
610 return 0;
611 }
612
613 { // mutex scope
614 std::lock_guard<std::mutex> guard( m_indiMutex );
616 } // mutex scope
617
618 if( !std::isfinite( zd ) )
619 {
620 log<software_error>( "received non-finite zenith distance in ADC tracker" );
621 return 0;
622 }
623
624 float dadc1 = 0.0;
625 float dadc2 = 0.0;
626
627 if( zd > maxZD )
628 {
629 dadc1 = static_cast<float>( m_lupADC1.back() );
630 dadc2 = static_cast<float>( m_lupADC2.back() );
631 }
632 else if( zd >= minZD )
633 {
634 try
635 {
636 dadc1 = interpolateADC1( zd );
637 dadc2 = interpolateADC2( zd );
638 }
639 catch( const std::exception &e )
640 {
641 log<software_error>( std::string( "exception interpolating ADC targets: " ) + e.what() );
642 return 0;
643 }
644 catch( ... )
645 {
646 log<software_error>( "unknown exception interpolating ADC targets" );
647 return 0;
648 }
649 }
650 else
651 {
652 }
653
656
657 if( !std::isfinite( adc1 ) || !std::isfinite( adc2 ) )
658 {
659 log<software_error>( "computed non-finite ADC target" );
660 return 0;
661 }
662
663 if( sendADC1Position( adc1 ) < 0 || sendADC2Position( adc2 ) < 0 )
664 {
665 log<software_error>( "failed to send ADC target positions" );
666 }
667
668 return 0;
669}
670
672{
674
675 return 0;
676}
677
679{
680 if( sendNewProperty( m_indiP_adc1pos, "target", adc1 ) < 0 )
681 {
682 return log<software_error, -1>( "failed to send ADC 1 target" );
683 }
684
685 return 0;
686}
687
689{
690 if( sendNewProperty( m_indiP_adc2pos, "target", adc2 ) < 0 )
691 {
692 return log<software_error, -1>( "failed to send ADC 2 target" );
693 }
694
695 return 0;
696}
697
703
705{
706 return static_cast<float>( std::fabs( m_terpADC1( zd ) ) );
707}
708
710{
711 return static_cast<float>( std::fabs( m_terpADC2( zd ) ) );
712}
713
714float adcTracker::extractZD( const pcf::IndiProperty &ipRecv )
715{
716 return ipRecv["zd"].get<float>();
717}
718
719void adcTracker::updateStatusSwitch( pcf::IndiProperty &prop, bool on )
720{
721 if( !prop.find( "state" ) )
722 {
723 return;
724 }
725
726 pcf::IndiElement::SwitchStateType newVal = on ? pcf::IndiElement::On : pcf::IndiElement::Off;
727 pcf::IndiProperty::PropertyStateType state = on ? INDI_OK : INDI_IDLE;
728
729 if( prop["state"].getSwitchState() == newVal && prop.getState() == state )
730 {
731 return;
732 }
733
734 prop["state"].setSwitchState( newVal );
735 prop.setState( state );
736 prop.setTimeStamp( pcf::TimeStamp() );
737
738 if( m_indiDriver )
739 {
740 m_indiDriver->sendSetProperty( prop );
741 }
742}
743
744adcTracker::zdLimitState adcTracker::updateZDLimitState( bool haveZD, float zd, float minZD, float maxZD )
745{
747
748 if( haveZD && std::isfinite( zd ) )
749 {
750 if( zd < minZD )
751 {
753 }
754 else if( zd > maxZD )
755 {
757 }
758 else
759 {
761 }
762 }
763
766
768
769 return nextState;
770}
771
772void adcTracker::logZDLimitCrossing( zdLimitState state, float zd, float minZD, float maxZD )
773{
775 {
776 return;
777 }
778
780 {
781 log<text_log>( "ADC tracker below minZD: zd=" + std::to_string( zd ) + " minZD=" + std::to_string( minZD ),
783 }
784 else if( state == zdLimitState::aboveMax )
785 {
786 log<text_log>( "ADC tracker above maxZD: zd=" + std::to_string( zd ) + " maxZD=" + std::to_string( maxZD ),
788 }
789
791}
792
793INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_tracking )( const pcf::IndiProperty &ipRecv )
794{
795 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tracking, ipRecv );
796
797 if( !ipRecv.find( "toggle" ) )
798 return 0;
799
800 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
801 {
802 updateSwitchIfChanged( m_indiP_tracking, "toggle", pcf::IndiElement::On, INDI_IDLE );
803
804 { // mutex scope
805 std::lock_guard<std::mutex> guard( m_indiMutex );
806 m_tracking = true;
807 m_lastUpdate = 0;
808 }
809
810 log<text_log>( "started ADC rotation tracking" );
811 }
812 else
813 {
814 updateSwitchIfChanged( m_indiP_tracking, "toggle", pcf::IndiElement::Off, INDI_IDLE );
815
816 { // mutex scope
817 std::lock_guard<std::mutex> guard( m_indiMutex );
818 m_tracking = false;
819 m_lastUpdate = 0;
820 m_lastLoggedZDLimitState = zdLimitState::unknown;
821 }
822
823 log<text_log>( "stopped ADC rotation tracking" );
824 }
825
826 recordADCTrack();
827
828 return 0;
829}
830
831INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaAngle )( const pcf::IndiProperty &ipRecv )
832{
833 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaAngle, ipRecv );
834
835 float target;
836
837 if( indiTargetUpdate( m_indiP_deltaAngle, target, ipRecv ) < 0 )
838 {
839 return log<software_error, -1>();
840 }
841
842 { // mutex scope
843 std::lock_guard<std::mutex> guard( m_indiMutex );
844
845 m_deltaAngle = target;
846 updateIfChanged( m_indiP_deltaAngle, "current", m_deltaAngle );
847 } // mutex scope
848
849 log<text_log>( "set deltaAngle to " + std::to_string( m_deltaAngle ) );
850
851 recordADCTrack();
852
853 return 0;
854}
855
856INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaADC1 )( const pcf::IndiProperty &ipRecv )
857{
858 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaADC1, ipRecv );
859
860 float target;
861
862 if( indiTargetUpdate( m_indiP_deltaADC1, target, ipRecv ) < 0 )
863 {
864 return log<software_error, -1>();
865 }
866
867 { // mutex scope
868 std::lock_guard<std::mutex> guard( m_indiMutex );
869
870 m_adc1delta = target;
871 updateIfChanged( m_indiP_deltaADC1, "current", m_adc1delta );
872 } // mutex scope
873
874 log<text_log>( "set deltaADC1 to " + std::to_string( m_adc1delta ) );
875
876 recordADCTrack();
877
878 return 0;
879}
880
881INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaADC2 )( const pcf::IndiProperty &ipRecv )
882{
883 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaADC2, ipRecv );
884
885 float target;
886
887 if( indiTargetUpdate( m_indiP_deltaADC2, target, ipRecv ) < 0 )
888 {
889 return log<software_error, -1>();
890 }
891
892 { // mutex scope
893 std::lock_guard<std::mutex> guard( m_indiMutex );
894
895 m_adc2delta = target;
896 updateIfChanged( m_indiP_deltaADC2, "current", m_adc2delta );
897 } // mutex scope
898
899 log<text_log>( "set deltaADC2 to " + std::to_string( m_adc2delta ) );
900
901 recordADCTrack();
902
903 return 0;
904}
905
906INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_minZD )( const pcf::IndiProperty &ipRecv )
907{
908 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_minZD, ipRecv );
909
910 float target;
911 float zd = 0;
912 float maxZD = 0;
913 bool haveZD = false;
914
915 if( indiTargetUpdate( m_indiP_minZD, target, ipRecv ) < 0 )
916 {
917 return log<software_error, -1>();
918 }
919
920 { // mutex scope
921 std::lock_guard<std::mutex> guard( m_indiMutex );
922
923 m_minZD = target;
924 updateIfChanged( m_indiP_minZD, "current", m_minZD );
925 zd = m_zd;
926 maxZD = m_maxZD;
927 haveZD = m_lookupReady && m_haveZD;
928 } // mutex scope
929
930 log<text_log>( "set minZD to " + std::to_string( m_minZD ) );
931
932 recordADCTrack();
933 updateZDLimitState( haveZD, zd, target, maxZD );
934
935 return 0;
936}
937
938INDI_SETCALLBACK_DEFN( adcTracker, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
939{
940 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_teldata, ipRecv );
941
942 if( !ipRecv.find( "zd" ) )
943 return 0;
944
945 float zd = 0;
946 bool lookupReady = false;
947 float minZD = 0;
948 float maxZD = 0;
949
950 try
951 {
952 zd = extractZD( ipRecv );
953 }
954 catch( const std::exception &e )
955 {
956 log<software_error>( std::string( "exception reading teldata.zd: " ) + e.what() );
957 return 0;
958 }
959 catch( ... )
960 {
961 log<software_error>( "unknown exception reading teldata.zd" );
962 return 0;
963 }
964
965 if( !std::isfinite( zd ) )
966 {
967 log<software_error>( "received non-finite teldata.zd" );
968 return 0;
969 }
970
971 { // mutex scope
972 std::lock_guard<std::mutex> guard( m_indiMutex );
973 m_zd = zd;
974 m_haveZD = true;
975 lookupReady = m_lookupReady;
976 minZD = m_minZD;
977 maxZD = m_maxZD;
978 } // mutex scope
979
980 updateZDLimitState( lookupReady, zd, minZD, maxZD );
981
982 return 0;
983}
984
989
991{
992 return recordADCTrack( true );
993}
994
996{
997 static bool tracking = false;
998 static float deltaAngle = 0;
999 static float adc1delta = 0;
1000 static float adc2delta = 0;
1001 static float minZD = 0;
1002
1003 bool nextTracking = false;
1004 float nextDeltaAngle = 0;
1005 float nextADC1delta = 0;
1006 float nextADC2delta = 0;
1007 float nextMinZD = 0;
1008
1009 { // mutex scope
1010 std::lock_guard<std::mutex> guard( m_indiMutex );
1011
1017 } // mutex scope
1018
1019 if( nextTracking != tracking || nextDeltaAngle != deltaAngle || nextADC1delta != adc1delta ||
1021 {
1023
1024 tracking = nextTracking;
1028 minZD = nextMinZD;
1029 }
1030
1031 return 0;
1032}
1033
1034} // namespace app
1035} // namespace MagAOX
1036
1037#endif // adcTracker_hpp
The base-class for XWCTk applications.
stateCodes::stateCodeT state()
Get the current state code.
int registerIndiPropertyNew(pcf::IndiProperty &prop, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for.
int createStandardIndiToggleSw(pcf::IndiProperty &prop, const std::string &name, const std::string &label="", const std::string &group="")
Create a standard R/W INDI switch with a single toggle element.
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
std::string m_calibDir
The path to calibration files for MagAOX.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
std::mutex m_indiMutex
Mutex for locking INDI communications.
std::string configName()
Get the config name.
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 ADC Tracker.
bool m_tracking
True when automatic ADC updates are enabled.
virtual float interpolateADC1(float zd)
Interpolate the ADC 1 lookup-table value for a zenith distance.
int recordTelem(const telem_adctrack *)
pcf::IndiProperty m_indiP_deltaADC1
The user offset applied only to ADC 1.
virtual int appShutdown()
Shutdown the app.
zdLimitState m_zdLimitState
Current ZD limit state reflected in the status properties.
float m_maxZD
The maximum zenith distance represented by the loaded lookup table.
std::vector< double > m_lupZD
Lookup-table zenith-distance samples.
std::vector< double > m_lupADC1
Lookup-table ADC 1 offsets corresponding to m_lupZD.
pcf::IndiProperty m_indiP_belowMinZD
Status switch indicating that the current ZD is below minZD.
int recordADCTrack(bool force=false)
virtual void setupInterpolators()
Initialize the ADC lookup interpolators from the loaded lookup-table data.
virtual float extractZD(const pcf::IndiProperty &ipRecv)
Extract the zenith distance from the incoming TCS INDI property.
zdLimitState m_lastLoggedZDLimitState
Last ZD limit state already announced by appLogic threshold-crossing warnings.
void logZDLimitCrossing(zdLimitState state, float zd, float minZD, float maxZD)
Emit a one-time threshold-crossing warning when appLogic enters a new out-of-range state.
dev::telemeter< adcTracker > telemeterT
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
float m_deltaAngle
The offset angle to apply to the looked-up values, applied to both. Default is 0.
pcf::IndiProperty m_indiP_deltaADC2
The user offset applied only to ADC 2.
pcf::IndiProperty m_indiP_teldata
The subscribed TCS property providing zenith distance updates.
INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_tracking)
Handle new tracking toggle requests.
INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_deltaADC2)
Handle new ADC 2 delta-angle requests.
~adcTracker() noexcept
D'tor, declared and defined for noexcept.
std::string m_adc2DevName
The device name of the ADC 2 stage. Default is 'stageadc2'.
virtual int sendADC1Position(float adc1)
Send the ADC 1 target command.
INDI_SETCALLBACK_DECL(adcTracker, m_indiP_teldata)
Handle incoming telescope data updates.
pcf::IndiProperty m_indiP_tracking
The INDI toggle used to enable or disable tracking.
double m_lastUpdate
Timestamp of the last ADC command dispatched by the tracker.
void updateStatusSwitch(pcf::IndiProperty &prop, bool on)
Update a local status switch and publish it if INDI is active.
mx::math::gslInterpolator< mx::math::gsl_interp_linear< double > > m_terpADC1
ADC 1 interpolator built from the lookup table.
bool m_haveZD
True once at least one valid zenith distance has been received.
float m_zd
The most recent finite zenith distance received from the TCS interface.
pcf::IndiProperty m_indiP_deltaAngle
The shared user offset applied to both ADC targets.
std::string m_adc1DevName
The device name of the ADC 1 stage. Default is 'stageadc1'.
pcf::IndiProperty m_indiP_adc2pos
The outbound ADC 2 stage position command property.
@ aboveMax
The current ZD is above maxZD.
@ inRange
The current ZD is within the usable lookup-table range.
@ unknown
No valid range status is currently available.
@ belowMin
The current ZD is below minZD.
INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_deltaAngle)
Handle new shared delta-angle requests.
zdLimitState updateZDLimitState(bool haveZD, float zd, float minZD, float maxZD)
Update the min/max ZD status properties and return the resulting ZD limit state.
friend class adcTracker_test
bool m_lookupReady
True once the ADC lookup table has been validated and the interpolators are ready.
virtual void setupConfig()
Set up configuration entries.
virtual float interpolateADC2(float zd)
Interpolate the ADC 2 lookup-table value for a zenith distance.
pcf::IndiProperty m_indiP_minZD
The user-configurable minimum zenith distance for interpolation.
std::string m_tcsDevName
The device name of the TCS interface providing 'teldata.zd'. Default is 'tcsi'.
pcf::IndiProperty m_indiP_aboveMaxZD
Status switch indicating that the current ZD is above maxZD.
mx::math::gslInterpolator< mx::math::gsl_interp_linear< double > > m_terpADC2
ADC 2 interpolator built from the lookup table.
float m_updateInterval
The interval at which to update positions, in seconds. Default is 10 secs.
float m_adc1zero
The starting point for ADC 1. Default is 0.
adcTracker()
Default c'tor.
virtual int appStartup()
Startup function.
virtual int sendADC2Position(float adc2)
Send the ADC 2 target command.
int m_adc1lupsign
The sign to apply to the lookup table value for ADC 1.
float m_adc2zero
The starting point for ADC 2. Default is 0.
pcf::IndiProperty m_indiP_adc1pos
The outbound ADC 1 stage position command property.
INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_minZD)
Handle new minimum-zenith-distance requests.
int m_adc2lupsign
The sign to apply to the lookup table value for ADC 2.
std::vector< double > m_lupADC2
Lookup-table ADC 2 offsets corresponding to m_lupZD.
virtual int appLogic()
Implementation of the FSM for adcTracker.
float m_minZD
The minimum zenith distance at which to interpolate and move the ADCs. Default is 5....
INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_deltaADC1)
Handle new ADC 1 delta-angle requests.
virtual void loadConfig()
Load configuration values.
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
#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 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)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:19
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
A device base class which saves telemetry.
Definition telemeter.hpp:75
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
@ READY
The device is ready for operation, but is not operating.
Software CRITICAL log entry.
Software ERR log entry.
Log entry recording ADC tracker operator-adjustable state.
#define TELEMETER_APP_LOGIC
Call telemeter::appLogic with error checking.
#define TELEMETER_LOAD_CONFIG(cfig)
Call telemeter::loadConfig with error checking.
#define TELEMETER_APP_STARTUP
Call telemeter::appStartup with error checking.
#define TELEMETER_SETUP_CONFIG(cfig)
Call telemeter::setupConfig with error checking.
#define TELEMETER_APP_SHUTDOWN
Call telemeter::appShutdown with error checking.