Line data Source code
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 :
32 : namespace MagAOX
33 : {
34 : namespace app
35 : {
36 :
37 : /// The MagAO-X ADC Tracker
38 : /**
39 : * \ingroup adcTracker
40 : */
41 : class 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 :
49 : typedef dev::telemeter<adcTracker> telemeterT;
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 :
120 : zdLimitState m_zdLimitState{
121 : zdLimitState::unknown }; ///< Current ZD limit state reflected in the status properties.
122 : zdLimitState m_lastLoggedZDLimitState{
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.
130 44 : ~adcTracker() noexcept
131 44 : {
132 44 : }
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.
220 1 : INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_tracking );
221 :
222 : /// Handle new shared delta-angle requests.
223 1 : INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_deltaAngle );
224 : /// Handle new ADC 1 delta-angle requests.
225 1 : INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_deltaADC1 );
226 : /// Handle new ADC 2 delta-angle requests.
227 1 : INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_deltaADC2 );
228 :
229 : /// Handle new minimum-zenith-distance requests.
230 1 : INDI_NEWCALLBACK_DECL( adcTracker, m_indiP_minZD );
231 :
232 : /// Handle incoming telescope data updates.
233 1 : INDI_SETCALLBACK_DECL( adcTracker, m_indiP_teldata );
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 :
250 484 : adcTracker::adcTracker() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
251 : {
252 :
253 44 : return;
254 0 : }
255 :
256 2 : void adcTracker::setupConfig()
257 : {
258 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 28 : 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 2 : TELEMETER_SETUP_CONFIG( config );
392 2 : }
393 :
394 2 : int adcTracker::loadConfigImpl( mx::app::appConfigurator &_config )
395 : {
396 4 : _config( m_lookupFile, "adcs.lookupFile" );
397 4 : _config( m_adc1zero, "adcs.adc1zero" );
398 4 : _config( m_adc1lupsign, "adcs.adc1lupsign" );
399 4 : _config( m_adc2zero, "adcs.adc2zero" );
400 4 : _config( m_adc2lupsign, "adcs.adc2lupsign" );
401 4 : _config( m_deltaAngle, "adcs.deltaAngle" );
402 4 : _config( m_adc1delta, "adcs.adc1delta" );
403 4 : _config( m_adc2delta, "adcs.adc2delta" );
404 4 : _config( m_minZD, "adcs.minZD" );
405 4 : _config( m_adc1DevName, "adcs.adc1DevName" );
406 4 : _config( m_adc2DevName, "adcs.adc2DevName" );
407 :
408 4 : _config( m_tcsDevName, "tcs.devName" );
409 :
410 2 : _config( m_updateInterval, "tracking.updateInterval" );
411 :
412 2 : TELEMETER_LOAD_CONFIG( _config );
413 :
414 2 : return 0;
415 : }
416 :
417 2 : void adcTracker::loadConfig()
418 : {
419 2 : loadConfigImpl( config );
420 2 : }
421 :
422 20 : int adcTracker::appStartup()
423 : {
424 20 : TELEMETER_APP_STARTUP;
425 :
426 20 : std::string luppath = m_calibDir + "/" + m_lookupFile;
427 :
428 20 : if( mx::ioutils::readColumns<mx::ioutils::readColCommaDelim>( luppath, m_lupZD, m_lupADC1, m_lupADC2 ) !=
429 : mx::error_t::noerror )
430 : {
431 1 : return log<software_critical, -1>( "error reading lookup table from " + luppath );
432 : }
433 :
434 19 : if( m_lupZD.size() != m_lupADC1.size() || m_lupZD.size() != m_lupADC2.size() )
435 : {
436 1 : return log<software_critical, -1>( "inconsistent sizes in " + luppath );
437 : }
438 :
439 18 : if( m_lupZD.size() < 2 )
440 : {
441 1 : return log<software_critical, -1>( "lookup table must contain at least two rows in " + luppath );
442 : }
443 :
444 67 : for( size_t n = 0; n < m_lupZD.size(); ++n )
445 : {
446 51 : if( !std::isfinite( m_lupZD[n] ) || !std::isfinite( m_lupADC1[n] ) || !std::isfinite( m_lupADC2[n] ) )
447 : {
448 0 : return log<software_critical, -1>( "non-finite lookup table value at row " + std::to_string( n ) + " in " +
449 0 : luppath );
450 : }
451 :
452 51 : if( n > 0 && m_lupZD[n] <= m_lupZD[n - 1] )
453 : {
454 2 : return log<software_critical, -1>( "lookup table zenith distances must be strictly increasing in " +
455 1 : luppath );
456 : }
457 : }
458 :
459 48 : log<text_log>( "Read ADC lookup table " + luppath + " with " + std::to_string( m_lupZD.size() ) +
460 64 : " entries spanning ZD 0 to " + std::to_string( m_lupZD.back() ) );
461 :
462 : try
463 : {
464 16 : setupInterpolators();
465 : }
466 2 : catch( const std::exception &e )
467 : {
468 4 : return log<software_critical, -1>( std::string( "exception setting up ADC interpolators from " ) + luppath +
469 5 : ": " + e.what() );
470 1 : }
471 1 : catch( ... )
472 : {
473 1 : return log<software_critical, -1>( "unknown exception setting up ADC interpolators from " + luppath );
474 1 : }
475 :
476 14 : m_maxZD = static_cast<float>( m_lupZD.back() );
477 14 : m_lookupReady = true;
478 :
479 84 : createStandardIndiToggleSw( m_indiP_tracking, "tracking" );
480 14 : registerIndiPropertyNew( m_indiP_tracking, INDI_NEWCALLBACK( m_indiP_tracking ) );
481 :
482 112 : createStandardIndiNumber<float>( m_indiP_deltaAngle, "deltaAngle", -180.0, 180.0, 0, "%0.2f" );
483 28 : m_indiP_deltaAngle["target"].set( m_deltaAngle );
484 28 : m_indiP_deltaAngle["current"].set( m_deltaAngle );
485 14 : registerIndiPropertyNew( m_indiP_deltaAngle, INDI_NEWCALLBACK( m_indiP_deltaAngle ) );
486 :
487 112 : createStandardIndiNumber<float>( m_indiP_deltaADC1, "deltaADC1", -180.0, 180.0, 0, "%0.2f" );
488 28 : m_indiP_deltaADC1["target"].set( m_adc1delta );
489 28 : m_indiP_deltaADC1["current"].set( m_adc1delta );
490 14 : registerIndiPropertyNew( m_indiP_deltaADC1, INDI_NEWCALLBACK( m_indiP_deltaADC1 ) );
491 :
492 112 : createStandardIndiNumber<float>( m_indiP_deltaADC2, "deltaADC2", -180.0, 180.0, 0, "%0.2f" );
493 28 : m_indiP_deltaADC2["target"].set( m_adc2delta );
494 28 : m_indiP_deltaADC2["current"].set( m_adc2delta );
495 14 : registerIndiPropertyNew( m_indiP_deltaADC2, INDI_NEWCALLBACK( m_indiP_deltaADC2 ) );
496 :
497 112 : createStandardIndiNumber<float>( m_indiP_minZD, "minZD", 0.0, 90.0, 0, "%0.2f" );
498 28 : m_indiP_minZD["target"].set( m_minZD );
499 28 : m_indiP_minZD["current"].set( m_minZD );
500 14 : registerIndiPropertyNew( m_indiP_minZD, INDI_NEWCALLBACK( m_indiP_minZD ) );
501 :
502 14 : m_indiP_belowMinZD = pcf::IndiProperty( pcf::IndiProperty::Switch );
503 14 : m_indiP_belowMinZD.setDevice( configName() );
504 28 : m_indiP_belowMinZD.setName( "belowMinZD" );
505 14 : m_indiP_belowMinZD.setPerm( pcf::IndiProperty::ReadOnly );
506 14 : m_indiP_belowMinZD.setRule( pcf::IndiProperty::AtMostOne );
507 14 : m_indiP_belowMinZD.setState( INDI_IDLE );
508 28 : m_indiP_belowMinZD.setLabel( "Below minZD" );
509 28 : m_indiP_belowMinZD.setGroup( "status" );
510 28 : m_indiP_belowMinZD.add( pcf::IndiElement( "state" ) );
511 28 : m_indiP_belowMinZD["state"].setSwitchState( pcf::IndiElement::Off );
512 14 : m_indiP_belowMinZD.setPerm( pcf::IndiProperty::ReadOnly );
513 14 : registerIndiPropertyNew( m_indiP_belowMinZD, nullptr );
514 :
515 14 : m_indiP_aboveMaxZD = pcf::IndiProperty( pcf::IndiProperty::Switch );
516 14 : m_indiP_aboveMaxZD.setDevice( configName() );
517 28 : m_indiP_aboveMaxZD.setName( "aboveMaxZD" );
518 14 : m_indiP_aboveMaxZD.setPerm( pcf::IndiProperty::ReadOnly );
519 14 : m_indiP_aboveMaxZD.setRule( pcf::IndiProperty::AtMostOne );
520 14 : m_indiP_aboveMaxZD.setState( INDI_IDLE );
521 28 : m_indiP_aboveMaxZD.setLabel( "Above maxZD" );
522 28 : m_indiP_aboveMaxZD.setGroup( "status" );
523 28 : m_indiP_aboveMaxZD.add( pcf::IndiElement( "state" ) );
524 28 : m_indiP_aboveMaxZD["state"].setSwitchState( pcf::IndiElement::Off );
525 14 : m_indiP_aboveMaxZD.setPerm( pcf::IndiProperty::ReadOnly );
526 14 : registerIndiPropertyNew( m_indiP_aboveMaxZD, nullptr );
527 :
528 42 : REG_INDI_SETPROP( m_indiP_teldata, m_tcsDevName, "teldata" );
529 :
530 14 : m_indiP_adc1pos = pcf::IndiProperty( pcf::IndiProperty::Number );
531 14 : m_indiP_adc1pos.setDevice( m_adc1DevName );
532 28 : m_indiP_adc1pos.setName( "position" );
533 28 : m_indiP_adc1pos.add( pcf::IndiElement( "target" ) );
534 :
535 14 : m_indiP_adc2pos = pcf::IndiProperty( pcf::IndiProperty::Number );
536 14 : m_indiP_adc2pos.setDevice( m_adc2DevName );
537 28 : m_indiP_adc2pos.setName( "position" );
538 28 : m_indiP_adc2pos.add( pcf::IndiElement( "target" ) );
539 :
540 14 : recordADCTrack( true );
541 14 : updateZDLimitState( false, 0.0f, m_minZD, m_maxZD );
542 :
543 14 : state( stateCodes::READY );
544 :
545 14 : return 0;
546 20 : }
547 :
548 10 : int adcTracker::appLogic()
549 : {
550 10 : TELEMETER_APP_LOGIC;
551 :
552 10 : const double now = mx::sys::get_curr_time();
553 :
554 10 : bool tracking = false;
555 10 : bool lookupReady = false;
556 10 : bool haveZD = false;
557 10 : float zd = 0;
558 10 : float minZD = 0;
559 10 : float deltaAngle = 0;
560 10 : float adc1delta = 0;
561 10 : float adc2delta = 0;
562 10 : float adc1zero = 0;
563 10 : float adc2zero = 0;
564 10 : float maxZD = 0;
565 10 : float lastUpdate = 0;
566 10 : int adc1lupsign = 1;
567 10 : int adc2lupsign = 1;
568 :
569 : { // mutex scope
570 10 : std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
571 :
572 10 : if( !lock.owns_lock() )
573 : {
574 1 : return 0;
575 : }
576 :
577 9 : tracking = m_tracking;
578 9 : lookupReady = m_lookupReady;
579 9 : haveZD = m_haveZD;
580 9 : zd = m_zd;
581 9 : minZD = m_minZD;
582 9 : deltaAngle = m_deltaAngle;
583 9 : adc1delta = m_adc1delta;
584 9 : adc2delta = m_adc2delta;
585 9 : adc1zero = m_adc1zero;
586 9 : adc2zero = m_adc2zero;
587 9 : adc1lupsign = m_adc1lupsign;
588 9 : adc2lupsign = m_adc2lupsign;
589 9 : maxZD = m_maxZD;
590 9 : lastUpdate = m_lastUpdate;
591 :
592 9 : if( !tracking )
593 : {
594 1 : m_lastUpdate = 0;
595 : }
596 10 : } // mutex scope
597 :
598 9 : zdLimitState limitState = updateZDLimitState( lookupReady && haveZD, zd, minZD, maxZD );
599 :
600 9 : if( !tracking )
601 : {
602 1 : m_lastLoggedZDLimitState = zdLimitState::unknown;
603 1 : return 0;
604 : }
605 :
606 8 : logZDLimitCrossing( limitState, zd, minZD, maxZD );
607 :
608 8 : if( !lookupReady || !haveZD || now - lastUpdate <= m_updateInterval )
609 : {
610 1 : return 0;
611 : }
612 :
613 : { // mutex scope
614 7 : std::lock_guard<std::mutex> guard( m_indiMutex );
615 7 : m_lastUpdate = now;
616 7 : } // mutex scope
617 :
618 7 : if( !std::isfinite( zd ) )
619 : {
620 0 : log<software_error>( "received non-finite zenith distance in ADC tracker" );
621 0 : return 0;
622 : }
623 :
624 7 : float dadc1 = 0.0;
625 7 : float dadc2 = 0.0;
626 :
627 7 : if( zd > maxZD )
628 : {
629 1 : dadc1 = static_cast<float>( m_lupADC1.back() );
630 1 : dadc2 = static_cast<float>( m_lupADC2.back() );
631 : }
632 6 : else if( zd >= minZD )
633 : {
634 : try
635 : {
636 5 : dadc1 = interpolateADC1( zd );
637 3 : dadc2 = interpolateADC2( zd );
638 : }
639 2 : catch( const std::exception &e )
640 : {
641 3 : log<software_error>( std::string( "exception interpolating ADC targets: " ) + e.what() );
642 1 : return 0;
643 1 : }
644 1 : catch( ... )
645 : {
646 1 : log<software_error>( "unknown exception interpolating ADC targets" );
647 1 : return 0;
648 1 : }
649 : }
650 : else
651 : {
652 : }
653 :
654 5 : float adc1 = adc1zero + adc1lupsign * ( dadc1 + adc1delta + deltaAngle );
655 5 : float adc2 = adc2zero + adc2lupsign * ( dadc2 + adc2delta + deltaAngle );
656 :
657 5 : if( !std::isfinite( adc1 ) || !std::isfinite( adc2 ) )
658 : {
659 0 : log<software_error>( "computed non-finite ADC target" );
660 0 : return 0;
661 : }
662 :
663 5 : if( sendADC1Position( adc1 ) < 0 || sendADC2Position( adc2 ) < 0 )
664 : {
665 2 : log<software_error>( "failed to send ADC target positions" );
666 : }
667 :
668 5 : return 0;
669 : }
670 :
671 1 : int adcTracker::appShutdown()
672 : {
673 1 : TELEMETER_APP_SHUTDOWN;
674 :
675 1 : return 0;
676 : }
677 :
678 1 : int adcTracker::sendADC1Position( float adc1 )
679 : {
680 3 : if( sendNewProperty( m_indiP_adc1pos, "target", adc1 ) < 0 )
681 : {
682 1 : return log<software_error, -1>( "failed to send ADC 1 target" );
683 : }
684 :
685 0 : return 0;
686 : }
687 :
688 1 : int adcTracker::sendADC2Position( float adc2 )
689 : {
690 3 : if( sendNewProperty( m_indiP_adc2pos, "target", adc2 ) < 0 )
691 : {
692 1 : return log<software_error, -1>( "failed to send ADC 2 target" );
693 : }
694 :
695 0 : return 0;
696 : }
697 :
698 14 : void adcTracker::setupInterpolators()
699 : {
700 14 : m_terpADC1.setup( m_lupZD, m_lupADC1 );
701 14 : m_terpADC2.setup( m_lupZD, m_lupADC2 );
702 14 : }
703 :
704 3 : float adcTracker::interpolateADC1( float zd )
705 : {
706 3 : return static_cast<float>( std::fabs( m_terpADC1( zd ) ) );
707 : }
708 :
709 3 : float adcTracker::interpolateADC2( float zd )
710 : {
711 3 : return static_cast<float>( std::fabs( m_terpADC2( zd ) ) );
712 : }
713 :
714 3 : float adcTracker::extractZD( const pcf::IndiProperty &ipRecv )
715 : {
716 6 : return ipRecv["zd"].get<float>();
717 : }
718 :
719 60 : void adcTracker::updateStatusSwitch( pcf::IndiProperty &prop, bool on )
720 : {
721 120 : if( !prop.find( "state" ) )
722 : {
723 58 : return;
724 : }
725 :
726 46 : pcf::IndiElement::SwitchStateType newVal = on ? pcf::IndiElement::On : pcf::IndiElement::Off;
727 46 : pcf::IndiProperty::PropertyStateType state = on ? INDI_OK : INDI_IDLE;
728 :
729 138 : if( prop["state"].getSwitchState() == newVal && prop.getState() == state )
730 : {
731 44 : return;
732 : }
733 :
734 2 : prop["state"].setSwitchState( newVal );
735 2 : prop.setState( state );
736 2 : prop.setTimeStamp( pcf::TimeStamp() );
737 :
738 2 : if( m_indiDriver )
739 : {
740 0 : m_indiDriver->sendSetProperty( prop );
741 : }
742 : }
743 :
744 30 : adcTracker::zdLimitState adcTracker::updateZDLimitState( bool haveZD, float zd, float minZD, float maxZD )
745 : {
746 30 : zdLimitState nextState = zdLimitState::unknown;
747 :
748 30 : if( haveZD && std::isfinite( zd ) )
749 : {
750 8 : if( zd < minZD )
751 : {
752 1 : nextState = zdLimitState::belowMin;
753 : }
754 7 : else if( zd > maxZD )
755 : {
756 1 : nextState = zdLimitState::aboveMax;
757 : }
758 : else
759 : {
760 6 : nextState = zdLimitState::inRange;
761 : }
762 : }
763 :
764 30 : updateStatusSwitch( m_indiP_belowMinZD, nextState == zdLimitState::belowMin );
765 30 : updateStatusSwitch( m_indiP_aboveMaxZD, nextState == zdLimitState::aboveMax );
766 :
767 30 : m_zdLimitState = nextState;
768 :
769 30 : return nextState;
770 : }
771 :
772 8 : void adcTracker::logZDLimitCrossing( zdLimitState state, float zd, float minZD, float maxZD )
773 : {
774 8 : if( state == m_lastLoggedZDLimitState )
775 : {
776 1 : return;
777 : }
778 :
779 7 : if( state == zdLimitState::belowMin )
780 : {
781 1 : log<text_log>( "ADC tracker below minZD: zd=" + std::to_string( zd ) + " minZD=" + std::to_string( minZD ),
782 : logPrio::LOG_WARNING );
783 : }
784 6 : else if( state == zdLimitState::aboveMax )
785 : {
786 1 : log<text_log>( "ADC tracker above maxZD: zd=" + std::to_string( zd ) + " maxZD=" + std::to_string( maxZD ),
787 : logPrio::LOG_WARNING );
788 : }
789 :
790 7 : m_lastLoggedZDLimitState = state;
791 : }
792 :
793 5 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_tracking )( const pcf::IndiProperty &ipRecv )
794 : {
795 5 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tracking, ipRecv );
796 :
797 8 : if( !ipRecv.find( "toggle" ) )
798 1 : return 0;
799 :
800 6 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
801 : {
802 4 : updateSwitchIfChanged( m_indiP_tracking, "toggle", pcf::IndiElement::On, INDI_IDLE );
803 :
804 : { // mutex scope
805 2 : std::lock_guard<std::mutex> guard( m_indiMutex );
806 2 : m_tracking = true;
807 2 : m_lastUpdate = 0;
808 2 : }
809 :
810 2 : log<text_log>( "started ADC rotation tracking" );
811 : }
812 : else
813 : {
814 2 : updateSwitchIfChanged( m_indiP_tracking, "toggle", pcf::IndiElement::Off, INDI_IDLE );
815 :
816 : { // mutex scope
817 1 : std::lock_guard<std::mutex> guard( m_indiMutex );
818 1 : m_tracking = false;
819 1 : m_lastUpdate = 0;
820 1 : m_lastLoggedZDLimitState = zdLimitState::unknown;
821 1 : }
822 :
823 1 : log<text_log>( "stopped ADC rotation tracking" );
824 : }
825 :
826 3 : recordADCTrack();
827 :
828 3 : return 0;
829 : }
830 :
831 4 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaAngle )( const pcf::IndiProperty &ipRecv )
832 : {
833 4 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaAngle, ipRecv );
834 :
835 : float target;
836 :
837 3 : if( indiTargetUpdate( m_indiP_deltaAngle, target, ipRecv ) < 0 )
838 : {
839 1 : return log<software_error, -1>();
840 : }
841 :
842 : { // mutex scope
843 2 : std::lock_guard<std::mutex> guard( m_indiMutex );
844 :
845 2 : m_deltaAngle = target;
846 4 : updateIfChanged( m_indiP_deltaAngle, "current", m_deltaAngle );
847 2 : } // mutex scope
848 :
849 2 : log<text_log>( "set deltaAngle to " + std::to_string( m_deltaAngle ) );
850 :
851 2 : recordADCTrack();
852 :
853 2 : return 0;
854 : }
855 :
856 4 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaADC1 )( const pcf::IndiProperty &ipRecv )
857 : {
858 4 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaADC1, ipRecv );
859 :
860 : float target;
861 :
862 4 : if( indiTargetUpdate( m_indiP_deltaADC1, target, ipRecv ) < 0 )
863 : {
864 1 : return log<software_error, -1>();
865 : }
866 :
867 : { // mutex scope
868 3 : std::lock_guard<std::mutex> guard( m_indiMutex );
869 :
870 3 : m_adc1delta = target;
871 6 : updateIfChanged( m_indiP_deltaADC1, "current", m_adc1delta );
872 3 : } // mutex scope
873 :
874 3 : log<text_log>( "set deltaADC1 to " + std::to_string( m_adc1delta ) );
875 :
876 3 : recordADCTrack();
877 :
878 3 : return 0;
879 : }
880 :
881 4 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_deltaADC2 )( const pcf::IndiProperty &ipRecv )
882 : {
883 4 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_deltaADC2, ipRecv );
884 :
885 : float target;
886 :
887 4 : if( indiTargetUpdate( m_indiP_deltaADC2, target, ipRecv ) < 0 )
888 : {
889 1 : return log<software_error, -1>();
890 : }
891 :
892 : { // mutex scope
893 3 : std::lock_guard<std::mutex> guard( m_indiMutex );
894 :
895 3 : m_adc2delta = target;
896 6 : updateIfChanged( m_indiP_deltaADC2, "current", m_adc2delta );
897 3 : } // mutex scope
898 :
899 3 : log<text_log>( "set deltaADC2 to " + std::to_string( m_adc2delta ) );
900 :
901 3 : recordADCTrack();
902 :
903 3 : return 0;
904 : }
905 :
906 4 : INDI_NEWCALLBACK_DEFN( adcTracker, m_indiP_minZD )( const pcf::IndiProperty &ipRecv )
907 : {
908 4 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_minZD, ipRecv );
909 :
910 : float target;
911 4 : float zd = 0;
912 4 : float maxZD = 0;
913 4 : bool haveZD = false;
914 :
915 4 : if( indiTargetUpdate( m_indiP_minZD, target, ipRecv ) < 0 )
916 : {
917 1 : return log<software_error, -1>();
918 : }
919 :
920 : { // mutex scope
921 3 : std::lock_guard<std::mutex> guard( m_indiMutex );
922 :
923 3 : m_minZD = target;
924 6 : updateIfChanged( m_indiP_minZD, "current", m_minZD );
925 3 : zd = m_zd;
926 3 : maxZD = m_maxZD;
927 3 : haveZD = m_lookupReady && m_haveZD;
928 3 : } // mutex scope
929 :
930 3 : log<text_log>( "set minZD to " + std::to_string( m_minZD ) );
931 :
932 3 : recordADCTrack();
933 3 : updateZDLimitState( haveZD, zd, target, maxZD );
934 :
935 3 : return 0;
936 : }
937 :
938 8 : INDI_SETCALLBACK_DEFN( adcTracker, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
939 : {
940 8 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_teldata, ipRecv );
941 :
942 14 : if( !ipRecv.find( "zd" ) )
943 1 : return 0;
944 :
945 6 : float zd = 0;
946 6 : bool lookupReady = false;
947 6 : float minZD = 0;
948 6 : float maxZD = 0;
949 :
950 : try
951 : {
952 6 : zd = extractZD( ipRecv );
953 : }
954 2 : catch( const std::exception &e )
955 : {
956 3 : log<software_error>( std::string( "exception reading teldata.zd: " ) + e.what() );
957 1 : return 0;
958 1 : }
959 1 : catch( ... )
960 : {
961 1 : log<software_error>( "unknown exception reading teldata.zd" );
962 1 : return 0;
963 1 : }
964 :
965 4 : if( !std::isfinite( zd ) )
966 : {
967 0 : log<software_error>( "received non-finite teldata.zd" );
968 0 : return 0;
969 : }
970 :
971 : { // mutex scope
972 4 : std::lock_guard<std::mutex> guard( m_indiMutex );
973 4 : m_zd = zd;
974 4 : m_haveZD = true;
975 4 : lookupReady = m_lookupReady;
976 4 : minZD = m_minZD;
977 4 : maxZD = m_maxZD;
978 4 : } // mutex scope
979 :
980 4 : updateZDLimitState( lookupReady, zd, minZD, maxZD );
981 :
982 4 : return 0;
983 : }
984 :
985 10 : int adcTracker::checkRecordTimes()
986 : {
987 10 : return telemeterT::checkRecordTimes( telem_adctrack() );
988 : }
989 :
990 1 : int adcTracker::recordTelem( const telem_adctrack * )
991 : {
992 1 : return recordADCTrack( true );
993 : }
994 :
995 29 : int adcTracker::recordADCTrack( bool force )
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 29 : bool nextTracking = false;
1004 29 : float nextDeltaAngle = 0;
1005 29 : float nextADC1delta = 0;
1006 29 : float nextADC2delta = 0;
1007 29 : float nextMinZD = 0;
1008 :
1009 : { // mutex scope
1010 29 : std::lock_guard<std::mutex> guard( m_indiMutex );
1011 :
1012 29 : nextTracking = m_tracking;
1013 29 : nextDeltaAngle = m_deltaAngle;
1014 29 : nextADC1delta = m_adc1delta;
1015 29 : nextADC2delta = m_adc2delta;
1016 29 : nextMinZD = m_minZD;
1017 29 : } // mutex scope
1018 :
1019 29 : if( nextTracking != tracking || nextDeltaAngle != deltaAngle || nextADC1delta != adc1delta ||
1020 16 : nextADC2delta != adc2delta || nextMinZD != minZD || force )
1021 : {
1022 29 : telem<telem_adctrack>( { nextTracking, nextDeltaAngle, nextADC1delta, nextADC2delta, nextMinZD } );
1023 :
1024 29 : tracking = nextTracking;
1025 29 : deltaAngle = nextDeltaAngle;
1026 29 : adc1delta = nextADC1delta;
1027 29 : adc2delta = nextADC2delta;
1028 29 : minZD = nextMinZD;
1029 : }
1030 :
1031 29 : return 0;
1032 : }
1033 :
1034 : } // namespace app
1035 : } // namespace MagAOX
1036 :
1037 : #endif // adcTracker_hpp
|