Line data Source code
1 : /** \file kTracker.hpp
2 : * \brief The MagAO-X K-mirror rotation tracker header file
3 : *
4 : * \ingroup kTracker_files
5 : */
6 :
7 : #ifndef kTracker_hpp
8 : #define kTracker_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 : #include <cmath>
14 :
15 : #include <mx/math/gslInterpolation.hpp>
16 : #include <mx/ioutils/readColumns.hpp>
17 :
18 : /** \defgroup kTracker
19 : * \brief The MagAO-X application to track pupil rotation with the k-mirror.
20 : *
21 : * <a href="../handbook/operating/software/apps/kTracker.html">Application Documentation</a>
22 : *
23 : * \ingroup apps
24 : *
25 : */
26 :
27 : /** \defgroup kTracker_files
28 : * \ingroup kTracker
29 : */
30 :
31 : namespace MagAOX
32 : {
33 : namespace app
34 : {
35 :
36 : /// The MagAO-X K-mirror tracker.
37 : /**
38 : * \ingroup kTracker
39 : */
40 : class kTracker : public MagAOXApp<true>
41 : {
42 :
43 : // Give the test harness access.
44 : friend class kTracker_test;
45 :
46 : protected:
47 : /** \name Configurable Parameters
48 : *@{
49 : */
50 :
51 : float m_zero{ 0 }; ///< The starting point for the K-mirror at zd = 0.
52 :
53 : int m_sign{ 1 }; ///< The sign to apply to the zenith distance to rotate the K-mirror.
54 :
55 : std::string m_devName{ "stagek" }; ///< The device name of the K-mirror stage. Default is 'stagek'.
56 : std::string m_tcsDevName{
57 : "tcsi" }; ///< The device name of the TCS interface providing 'teldata.zd'. Default is 'tcsi'.
58 :
59 : float m_updateInterval{ 10 }; ///< The interval at which to update positions, in seconds. Default is 10 secs.
60 :
61 : ///@}
62 :
63 : bool m_tracking{ false }; ///< True when automatic K-mirror updates are enabled.
64 :
65 : float m_zd{ 0 }; ///< The most recent finite zenith distance received from the TCS interface.
66 :
67 : bool m_haveZD{ false }; ///< True once at least one valid zenith distance has been received.
68 :
69 : double m_lastUpdate{ 0 }; ///< Timestamp of the last stage command dispatched by the tracker.
70 :
71 : public:
72 : /// Default c'tor.
73 : kTracker();
74 :
75 : /// D'tor, declared and defined for noexcept.
76 6 : ~kTracker() noexcept
77 6 : {
78 6 : }
79 :
80 : /// Set up configuration entries.
81 : virtual void setupConfig();
82 :
83 : /// Implementation of loadConfig logic, separated for testing.
84 : /** This is called by loadConfig().
85 : */
86 : int loadConfigImpl(
87 : mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
88 :
89 : /// Load configuration values.
90 : virtual void loadConfig();
91 :
92 : /// Startup function
93 : /**
94 : *
95 : */
96 : virtual int appStartup();
97 :
98 : /// Implementation of the FSM for kTracker.
99 : /**
100 : * \returns 0 on no critical error
101 : * \returns -1 on an error requiring shutdown
102 : */
103 : virtual int appLogic();
104 :
105 : /// Shutdown the app.
106 : /**
107 : *
108 : */
109 : virtual int appShutdown();
110 :
111 : /** @name INDI
112 : *
113 : * @{
114 : */
115 : protected:
116 : pcf::IndiProperty m_indiP_tracking; ///< The INDI toggle used to enable or disable tracking.
117 :
118 : pcf::IndiProperty m_indiP_teldata; ///< The subscribed TCS property providing zenith distance updates.
119 :
120 : pcf::IndiProperty m_indiP_kpos; ///< The outbound K-mirror stage position command property.
121 :
122 : public:
123 : /// Handle new tracking toggle requests.
124 0 : INDI_NEWCALLBACK_DECL( kTracker, m_indiP_tracking );
125 :
126 : /// Handle incoming telescope data updates.
127 0 : INDI_SETCALLBACK_DECL( kTracker, m_indiP_teldata );
128 :
129 : ///@}
130 : };
131 :
132 42 : kTracker::kTracker() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
133 : {
134 :
135 6 : return;
136 0 : }
137 :
138 0 : void kTracker::setupConfig()
139 : {
140 0 : config.add( "k.zero",
141 : "",
142 : "k.zero",
143 : argType::Required,
144 : "k",
145 : "zero",
146 : false,
147 : "float",
148 : "The k-mirror zero position. Default is -40.0." );
149 :
150 0 : config.add( "k.sign",
151 : "",
152 : "k.sign",
153 : argType::Required,
154 : "k",
155 : "sign",
156 : false,
157 : "int",
158 : "The k-mirror rotation sign. Default is +1." );
159 :
160 0 : config.add( "k.devName",
161 : "",
162 : "k.devName",
163 : argType::Required,
164 : "k",
165 : "devName",
166 : false,
167 : "string",
168 : "The device name of the k-mirrorstage. Default is 'stagek'" );
169 :
170 0 : config.add( "tcs.devName",
171 : "",
172 : "tcs.devName",
173 : argType::Required,
174 : "tcs",
175 : "devName",
176 : false,
177 : "string",
178 : "The device name of the TCS Interface providing 'teldata.zd'. Default is 'tcsi'" );
179 :
180 0 : config.add( "tracking.updateInterval",
181 : "",
182 : "tracking.updateInterval",
183 : argType::Required,
184 : "tracking",
185 : "updateInterval",
186 : false,
187 : "float",
188 : "The interval at which to update positions, in seconds. Default is 10 secs." );
189 0 : }
190 :
191 0 : int kTracker::loadConfigImpl( mx::app::appConfigurator &_config )
192 : {
193 0 : _config( m_zero, "k.zero" );
194 0 : _config( m_sign, "k.sign" );
195 0 : _config( m_devName, "k.devName" );
196 :
197 0 : _config( m_tcsDevName, "tcs.devName" );
198 :
199 0 : _config( m_updateInterval, "tracking.updateInterval" );
200 :
201 0 : return 0;
202 : }
203 :
204 0 : void kTracker::loadConfig()
205 : {
206 0 : loadConfigImpl( config );
207 0 : }
208 :
209 0 : int kTracker::appStartup()
210 : {
211 :
212 0 : createStandardIndiToggleSw( m_indiP_tracking, "tracking" );
213 0 : registerIndiPropertyNew( m_indiP_tracking, INDI_NEWCALLBACK( m_indiP_tracking ) );
214 :
215 0 : REG_INDI_SETPROP( m_indiP_teldata, m_tcsDevName, "teldata" );
216 :
217 0 : m_indiP_kpos = pcf::IndiProperty( pcf::IndiProperty::Number );
218 0 : m_indiP_kpos.setDevice( m_devName );
219 0 : m_indiP_kpos.setName( "position" );
220 0 : m_indiP_kpos.add( pcf::IndiElement( "target" ) );
221 :
222 0 : state( stateCodes::READY );
223 :
224 0 : return 0;
225 : }
226 :
227 0 : int kTracker::appLogic()
228 : {
229 0 : const double now = mx::sys::get_curr_time();
230 :
231 0 : float k = 0;
232 :
233 : { // mutex scope
234 0 : std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
235 :
236 0 : if( !lock.owns_lock() )
237 : {
238 0 : return 0;
239 : }
240 :
241 0 : if( !m_tracking )
242 : {
243 0 : m_lastUpdate = 0;
244 0 : return 0;
245 : }
246 :
247 0 : if( !m_haveZD || now - m_lastUpdate <= m_updateInterval )
248 : {
249 0 : return 0;
250 : }
251 :
252 0 : k = m_zero + m_sign * 0.5f * m_zd;
253 0 : m_lastUpdate = now;
254 0 : } // mutex scope
255 :
256 0 : if( !std::isfinite( k ) )
257 : {
258 0 : log<software_error>( { __FILE__, __LINE__, "computed non-finite K-mirror target" } );
259 0 : return 0;
260 : }
261 :
262 0 : std::cerr << "Sending k-mirror to: " << k << "\n";
263 :
264 0 : if( sendNewProperty( m_indiP_kpos, "target", k ) < 0 )
265 : {
266 0 : log<software_error>( { __FILE__, __LINE__, "failed to send K-mirror target" } );
267 : }
268 :
269 0 : return 0;
270 : }
271 :
272 0 : int kTracker::appShutdown()
273 : {
274 0 : return 0;
275 : }
276 :
277 3 : INDI_NEWCALLBACK_DEFN( kTracker, m_indiP_tracking )( const pcf::IndiProperty &ipRecv )
278 : {
279 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tracking, ipRecv );
280 :
281 : if( !ipRecv.find( "toggle" ) )
282 : return 0;
283 :
284 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
285 : {
286 : updateSwitchIfChanged( m_indiP_tracking, "toggle", pcf::IndiElement::On, INDI_IDLE );
287 :
288 : { // mutex scope
289 : std::lock_guard<std::mutex> guard( m_indiMutex );
290 : m_tracking = true;
291 : m_lastUpdate = 0;
292 : }
293 :
294 : log<text_log>( "started K-mirror rotation tracking" );
295 : }
296 : else
297 : {
298 : updateSwitchIfChanged( m_indiP_tracking, "toggle", pcf::IndiElement::Off, INDI_IDLE );
299 :
300 : { // mutex scope
301 : std::lock_guard<std::mutex> guard( m_indiMutex );
302 : m_tracking = false;
303 : m_lastUpdate = 0;
304 : }
305 :
306 : log<text_log>( "stopped K-mirror rotation tracking" );
307 : }
308 :
309 : return 0;
310 : }
311 :
312 3 : INDI_SETCALLBACK_DEFN( kTracker, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
313 : {
314 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_teldata, ipRecv );
315 :
316 : if( !ipRecv.find( "zd" ) )
317 : return 0;
318 :
319 : float zd = 0;
320 :
321 : try
322 : {
323 : zd = ipRecv["zd"].get<float>();
324 : }
325 : catch( const std::exception &e )
326 : {
327 : log<software_error>( { __FILE__, __LINE__, std::string( "exception reading teldata.zd: " ) + e.what() } );
328 : return 0;
329 : }
330 : catch( ... )
331 : {
332 : log<software_error>( { __FILE__, __LINE__, "unknown exception reading teldata.zd" } );
333 : return 0;
334 : }
335 :
336 : if( !std::isfinite( zd ) )
337 : {
338 : log<software_error>( { __FILE__, __LINE__, "received non-finite teldata.zd" } );
339 : return 0;
340 : }
341 :
342 : { // mutex scope
343 : std::lock_guard<std::mutex> guard( m_indiMutex );
344 : m_zd = zd;
345 : m_haveZD = true;
346 : }
347 :
348 : return 0;
349 : }
350 :
351 : } // namespace app
352 : } // namespace MagAOX
353 :
354 : #endif // kTracker_hpp
|