Line data Source code
1 : /** \file adcTracker.hpp
2 : * \brief The MagAO-X ADC Tracker header file
3 : *
4 : * \ingroup adcTracker_files
5 : */
6 :
7 : #ifndef adcTracker_hpp
8 : #define adcTracker_hpp
9 :
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 <mx/math/gslInterpolator.hpp>
15 : #include <mx/ioutils/readColumns.hpp>
16 :
17 : /** \defgroup adcTracker
18 : * \brief The MagAO-X application to track sky rotation with the atmospheric dispersion corrector.
19 : *
20 : * <a href="../handbook/operating/software/apps/adcTracker.html">Application Documentation</a>
21 : *
22 : * \ingroup apps
23 : *
24 : */
25 :
26 : /** \defgroup adcTracker_files
27 : * \ingroup adcTracker
28 : */
29 :
30 : namespace MagAOX
31 : {
32 : namespace app
33 : {
34 :
35 : /// The MagAO-X ADC Tracker
36 : /**
37 : * \ingroup adcTracker
38 : */
39 : class adcTracker : public MagAOXApp<true>
40 : {
41 :
42 : //Give the test harness access.
43 : friend class adcTracker_test;
44 :
45 : protected:
46 :
47 : /** \name Configurable Parameters
48 : *@{
49 : */
50 :
51 : //here add parameters which will be config-able at runtime
52 : std::string m_lookupFile {"adc_lookup_table.txt"}; ///< The name of the file, in the calib directory, containing the adc lookup table. Default is 'adc_lookup_table.txt'.
53 :
54 :
55 : float m_adc1zero {0}; ///< The starting point for ADC 1. Default is 0.
56 :
57 : int m_adc1lupsign {1}; ///< The sign to apply to the lookup table value for ADC 1
58 :
59 : float m_adc2zero {0}; ///< The starting point for ADC 2. Default is 0.
60 :
61 : int m_adc2lupsign {1}; ///< The sign to apply to the lookup table value for ADC 2
62 :
63 : float m_deltaAngle {0}; ///< The offset angle to apply to the looked-up values, applied to both. Default is 0.
64 :
65 : float m_adc1delta {0}; ///< The offset angle to apply to the looked-up value for ADC 1, applied in addition to deltaAngle. Default is 0.
66 :
67 : float m_adc2delta {0}; ///< The offset angle to apply to the looked-up value for ADC 2, applied in addition to deltaAngle. Default is 0.
68 :
69 : float m_minZD {5.1}; ///< "The minimum zenith distance at which to interpolate and move the ADCs. Default is 0.
70 :
71 :
72 : std::string m_adc1DevName {"stageadc1"}; ///< The device name of the ADC 1 stage. Default is 'stageadc1'
73 : std::string m_adc2DevName {"stageadc2"}; ///< The device name of the ADC 2 stage. Default is 'stageadc2'
74 :
75 : std::string m_tcsDevName {"tcsi"}; ///< The device name of the TCS Interface providing 'teldata.zd'. Default is 'tcsi'
76 :
77 : float m_updateInterval {10};
78 :
79 : ///@}
80 :
81 : float m_maxZD {60};
82 :
83 : std::vector<double> m_lupZD;
84 : std::vector<double> m_lupADC1;
85 : std::vector<double> m_lupADC2;
86 :
87 : mx::math::gslInterpolator<mx::math::gsl_interp_linear<double>> m_terpADC1;
88 : mx::math::gslInterpolator<mx::math::gsl_interp_linear<double>> m_terpADC2;
89 :
90 : bool m_tracking {false};
91 :
92 : float m_zd {0};
93 :
94 : public:
95 : /// Default c'tor.
96 : adcTracker();
97 :
98 : /// D'tor, declared and defined for noexcept.
99 18 : ~adcTracker() noexcept
100 18 : {}
101 :
102 : virtual void setupConfig();
103 :
104 : /// Implementation of loadConfig logic, separated for testing.
105 : /** This is called by loadConfig().
106 : */
107 : int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
108 :
109 : virtual void loadConfig();
110 :
111 : /// Startup function
112 : /**
113 : *
114 : */
115 : virtual int appStartup();
116 :
117 : /// Implementation of the FSM for adcTracker.
118 : /**
119 : * \returns 0 on no critical error
120 : * \returns -1 on an error requiring shutdown
121 : */
122 : virtual int appLogic();
123 :
124 : /// Shutdown the app.
125 : /**
126 : *
127 : */
128 : virtual int appShutdown();
129 :
130 :
131 : /** @name INDI
132 : *
133 : * @{
134 : */
135 : protected:
136 :
137 : pcf::IndiProperty m_indiP_tracking;
138 :
139 : pcf::IndiProperty m_indiP_deltaAngle;
140 : pcf::IndiProperty m_indiP_deltaADC1;
141 : pcf::IndiProperty m_indiP_deltaADC2;
142 :
143 : pcf::IndiProperty m_indiP_minZD;
144 :
145 :
146 : pcf::IndiProperty m_indiP_teldata;
147 :
148 :
149 : pcf::IndiProperty m_indiP_adc1pos;
150 : pcf::IndiProperty m_indiP_adc2pos;
151 :
152 : public:
153 0 : INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_tracking);
154 :
155 0 : INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_deltaAngle);
156 0 : INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_deltaADC1);
157 0 : INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_deltaADC2);
158 :
159 0 : INDI_NEWCALLBACK_DECL(adcTracker, m_indiP_minZD);
160 :
161 0 : INDI_SETCALLBACK_DECL(adcTracker, m_indiP_teldata);
162 :
163 :
164 :
165 : ///@}
166 : };
167 :
168 198 : adcTracker::adcTracker() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
169 : {
170 :
171 18 : return;
172 0 : }
173 :
174 0 : void adcTracker::setupConfig()
175 : {
176 0 : config.add("adcs.lookupFile", "", "adcs.lookupFile", argType::Required, "adcs", "lookupFile", false, "string", "The name of the file, in the calib directory, containing the adc lookup table. Default is 'adc_lookup_table.txt'.");
177 :
178 0 : config.add("adcs.adc1zero", "", "adcs.adc1zero", argType::Required, "adcs", "adc1zero", false, "float", "The starting point for ADC 1. Default is 0.");
179 :
180 0 : config.add("adcs.adc1lupsign", "", "adcs.adc1lupsign", argType::Required, "adcs", "adc1lupsign", false, "int", "The sign to apply for the LUP values for ADC 1. Default is +1.");
181 :
182 0 : config.add("adcs.adc2zero", "", "adcs.adc2zero", argType::Required, "adcs", "adc2zero", false, "float", "The starting point for ADC 2. Default is 0.");
183 :
184 0 : config.add("adcs.adc2lupsign", "", "adcs.adc2lupsign", argType::Required, "adcs", "adc2lupsign", false, "int", "The sign to apply for the LUP values for ADC 2. Default is +1.");
185 :
186 0 : config.add("adcs.deltaAngle", "", "adcs.deltaAngle", argType::Required, "adcs", "deltaAngle", false, "float", "The offset angle to apply to the looked-up values, applied to both. Default is 0.");
187 :
188 0 : config.add("adcs.adc1delta", "", "adcs.adc1delta", argType::Required, "adcs", "adc1delta", false, "float", "The offset angle to apply to the looked-up value for ADC 1, applied in addition to deltaAngle. Default is 0.");
189 :
190 0 : config.add("adcs.adc2delta", "", "adcs.adc2delta", argType::Required, "adcs", "adc2delta", false, "float", "The offset angle to apply to the looked-up value for ADC 2, applied in addition to deltaAngle. Default is 0.");
191 :
192 0 : config.add("adcs.minZD", "", "adcs.minZD", argType::Required, "adcs", "minZD", false, "float", "The minimum zenith distance at which to interpolate and move the ADCs. Default is 5.1");
193 :
194 0 : config.add("adcs.adc1DevName", "", "adcs.adc1devName", argType::Required, "adcs", "adc1DevName", false, "string", "The device name of the ADC 1 stage. Default is 'stageadc1'");
195 :
196 0 : config.add("adcs.adc2DevName", "", "adcs.adc2devName", argType::Required, "adcs", "adc2DevName", false, "string", "The device name of the ADC 2 stage. Default is 'stageadc2'");
197 :
198 0 : config.add("tcs.devName", "", "tcs.devName", argType::Required, "tcs", "devName", false, "string", "The device name of the TCS Interface providing 'teldata.zd'. Default is 'tcsi'");
199 :
200 0 : config.add("tracking.updateInterval", "", "tracking.updateInterval", argType::Required, "tracking", "updateInterval", false, "float", "The interval at which to update positions, in seconds. Default is 10 secs.");
201 0 : }
202 :
203 0 : int adcTracker::loadConfigImpl( mx::app::appConfigurator & _config )
204 : {
205 0 : _config(m_lookupFile, "adcs.lookupFile");
206 0 : _config(m_adc1zero, "adcs.adc1zero");
207 0 : _config(m_adc1lupsign, "adcs.adc1lupsign");
208 0 : _config(m_adc2zero, "adcs.adc2zero");
209 0 : _config(m_adc2lupsign, "adcs.adc2lupsign");
210 0 : _config(m_deltaAngle, "adcs.deltaAngle");
211 0 : _config(m_adc1delta, "adcs.adc1delta");
212 0 : _config(m_adc2delta, "adcs.adc2delta");
213 0 : _config(m_minZD, "adcs.minZD");
214 0 : _config(m_adc1DevName, "adcs.adc1DevName");
215 0 : _config(m_adc2DevName, "adcs.adc2DevName");
216 :
217 0 : _config(m_tcsDevName, "tcs.devName");
218 :
219 0 : _config(m_updateInterval, "tracking.updateInterval");
220 :
221 0 : return 0;
222 : }
223 :
224 0 : void adcTracker::loadConfig()
225 : {
226 0 : loadConfigImpl(config);
227 0 : }
228 :
229 0 : int adcTracker::appStartup()
230 : {
231 :
232 0 : std::string luppath = m_calibDir + "/" + m_lookupFile;
233 :
234 0 : std::cerr << "Reading " << luppath << "\n";
235 :
236 0 : if(mx::ioutils::readColumns<mx::ioutils::readColCommaDelim>(luppath, m_lupZD, m_lupADC1, m_lupADC2) != mx::error_t::noerror)
237 : {
238 0 : log<software_critical>({__FILE__,__LINE__, "error reading lookup table from " + luppath});
239 0 : return -1;
240 : }
241 :
242 0 : if(m_lupZD.size() != m_lupADC1.size() || m_lupZD.size()!= m_lupADC2.size())
243 : {
244 0 : log<software_critical>({__FILE__,__LINE__, "inconsistent sizes in " + luppath});
245 0 : return -1;
246 : }
247 :
248 0 : log<text_log>("Read " + std::to_string(m_lupZD.size()) + " points from " + m_lookupFile);
249 :
250 0 : m_terpADC1.setup(m_lupZD, m_lupADC1);
251 0 : m_terpADC2.setup(m_lupZD, m_lupADC2);
252 :
253 0 : createStandardIndiToggleSw( m_indiP_tracking, "tracking");
254 0 : registerIndiPropertyNew( m_indiP_tracking, INDI_NEWCALLBACK(m_indiP_tracking));
255 :
256 0 : createStandardIndiNumber<float>( m_indiP_deltaAngle, "deltaAngle", -180.0, 180.0, 0, "%0.2f");
257 0 : m_indiP_deltaAngle["target"].set(m_deltaAngle);
258 0 : m_indiP_deltaAngle["current"].set(m_deltaAngle);
259 0 : registerIndiPropertyNew( m_indiP_deltaAngle, INDI_NEWCALLBACK(m_indiP_deltaAngle));
260 :
261 0 : createStandardIndiNumber<float>( m_indiP_deltaADC1, "deltaADC1", -180.0, 180.0, 0, "%0.2f");
262 0 : m_indiP_deltaADC1["target"].set(m_adc1delta);
263 0 : m_indiP_deltaADC1["current"].set(m_adc1delta);
264 0 : registerIndiPropertyNew( m_indiP_deltaADC1, INDI_NEWCALLBACK(m_indiP_deltaADC1));
265 :
266 0 : createStandardIndiNumber<float>( m_indiP_deltaADC2, "deltaADC2", -180.0, 180.0, 0, "%0.2f");
267 0 : m_indiP_deltaADC2["target"].set(m_adc2delta);
268 0 : m_indiP_deltaADC2["current"].set(m_adc2delta);
269 0 : registerIndiPropertyNew( m_indiP_deltaADC2, INDI_NEWCALLBACK(m_indiP_deltaADC2));
270 :
271 0 : createStandardIndiNumber<float>( m_indiP_minZD, "minZD", 0.0, 90.0, 0, "%0.2f");
272 0 : m_indiP_minZD["target"].set(m_minZD);
273 0 : m_indiP_minZD["current"].set(m_minZD);
274 0 : registerIndiPropertyNew( m_indiP_minZD, INDI_NEWCALLBACK(m_indiP_minZD));
275 :
276 0 : REG_INDI_SETPROP(m_indiP_teldata, m_tcsDevName, "teldata");
277 :
278 0 : m_indiP_adc1pos = pcf::IndiProperty(pcf::IndiProperty::Number);
279 0 : m_indiP_adc1pos.setDevice(m_adc1DevName);
280 0 : m_indiP_adc1pos.setName("position");
281 0 : m_indiP_adc1pos.add(pcf::IndiElement("target"));
282 :
283 0 : m_indiP_adc2pos = pcf::IndiProperty(pcf::IndiProperty::Number);
284 0 : m_indiP_adc2pos.setDevice(m_adc2DevName);
285 0 : m_indiP_adc2pos.setName("position");
286 0 : m_indiP_adc2pos.add(pcf::IndiElement("target"));
287 :
288 0 : state(stateCodes::READY);
289 :
290 0 : return 0;
291 0 : }
292 :
293 0 : int adcTracker::appLogic()
294 : {
295 :
296 : static double lastupdate = 0;
297 :
298 0 : if(m_tracking && mx::sys::get_curr_time() - lastupdate > m_updateInterval)
299 : {
300 0 : float dadc1 = 0.0;
301 0 : float dadc2 = 0.0;
302 :
303 0 : if(m_zd > m_lupZD.back())
304 : {
305 0 : std::cerr << "end of lup\n";
306 0 : dadc1 = m_lupADC1.back();
307 0 : dadc2 = m_lupADC2.back();
308 : }
309 0 : else if(m_zd >= m_minZD)
310 : {
311 0 : dadc1 = fabs(m_terpADC1(m_zd));
312 0 : dadc2 = fabs(m_terpADC2(m_zd));
313 : }
314 : else
315 : {
316 0 : std::cerr << "zenith limit\n";
317 : }
318 :
319 0 : float adc1 = m_adc1zero + m_adc1lupsign*(dadc1 + m_adc1delta + m_deltaAngle);
320 0 : float adc2 = m_adc2zero + m_adc2lupsign*(dadc2 + m_adc2delta + m_deltaAngle);
321 :
322 0 : std::cerr << "Sending adcs to: " << adc1 << " " << adc2 << "\n";
323 :
324 :
325 0 : m_indiP_adc1pos["target"] = adc1;
326 0 : sendNewProperty (m_indiP_adc1pos);
327 :
328 0 : m_indiP_adc2pos["target"] = adc2;
329 0 : sendNewProperty (m_indiP_adc2pos);
330 :
331 0 : lastupdate = mx::sys::get_curr_time();
332 : }
333 0 : else if(!m_tracking) lastupdate = 0;
334 :
335 0 : return 0;
336 : }
337 :
338 0 : int adcTracker::appShutdown()
339 : {
340 0 : return 0;
341 : }
342 :
343 3 : INDI_NEWCALLBACK_DEFN(adcTracker, m_indiP_tracking)(const pcf::IndiProperty &ipRecv)
344 : {
345 3 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_tracking, ipRecv);
346 :
347 : if(!ipRecv.find("toggle")) return 0;
348 :
349 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
350 : {
351 : updateSwitchIfChanged(m_indiP_tracking, "toggle", pcf::IndiElement::On, INDI_IDLE);
352 :
353 : m_tracking = true;
354 :
355 : log<text_log>("started ADC rotation tracking");
356 : }
357 : else
358 : {
359 : updateSwitchIfChanged(m_indiP_tracking, "toggle", pcf::IndiElement::Off, INDI_IDLE);
360 :
361 : m_tracking = false;
362 :
363 : log<text_log>("stopped ADC rotation tracking");
364 : }
365 :
366 : return 0;
367 : }
368 :
369 3 : INDI_NEWCALLBACK_DEFN(adcTracker, m_indiP_deltaAngle)(const pcf::IndiProperty &ipRecv)
370 : {
371 3 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_deltaAngle, ipRecv);
372 :
373 : float target;
374 :
375 : if( indiTargetUpdate( m_indiP_deltaAngle, target, ipRecv) < 0)
376 : {
377 : log<software_error>({__FILE__,__LINE__});
378 : return -1;
379 : }
380 :
381 : m_deltaAngle = target;
382 :
383 : std::lock_guard<std::mutex> guard(m_indiMutex);
384 :
385 : updateIfChanged(m_indiP_deltaAngle, "current", m_deltaAngle);
386 :
387 : log<text_log>("set deltaAngle to " + std::to_string(m_deltaAngle));
388 :
389 : return 0;
390 : }
391 :
392 3 : INDI_NEWCALLBACK_DEFN(adcTracker, m_indiP_deltaADC1)(const pcf::IndiProperty &ipRecv)
393 : {
394 3 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_deltaADC1, ipRecv);
395 :
396 : float target;
397 :
398 : if( indiTargetUpdate( m_indiP_deltaADC1, target, ipRecv) < 0)
399 : {
400 : log<software_error>({__FILE__,__LINE__});
401 : return -1;
402 : }
403 :
404 : m_adc1delta = target;
405 :
406 : std::lock_guard<std::mutex> guard(m_indiMutex);
407 :
408 : updateIfChanged(m_indiP_deltaADC1, "current", m_adc1delta);
409 :
410 : log<text_log>("set deltaADC1 to " + std::to_string(m_adc1delta));
411 :
412 : return 0;
413 : }
414 :
415 3 : INDI_NEWCALLBACK_DEFN(adcTracker, m_indiP_deltaADC2)(const pcf::IndiProperty &ipRecv)
416 : {
417 3 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_deltaADC2, ipRecv);
418 :
419 : float target;
420 :
421 : if( indiTargetUpdate( m_indiP_deltaADC2, target, ipRecv) < 0)
422 : {
423 : log<software_error>({__FILE__,__LINE__});
424 : return -1;
425 : }
426 :
427 : m_adc2delta = target;
428 :
429 : std::lock_guard<std::mutex> guard(m_indiMutex);
430 :
431 : updateIfChanged(m_indiP_deltaADC2, "current", m_adc2delta);
432 :
433 : log<text_log>("set deltaADC2 to " + std::to_string(m_adc2delta));
434 :
435 : return 0;
436 : }
437 :
438 3 : INDI_NEWCALLBACK_DEFN(adcTracker, m_indiP_minZD)(const pcf::IndiProperty &ipRecv)
439 : {
440 3 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_minZD, ipRecv);
441 :
442 : float target;
443 :
444 : if( indiTargetUpdate( m_indiP_minZD, target, ipRecv) < 0)
445 : {
446 : log<software_error>({__FILE__,__LINE__});
447 : return -1;
448 : }
449 :
450 : m_minZD = target;
451 :
452 : std::lock_guard<std::mutex> guard(m_indiMutex);
453 :
454 : updateIfChanged(m_indiP_minZD, "current", m_minZD);
455 :
456 : log<text_log>("set minZD to " + std::to_string(m_minZD));
457 :
458 : return 0;
459 : }
460 :
461 3 : INDI_SETCALLBACK_DEFN(adcTracker, m_indiP_teldata)(const pcf::IndiProperty &ipRecv)
462 : {
463 3 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_teldata, ipRecv);
464 :
465 :
466 : if(!ipRecv.find("zd")) return 0;
467 :
468 : m_zd = ipRecv["zd"].get<float>();
469 :
470 : return 0;
471 : }
472 :
473 : } //namespace app
474 : } //namespace MagAOX
475 :
476 : #endif //adcTracker_hpp
477 :
|