Line data Source code
1 : /** \file observerCtrl.hpp
2 : * \brief The MagAO-X Observer Controller header file
3 : *
4 : * \ingroup observerCtrl_files
5 : */
6 :
7 : #ifndef observerCtrl_hpp
8 : #define observerCtrl_hpp
9 :
10 : #include <map>
11 : #include <mx/math/geo.hpp>
12 :
13 : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
14 : #include "../../magaox_git_version.h"
15 :
16 : /** \defgroup observerCtrl
17 : * \brief The MagAO-X Observer Controller application
18 : *
19 : * <a href="../handbook/operating/software/apps/observerCtrl.html">Application Documentation</a>
20 : *
21 : * \ingroup apps
22 : *
23 : */
24 :
25 : /** \defgroup observerCtrl_files
26 : * \ingroup observerCtrl
27 : */
28 :
29 : namespace MagAOX
30 : {
31 : namespace app
32 : {
33 :
34 : /// The MagAO-X Observer Controller
35 : /**
36 : * \ingroup observerCtrl
37 : */
38 : class observerCtrl : public MagAOXApp<true>, public dev::telemeter<observerCtrl>
39 : {
40 :
41 : // Give the test harness access.
42 : friend class observerCtrl_test;
43 :
44 : friend class dev::telemeter<observerCtrl>;
45 :
46 : typedef dev::telemeter<observerCtrl> telemeterT;
47 :
48 : typedef std::chrono::time_point<std::chrono::steady_clock> timePointT;
49 : typedef std::string timeStampT;
50 : typedef std::chrono::duration<double> durationT;
51 : std::string timeStampAsISO8601( const std::chrono::time_point<std::chrono::system_clock> &tp );
52 :
53 : protected:
54 : /** \name Configurable Parameters
55 : *@{
56 : */
57 :
58 : std::vector<std::string> m_streamWriters; ///< The stream writers to stop and start
59 :
60 : std::string m_tcsDev{ "tcsi" };
61 : std::string m_catalogProp{ "catalog" };
62 : std::string m_objEl{ "object" };
63 : std::string m_catdataProp{ "catdata" };
64 : std::string m_raEl{ "ra" };
65 : std::string m_decEl{ "dec" };
66 : std::string m_labModeProp{ "labMode" };
67 :
68 : std::string m_teldataProp{ "teldata" };
69 : std::string m_parangEl{ "pa" };
70 :
71 : std::string m_loopDev{ "holoop" };
72 : std::string m_loopStateProp{ "loop_state" };
73 :
74 : ///@}
75 :
76 : /// The observer specification
77 : struct observer
78 : {
79 : std::string m_fullName; ///< Obsever's full name
80 : std::string m_pfoa; ///< Observer's preferred forma of address
81 : std::string m_pronunciation; ///< Guide for the TTS to pronounced the pfoa (defaults to pfoa)
82 : std::string m_email; ///< Observer's email. Must be unique.
83 : std::string m_sanitizedEmail; ///< Observer's email sanitized for use in INDI properties
84 : std::string m_institution; ///< The observer's institution
85 : };
86 :
87 : typedef std::map<std::string, observer> observerMapT;
88 :
89 : observerMapT m_observers; ///< The observers from the configuration file
90 :
91 : observer m_currentObserver; ///< The current selected observer
92 :
93 : observer m_currentOperator; ///< The current selected observer
94 :
95 : std::string m_obsName; ///< The name of the observation.
96 : double m_obsDuration{ 0 }; ///< The desired duration of the observation. If 0 then until stopped.
97 :
98 : bool m_observing{ false }; ///< Flag indicating whether or not we are in an observation
99 :
100 : std::string m_target;
101 :
102 : std::string m_catObj;
103 :
104 : std::string m_catRA;
105 : std::string m_catDec;
106 :
107 : bool m_loop{ false }; ///< Flag tracking loop state. true is loop closed.
108 :
109 : bool m_labMode{ false }; ///< Flag tracking whether the TCS interface is in lab mode.
110 :
111 : bool m_newTargetBlock{ true }; /**< Flag to indicate that this is a new target block. This starts out as true
112 : but becomes false on the first observation. Then becomes true when the
113 : loop closes for the first time after a target change. */
114 : bool m_newTarget{ false }; /**< Flag to track when the target changes. Occurs either automatically on a TCS update
115 : or on a user override.*/
116 :
117 : /// The start time of the current observation
118 : timePointT m_obsStartTime;
119 :
120 : /// The UTC start time of the current observation
121 : timeStampT m_obsStartTimeStamp;
122 :
123 : /// The parallactic angle at the start of the observation
124 : double m_obsStartParang{ 0 };
125 :
126 : /// The start time of the current target
127 : timePointT m_tgtStartTime;
128 :
129 : /// The UTC start time of the current target
130 : timeStampT m_tgtStartTimeStamp;
131 :
132 : /// The parallactic angle at the start of observing the current target
133 : double m_tgtStartParang{ 0 };
134 :
135 : /// The current parallactic angle
136 : double m_parang;
137 :
138 : /// The current target time. Only updated while observing.
139 : durationT m_tgtTime;
140 :
141 : /// The current target angle. Only updated while observing.
142 : double m_tgtAng{ 0 };
143 :
144 : public:
145 : /// Default c'tor.
146 : observerCtrl();
147 :
148 : /// D'tor, declared and defined for noexcept.
149 12 : ~observerCtrl() noexcept
150 12 : {
151 12 : }
152 :
153 : virtual void setupConfig();
154 :
155 : /// Implementation of loadConfig logic, separated for testing.
156 : /** This is called by loadConfig().
157 : */
158 : int loadConfigImpl( mx::app::appConfigurator &_config /**< [in] an application configuration
159 : from which to load values*/
160 : );
161 :
162 : virtual void loadConfig();
163 :
164 : /// Startup function
165 : /**
166 : *
167 : */
168 : virtual int appStartup();
169 :
170 : /// Implementation of the FSM for observerCtrl.
171 : /**
172 : * \returns 0 on no critical error
173 : * \returns -1 on an error requiring shutdown
174 : */
175 : virtual int appLogic();
176 :
177 : /// Shutdown the app.
178 : /**
179 : *
180 : */
181 : virtual int appShutdown();
182 :
183 : void startObserving();
184 :
185 : void stopObserving();
186 :
187 : ///\name INDI
188 : /** @{
189 : */
190 : protected:
191 : pcf::IndiProperty m_indiP_observers; ///< Selection switch to allow selection of the observer
192 : pcf::IndiProperty m_indiP_observer; ///< Text which contains the specifications of the current observer
193 :
194 : pcf::IndiProperty m_indiP_operators; ///< Selection switch to allow selection of the observer
195 : pcf::IndiProperty m_indiP_operator; ///< Text which contains the specifications of the current observer
196 :
197 : pcf::IndiProperty m_indiP_obsName; /**< The current observation name, used to specify the
198 : purpose of the observation*/
199 : pcf::IndiProperty m_indiP_observing; ///< Toggle switch to trigger observation
200 : pcf::IndiProperty m_indiP_obsDuration; ///< Number to set the desired duration of observation
201 : pcf::IndiProperty m_indiP_obsStart; ///< String timestamp indicating the start for target/observation
202 : pcf::IndiProperty m_indiP_obsTime; ///< Number tracking the elapsed time
203 : pcf::IndiProperty m_indiP_obsAngle; ///< Number tracking the change in angle
204 : pcf::IndiProperty m_indiP_sws; ///< Selection to switch which stream writers are enabled
205 : pcf::IndiProperty m_indiP_userlog; ///< Text to enter a user log
206 :
207 : pcf::IndiProperty m_indiP_resetTarget; ///< Reset the target statistics
208 :
209 : pcf::IndiProperty m_indiP_target; ///< The target name, which can be overridden by the user
210 :
211 : pcf::IndiProperty m_indiP_tcsTarget; ///< Set the target to match TCS catObj
212 :
213 : pcf::IndiProperty m_indiP_catalog; ///< Catalog text data
214 : pcf::IndiProperty m_indiP_catdata; ///< Catalog numeric data
215 : pcf::IndiProperty m_indiP_teldata; ///< Telescope data (for parang)
216 :
217 : pcf::IndiProperty m_indiP_labMode; ///< Tracks whether TCS is in lab mode.
218 :
219 : pcf::IndiProperty m_indiP_loop; ///< Tracks the loop state
220 :
221 : public:
222 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_observers );
223 :
224 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_operators );
225 :
226 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_obsName );
227 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_observing );
228 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_obsDuration );
229 :
230 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_sws );
231 :
232 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_userlog );
233 :
234 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_resetTarget );
235 :
236 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_target );
237 :
238 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_tcsTarget );
239 :
240 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_catalog );
241 :
242 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_catdata );
243 :
244 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_teldata );
245 :
246 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_labMode );
247 :
248 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_loop );
249 :
250 : ///@}
251 :
252 : /** \name Telemeter Interface
253 : *
254 : * @{
255 : */
256 : int checkRecordTimes();
257 :
258 : int recordTelem( const telem_observer * );
259 :
260 : int recordObserver( bool force = false );
261 :
262 : ///@}
263 : };
264 :
265 300 : observerCtrl::observerCtrl() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
266 : {
267 12 : return;
268 0 : }
269 :
270 0 : void observerCtrl::setupConfig()
271 : {
272 0 : config.add( "stream.writers",
273 : "",
274 : "stream.writers",
275 : argType::Required,
276 : "stream",
277 : "writers",
278 : false,
279 : "string",
280 : "The device names of the stream writers to control." );
281 :
282 0 : dev::telemeter<observerCtrl>::setupConfig( config );
283 0 : }
284 :
285 0 : int observerCtrl::loadConfigImpl( mx::app::appConfigurator &_config )
286 : {
287 0 : _config( m_streamWriters, "stream.writers" );
288 :
289 0 : std::vector<std::string> sections;
290 :
291 0 : _config.unusedSections( sections );
292 :
293 0 : if( sections.size() == 0 )
294 : {
295 0 : log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
296 0 : return -1;
297 : }
298 :
299 0 : for( size_t i = 0; i < sections.size(); ++i )
300 : {
301 0 : bool pfoaSet = _config.isSetUnused( mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
302 0 : if( !pfoaSet )
303 0 : continue;
304 :
305 0 : std::string email = sections[i];
306 :
307 0 : std::string pfoa;
308 0 : _config.configUnused( pfoa, mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
309 :
310 0 : std::string pronunciation;
311 0 : _config.configUnused( pronunciation, mx::app::iniFile::makeKey( sections[i], "pronunciation" ) );
312 :
313 0 : if( pronunciation == "" )
314 : {
315 0 : pronunciation = pfoa;
316 : }
317 :
318 0 : std::string fullName;
319 0 : _config.configUnused( fullName, mx::app::iniFile::makeKey( sections[i], "full_name" ) );
320 :
321 0 : std::string institution;
322 0 : _config.configUnused( institution, mx::app::iniFile::makeKey( sections[i], "institution" ) );
323 :
324 0 : std::string sanitizedEmail = "";
325 0 : for( size_t n = 0; n < email.size(); ++n )
326 : {
327 0 : if( email[n] == '@' )
328 : {
329 0 : sanitizedEmail = sanitizedEmail + "-at-";
330 : }
331 0 : else if( email[n] == '.' )
332 : {
333 0 : sanitizedEmail = sanitizedEmail + "-dot-";
334 : }
335 : else
336 : {
337 0 : sanitizedEmail.push_back( email[n] );
338 : }
339 : }
340 0 : m_observers[email] = observer( { fullName, pfoa, pronunciation, email, sanitizedEmail, institution } );
341 0 : }
342 :
343 0 : return 0;
344 0 : }
345 :
346 0 : void observerCtrl::loadConfig()
347 : {
348 0 : if( loadConfigImpl( config ) < 0 )
349 : {
350 0 : m_shutdown = 1;
351 0 : return;
352 : }
353 :
354 0 : if( m_observers.size() < 1 )
355 : {
356 0 : log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
357 0 : m_shutdown = 1;
358 0 : return;
359 : }
360 :
361 0 : dev::telemeter<observerCtrl>::loadConfig( config );
362 : }
363 :
364 0 : int observerCtrl::appStartup()
365 : {
366 0 : std::vector<std::string> sanitizedEmails;
367 0 : std::vector<std::string> emails;
368 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
369 : {
370 0 : sanitizedEmails.push_back( it->second.m_sanitizedEmail );
371 0 : emails.push_back( it->second.m_email );
372 : }
373 :
374 0 : if( createStandardIndiSelectionSw( m_indiP_observers, "observers", sanitizedEmails, emails ) < 0 )
375 : {
376 0 : log<software_critical>( { __FILE__, __LINE__ } );
377 0 : return -1;
378 : }
379 :
380 : // Set to default user of jared
381 : ///\todo do something else. maybe a default user is specified in the config?
382 0 : for( auto &it : m_observers )
383 : {
384 0 : if( it.first.find( "jrmales" ) != std::string::npos )
385 : {
386 0 : m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
387 0 : m_currentObserver = it.second;
388 : }
389 : else
390 : {
391 0 : m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
392 : }
393 : }
394 :
395 0 : REG_INDI_NEWPROP_NOSETUP( m_indiP_observers );
396 :
397 0 : if( createStandardIndiSelectionSw( m_indiP_operators, "operators", sanitizedEmails, emails ) < 0 )
398 : {
399 0 : log<software_critical>( { __FILE__, __LINE__ } );
400 0 : return -1;
401 : }
402 :
403 : // Set to default user of jared
404 : ///\todo do something else. maybe a default user is specified in the config?
405 0 : for( auto &it : m_observers )
406 : {
407 0 : if( it.first.find( "jrmales" ) != std::string::npos )
408 : {
409 0 : m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
410 0 : m_currentOperator = it.second;
411 : }
412 : else
413 : {
414 0 : m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
415 : }
416 : }
417 :
418 0 : REG_INDI_NEWPROP_NOSETUP( m_indiP_operators );
419 :
420 0 : CREATE_REG_INDI_NEW_TEXT( m_indiP_obsName, "obs_name", "Observation Name", "Observer" );
421 :
422 0 : CREATE_REG_INDI_NEW_TOGGLESWITCH( m_indiP_observing, "obs_on" );
423 :
424 0 : CREATE_REG_INDI_NEW_NUMBERD( m_indiP_obsDuration, "obs_duration", 0, 300, 0.1, "%0.1f", "Duration", "Observer" );
425 :
426 0 : CREATE_REG_INDI_RO_NUMBER( m_indiP_obsTime, "obs_time", "Observation Time", "Observer" );
427 0 : indi::addNumberElement<double>( m_indiP_obsTime, "observation", 0, 14400, 0.1, "%0.1f", "Current Obs" );
428 0 : indi::addNumberElement<double>( m_indiP_obsTime, "target", 0, 14400, 0.1, "%0.1f", "Target" );
429 :
430 0 : REG_INDI_NEWPROP_NOCB( m_indiP_obsStart, "obs_start", pcf::IndiProperty::Text );
431 0 : indi::addTextElement( m_indiP_obsStart, "observation" );
432 0 : indi::addTextElement( m_indiP_obsStart, "target" );
433 :
434 0 : CREATE_REG_INDI_RO_NUMBER( m_indiP_obsAngle, "obs_delta_parang", "Change in Par. Ang.", "Observer" );
435 0 : indi::addNumberElement<double>( m_indiP_obsAngle, "observation", 0, 360, 0.1, "%0.1f", "Current Obs" );
436 0 : indi::addNumberElement<double>( m_indiP_obsAngle, "target", 0, 360, 0.1, "%0.1f", "Target" );
437 :
438 0 : REG_INDI_NEWPROP_NOCB( m_indiP_observer, "current_observer", pcf::IndiProperty::Text );
439 0 : indi::addTextElement( m_indiP_observer, "full_name" );
440 0 : indi::addTextElement( m_indiP_observer, "email" );
441 0 : indi::addTextElement( m_indiP_observer, "pfoa" );
442 0 : indi::addTextElement( m_indiP_observer, "pronunciation" );
443 0 : indi::addTextElement( m_indiP_observer, "institution" );
444 :
445 0 : REG_INDI_NEWPROP_NOCB( m_indiP_operator, "current_operator", pcf::IndiProperty::Text );
446 0 : indi::addTextElement( m_indiP_operator, "full_name" );
447 0 : indi::addTextElement( m_indiP_operator, "email" );
448 0 : indi::addTextElement( m_indiP_operator, "pfoa" );
449 0 : indi::addTextElement( m_indiP_operator, "pronunciation" );
450 0 : indi::addTextElement( m_indiP_operator, "institution" );
451 :
452 0 : m_indiP_sws = pcf::IndiProperty( pcf::IndiProperty::Switch );
453 0 : m_indiP_sws.setDevice( configName() );
454 0 : m_indiP_sws.setName( "writers" );
455 0 : m_indiP_sws.setPerm( pcf::IndiProperty::ReadWrite );
456 0 : m_indiP_sws.setState( pcf::IndiProperty::Idle );
457 0 : m_indiP_sws.setRule( pcf::IndiProperty::AnyOfMany );
458 :
459 0 : for( size_t n = 0; n < m_streamWriters.size(); ++n )
460 : {
461 0 : m_indiP_sws.add( pcf::IndiElement( m_streamWriters[n], pcf::IndiElement::Off ) );
462 : }
463 :
464 0 : REG_INDI_NEWPROP_NOSETUP( m_indiP_sws );
465 :
466 0 : m_indiP_userlog = pcf::IndiProperty( pcf::IndiProperty::Text );
467 0 : m_indiP_userlog.setDevice( configName() );
468 0 : m_indiP_userlog.setName( "user_log" );
469 0 : m_indiP_userlog.setPerm( pcf::IndiProperty::ReadWrite );
470 0 : m_indiP_userlog.setState( pcf::IndiProperty::Idle );
471 0 : m_indiP_userlog.add( pcf::IndiElement( "email" ) );
472 0 : m_indiP_userlog.add( pcf::IndiElement( "message" ) );
473 0 : m_indiP_userlog.add( pcf::IndiElement( "time_s" ) );
474 0 : m_indiP_userlog.add( pcf::IndiElement( "time_ns" ) );
475 :
476 0 : REG_INDI_NEWPROP_NOSETUP( m_indiP_userlog );
477 :
478 0 : CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_resetTarget, "target_reset" );
479 :
480 0 : CREATE_REG_INDI_NEW_TEXT( m_indiP_target, "target", "Target", "Observer" );
481 :
482 0 : CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_tcsTarget, "target_load_from_tcs" );
483 :
484 0 : REG_INDI_SETPROP( m_indiP_catalog, m_tcsDev, m_catalogProp );
485 0 : REG_INDI_SETPROP( m_indiP_catdata, m_tcsDev, m_catdataProp );
486 0 : REG_INDI_SETPROP( m_indiP_teldata, m_tcsDev, m_teldataProp );
487 0 : REG_INDI_SETPROP( m_indiP_labMode, m_tcsDev, m_labModeProp );
488 :
489 0 : REG_INDI_SETPROP( m_indiP_loop, m_loopDev, m_loopStateProp );
490 :
491 0 : TELEMETER_APP_STARTUP;
492 :
493 0 : state( stateCodes::READY );
494 0 : return 0;
495 0 : }
496 :
497 0 : int observerCtrl::appLogic()
498 : {
499 :
500 0 : std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
501 :
502 0 : if( lock.owns_lock() )
503 : {
504 0 : updatesIfChanged<std::string>( m_indiP_observer,
505 : { "full_name", "email", "pfoa", "pronunciation", "institution" },
506 0 : { m_currentObserver.m_fullName,
507 0 : m_currentObserver.m_email,
508 0 : m_currentObserver.m_pfoa,
509 0 : m_currentObserver.m_pronunciation,
510 0 : m_currentObserver.m_institution } );
511 :
512 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
513 : {
514 0 : if( it->first == m_currentObserver.m_email )
515 : {
516 0 : updateSwitchIfChanged(
517 0 : m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
518 : }
519 : else
520 : {
521 0 : updateSwitchIfChanged(
522 0 : m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
523 : }
524 : }
525 :
526 0 : updatesIfChanged<std::string>( m_indiP_operator,
527 : { "full_name", "email", "pfoa", "pronunciation", "institution" },
528 0 : { m_currentOperator.m_fullName,
529 0 : m_currentOperator.m_email,
530 0 : m_currentOperator.m_pfoa,
531 0 : m_currentOperator.m_pronunciation,
532 0 : m_currentOperator.m_institution } );
533 :
534 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
535 : {
536 0 : if( it->first == m_currentOperator.m_email )
537 : {
538 0 : updateSwitchIfChanged(
539 0 : m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
540 : }
541 : else
542 : {
543 0 : updateSwitchIfChanged(
544 0 : m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
545 : }
546 : }
547 :
548 0 : updatesIfChanged<std::string>( m_indiP_obsName, { "current", "target" }, { m_obsName, m_obsName } );
549 :
550 0 : updatesIfChanged<double>( m_indiP_obsDuration, { "current", "target" }, { m_obsDuration, m_obsDuration } );
551 :
552 0 : if( m_observing )
553 : {
554 0 : timePointT ct = std::chrono::steady_clock::now();
555 0 : const std::chrono::duration<double> obstime = ct - m_obsStartTime;
556 0 : m_tgtTime = ct - m_tgtStartTime;
557 :
558 0 : double obsang = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_obsStartParang );
559 0 : m_tgtAng = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_tgtStartParang );
560 :
561 0 : updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
562 0 : updatesIfChanged<double>(
563 0 : m_indiP_obsTime, { "observation", "target" }, { obstime.count(), m_tgtTime.count() } );
564 :
565 0 : updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { obsang, m_tgtAng } );
566 0 : updatesIfChanged<std::string>(
567 0 : m_indiP_obsStart, { "observation", "target" }, { m_obsStartTimeStamp, m_tgtStartTimeStamp } );
568 :
569 0 : if( m_obsDuration > 0.0 && obstime.count() > m_obsDuration )
570 : {
571 0 : stopObserving();
572 : }
573 : }
574 : else
575 : {
576 0 : updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
577 0 : updatesIfChanged<std::string>( m_indiP_obsStart, { "observation", "target" }, { "", m_tgtStartTimeStamp } );
578 0 : updatesIfChanged<double>( m_indiP_obsTime, { "observation", "target" }, { 0.0, m_tgtTime.count() } );
579 0 : updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { 0.0, m_tgtAng } );
580 : }
581 : }
582 :
583 0 : TELEMETER_APP_LOGIC;
584 :
585 0 : return 0;
586 0 : }
587 :
588 0 : int observerCtrl::appShutdown()
589 : {
590 0 : TELEMETER_APP_SHUTDOWN;
591 :
592 0 : return 0;
593 : }
594 :
595 0 : void observerCtrl::startObserving()
596 : {
597 0 : for( size_t n = 0; n < m_streamWriters.size(); ++n )
598 : {
599 0 : if( m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On )
600 : {
601 0 : pcf::IndiProperty ip( pcf::IndiProperty::Switch );
602 :
603 0 : ip.setDevice( m_streamWriters[n] + "-sw" );
604 0 : ip.setName( "writing" );
605 0 : ip.add( pcf::IndiElement( "toggle" ) );
606 0 : ip["toggle"].setSwitchState( pcf::IndiElement::On );
607 :
608 0 : sendNewProperty( ip );
609 0 : }
610 : }
611 :
612 0 : mx::sys::sleep( 1 );
613 :
614 0 : m_obsStartTime = std::chrono::steady_clock::now();
615 0 : m_obsStartTimeStamp = timeStampAsISO8601( std::chrono::system_clock::now() );
616 0 : m_obsStartParang = m_parang;
617 :
618 0 : if( m_newTargetBlock || m_labMode ) // We always reset if in lab mode
619 : {
620 0 : m_tgtStartTime = m_obsStartTime;
621 0 : m_tgtStartTimeStamp = m_obsStartTimeStamp;
622 0 : m_tgtStartParang = m_obsStartParang;
623 :
624 0 : m_newTargetBlock = false;
625 : }
626 :
627 0 : m_observing = true;
628 0 : recordObserver( true );
629 0 : }
630 :
631 0 : void observerCtrl::stopObserving()
632 : {
633 0 : m_observing = false;
634 0 : recordObserver( true );
635 :
636 0 : for( size_t n = 0; n < m_streamWriters.size(); ++n )
637 : {
638 0 : if( m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On )
639 : {
640 0 : pcf::IndiProperty ip( pcf::IndiProperty::Switch );
641 :
642 0 : ip.setDevice( m_streamWriters[n] + "-sw" );
643 0 : ip.setName( "writing" );
644 0 : ip.add( pcf::IndiElement( "toggle" ) );
645 0 : ip["toggle"].setSwitchState( pcf::IndiElement::Off );
646 :
647 0 : sendNewProperty( ip );
648 0 : }
649 : }
650 0 : }
651 :
652 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observers )( const pcf::IndiProperty &ipRecv )
653 : {
654 :
655 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_observers, ipRecv );
656 :
657 : // look for selected mode switch which matches a known mode. Make sure only one is selected.
658 : std::string newEmail = "";
659 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
660 : {
661 : if( !ipRecv.find( it->second.m_sanitizedEmail ) )
662 : continue;
663 :
664 : if( ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
665 : {
666 : if( newEmail != "" )
667 : {
668 : log<text_log>( "More than one observer selected", logPrio::LOG_ERROR );
669 : return -1;
670 : }
671 :
672 : newEmail = it->first;
673 : }
674 : }
675 :
676 : if( newEmail == "" )
677 : {
678 : std::cerr << "nothing\n";
679 : return 0;
680 : }
681 :
682 : {
683 : std::unique_lock<std::mutex> lock( m_indiMutex );
684 :
685 : m_currentObserver = m_observers[newEmail];
686 :
687 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
688 : {
689 : if( it->first == m_currentObserver.m_sanitizedEmail )
690 : {
691 : updateSwitchIfChanged(
692 : m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
693 : }
694 : else
695 : {
696 : updateSwitchIfChanged(
697 : m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
698 : }
699 : }
700 : }
701 :
702 : log<logger::observer>( { m_currentObserver.m_fullName,
703 : m_currentObserver.m_pfoa,
704 : m_currentObserver.m_email,
705 : m_currentObserver.m_institution } );
706 :
707 : return 0;
708 : }
709 :
710 0 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_operators )( const pcf::IndiProperty &ipRecv )
711 : {
712 :
713 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_operators, ipRecv );
714 :
715 : // look for selected mode switch which matches a known mode. Make sure only one is selected.
716 : std::string newEmail = "";
717 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
718 : {
719 : if( !ipRecv.find( it->second.m_sanitizedEmail ) )
720 : continue;
721 :
722 : if( ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
723 : {
724 : if( newEmail != "" )
725 : {
726 : log<text_log>( "More than one operator selected", logPrio::LOG_ERROR );
727 : return -1;
728 : }
729 :
730 : newEmail = it->first;
731 : }
732 : }
733 :
734 : if( newEmail == "" )
735 : {
736 : std::cerr << "nothing\n";
737 : return 0;
738 : }
739 :
740 : {
741 : std::unique_lock<std::mutex> lock( m_indiMutex );
742 :
743 : m_currentOperator = m_observers[newEmail];
744 :
745 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
746 : {
747 : if( it->first == m_currentOperator.m_sanitizedEmail )
748 : {
749 : updateSwitchIfChanged(
750 : m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
751 : }
752 : else
753 : {
754 : updateSwitchIfChanged(
755 : m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
756 : }
757 : }
758 : }
759 :
760 : log<logger::ao_operator>( { m_currentOperator.m_fullName,
761 : m_currentOperator.m_pfoa,
762 : m_currentOperator.m_email,
763 : m_currentOperator.m_institution } );
764 :
765 : return 0;
766 : }
767 :
768 0 : std::string observerCtrl::timeStampAsISO8601( const std::chrono::time_point<std::chrono::system_clock> &tp )
769 : {
770 : // Convert to time_t for whole seconds
771 0 : std::time_t t = std::chrono::system_clock::to_time_t( tp );
772 0 : std::tm tm = *std::gmtime( &t );
773 :
774 : // Get nanoseconds
775 0 : auto duration = tp.time_since_epoch();
776 0 : auto seconds = std::chrono::duration_cast<std::chrono::seconds>( duration );
777 0 : auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>( duration - seconds ).count();
778 :
779 : // Format ISO 8601 with nanoseconds
780 0 : std::stringstream ss;
781 0 : ss << std::put_time( &tm, "%Y-%m-%dT%H:%M:%S" );
782 0 : ss << '.' << std::setw( 9 ) << std::setfill( '0' ) << nanos << "Z"; // Z for UTC
783 :
784 0 : return ss.str();
785 0 : }
786 :
787 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsName )( const pcf::IndiProperty &ipRecv )
788 : {
789 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsName, ipRecv );
790 :
791 : std::string target;
792 :
793 : std::unique_lock<std::mutex> lock( m_indiMutex );
794 :
795 : if( indiTargetUpdate( m_indiP_obsName, target, ipRecv, true ) < 0 )
796 : {
797 : log<software_error>( { __FILE__, __LINE__ } );
798 : return -1;
799 : }
800 :
801 : m_obsName = target;
802 :
803 : return 0;
804 : }
805 :
806 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observing )( const pcf::IndiProperty &ipRecv )
807 : {
808 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_observing, ipRecv );
809 :
810 : if( !ipRecv.find( "toggle" ) )
811 : {
812 : return 0;
813 : }
814 :
815 : recordObserver( true );
816 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
817 : {
818 : std::unique_lock<std::mutex> lock( m_indiMutex );
819 : startObserving();
820 : updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
821 : }
822 : else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
823 : {
824 : std::unique_lock<std::mutex> lock( m_indiMutex );
825 : stopObserving();
826 : updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
827 : }
828 :
829 : return 0;
830 : }
831 :
832 0 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsDuration )( const pcf::IndiProperty &ipRecv )
833 : {
834 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsDuration, ipRecv );
835 :
836 : return indiTargetUpdate( m_indiP_obsDuration, m_obsDuration, ipRecv, false );
837 : }
838 :
839 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_sws )( const pcf::IndiProperty &ipRecv )
840 : {
841 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_sws, ipRecv );
842 :
843 : if( m_observing == true )
844 : {
845 : log<text_log>( { "Can't change stream writers while observing" }, logPrio::LOG_WARNING );
846 : return 0;
847 : }
848 :
849 : for( size_t n = 0; n < m_streamWriters.size(); ++n )
850 : {
851 : if( !ipRecv.find( m_streamWriters[n] ) )
852 : {
853 : continue;
854 : }
855 :
856 : std::unique_lock<std::mutex> lock( m_indiMutex );
857 : updateSwitchIfChanged( m_indiP_sws, m_streamWriters[n], ipRecv[m_streamWriters[n]].getSwitchState() );
858 : }
859 :
860 : return 0;
861 : }
862 :
863 0 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_userlog )( const pcf::IndiProperty &ipRecv )
864 : {
865 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_userlog, ipRecv );
866 :
867 : std::string email;
868 : std::string message;
869 :
870 : timespecX ts{};
871 :
872 : if( ipRecv.find( "email" ) )
873 : {
874 : email = ipRecv["email"].get();
875 : }
876 :
877 : if( ipRecv.find( "message" ) )
878 : {
879 : message = ipRecv["message"].get();
880 : }
881 :
882 : if( message == "" )
883 : {
884 : return 0;
885 : }
886 :
887 : if( email == "" )
888 : {
889 : email = m_currentObserver.m_email;
890 : }
891 :
892 : if( ipRecv.find( "time_s" ) )
893 : {
894 : ts.time_s = ipRecv["time_s"].get<flatlogs::secT>();
895 : }
896 :
897 : if( ipRecv.find( "time_ns" ) )
898 : {
899 : ts.time_ns = ipRecv["time_ns"].get<flatlogs::nanosecT>();
900 : }
901 :
902 : if( ts.time_s != 0 )
903 : {
904 : m_log.template log<user_log>( ts, { email, message }, logPrio::LOG_INFO );
905 : }
906 : else
907 : {
908 : log<user_log>( { email, message } );
909 : }
910 :
911 : return 0;
912 : }
913 :
914 0 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_resetTarget )( const pcf::IndiProperty &ipRecv )
915 : {
916 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_resetTarget, ipRecv );
917 :
918 : if( !ipRecv.find( "request" ) )
919 : {
920 : return 0;
921 : }
922 :
923 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
924 : {
925 : if( m_observing )
926 : {
927 : m_tgtStartTime = m_obsStartTime;
928 : m_tgtStartParang = m_obsStartParang;
929 : }
930 : else
931 : {
932 : m_newTargetBlock = true;
933 : }
934 : }
935 :
936 : return 0;
937 : }
938 :
939 0 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_target )( const pcf::IndiProperty &ipRecv )
940 : {
941 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_target, ipRecv );
942 :
943 : std::string target;
944 : if( indiTargetUpdate( m_indiP_target, target, ipRecv ) < 0 )
945 : {
946 : return log<software_error, -1>( { __FILE__, __LINE__ } );
947 : }
948 :
949 : if( target != m_target )
950 : {
951 : if( m_observing )
952 : {
953 : m_tgtStartTime = m_obsStartTime;
954 : m_tgtStartParang = m_obsStartParang;
955 : }
956 : else
957 : {
958 : m_newTargetBlock = true;
959 : }
960 :
961 : m_target = target;
962 : m_newTarget = true;
963 :
964 : log<text_log>( "Target updated by observer to " + m_target, logPrio::LOG_NOTICE );
965 :
966 : std::unique_lock<std::mutex> lock( m_indiMutex );
967 : updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
968 : }
969 :
970 : return 0;
971 : }
972 :
973 0 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_tcsTarget )( const pcf::IndiProperty &ipRecv )
974 : {
975 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tcsTarget, ipRecv );
976 :
977 : if( !ipRecv.find( "request" ) )
978 : {
979 : return 0;
980 : }
981 :
982 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
983 : {
984 : if( m_target != m_catObj )
985 : {
986 : if( m_observing )
987 : {
988 : m_tgtStartTime = m_obsStartTime;
989 : m_tgtStartParang = m_obsStartParang;
990 : }
991 : else
992 : {
993 : m_newTargetBlock = true;
994 : }
995 :
996 : m_target = m_catObj;
997 : m_newTarget = true;
998 :
999 : log<text_log>( "Target updated by observer to TCS target: " + m_target, logPrio::LOG_NOTICE );
1000 : updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
1001 : }
1002 : }
1003 :
1004 : return 0;
1005 : }
1006 :
1007 0 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catalog )( const pcf::IndiProperty &ipRecv )
1008 : {
1009 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_catalog, ipRecv );
1010 :
1011 : if( !ipRecv.find( "object" ) )
1012 : {
1013 : return -1;
1014 : }
1015 :
1016 : std::string object = ipRecv["object"].get();
1017 :
1018 : if( object != m_catObj )
1019 : {
1020 : m_catObj = object;
1021 : //m_target = object;
1022 : //m_newTarget = true;
1023 :
1024 : // Always log change in name (different from RA and DEC)
1025 : log<text_log>( "TCS target updated to " + m_catObj, logPrio::LOG_NOTICE );
1026 :
1027 : //std::unique_lock<std::mutex> lock( m_indiMutex );
1028 : //updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
1029 : }
1030 :
1031 : return 0;
1032 : }
1033 :
1034 0 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catdata )( const pcf::IndiProperty &ipRecv )
1035 : {
1036 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_catdata, ipRecv );
1037 :
1038 : bool change = false;
1039 : if( ipRecv.find( "ra" ) )
1040 : {
1041 : std::string ra = ipRecv["ra"].get();
1042 :
1043 : if( ra != m_catRA )
1044 : {
1045 : m_catRA = ra;
1046 : //m_target = m_catObj;
1047 :
1048 : if( !m_newTarget ) // Only log if not already new
1049 : {
1050 : change = true;
1051 : m_newTarget = true;
1052 : }
1053 :
1054 : /*std::unique_lock<std::mutex> lock( m_indiMutex );
1055 : updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );*/
1056 : }
1057 : }
1058 :
1059 : if( ipRecv.find( "dec" ) )
1060 : {
1061 : std::string dec = ipRecv["dec"].get();
1062 :
1063 : if( dec != m_catDec )
1064 : {
1065 : m_catDec = dec;
1066 : //m_target = m_catObj;
1067 :
1068 : if( !m_newTarget ) // Only log if not already new
1069 : {
1070 : change = true;
1071 : m_newTarget = true;
1072 : }
1073 :
1074 : /*std::unique_lock<std::mutex> lock( m_indiMutex );
1075 : updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );*/
1076 : }
1077 : }
1078 :
1079 : if( change ) // Only log if not already new
1080 : {
1081 : log<text_log>( "Pointing change. Probable target change.", logPrio::LOG_NOTICE );
1082 : }
1083 :
1084 : return 0;
1085 : }
1086 :
1087 0 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
1088 : {
1089 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_teldata, ipRecv );
1090 :
1091 : if( ipRecv.find( "pa" ) )
1092 : {
1093 : m_parang = ipRecv["pa"].get<double>();
1094 : }
1095 :
1096 : return 0;
1097 : }
1098 :
1099 0 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_labMode )( const pcf::IndiProperty &ipRecv )
1100 : {
1101 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_labMode, ipRecv );
1102 :
1103 : if( ipRecv.find( "toggle" ) )
1104 : {
1105 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1106 : {
1107 : m_labMode = true;
1108 : }
1109 : else
1110 : {
1111 : m_labMode = false;
1112 : }
1113 : }
1114 :
1115 : std::cerr << "got labmode: " << m_labMode << '\n';
1116 : return 0;
1117 : }
1118 :
1119 0 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_loop )( const pcf::IndiProperty &ipRecv )
1120 : {
1121 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_loop, ipRecv );
1122 :
1123 : if( ipRecv.find( "toggle" ) )
1124 : {
1125 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1126 : {
1127 : //Now that we're manually synch-ing the target name, we don't need loop closed logic
1128 : //i.e. the target block starts when the observation starts after setting the name.
1129 : /*if( m_newTarget == true && !m_loop )
1130 : {
1131 : m_newTargetBlock = true;
1132 : m_newTarget = false;
1133 : }*/
1134 :
1135 : m_loop = true;
1136 : }
1137 : else
1138 : {
1139 : m_loop = false;
1140 : }
1141 : }
1142 :
1143 : return 0;
1144 : }
1145 :
1146 0 : inline int observerCtrl::checkRecordTimes()
1147 : {
1148 0 : return telemeter<observerCtrl>::checkRecordTimes( telem_observer() );
1149 : }
1150 :
1151 0 : inline int observerCtrl::recordTelem( const telem_observer * )
1152 : {
1153 0 : return recordObserver( true );
1154 : }
1155 :
1156 0 : inline int observerCtrl::recordObserver( bool force )
1157 : {
1158 0 : static std::string last_email;
1159 0 : static std::string last_obsName;
1160 : static bool last_observing;
1161 0 : static std::string last_target;
1162 0 : static std::string last_operator;
1163 :
1164 0 : if( last_email != m_currentObserver.m_email || last_obsName != m_obsName || last_observing != m_observing ||
1165 0 : last_target != m_target || last_operator != m_currentOperator.m_email || force )
1166 : {
1167 0 : telem<telem_observer>(
1168 0 : { m_currentObserver.m_email, m_obsName, m_observing, m_target, m_currentOperator.m_email } );
1169 :
1170 0 : last_email = m_currentObserver.m_email;
1171 0 : last_obsName = m_obsName;
1172 0 : last_observing = m_observing;
1173 0 : last_target = m_target;
1174 0 : last_operator = m_currentOperator.m_email;
1175 : }
1176 :
1177 0 : return 0;
1178 : }
1179 :
1180 : } // namespace app
1181 : } // namespace MagAOX
1182 :
1183 : #endif // observerCtrl_hpp
|