API
 
Loading...
Searching...
No Matches
observerCtrl.hpp
Go to the documentation of this file.
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
29namespace MagAOX
30{
31namespace app
32{
33
34/// The MagAO-X Observer Controller
35/**
36 * \ingroup observerCtrl
37 */
38class 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
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
119
120 /// The UTC start time of the current observation
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
128
129 /// The UTC start time of the current target
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.
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.
150 {
151 }
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:
223
225
229
231
233
235
237
239
241
243
245
247
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
265observerCtrl::observerCtrl() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
266{
267 return;
268}
269
271{
272 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
283}
284
285int observerCtrl::loadConfigImpl( mx::app::appConfigurator &_config )
286{
287 _config( m_streamWriters, "stream.writers" );
288
289 std::vector<std::string> sections;
290
291 _config.unusedSections( sections );
292
293 if( sections.size() == 0 )
294 {
295 log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
296 return -1;
297 }
298
299 for( size_t i = 0; i < sections.size(); ++i )
300 {
301 bool pfoaSet = _config.isSetUnused( mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
302 if( !pfoaSet )
303 continue;
304
305 std::string email = sections[i];
306
307 std::string pfoa;
308 _config.configUnused( pfoa, mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
309
310 std::string pronunciation;
311 _config.configUnused( pronunciation, mx::app::iniFile::makeKey( sections[i], "pronunciation" ) );
312
313 if( pronunciation == "" )
314 {
316 }
317
318 std::string fullName;
319 _config.configUnused( fullName, mx::app::iniFile::makeKey( sections[i], "full_name" ) );
320
321 std::string institution;
322 _config.configUnused( institution, mx::app::iniFile::makeKey( sections[i], "institution" ) );
323
324 std::string sanitizedEmail = "";
325 for( size_t n = 0; n < email.size(); ++n )
326 {
327 if( email[n] == '@' )
328 {
330 }
331 else if( email[n] == '.' )
332 {
333 sanitizedEmail = sanitizedEmail + "-dot-";
334 }
335 else
336 {
337 sanitizedEmail.push_back( email[n] );
338 }
339 }
340 m_observers[email] = observer( { fullName, pfoa, pronunciation, email, sanitizedEmail, institution } );
341 }
342
343 return 0;
344}
345
347{
348 if( loadConfigImpl( config ) < 0 )
349 {
350 m_shutdown = 1;
351 return;
352 }
353
354 if( m_observers.size() < 1 )
355 {
356 log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
357 m_shutdown = 1;
358 return;
359 }
360
362}
363
365{
366 std::vector<std::string> sanitizedEmails;
367 std::vector<std::string> emails;
368 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
369 {
370 sanitizedEmails.push_back( it->second.m_sanitizedEmail );
371 emails.push_back( it->second.m_email );
372 }
373
375 {
377 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 for( auto &it : m_observers )
383 {
384 if( it.first.find( "jrmales" ) != std::string::npos )
385 {
386 m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
387 m_currentObserver = it.second;
388 }
389 else
390 {
391 m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
392 }
393 }
394
396
398 {
400 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 for( auto &it : m_observers )
406 {
407 if( it.first.find( "jrmales" ) != std::string::npos )
408 {
409 m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
410 m_currentOperator = it.second;
411 }
412 else
413 {
414 m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
415 }
416 }
417
419
420 CREATE_REG_INDI_NEW_TEXT( m_indiP_obsName, "obs_name", "Observation Name", "Observer" );
421
423
424 CREATE_REG_INDI_NEW_NUMBERD( m_indiP_obsDuration, "obs_duration", 0, 300, 0.1, "%0.1f", "Duration", "Observer" );
425
426 CREATE_REG_INDI_RO_NUMBER( m_indiP_obsTime, "obs_time", "Observation Time", "Observer" );
427 indi::addNumberElement<double>( m_indiP_obsTime, "observation", 0, 14400, 0.1, "%0.1f", "Current Obs" );
428 indi::addNumberElement<double>( m_indiP_obsTime, "target", 0, 14400, 0.1, "%0.1f", "Target" );
429
430 REG_INDI_NEWPROP_NOCB( m_indiP_obsStart, "obs_start", pcf::IndiProperty::Text );
431 indi::addTextElement( m_indiP_obsStart, "observation" );
433
434 CREATE_REG_INDI_RO_NUMBER( m_indiP_obsAngle, "obs_delta_parang", "Change in Par. Ang.", "Observer" );
435 indi::addNumberElement<double>( m_indiP_obsAngle, "observation", 0, 360, 0.1, "%0.1f", "Current Obs" );
436 indi::addNumberElement<double>( m_indiP_obsAngle, "target", 0, 360, 0.1, "%0.1f", "Target" );
437
438 REG_INDI_NEWPROP_NOCB( m_indiP_observer, "current_observer", pcf::IndiProperty::Text );
442 indi::addTextElement( m_indiP_observer, "pronunciation" );
443 indi::addTextElement( m_indiP_observer, "institution" );
444
445 REG_INDI_NEWPROP_NOCB( m_indiP_operator, "current_operator", pcf::IndiProperty::Text );
449 indi::addTextElement( m_indiP_operator, "pronunciation" );
450 indi::addTextElement( m_indiP_operator, "institution" );
451
452 m_indiP_sws = pcf::IndiProperty( pcf::IndiProperty::Switch );
453 m_indiP_sws.setDevice( configName() );
454 m_indiP_sws.setName( "writers" );
455 m_indiP_sws.setPerm( pcf::IndiProperty::ReadWrite );
456 m_indiP_sws.setState( pcf::IndiProperty::Idle );
457 m_indiP_sws.setRule( pcf::IndiProperty::AnyOfMany );
458
459 for( size_t n = 0; n < m_streamWriters.size(); ++n )
460 {
461 m_indiP_sws.add( pcf::IndiElement( m_streamWriters[n], pcf::IndiElement::Off ) );
462 }
463
465
466 m_indiP_userlog = pcf::IndiProperty( pcf::IndiProperty::Text );
467 m_indiP_userlog.setDevice( configName() );
468 m_indiP_userlog.setName( "user_log" );
469 m_indiP_userlog.setPerm( pcf::IndiProperty::ReadWrite );
470 m_indiP_userlog.setState( pcf::IndiProperty::Idle );
471 m_indiP_userlog.add( pcf::IndiElement( "email" ) );
472 m_indiP_userlog.add( pcf::IndiElement( "message" ) );
473 m_indiP_userlog.add( pcf::IndiElement( "time_s" ) );
474 m_indiP_userlog.add( pcf::IndiElement( "time_ns" ) );
475
477
479
480 CREATE_REG_INDI_NEW_TEXT( m_indiP_target, "target", "Target", "Observer" );
481
482 CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_tcsTarget, "target_load_from_tcs" );
483
488
490
492
494 return 0;
495}
496
498{
499
500 std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
501
502 if( lock.owns_lock() )
503 {
505 { "full_name", "email", "pfoa", "pronunciation", "institution" },
511
512 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
513 {
514 if( it->first == m_currentObserver.m_email )
515 {
517 m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
518 }
519 else
520 {
522 m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
523 }
524 }
525
527 { "full_name", "email", "pfoa", "pronunciation", "institution" },
533
534 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
535 {
536 if( it->first == m_currentOperator.m_email )
537 {
539 m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
540 }
541 else
542 {
544 m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
545 }
546 }
547
548 updatesIfChanged<std::string>( m_indiP_obsName, { "current", "target" }, { m_obsName, m_obsName } );
549
551
552 if( m_observing )
553 {
554 timePointT ct = std::chrono::steady_clock::now();
555 const std::chrono::duration<double> obstime = ct - m_obsStartTime;
557
558 double obsang = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_obsStartParang );
559 m_tgtAng = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_tgtStartParang );
560
561 updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
563 m_indiP_obsTime, { "observation", "target" }, { obstime.count(), m_tgtTime.count() } );
564
565 updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { obsang, m_tgtAng } );
567 m_indiP_obsStart, { "observation", "target" }, { m_obsStartTimeStamp, m_tgtStartTimeStamp } );
568
569 if( m_obsDuration > 0.0 && obstime.count() > m_obsDuration )
570 {
572 }
573 }
574 else
575 {
576 updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
577 updatesIfChanged<std::string>( m_indiP_obsStart, { "observation", "target" }, { "", m_tgtStartTimeStamp } );
578 updatesIfChanged<double>( m_indiP_obsTime, { "observation", "target" }, { 0.0, m_tgtTime.count() } );
579 updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { 0.0, m_tgtAng } );
580 }
581 }
582
584
585 return 0;
586}
587
589{
591
592 return 0;
593}
594
596{
597 for( size_t n = 0; n < m_streamWriters.size(); ++n )
598 {
599 if( m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On )
600 {
601 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
602
603 ip.setDevice( m_streamWriters[n] + "-sw" );
604 ip.setName( "writing" );
605 ip.add( pcf::IndiElement( "toggle" ) );
606 ip["toggle"].setSwitchState( pcf::IndiElement::On );
607
609 }
610 }
611
612 mx::sys::sleep( 1 );
613
614 m_obsStartTime = std::chrono::steady_clock::now();
615 m_obsStartTimeStamp = timeStampAsISO8601( std::chrono::system_clock::now() );
617
618 if( m_newTargetBlock || m_labMode ) // We always reset if in lab mode
619 {
623
624 m_newTargetBlock = false;
625 }
626
627 m_observing = true;
628 recordObserver( true );
629}
630
632{
633 m_observing = false;
634 recordObserver( true );
635
636 for( size_t n = 0; n < m_streamWriters.size(); ++n )
637 {
638 if( m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On )
639 {
640 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
641
642 ip.setDevice( m_streamWriters[n] + "-sw" );
643 ip.setName( "writing" );
644 ip.add( pcf::IndiElement( "toggle" ) );
645 ip["toggle"].setSwitchState( pcf::IndiElement::Off );
646
648 }
649 }
650}
651
652INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observers )( const pcf::IndiProperty &ipRecv )
653{
654
655 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
710INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_operators )( const pcf::IndiProperty &ipRecv )
711{
712
713 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
768std::string observerCtrl::timeStampAsISO8601( const std::chrono::time_point<std::chrono::system_clock> &tp )
769{
770 // Convert to time_t for whole seconds
771 std::time_t t = std::chrono::system_clock::to_time_t( tp );
772 std::tm tm = *std::gmtime( &t );
773
774 // Get nanoseconds
775 auto duration = tp.time_since_epoch();
776 auto seconds = std::chrono::duration_cast<std::chrono::seconds>( duration );
777 auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>( duration - seconds ).count();
778
779 // Format ISO 8601 with nanoseconds
780 std::stringstream ss;
781 ss << std::put_time( &tm, "%Y-%m-%dT%H:%M:%S" );
782 ss << '.' << std::setw( 9 ) << std::setfill( '0' ) << nanos << "Z"; // Z for UTC
783
784 return ss.str();
785}
786
787INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsName )( const pcf::IndiProperty &ipRecv )
788{
789 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
806INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observing )( const pcf::IndiProperty &ipRecv )
807{
808 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
832INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsDuration )( const pcf::IndiProperty &ipRecv )
833{
834 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsDuration, ipRecv );
835
836 return indiTargetUpdate( m_indiP_obsDuration, m_obsDuration, ipRecv, false );
837}
838
839INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_sws )( const pcf::IndiProperty &ipRecv )
840{
841 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
863INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_userlog )( const pcf::IndiProperty &ipRecv )
864{
865 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
914INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_resetTarget )( const pcf::IndiProperty &ipRecv )
915{
916 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
939INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_target )( const pcf::IndiProperty &ipRecv )
940{
941 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
973INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_tcsTarget )( const pcf::IndiProperty &ipRecv )
974{
975 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
1007INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catalog )( const pcf::IndiProperty &ipRecv )
1008{
1009 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
1034INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catdata )( const pcf::IndiProperty &ipRecv )
1035{
1036 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
1087INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
1088{
1089 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
1099INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_labMode )( const pcf::IndiProperty &ipRecv )
1100{
1101 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
1119INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_loop )( const pcf::IndiProperty &ipRecv )
1120{
1121 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
1150
1152{
1153 return recordObserver( true );
1154}
1155
1156inline int observerCtrl::recordObserver( bool force )
1157{
1158 static std::string last_email;
1159 static std::string last_obsName;
1160 static bool last_observing;
1161 static std::string last_target;
1162 static std::string last_operator;
1163
1166 {
1169
1175 }
1176
1177 return 0;
1178}
1179
1180} // namespace app
1181} // namespace MagAOX
1182
1183#endif // observerCtrl_hpp
The base-class for MagAO-X applications.
Definition MagAOXApp.hpp:73
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI switch element value if it has changed.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
std::mutex m_indiMutex
Mutex for locking INDI communications.
std::string configName()
Get the config name.
int createStandardIndiSelectionSw(pcf::IndiProperty &prop, const std::string &name, const std::vector< std::string > &elements, const std::vector< std::string > &elementLabels, const std::string &label="", const std::string &group="")
Create a standard R/W INDI selection (one of many) switch with vector of elements and element labels.
int sendNewProperty(const pcf::IndiProperty &ipSend, const std::string &el, const T &newVal)
Send a newProperty command to another device (using the INDI Client interface)
The MagAO-X Observer Controller.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_loop)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_resetTarget)
pcf::IndiProperty m_indiP_observer
Text which contains the specifications of the current observer.
std::string m_pronunciation
Guide for the TTS to pronounced the pfoa (defaults to pfoa)
std::chrono::time_point< std::chrono::steady_clock > timePointT
pcf::IndiProperty m_indiP_obsName
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_obsDuration)
double m_parang
The current parallactic angle.
int recordTelem(const telem_observer *)
timePointT m_obsStartTime
The start time of the current observation.
pcf::IndiProperty m_indiP_resetTarget
Reset the target statistics.
std::string m_sanitizedEmail
Observer's email sanitized for use in INDI properties.
pcf::IndiProperty m_indiP_tcsTarget
Set the target to match TCS catObj.
pcf::IndiProperty m_indiP_obsTime
Number tracking the elapsed time.
pcf::IndiProperty m_indiP_obsAngle
Number tracking the change in angle.
durationT m_tgtTime
The current target time. Only updated while observing.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_operators)
virtual int appLogic()
Implementation of the FSM for observerCtrl.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_labMode)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_observing)
std::string m_pfoa
Observer's preferred forma of address.
pcf::IndiProperty m_indiP_observers
Selection switch to allow selection of the observer.
int recordObserver(bool force=false)
std::string m_institution
The observer's institution.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_sws)
pcf::IndiProperty m_indiP_obsStart
String timestamp indicating the start for target/observation.
double m_obsDuration
The desired duration of the observation. If 0 then until stopped.
pcf::IndiProperty m_indiP_labMode
Tracks whether TCS is in lab mode.
~observerCtrl() noexcept
D'tor, declared and defined for noexcept.
double m_obsStartParang
The parallactic angle at the start of the observation.
pcf::IndiProperty m_indiP_catalog
Catalog text data.
std::chrono::duration< double > durationT
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_obsName)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_observers)
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_catalog)
dev::telemeter< observerCtrl > telemeterT
pcf::IndiProperty m_indiP_operator
Text which contains the specifications of the current observer.
std::vector< std::string > m_streamWriters
The stream writers to stop and start.
pcf::IndiProperty m_indiP_obsDuration
Number to set the desired duration of observation.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_teldata)
std::string m_fullName
Obsever's full name.
pcf::IndiProperty m_indiP_loop
Tracks the loop state.
pcf::IndiProperty m_indiP_target
The target name, which can be overridden by the user.
timeStampT m_obsStartTimeStamp
The UTC start time of the current observation.
pcf::IndiProperty m_indiP_operators
Selection switch to allow selection of the observer.
virtual int appStartup()
Startup function.
bool m_observing
Flag indicating whether or not we are in an observation.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_userlog)
double m_tgtStartParang
The parallactic angle at the start of observing the current target.
pcf::IndiProperty m_indiP_userlog
Text to enter a user log.
std::string timeStampAsISO8601(const std::chrono::time_point< std::chrono::system_clock > &tp)
pcf::IndiProperty m_indiP_observing
Toggle switch to trigger observation.
pcf::IndiProperty m_indiP_catdata
Catalog numeric data.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_catdata)
timePointT m_tgtStartTime
The start time of the current target.
double m_tgtAng
The current target angle. Only updated while observing.
virtual int appShutdown()
Shutdown the app.
bool m_labMode
Flag tracking whether the TCS interface is in lab mode.
bool m_loop
Flag tracking loop state. true is loop closed.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
std::string m_obsName
The name of the observation.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_tcsTarget)
std::string m_email
Observer's email. Must be unique.
observerMapT m_observers
The observers from the configuration file.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_target)
observer m_currentObserver
The current selected observer.
timeStampT m_tgtStartTimeStamp
The UTC start time of the current target.
observer m_currentOperator
The current selected observer.
std::map< std::string, observer > observerMapT
pcf::IndiProperty m_indiP_sws
Selection to switch which stream writers are enabled.
pcf::IndiProperty m_indiP_teldata
Telescope data (for parang)
The observer specification.
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
#define CREATE_REG_INDI_NEW_TOGGLESWITCH(prop, name)
Create and register a NEW INDI property as a standard toggle switch, using the standard callback name...
#define CREATE_REG_INDI_NEW_REQUESTSWITCH(prop, name)
Create and register a NEW INDI property as a standard request switch, using the standard callback nam...
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
#define CREATE_REG_INDI_NEW_NUMBERD(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as double, using the standard callback n...
#define REG_INDI_NEWPROP_NOSETUP(prop)
Register a NEW INDI property with the class, using the standard callback name.
#define CREATE_REG_INDI_NEW_TEXT(prop, name, label, group)
Create and register a NEW INDI property as a standard text, using the standard callback name.
#define CREATE_REG_INDI_RO_NUMBER(prop, name, label, group)
Create and register a RO INDI property as a number, using the standard callback name.
uint32_t secT
The type used for seconds.
Definition logDefs.hpp:29
@ READY
The device is ready for operation, but is not operating.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
#define INDI_IDLE
Definition indiUtils.hpp:27
#define INDI_OK
Definition indiUtils.hpp:28
int addTextElement(pcf::IndiProperty &prop, const std::string &name, const std::string &label="")
Add a standard INDI Text element.
Definition indiUtils.hpp:37
const pcf::IndiProperty & ipRecv
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:26
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
static constexpr logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
static constexpr logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
static constexpr logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
uint32_t nanosecT
The type used for nanoseconds.
Definition logDefs.hpp:34
A device base class which saves telemetry.
Definition telemeter.hpp:69
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Software ERR log entry.
Log entry recording the build-time git state.
A fixed-width timespec structure.
Definition timespecX.hpp:35
secT time_s
Time since the Unix epoch.
Definition timespecX.hpp:36
#define TELEMETER_APP_LOGIC
Call telemeter::appLogic with error checking.
#define TELEMETER_APP_STARTUP
Call telemeter::appStartup with error checking.
#define TELEMETER_APP_SHUTDOWN
Call telemeter::appShutdown with error checking.