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
52 /// Format a system-clock time point as an ISO 8601 UTC timestamp.
53 std::string timeStampAsISO8601(
54 const std::chrono::time_point<std::chrono::system_clock> &tp /**< [in] the time point to format */ );
55
56 protected:
57 /** \name Configurable Parameters
58 *@{
59 */
60
61 std::vector<std::string> m_streamWriters; ///< The configured stream writers available for user selection.
62
63 std::vector<std::string> m_defStreamWriters; ///< The configured stream writers always managed for observations.
64
65 /// Tracks the remote `writing` properties for configured stream writers.
66 std::map<std::string, pcf::IndiProperty> m_indiP_streamWriterWriting;
67
68 /// Tracks whether each configured stream writer is user-selectable in INDI.
69 std::map<std::string, bool> m_streamWriterSelectable;
70
71 /// Reverse lookup from remote stream writer device name to configured writer name.
72 std::map<std::string, std::string> m_streamWriterDevices;
73
74 /// Tracks the last known remote writing state for each configured stream writer.
75 std::map<std::string, bool> m_streamWriterWriting;
76
77 /// Tracks whether a remote writing state has been received for each configured stream writer.
78 std::map<std::string, bool> m_streamWriterWritingKnown;
79
80 /// Tracks whether observerCtrl started each stream writer for the current observation.
81 std::map<std::string, bool> m_streamWriterStartedByObserver;
82
83 std::string m_tcsDev{ "tcsi" };
84 std::string m_catalogProp{ "catalog" };
85 std::string m_objEl{ "object" };
86 std::string m_catdataProp{ "catdata" };
87 std::string m_raEl{ "ra" };
88 std::string m_decEl{ "dec" };
89 std::string m_labModeProp{ "labMode" };
90
91 std::string m_teldataProp{ "teldata" };
92 std::string m_parangEl{ "pa" };
93
94 std::string m_loopDev{ "holoop" };
95 std::string m_loopStateProp{ "loop_state" };
96
97 ///@}
98
99 /// The observer specification
100 struct observer
101 {
102 std::string m_fullName; ///< Obsever's full name
103 std::string m_pfoa; ///< Observer's preferred forma of address
104 std::string m_pronunciation; ///< Guide for the TTS to pronounced the pfoa (defaults to pfoa)
105 std::string m_email; ///< Observer's email. Must be unique.
106 std::string m_sanitizedEmail; ///< Observer's email sanitized for use in INDI properties
107 std::string m_institution; ///< The observer's institution
108 };
109
110 typedef std::map<std::string, observer> observerMapT;
111
112 observerMapT m_observers; ///< The observers from the configuration file
113
114 observer m_currentObserver; ///< The current selected observer
115
116 observer m_currentOperator; ///< The current selected observer
117
118 std::string m_obsName; ///< The name of the observation.
119 double m_obsDuration{ 0 }; ///< The desired duration of the observation. If 0 then until stopped.
120
121 bool m_observing{ false }; ///< Flag indicating whether or not we are in an observation
122
123 std::string m_target; ///< The current target name shown to the observer.
124
125 std::string m_catObj; ///< The latest catalog object name reported by the TCS.
126
127 std::string m_catRA; ///< The latest catalog right ascension reported by the TCS.
128 std::string m_catDec; ///< The latest catalog declination reported by the TCS.
129
130 bool m_loop{ false }; ///< Flag tracking loop state. true is loop closed.
131
132 bool m_labMode{ false }; ///< Flag tracking whether the TCS interface is in lab mode.
133
134 bool m_newTargetBlock{ true }; /**< Flag to indicate that this is a new target block. This starts out as true
135 but becomes false on the first observation. Then becomes true when the
136 loop closes for the first time after a target change. */
137 bool m_newTarget{ false }; /**< Flag to track when the target changes. Occurs either automatically on a TCS update
138 or on a user override.*/
139
140 /// The start time of the current observation
142
143 /// The UTC start time of the current observation
145
146 /// The parallactic angle at the start of the observation
147 double m_obsStartParang{ 0 };
148
149 /// The start time of the current target
151
152 /// The UTC start time of the current target
154
155 /// The parallactic angle at the start of observing the current target
156 double m_tgtStartParang{ 0 };
157
158 /// The current parallactic angle.
159 double m_parang{ 0 };
160
161 /// Return the INDI device name used for a configured stream writer.
162 std::string
163 streamWriterDeviceName( const std::string &writerName /**< [in] the configured stream writer name */ ) const;
164
165 /// Return whether a stream writer is enabled for observation control.
166 bool streamWriterSelected( const std::string &writerName /**< [in] the configured stream writer name */ ) const;
167
168 /// Register one configured stream writer for remote writing-state tracking.
169 int registerStreamWriter( const std::string &writerName /**< [in] the configured stream writer name */,
170 bool userSelectable /**< [in] true if the writer should appear in the INDI selector */ );
171
172 /// Determine whether observerCtrl should start a stream writer for a new observation.
173 bool beginObservationStreamWriter( const std::string &writerName /**< [in] the configured stream writer name */ );
174
175 /// Determine whether observerCtrl should stop a stream writer when an observation ends.
176 bool endObservationStreamWriter( const std::string &writerName /**< [in] the configured stream writer name */ );
177
178 /// Send a writing toggle command to a configured stream writer.
179 int commandStreamWriter( const std::string &writerName /**< [in] the configured stream writer name */,
180 pcf::IndiElement::SwitchStateType state /**< [in] the requested writing switch state */ );
181
182 /// The current target time. Only updated while observing.
184
185 /// The current target angle. Only updated while observing.
186 double m_tgtAng{ 0 };
187
188 public:
189 /// Default c'tor.
190 observerCtrl();
191
192 /// D'tor, declared and defined for noexcept.
194 {
195 }
196
197 /// Set up the observerCtrl configuration parameters.
198 virtual void setupConfig();
199
200 /// Implementation of loadConfig logic, separated for testing.
201 /** This is called by loadConfig().
202 */
203 int loadConfigImpl( mx::app::appConfigurator &_config /**< [in] an application configuration
204 from which to load values*/
205 );
206
207 /// Load the observerCtrl configuration.
208 virtual void loadConfig();
209
210 /// Startup function
211 /**
212 *
213 */
214 virtual int appStartup();
215
216 /// Implementation of the FSM for observerCtrl.
217 /**
218 * \returns 0 on no critical error
219 * \returns -1 on an error requiring shutdown
220 */
221 virtual int appLogic();
222
223 /// Shutdown the app.
224 /**
225 *
226 */
227 virtual int appShutdown();
228
229 /// Start the current observation and any stream writers owned by observerCtrl.
230 void startObserving();
231
232 /// Stop the current observation and any stream writers owned by observerCtrl.
233 void stopObserving();
234
235 ///\name INDI
236 /** @{
237 */
238 protected:
239 pcf::IndiProperty m_indiP_observers; ///< Selection switch to allow selection of the observer
240 pcf::IndiProperty m_indiP_observer; ///< Text which contains the specifications of the current observer
241
242 pcf::IndiProperty m_indiP_operators; ///< Selection switch to allow selection of the observer
243 pcf::IndiProperty m_indiP_operator; ///< Text which contains the specifications of the current observer
244
245 pcf::IndiProperty m_indiP_obsName; /**< The current observation name, used to specify the
246 purpose of the observation*/
247 pcf::IndiProperty m_indiP_observing; ///< Toggle switch to trigger observation
248 pcf::IndiProperty m_indiP_obsDuration; ///< Number to set the desired duration of observation
249 pcf::IndiProperty m_indiP_obsStart; ///< String timestamp indicating the start for target/observation
250 pcf::IndiProperty m_indiP_obsTime; ///< Number tracking the elapsed time
251 pcf::IndiProperty m_indiP_obsAngle; ///< Number tracking the change in angle
252 pcf::IndiProperty m_indiP_sws; ///< Selection to switch which user-managed stream writers are enabled
253 pcf::IndiProperty m_indiP_userlog; ///< Text to enter a user log
254
255 pcf::IndiProperty m_indiP_resetTarget; ///< Reset the target statistics
256
257 pcf::IndiProperty m_indiP_target; ///< The target name, which can be overridden by the user
258
259 pcf::IndiProperty m_indiP_tcsTarget; ///< Set the target to match TCS catObj
260
261 pcf::IndiProperty m_indiP_catalog; ///< Catalog text data
262 pcf::IndiProperty m_indiP_catdata; ///< Catalog numeric data
263 pcf::IndiProperty m_indiP_teldata; ///< Telescope data (for parang)
264
265 pcf::IndiProperty m_indiP_labMode; ///< Tracks whether TCS is in lab mode.
266
267 pcf::IndiProperty m_indiP_loop; ///< Tracks the loop state
268
269 public:
271
273
277
279
281
283
285
287
289
291
293
295
297
298 /// Handle remote stream writer `writing` property updates.
299 int
300 setCallBack_streamWriterWriting( const pcf::IndiProperty &ipRecv /**< [in] the remote writing property update */ );
301
302 /// Static wrapper for remote stream writer `writing` property updates.
304 void *app /**< [in] the application instance */,
305 const pcf::IndiProperty &ipRecv /**< [in] the remote writing property update */ );
306
307 ///@}
308
309 /** \name Telemeter Interface
310 *
311 * @{
312 */
313 int checkRecordTimes();
314
315 int recordTelem( const telem_observer * );
316
317 int recordObserver( bool force = false );
318
319 ///@}
320};
321
322inline int observerCtrl::st_setCallBack_streamWriterWriting( void *app, const pcf::IndiProperty &ipRecv )
323{
324 return static_cast<observerCtrl *>( app )->setCallBack_streamWriterWriting( ipRecv );
325}
326
327observerCtrl::observerCtrl() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
328{
329 return;
330}
331
332inline std::string observerCtrl::streamWriterDeviceName( const std::string &writerName ) const
333{
334 return writerName + "-sw";
335}
336
337inline bool observerCtrl::streamWriterSelected( const std::string &writerName ) const
338{
341 {
342 return false;
343 }
344
345 if( !streamWriterIt->second )
346 {
347 return true;
348 }
349
350 return m_indiP_sws.find( writerName ) && m_indiP_sws[writerName].getSwitchState() == pcf::IndiElement::On;
351}
352
353inline int observerCtrl::registerStreamWriter( const std::string &writerName, bool userSelectable )
354{
355 const std::string deviceName = streamWriterDeviceName( writerName );
356
357 if( m_streamWriterSelectable.count( writerName ) > 0 )
358 {
359 return log<software_error, -1>( { __FILE__, __LINE__, "duplicate configured stream writer " + writerName } );
360 }
361
367
370 {
371 return log<software_error, -1>(
372 { __FILE__, __LINE__, "failed to register stream writer property for " + writerName } );
373 }
374
375 return 0;
376}
377
378inline bool observerCtrl::beginObservationStreamWriter( const std::string &writerName )
379{
381 {
383 return false;
384 }
385
386 bool shouldStart = true;
388 {
389 shouldStart = false;
390 }
391
394
395 return shouldStart;
396}
397
398inline bool observerCtrl::endObservationStreamWriter( const std::string &writerName )
399{
402
403 return shouldStop;
404}
405
406inline int observerCtrl::commandStreamWriter( const std::string &writerName, pcf::IndiElement::SwitchStateType state )
407{
408 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
409
410 ip.setDevice( streamWriterDeviceName( writerName ) );
411 ip.setName( "writing" );
412 ip.add( pcf::IndiElement( "toggle" ) );
413 ip["toggle"].setSwitchState( state );
414
415 return sendNewProperty( ip );
416}
417
419{
420 config.add( "stream.writers",
421 "",
422 "stream.writers",
423 argType::Optional,
424 "stream",
425 "writers",
426 false,
427 "string",
428 "The device names of the stream writers available for user selection." );
429
430 config.add( "stream.defWriters",
431 "",
432 "stream.defWriters",
433 argType::Optional,
434 "stream",
435 "defWriters",
436 false,
437 "string",
438 "The device names of the stream writers always controlled for each observation." );
439
441}
442
443int observerCtrl::loadConfigImpl( mx::app::appConfigurator &_config )
444{
445 _config( m_streamWriters, "stream.writers" );
446 _config( m_defStreamWriters, "stream.defWriters" );
447
448 std::vector<std::string> sections;
449
450 _config.unusedSections( sections );
451
452 if( sections.size() == 0 )
453 {
454 log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
455 return -1;
456 }
457
458 for( size_t i = 0; i < sections.size(); ++i )
459 {
460 bool pfoaSet = _config.isSetUnused( mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
461 if( !pfoaSet )
462 continue;
463
464 std::string email = sections[i];
465
466 std::string pfoa;
467 _config.configUnused( pfoa, mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
468
469 std::string pronunciation;
470 _config.configUnused( pronunciation, mx::app::iniFile::makeKey( sections[i], "pronunciation" ) );
471
472 if( pronunciation == "" )
473 {
474 pronunciation = pfoa;
475 }
476
477 std::string fullName;
478 _config.configUnused( fullName, mx::app::iniFile::makeKey( sections[i], "full_name" ) );
479
480 std::string institution;
481 _config.configUnused( institution, mx::app::iniFile::makeKey( sections[i], "institution" ) );
482
483 std::string sanitizedEmail = "";
484 for( size_t n = 0; n < email.size(); ++n )
485 {
486 if( email[n] == '@' )
487 {
489 }
490 else if( email[n] == '.' )
491 {
492 sanitizedEmail = sanitizedEmail + "-dot-";
493 }
494 else
495 {
496 sanitizedEmail.push_back( email[n] );
497 }
498 }
499 m_observers[email] = observer( { fullName, pfoa, pronunciation, email, sanitizedEmail, institution } );
500 }
501
502 return 0;
503}
504
506{
507 if( loadConfigImpl( config ) < 0 )
508 {
509 m_shutdown = 1;
510 return;
511 }
512
513 if( m_observers.size() < 1 )
514 {
515 log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
516 m_shutdown = 1;
517 return;
518 }
519
521}
522
524{
525 std::vector<std::string> sanitizedEmails;
526 std::vector<std::string> emails;
527 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
528 {
529 sanitizedEmails.push_back( it->second.m_sanitizedEmail );
530 emails.push_back( it->second.m_email );
531 }
532
534 {
536 return -1;
537 }
538
539 // Set to default user of jared
540 ///\todo do something else. maybe a default user is specified in the config?
541 for( auto &it : m_observers )
542 {
543 if( it.first.find( "jrmales" ) != std::string::npos )
544 {
545 m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
546 m_currentObserver = it.second;
547 }
548 else
549 {
550 m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
551 }
552 }
553
555
557 {
559 return -1;
560 }
561
562 // Set to default user of jared
563 ///\todo do something else. maybe a default user is specified in the config?
564 for( auto &it : m_observers )
565 {
566 if( it.first.find( "jrmales" ) != std::string::npos )
567 {
568 m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
569 m_currentOperator = it.second;
570 }
571 else
572 {
573 m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
574 }
575 }
576
578
579 CREATE_REG_INDI_NEW_TEXT( m_indiP_obsName, "obs_name", "Observation Name", "Observer" );
580
582
583 CREATE_REG_INDI_NEW_NUMBERD( m_indiP_obsDuration, "obs_duration", 0, 300, 0.1, "%0.1f", "Duration", "Observer" );
584
585 CREATE_REG_INDI_RO_NUMBER( m_indiP_obsTime, "obs_time", "Observation Time", "Observer" );
586 indi::addNumberElement<double>( m_indiP_obsTime, "observation", 0, 14400, 0.1, "%0.1f", "Current Obs" );
587 indi::addNumberElement<double>( m_indiP_obsTime, "target", 0, 14400, 0.1, "%0.1f", "Target" );
588
589 REG_INDI_NEWPROP_NOCB( m_indiP_obsStart, "obs_start", pcf::IndiProperty::Text );
590 indi::addTextElement( m_indiP_obsStart, "observation" );
592
593 CREATE_REG_INDI_RO_NUMBER( m_indiP_obsAngle, "obs_delta_parang", "Change in Par. Ang.", "Observer" );
594 indi::addNumberElement<double>( m_indiP_obsAngle, "observation", 0, 360, 0.1, "%0.1f", "Current Obs" );
595 indi::addNumberElement<double>( m_indiP_obsAngle, "target", 0, 360, 0.1, "%0.1f", "Target" );
596
597 REG_INDI_NEWPROP_NOCB( m_indiP_observer, "current_observer", pcf::IndiProperty::Text );
601 indi::addTextElement( m_indiP_observer, "pronunciation" );
602 indi::addTextElement( m_indiP_observer, "institution" );
603
604 REG_INDI_NEWPROP_NOCB( m_indiP_operator, "current_operator", pcf::IndiProperty::Text );
608 indi::addTextElement( m_indiP_operator, "pronunciation" );
609 indi::addTextElement( m_indiP_operator, "institution" );
610
611 m_indiP_sws = pcf::IndiProperty( pcf::IndiProperty::Switch );
612 m_indiP_sws.setDevice( configName() );
613 m_indiP_sws.setName( "writers" );
614 m_indiP_sws.setPerm( pcf::IndiProperty::ReadWrite );
615 m_indiP_sws.setState( pcf::IndiProperty::Idle );
616 m_indiP_sws.setRule( pcf::IndiProperty::AnyOfMany );
617
618 for( size_t n = 0; n < m_streamWriters.size(); ++n )
619 {
620 m_indiP_sws.add( pcf::IndiElement( m_streamWriters[n], pcf::IndiElement::Off ) );
621
622 if( registerStreamWriter( m_streamWriters[n], true ) < 0 )
623 {
624 return log<software_error, -1>( { __FILE__, __LINE__ } );
625 }
626 }
627
628 for( size_t n = 0; n < m_defStreamWriters.size(); ++n )
629 {
630 if( registerStreamWriter( m_defStreamWriters[n], false ) < 0 )
631 {
632 return log<software_error, -1>( { __FILE__, __LINE__ } );
633 }
634 }
635
637
638 m_indiP_userlog = pcf::IndiProperty( pcf::IndiProperty::Text );
639 m_indiP_userlog.setDevice( configName() );
640 m_indiP_userlog.setName( "user_log" );
641 m_indiP_userlog.setPerm( pcf::IndiProperty::ReadWrite );
642 m_indiP_userlog.setState( pcf::IndiProperty::Idle );
643 m_indiP_userlog.add( pcf::IndiElement( "email" ) );
644 m_indiP_userlog.add( pcf::IndiElement( "message" ) );
645 m_indiP_userlog.add( pcf::IndiElement( "time_s" ) );
646 m_indiP_userlog.add( pcf::IndiElement( "time_ns" ) );
647
649
651
652 CREATE_REG_INDI_NEW_TEXT( m_indiP_target, "target", "Target", "Observer" );
653
654 CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_tcsTarget, "target_load_from_tcs" );
655
660
662
664
666 return 0;
667}
668
670{
671
672 std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
673
674 if( lock.owns_lock() )
675 {
677 { "full_name", "email", "pfoa", "pronunciation", "institution" },
683
684 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
685 {
686 if( it->first == m_currentObserver.m_email )
687 {
689 m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
690 }
691 else
692 {
694 m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
695 }
696 }
697
699 { "full_name", "email", "pfoa", "pronunciation", "institution" },
705
706 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
707 {
708 if( it->first == m_currentOperator.m_email )
709 {
711 m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
712 }
713 else
714 {
716 m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
717 }
718 }
719
720 updatesIfChanged<std::string>( m_indiP_obsName, { "current", "target" }, { m_obsName, m_obsName } );
721
723
724 if( m_observing )
725 {
726 timePointT ct = std::chrono::steady_clock::now();
727 const std::chrono::duration<double> obstime = ct - m_obsStartTime;
729
730 double obsang = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_obsStartParang );
731 m_tgtAng = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_tgtStartParang );
732
733 updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
735 m_indiP_obsTime, { "observation", "target" }, { obstime.count(), m_tgtTime.count() } );
736
737 updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { obsang, m_tgtAng } );
739 m_indiP_obsStart, { "observation", "target" }, { m_obsStartTimeStamp, m_tgtStartTimeStamp } );
740
741 if( m_obsDuration > 0.0 && obstime.count() > m_obsDuration )
742 {
744 }
745 }
746 else
747 {
748 updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
749 updatesIfChanged<std::string>( m_indiP_obsStart, { "observation", "target" }, { "", m_tgtStartTimeStamp } );
750 updatesIfChanged<double>( m_indiP_obsTime, { "observation", "target" }, { 0.0, m_tgtTime.count() } );
751 updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { 0.0, m_tgtAng } );
752 }
753 }
754
756
757 return 0;
758}
759
761{
763
764 return 0;
765}
766
768{
769 for( size_t n = 0; n < m_streamWriters.size(); ++n )
770 {
772 {
773 if( commandStreamWriter( m_streamWriters[n], pcf::IndiElement::On ) < 0 )
774 {
775 log<software_error>( { __FILE__, __LINE__, "failed to start stream writer " + m_streamWriters[n] } );
776 }
777 }
778 }
779
780 for( size_t n = 0; n < m_defStreamWriters.size(); ++n )
781 {
783 {
784 if( commandStreamWriter( m_defStreamWriters[n], pcf::IndiElement::On ) < 0 )
785 {
786 log<software_error>( { __FILE__, __LINE__, "failed to start stream writer " + m_defStreamWriters[n] } );
787 }
788 }
789 }
790
791 mx::sys::sleep( 1 );
792
793 m_obsStartTime = std::chrono::steady_clock::now();
794 m_obsStartTimeStamp = timeStampAsISO8601( std::chrono::system_clock::now() );
796
797 if( m_newTargetBlock || m_labMode ) // We always reset if in lab mode
798 {
802
803 m_newTargetBlock = false;
804 }
805
806 m_observing = true;
807 recordObserver( true );
808}
809
811{
812 m_observing = false;
813 recordObserver( true );
814
815 for( size_t n = 0; n < m_streamWriters.size(); ++n )
816 {
818 {
819 if( commandStreamWriter( m_streamWriters[n], pcf::IndiElement::Off ) < 0 )
820 {
821 log<software_error>( { __FILE__, __LINE__, "failed to stop stream writer " + m_streamWriters[n] } );
822 }
823 }
824 }
825
826 for( size_t n = 0; n < m_defStreamWriters.size(); ++n )
827 {
829 {
830 if( commandStreamWriter( m_defStreamWriters[n], pcf::IndiElement::Off ) < 0 )
831 {
832 log<software_error>( { __FILE__, __LINE__, "failed to stop stream writer " + m_defStreamWriters[n] } );
833 }
834 }
835 }
836}
837
839{
840 if( !ipRecv.hasValidDevice() || !ipRecv.find( "toggle" ) )
841 {
842 return 0;
843 }
844
845 auto streamWriterIt = m_streamWriterDevices.find( ipRecv.getDevice() );
847 {
848 return log<software_error, -1>(
849 { __FILE__, __LINE__, "received writing update for unknown stream writer " + ipRecv.getDevice() } );
850 }
851
852 { // mutex scope
853 std::lock_guard<std::mutex> lock( m_indiMutex );
854
855 m_streamWriterWriting[streamWriterIt->second] = ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On;
857 } // mutex scope
858
859 return 0;
860}
861
862INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observers )( const pcf::IndiProperty &ipRecv )
863{
864
865 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_observers, ipRecv );
866
867 // look for selected mode switch which matches a known mode. Make sure only one is selected.
868 std::string newEmail = "";
869 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
870 {
871 if( !ipRecv.find( it->second.m_sanitizedEmail ) )
872 continue;
873
874 if( ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
875 {
876 if( newEmail != "" )
877 {
878 log<text_log>( "More than one observer selected", logPrio::LOG_ERROR );
879 return -1;
880 }
881
882 newEmail = it->first;
883 }
884 }
885
886 if( newEmail == "" )
887 {
888 std::cerr << "nothing\n";
889 return 0;
890 }
891
892 {
893 std::unique_lock<std::mutex> lock( m_indiMutex );
894
895 m_currentObserver = m_observers[newEmail];
896
897 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
898 {
899 if( it->first == m_currentObserver.m_sanitizedEmail )
900 {
901 updateSwitchIfChanged(
902 m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
903 }
904 else
905 {
906 updateSwitchIfChanged(
907 m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
908 }
909 }
910 }
911
912 log<logger::observer>( { m_currentObserver.m_fullName,
913 m_currentObserver.m_pfoa,
914 m_currentObserver.m_email,
915 m_currentObserver.m_institution } );
916
917 return 0;
918}
919
920INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_operators )( const pcf::IndiProperty &ipRecv )
921{
922
923 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_operators, ipRecv );
924
925 // look for selected mode switch which matches a known mode. Make sure only one is selected.
926 std::string newEmail = "";
927 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
928 {
929 if( !ipRecv.find( it->second.m_sanitizedEmail ) )
930 continue;
931
932 if( ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
933 {
934 if( newEmail != "" )
935 {
936 log<text_log>( "More than one operator selected", logPrio::LOG_ERROR );
937 return -1;
938 }
939
940 newEmail = it->first;
941 }
942 }
943
944 if( newEmail == "" )
945 {
946 std::cerr << "nothing\n";
947 return 0;
948 }
949
950 {
951 std::unique_lock<std::mutex> lock( m_indiMutex );
952
953 m_currentOperator = m_observers[newEmail];
954
955 for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
956 {
957 if( it->first == m_currentOperator.m_sanitizedEmail )
958 {
959 updateSwitchIfChanged(
960 m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
961 }
962 else
963 {
964 updateSwitchIfChanged(
965 m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
966 }
967 }
968 }
969
970 log<logger::ao_operator>( { m_currentOperator.m_fullName,
971 m_currentOperator.m_pfoa,
972 m_currentOperator.m_email,
973 m_currentOperator.m_institution } );
974
975 return 0;
976}
977
978std::string observerCtrl::timeStampAsISO8601( const std::chrono::time_point<std::chrono::system_clock> &tp )
979{
980 // Convert to time_t for whole seconds
981 std::time_t t = std::chrono::system_clock::to_time_t( tp );
982 std::tm tm = *std::gmtime( &t );
983
984 // Get nanoseconds
985 auto duration = tp.time_since_epoch();
986 auto seconds = std::chrono::duration_cast<std::chrono::seconds>( duration );
987 auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>( duration - seconds ).count();
988
989 // Format ISO 8601 with nanoseconds
990 std::stringstream ss;
991 ss << std::put_time( &tm, "%Y-%m-%dT%H:%M:%S" );
992 ss << '.' << std::setw( 9 ) << std::setfill( '0' ) << nanos << "Z"; // Z for UTC
993
994 return ss.str();
995}
996
997INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsName )( const pcf::IndiProperty &ipRecv )
998{
999 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsName, ipRecv );
1000
1001 std::string target;
1002
1003 std::unique_lock<std::mutex> lock( m_indiMutex );
1004
1005 if( indiTargetUpdate( m_indiP_obsName, target, ipRecv, true ) < 0 )
1006 {
1007 log<software_error>( { __FILE__, __LINE__ } );
1008 return -1;
1009 }
1010
1011 m_obsName = target;
1012
1013 return 0;
1014}
1015
1016INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observing )( const pcf::IndiProperty &ipRecv )
1017{
1018 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_observing, ipRecv );
1019
1020 if( !ipRecv.find( "toggle" ) )
1021 {
1022 return 0;
1023 }
1024
1025 recordObserver( true );
1026 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1027 {
1028 std::unique_lock<std::mutex> lock( m_indiMutex );
1029 startObserving();
1030 updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
1031 }
1032 else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1033 {
1034 std::unique_lock<std::mutex> lock( m_indiMutex );
1035 stopObserving();
1036 updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
1037 }
1038
1039 return 0;
1040}
1041
1042INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsDuration )( const pcf::IndiProperty &ipRecv )
1043{
1044 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsDuration, ipRecv );
1045
1046 return indiTargetUpdate( m_indiP_obsDuration, m_obsDuration, ipRecv, false );
1047}
1048
1049INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_sws )( const pcf::IndiProperty &ipRecv )
1050{
1051 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_sws, ipRecv );
1052
1053 if( m_observing == true )
1054 {
1055 log<text_log>( { "Can't change stream writers while observing" }, logPrio::LOG_WARNING );
1056 return 0;
1057 }
1058
1059 for( size_t n = 0; n < m_streamWriters.size(); ++n )
1060 {
1061 if( !ipRecv.find( m_streamWriters[n] ) )
1062 {
1063 continue;
1064 }
1065
1066 std::unique_lock<std::mutex> lock( m_indiMutex );
1067 updateSwitchIfChanged( m_indiP_sws, m_streamWriters[n], ipRecv[m_streamWriters[n]].getSwitchState() );
1068 }
1069
1070 return 0;
1071}
1072
1073INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_userlog )( const pcf::IndiProperty &ipRecv )
1074{
1075 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_userlog, ipRecv );
1076
1077 std::string email;
1078 std::string message;
1079
1080 timespecX ts{};
1081
1082 if( ipRecv.find( "email" ) )
1083 {
1084 email = ipRecv["email"].get();
1085 }
1086
1087 if( ipRecv.find( "message" ) )
1088 {
1089 message = ipRecv["message"].get();
1090 }
1091
1092 if( message == "" )
1093 {
1094 return 0;
1095 }
1096
1097 if( email == "" )
1098 {
1099 email = m_currentObserver.m_email;
1100 }
1101
1102 if( ipRecv.find( "time_s" ) )
1103 {
1104 ts.time_s = ipRecv["time_s"].get<flatlogs::secT>();
1105 }
1106
1107 if( ipRecv.find( "time_ns" ) )
1108 {
1109 ts.time_ns = ipRecv["time_ns"].get<flatlogs::nanosecT>();
1110 }
1111
1112 if( ts.time_s != 0 )
1113 {
1114 m_log.template log<user_log>( ts, { email, message }, logPrio::LOG_INFO );
1115 }
1116 else
1117 {
1118 log<user_log>( { email, message } );
1119 }
1120
1121 return 0;
1122}
1123
1124INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_resetTarget )( const pcf::IndiProperty &ipRecv )
1125{
1126 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_resetTarget, ipRecv );
1127
1128 if( !ipRecv.find( "request" ) )
1129 {
1130 return 0;
1131 }
1132
1133 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1134 {
1135 if( m_observing )
1136 {
1137 m_tgtStartTime = m_obsStartTime;
1138 m_tgtStartParang = m_obsStartParang;
1139 }
1140 else
1141 {
1142 m_newTargetBlock = true;
1143 }
1144 }
1145
1146 return 0;
1147}
1148
1149INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_target )( const pcf::IndiProperty &ipRecv )
1150{
1151 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_target, ipRecv );
1152
1153 std::string target;
1154 if( indiTargetUpdate( m_indiP_target, target, ipRecv ) < 0 )
1155 {
1156 return log<software_error, -1>( { __FILE__, __LINE__ } );
1157 }
1158
1159 if( target != m_target )
1160 {
1161 if( m_observing )
1162 {
1163 m_tgtStartTime = m_obsStartTime;
1164 m_tgtStartParang = m_obsStartParang;
1165 }
1166 else
1167 {
1168 m_newTargetBlock = true;
1169 }
1170
1171 m_target = target;
1172 m_newTarget = true;
1173
1174 log<text_log>( "Target updated by observer to " + m_target, logPrio::LOG_NOTICE );
1175
1176 std::unique_lock<std::mutex> lock( m_indiMutex );
1177 updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
1178 }
1179
1180 return 0;
1181}
1182
1183INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_tcsTarget )( const pcf::IndiProperty &ipRecv )
1184{
1185 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tcsTarget, ipRecv );
1186
1187 if( !ipRecv.find( "request" ) )
1188 {
1189 return 0;
1190 }
1191
1192 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1193 {
1194 if( m_target != m_catObj )
1195 {
1196 if( m_observing )
1197 {
1198 m_tgtStartTime = m_obsStartTime;
1199 m_tgtStartParang = m_obsStartParang;
1200 }
1201 else
1202 {
1203 m_newTargetBlock = true;
1204 }
1205
1206 m_target = m_catObj;
1207 m_newTarget = true;
1208
1209 log<text_log>( "Target updated by observer to TCS target: " + m_target, logPrio::LOG_NOTICE );
1210 updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
1211 }
1212 }
1213
1214 return 0;
1215}
1216
1217INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catalog )( const pcf::IndiProperty &ipRecv )
1218{
1219 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_catalog, ipRecv );
1220
1221 if( !ipRecv.find( "object" ) )
1222 {
1223 return -1;
1224 }
1225
1226 std::string object = ipRecv["object"].get();
1227
1228 if( object != m_catObj )
1229 {
1230 m_catObj = object;
1231 // m_target = object;
1232 // m_newTarget = true;
1233
1234 // Always log change in name (different from RA and DEC)
1235 log<text_log>( "TCS target updated to " + m_catObj, logPrio::LOG_NOTICE );
1236
1237 // std::unique_lock<std::mutex> lock( m_indiMutex );
1238 // updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
1239 }
1240
1241 return 0;
1242}
1243
1244INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catdata )( const pcf::IndiProperty &ipRecv )
1245{
1246 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_catdata, ipRecv );
1247
1248 bool change = false;
1249 if( ipRecv.find( "ra" ) )
1250 {
1251 std::string ra = ipRecv["ra"].get();
1252
1253 if( ra != m_catRA )
1254 {
1255 m_catRA = ra;
1256 // m_target = m_catObj;
1257
1258 if( !m_newTarget ) // Only log if not already new
1259 {
1260 change = true;
1261 m_newTarget = true;
1262 }
1263
1264 /*std::unique_lock<std::mutex> lock( m_indiMutex );
1265 updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );*/
1266 }
1267 }
1268
1269 if( ipRecv.find( "dec" ) )
1270 {
1271 std::string dec = ipRecv["dec"].get();
1272
1273 if( dec != m_catDec )
1274 {
1275 m_catDec = dec;
1276 // m_target = m_catObj;
1277
1278 if( !m_newTarget ) // Only log if not already new
1279 {
1280 change = true;
1281 m_newTarget = true;
1282 }
1283
1284 /*std::unique_lock<std::mutex> lock( m_indiMutex );
1285 updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );*/
1286 }
1287 }
1288
1289 if( change ) // Only log if not already new
1290 {
1291 log<text_log>( "Pointing change. Probable target change.", logPrio::LOG_NOTICE );
1292 }
1293
1294 return 0;
1295}
1296
1297INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
1298{
1299 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_teldata, ipRecv );
1300
1301 if( ipRecv.find( "pa" ) )
1302 {
1303 m_parang = ipRecv["pa"].get<double>();
1304 }
1305
1306 return 0;
1307}
1308
1309INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_labMode )( const pcf::IndiProperty &ipRecv )
1310{
1311 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_labMode, ipRecv );
1312
1313 if( ipRecv.find( "toggle" ) )
1314 {
1315 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1316 {
1317 m_labMode = true;
1318 }
1319 else
1320 {
1321 m_labMode = false;
1322 }
1323 }
1324
1325 std::cerr << "got labmode: " << m_labMode << '\n';
1326 return 0;
1327}
1328
1329INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_loop )( const pcf::IndiProperty &ipRecv )
1330{
1331 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_loop, ipRecv );
1332
1333 if( ipRecv.find( "toggle" ) )
1334 {
1335 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1336 {
1337 // Now that we're manually synch-ing the target name, we don't need loop closed logic
1338 // i.e. the target block starts when the observation starts after setting the name.
1339 /*if( m_newTarget == true && !m_loop )
1340 {
1341 m_newTargetBlock = true;
1342 m_newTarget = false;
1343 }*/
1344
1345 m_loop = true;
1346 }
1347 else
1348 {
1349 m_loop = false;
1350 }
1351 }
1352
1353 return 0;
1354}
1355
1360
1362{
1363 return recordObserver( true );
1364}
1365
1366inline int observerCtrl::recordObserver( bool force )
1367{
1368 static std::string last_email;
1369 static std::string last_obsName;
1370 static bool last_observing;
1371 static std::string last_target;
1372 static std::string last_operator;
1373
1376 {
1379
1385 }
1386
1387 return 0;
1388}
1389
1390} // namespace app
1391} // namespace MagAOX
1392
1393#endif // observerCtrl_hpp
The base-class for XWCTk applications.
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)
int registerIndiPropertySet(pcf::IndiProperty &prop, const std::string &devName, const std::string &propName, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is monitored for updates from others.
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)
void stopObserving()
Stop the current observation and any stream writers owned by observerCtrl.
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.
int setCallBack_streamWriterWriting(const pcf::IndiProperty &ipRecv)
Handle remote stream writer writing property updates.
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.
std::map< std::string, std::string > m_streamWriterDevices
Reverse lookup from remote stream writer device name to configured writer name.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_labMode)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_observing)
bool streamWriterSelected(const std::string &writerName) const
Return whether a stream writer is enabled for observation control.
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_catRA
The latest catalog right ascension reported by the TCS.
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.
std::string streamWriterDeviceName(const std::string &writerName) const
Return the INDI device name used for a configured stream writer.
~observerCtrl() noexcept
D'tor, declared and defined for noexcept.
double m_obsStartParang
The parallactic angle at the start of the observation.
std::string m_catDec
The latest catalog declination reported by the TCS.
pcf::IndiProperty m_indiP_catalog
Catalog text data.
std::map< std::string, bool > m_streamWriterStartedByObserver
Tracks whether observerCtrl started each stream writer for the current observation.
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)
virtual void loadConfig()
Load the observerCtrl configuration.
dev::telemeter< observerCtrl > telemeterT
std::vector< std::string > m_defStreamWriters
The configured stream writers always managed for observations.
pcf::IndiProperty m_indiP_operator
Text which contains the specifications of the current observer.
std::vector< std::string > m_streamWriters
The configured stream writers available for user selection.
bool endObservationStreamWriter(const std::string &writerName)
Determine whether observerCtrl should stop a stream writer when an observation ends.
pcf::IndiProperty m_indiP_obsDuration
Number to set the desired duration of observation.
std::map< std::string, bool > m_streamWriterSelectable
Tracks whether each configured stream writer is user-selectable in INDI.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_teldata)
std::map< std::string, pcf::IndiProperty > m_indiP_streamWriterWriting
Tracks the remote writing properties for configured stream writers.
int registerStreamWriter(const std::string &writerName, bool userSelectable)
Register one configured stream writer for remote writing-state tracking.
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.
std::string m_target
The current target name shown to the observer.
bool beginObservationStreamWriter(const std::string &writerName)
Determine whether observerCtrl should start a stream writer for a new observation.
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.
int commandStreamWriter(const std::string &writerName, pcf::IndiElement::SwitchStateType state)
Send a writing toggle command to a configured stream writer.
std::string timeStampAsISO8601(const std::chrono::time_point< std::chrono::system_clock > &tp)
Format a system-clock time point as an ISO 8601 UTC timestamp.
pcf::IndiProperty m_indiP_observing
Toggle switch to trigger observation.
void startObserving()
Start the current observation and any stream writers owned by observerCtrl.
pcf::IndiProperty m_indiP_catdata
Catalog numeric data.
std::string m_catObj
The latest catalog object name reported by the TCS.
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.
std::map< std::string, bool > m_streamWriterWriting
Tracks the last known remote writing state for each configured stream writer.
static int st_setCallBack_streamWriterWriting(void *app, const pcf::IndiProperty &ipRecv)
Static wrapper for remote stream writer writing property updates.
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 user-managed stream writers are enabled.
pcf::IndiProperty m_indiP_teldata
Telescope data (for parang)
virtual void setupConfig()
Set up the observerCtrl configuration parameters.
std::map< std::string, bool > m_streamWriterWritingKnown
Tracks whether a remote writing state has been received for each configured stream writer.
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
#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:19
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.
@ READY
The device is ready for operation, but is not operating.
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.