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