Line data Source code
1 : /** \file observerCtrl.hpp
2 : * \brief The MagAO-X Observer Controller header file
3 : *
4 : * \ingroup observerCtrl_files
5 : */
6 :
7 : #ifndef observerCtrl_hpp
8 : #define observerCtrl_hpp
9 :
10 : #include <map>
11 : #include <mx/math/geo.hpp>
12 :
13 : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
14 : #include "../../magaox_git_version.h"
15 :
16 : /** \defgroup observerCtrl
17 : * \brief The MagAO-X Observer Controller application
18 : *
19 : * <a href="../handbook/operating/software/apps/observerCtrl.html">Application Documentation</a>
20 : *
21 : * \ingroup apps
22 : *
23 : */
24 :
25 : /** \defgroup observerCtrl_files
26 : * \ingroup observerCtrl
27 : */
28 :
29 : namespace MagAOX
30 : {
31 : namespace app
32 : {
33 :
34 : /// The MagAO-X Observer Controller
35 : /**
36 : * \ingroup observerCtrl
37 : */
38 : class observerCtrl : public MagAOXApp<true>, public dev::telemeter<observerCtrl>
39 : {
40 :
41 : // Give the test harness access.
42 : friend class observerCtrl_test;
43 :
44 : friend class dev::telemeter<observerCtrl>;
45 :
46 : typedef dev::telemeter<observerCtrl> telemeterT;
47 :
48 : typedef std::chrono::time_point<std::chrono::steady_clock> timePointT;
49 :
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
116 : timePointT m_obsStartTime;
117 :
118 : /// The UTC start time of the current observation
119 : timeStampT m_obsStartTimeStamp;
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
125 : timePointT m_tgtStartTime;
126 :
127 : /// The UTC start time of the current target
128 : timeStampT m_tgtStartTimeStamp;
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.
137 : durationT m_tgtTime;
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.
147 41 : ~observerCtrl() noexcept
148 41 : {
149 41 : }
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:
220 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_observers );
221 :
222 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_operators );
223 :
224 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_obsName );
225 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_observing );
226 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_obsDuration );
227 :
228 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_sws );
229 :
230 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_userlog );
231 :
232 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_resetTarget );
233 :
234 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_target );
235 :
236 0 : INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_tcsTarget );
237 :
238 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_catalog );
239 :
240 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_catdata );
241 :
242 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_teldata );
243 :
244 0 : INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_labMode );
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 :
261 861 : observerCtrl::observerCtrl() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
262 : {
263 41 : return;
264 0 : }
265 :
266 0 : void observerCtrl::setupConfig()
267 : {
268 0 : 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 :
278 0 : dev::telemeter<observerCtrl>::setupConfig( config );
279 0 : }
280 :
281 0 : int observerCtrl::loadConfigImpl( mx::app::appConfigurator &_config )
282 : {
283 0 : _config( m_streamWriters, "stream.writers" );
284 :
285 0 : std::vector<std::string> sections;
286 :
287 0 : _config.unusedSections( sections );
288 :
289 0 : if( sections.size() == 0 )
290 : {
291 0 : log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
292 0 : return -1;
293 : }
294 :
295 0 : for( size_t i = 0; i < sections.size(); ++i )
296 : {
297 0 : bool pfoaSet = _config.isSetUnused( mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
298 0 : if( !pfoaSet )
299 0 : continue;
300 :
301 0 : std::string email = sections[i];
302 :
303 0 : std::string pfoa;
304 0 : _config.configUnused( pfoa, mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
305 :
306 0 : std::string pronunciation;
307 0 : _config.configUnused( pronunciation, mx::app::iniFile::makeKey( sections[i], "pronunciation" ) );
308 :
309 0 : if( pronunciation == "" )
310 : {
311 0 : pronunciation = pfoa;
312 : }
313 :
314 0 : std::string fullName;
315 0 : _config.configUnused( fullName, mx::app::iniFile::makeKey( sections[i], "full_name" ) );
316 :
317 0 : std::string institution;
318 0 : _config.configUnused( institution, mx::app::iniFile::makeKey( sections[i], "institution" ) );
319 :
320 0 : std::string sanitizedEmail = "";
321 0 : for( size_t n = 0; n < email.size(); ++n )
322 : {
323 0 : if( email[n] == '@' )
324 : {
325 0 : sanitizedEmail = sanitizedEmail + "-at-";
326 : }
327 0 : else if( email[n] == '.' )
328 : {
329 0 : sanitizedEmail = sanitizedEmail + "-dot-";
330 : }
331 : else
332 : {
333 0 : sanitizedEmail.push_back( email[n] );
334 : }
335 : }
336 0 : m_observers[email] = observer( { fullName, pfoa, pronunciation, email, sanitizedEmail, institution } );
337 0 : }
338 :
339 0 : return 0;
340 0 : }
341 :
342 0 : void observerCtrl::loadConfig()
343 : {
344 0 : if( loadConfigImpl( config ) < 0 )
345 : {
346 0 : m_shutdown = 1;
347 0 : return;
348 : }
349 :
350 0 : if( m_observers.size() < 1 )
351 : {
352 0 : log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
353 0 : m_shutdown = 1;
354 0 : return;
355 : }
356 :
357 0 : dev::telemeter<observerCtrl>::loadConfig( config );
358 : }
359 :
360 0 : int observerCtrl::appStartup()
361 : {
362 0 : std::vector<std::string> sanitizedEmails;
363 0 : std::vector<std::string> emails;
364 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
365 : {
366 0 : sanitizedEmails.push_back( it->second.m_sanitizedEmail );
367 0 : emails.push_back( it->second.m_email );
368 : }
369 :
370 0 : if( createStandardIndiSelectionSw( m_indiP_observers, "observers", sanitizedEmails, emails ) < 0 )
371 : {
372 0 : log<software_critical>( { __FILE__, __LINE__ } );
373 0 : 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 0 : for( auto &it : m_observers )
379 : {
380 0 : if( it.first.find( "jrmales" ) != std::string::npos )
381 : {
382 0 : m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
383 0 : m_currentObserver = it.second;
384 : }
385 : else
386 : {
387 0 : m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
388 : }
389 : }
390 :
391 0 : REG_INDI_NEWPROP_NOSETUP( m_indiP_observers );
392 :
393 0 : if( createStandardIndiSelectionSw( m_indiP_operators, "operators", sanitizedEmails, emails ) < 0 )
394 : {
395 0 : log<software_critical>( { __FILE__, __LINE__ } );
396 0 : 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 0 : for( auto &it : m_observers )
402 : {
403 0 : if( it.first.find( "jrmales" ) != std::string::npos )
404 : {
405 0 : m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
406 0 : m_currentOperator = it.second;
407 : }
408 : else
409 : {
410 0 : m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
411 : }
412 : }
413 :
414 0 : REG_INDI_NEWPROP_NOSETUP( m_indiP_operators );
415 :
416 0 : CREATE_REG_INDI_NEW_TEXT( m_indiP_obsName, "obs_name", "Observation Name", "Observer" );
417 :
418 0 : CREATE_REG_INDI_NEW_TOGGLESWITCH( m_indiP_observing, "obs_on" );
419 :
420 0 : CREATE_REG_INDI_NEW_NUMBERD( m_indiP_obsDuration, "obs_duration", 0, 300, 0.1, "%0.1f", "Duration", "Observer" );
421 :
422 0 : CREATE_REG_INDI_RO_NUMBER( m_indiP_obsTime, "obs_time", "Observation Time", "Observer" );
423 0 : indi::addNumberElement<double>( m_indiP_obsTime, "observation", 0, 14400, 0.1, "%0.1f", "Current Obs" );
424 0 : indi::addNumberElement<double>( m_indiP_obsTime, "target", 0, 14400, 0.1, "%0.1f", "Target" );
425 :
426 0 : REG_INDI_NEWPROP_NOCB( m_indiP_obsStart, "obs_start", pcf::IndiProperty::Text );
427 0 : indi::addTextElement( m_indiP_obsStart, "observation" );
428 0 : indi::addTextElement( m_indiP_obsStart, "target" );
429 :
430 0 : CREATE_REG_INDI_RO_NUMBER( m_indiP_obsAngle, "obs_delta_parang", "Change in Par. Ang.", "Observer" );
431 0 : indi::addNumberElement<double>( m_indiP_obsAngle, "observation", 0, 360, 0.1, "%0.1f", "Current Obs" );
432 0 : indi::addNumberElement<double>( m_indiP_obsAngle, "target", 0, 360, 0.1, "%0.1f", "Target" );
433 :
434 0 : REG_INDI_NEWPROP_NOCB( m_indiP_observer, "current_observer", pcf::IndiProperty::Text );
435 0 : indi::addTextElement( m_indiP_observer, "full_name" );
436 0 : indi::addTextElement( m_indiP_observer, "email" );
437 0 : indi::addTextElement( m_indiP_observer, "pfoa" );
438 0 : indi::addTextElement( m_indiP_observer, "pronunciation" );
439 0 : indi::addTextElement( m_indiP_observer, "institution" );
440 :
441 0 : REG_INDI_NEWPROP_NOCB( m_indiP_operator, "current_operator", pcf::IndiProperty::Text );
442 0 : indi::addTextElement( m_indiP_operator, "full_name" );
443 0 : indi::addTextElement( m_indiP_operator, "email" );
444 0 : indi::addTextElement( m_indiP_operator, "pfoa" );
445 0 : indi::addTextElement( m_indiP_operator, "pronunciation" );
446 0 : indi::addTextElement( m_indiP_operator, "institution" );
447 :
448 0 : m_indiP_sws = pcf::IndiProperty( pcf::IndiProperty::Switch );
449 0 : m_indiP_sws.setDevice( configName() );
450 0 : m_indiP_sws.setName( "writers" );
451 0 : m_indiP_sws.setPerm( pcf::IndiProperty::ReadWrite );
452 0 : m_indiP_sws.setState( pcf::IndiProperty::Idle );
453 0 : m_indiP_sws.setRule( pcf::IndiProperty::AnyOfMany );
454 :
455 0 : for( size_t n = 0; n < m_streamWriters.size(); ++n )
456 : {
457 0 : m_indiP_sws.add( pcf::IndiElement( m_streamWriters[n], pcf::IndiElement::Off ) );
458 : }
459 :
460 0 : REG_INDI_NEWPROP_NOSETUP( m_indiP_sws );
461 :
462 0 : m_indiP_userlog = pcf::IndiProperty( pcf::IndiProperty::Text );
463 0 : m_indiP_userlog.setDevice( configName() );
464 0 : m_indiP_userlog.setName( "user_log" );
465 0 : m_indiP_userlog.setPerm( pcf::IndiProperty::ReadWrite );
466 0 : m_indiP_userlog.setState( pcf::IndiProperty::Idle );
467 0 : m_indiP_userlog.add( pcf::IndiElement( "email" ) );
468 0 : m_indiP_userlog.add( pcf::IndiElement( "message" ) );
469 0 : m_indiP_userlog.add( pcf::IndiElement( "time_s" ) );
470 0 : m_indiP_userlog.add( pcf::IndiElement( "time_ns" ) );
471 :
472 0 : REG_INDI_NEWPROP_NOSETUP( m_indiP_userlog );
473 :
474 0 : CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_resetTarget, "target_reset" );
475 :
476 0 : CREATE_REG_INDI_NEW_TEXT( m_indiP_target, "target", "Target", "Observer" );
477 :
478 0 : CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_tcsTarget, "target_load_from_tcs" );
479 :
480 0 : REG_INDI_SETPROP( m_indiP_catalog, m_tcsDev, m_catalogProp );
481 0 : REG_INDI_SETPROP( m_indiP_catdata, m_tcsDev, m_catdataProp );
482 0 : REG_INDI_SETPROP( m_indiP_teldata, m_tcsDev, m_teldataProp );
483 0 : REG_INDI_SETPROP( m_indiP_labMode, m_tcsDev, m_labModeProp );
484 :
485 : //The new pointing flag
486 0 : if( registerIndiPropertyNew( m_indiP_newPointing,
487 : "new_pointing",
488 0 : pcf::IndiProperty::Switch,
489 0 : pcf::IndiProperty::ReadOnly,
490 0 : pcf::IndiProperty::Idle,
491 0 : pcf::IndiProperty::AtMostOne,
492 0 : nullptr ) < 0 )
493 : {
494 0 : return log<software_critical, -1>( { __FILE__, __LINE__ } );
495 : }
496 0 : m_indiP_newPointing.add(pcf::IndiElement("set"));
497 0 : if(m_newPointing)
498 : {
499 0 : m_indiP_newPointing["set"].setSwitchState(pcf::IndiElement::On);
500 : }
501 : else
502 : {
503 0 : m_indiP_newPointing["set"].setSwitchState(pcf::IndiElement::Off);
504 : }
505 :
506 0 : TELEMETER_APP_STARTUP;
507 :
508 0 : state( stateCodes::READY );
509 0 : return 0;
510 0 : }
511 :
512 0 : int observerCtrl::appLogic()
513 : {
514 :
515 0 : std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
516 :
517 0 : if( lock.owns_lock() )
518 : {
519 0 : updatesIfChanged<std::string>( m_indiP_observer,
520 : { "full_name", "email", "pfoa", "pronunciation", "institution" },
521 0 : { m_currentObserver.m_fullName,
522 0 : m_currentObserver.m_email,
523 0 : m_currentObserver.m_pfoa,
524 0 : m_currentObserver.m_pronunciation,
525 0 : m_currentObserver.m_institution } );
526 :
527 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
528 : {
529 0 : if( it->first == m_currentObserver.m_email )
530 : {
531 0 : updateSwitchIfChanged(
532 0 : m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
533 : }
534 : else
535 : {
536 0 : updateSwitchIfChanged(
537 0 : m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
538 : }
539 : }
540 :
541 0 : updatesIfChanged<std::string>( m_indiP_operator,
542 : { "full_name", "email", "pfoa", "pronunciation", "institution" },
543 0 : { m_currentOperator.m_fullName,
544 0 : m_currentOperator.m_email,
545 0 : m_currentOperator.m_pfoa,
546 0 : m_currentOperator.m_pronunciation,
547 0 : m_currentOperator.m_institution } );
548 :
549 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
550 : {
551 0 : if( it->first == m_currentOperator.m_email )
552 : {
553 0 : updateSwitchIfChanged(
554 0 : m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
555 : }
556 : else
557 : {
558 0 : updateSwitchIfChanged(
559 0 : m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
560 : }
561 : }
562 :
563 0 : updatesIfChanged<std::string>( m_indiP_obsName, { "current", "target" }, { m_obsName, m_obsName } );
564 :
565 0 : updatesIfChanged<double>( m_indiP_obsDuration, { "current", "target" }, { m_obsDuration, m_obsDuration } );
566 :
567 0 : if( m_observing )
568 : {
569 0 : timePointT ct = std::chrono::steady_clock::now();
570 0 : const std::chrono::duration<double> obstime = ct - m_obsStartTime;
571 0 : m_tgtTime = ct - m_tgtStartTime;
572 :
573 0 : double obsang = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_obsStartParang );
574 0 : m_tgtAng = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_tgtStartParang );
575 :
576 0 : updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
577 0 : updatesIfChanged<double>(
578 0 : m_indiP_obsTime, { "observation", "target" }, { obstime.count(), m_tgtTime.count() } );
579 :
580 0 : updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { obsang, m_tgtAng } );
581 0 : updatesIfChanged<std::string>(
582 0 : m_indiP_obsStart, { "observation", "target" }, { m_obsStartTimeStamp, m_tgtStartTimeStamp } );
583 :
584 0 : if( m_obsDuration > 0.0 && obstime.count() > m_obsDuration )
585 : {
586 0 : stopObserving();
587 : }
588 : }
589 : else
590 : {
591 0 : updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
592 0 : updatesIfChanged<std::string>( m_indiP_obsStart, { "observation", "target" }, { "", m_tgtStartTimeStamp } );
593 0 : updatesIfChanged<double>( m_indiP_obsTime, { "observation", "target" }, { 0.0, m_tgtTime.count() } );
594 0 : updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { 0.0, m_tgtAng } );
595 : }
596 : }
597 :
598 0 : TELEMETER_APP_LOGIC;
599 :
600 0 : return 0;
601 0 : }
602 :
603 0 : int observerCtrl::appShutdown()
604 : {
605 0 : TELEMETER_APP_SHUTDOWN;
606 :
607 0 : return 0;
608 : }
609 :
610 0 : void observerCtrl::startObserving()
611 : {
612 0 : for( size_t n = 0; n < m_streamWriters.size(); ++n )
613 : {
614 0 : if( m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On )
615 : {
616 0 : pcf::IndiProperty ip( pcf::IndiProperty::Switch );
617 :
618 0 : ip.setDevice( m_streamWriters[n] + "-sw" );
619 0 : ip.setName( "writing" );
620 0 : ip.add( pcf::IndiElement( "toggle" ) );
621 0 : ip["toggle"].setSwitchState( pcf::IndiElement::On );
622 :
623 0 : sendNewProperty( ip );
624 0 : }
625 : }
626 :
627 0 : mx::sys::sleep( 1 );
628 :
629 0 : m_obsStartTime = std::chrono::steady_clock::now();
630 0 : m_obsStartTimeStamp = timeStampAsISO8601( std::chrono::system_clock::now() );
631 0 : m_obsStartParang = m_parang;
632 :
633 0 : if( m_newTargetBlock || m_labMode ) // We always reset if in lab mode
634 : {
635 0 : m_tgtStartTime = m_obsStartTime;
636 0 : m_tgtStartTimeStamp = m_obsStartTimeStamp;
637 0 : m_tgtStartParang = m_obsStartParang;
638 :
639 0 : m_newTargetBlock = false;
640 : }
641 :
642 0 : m_observing = true;
643 0 : recordObserver( true );
644 0 : }
645 :
646 0 : void observerCtrl::stopObserving()
647 : {
648 0 : m_observing = false;
649 0 : recordObserver( true );
650 :
651 0 : for( size_t n = 0; n < m_streamWriters.size(); ++n )
652 : {
653 0 : if( m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On )
654 : {
655 0 : pcf::IndiProperty ip( pcf::IndiProperty::Switch );
656 :
657 0 : ip.setDevice( m_streamWriters[n] + "-sw" );
658 0 : ip.setName( "writing" );
659 0 : ip.add( pcf::IndiElement( "toggle" ) );
660 0 : ip["toggle"].setSwitchState( pcf::IndiElement::Off );
661 :
662 0 : sendNewProperty( ip );
663 0 : }
664 : }
665 0 : }
666 :
667 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observers )( const pcf::IndiProperty &ipRecv )
668 : {
669 :
670 3 : 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 0 : std::string newEmail = "";
674 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
675 : {
676 0 : if( !ipRecv.find( it->second.m_sanitizedEmail ) )
677 0 : continue;
678 :
679 0 : if( ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
680 : {
681 0 : if( newEmail != "" )
682 : {
683 0 : log<text_log>( "More than one observer selected", logPrio::LOG_ERROR );
684 0 : return -1;
685 : }
686 :
687 0 : newEmail = it->first;
688 : }
689 : }
690 :
691 0 : if( newEmail == "" )
692 : {
693 0 : std::cerr << "nothing\n";
694 0 : return 0;
695 : }
696 :
697 : {
698 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
699 :
700 0 : m_currentObserver = m_observers[newEmail];
701 :
702 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
703 : {
704 0 : if( it->first == m_currentObserver.m_sanitizedEmail )
705 : {
706 0 : updateSwitchIfChanged(
707 0 : m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
708 : }
709 : else
710 : {
711 0 : updateSwitchIfChanged(
712 0 : m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
713 : }
714 : }
715 0 : }
716 :
717 0 : log<logger::observer>( { m_currentObserver.m_fullName,
718 0 : m_currentObserver.m_pfoa,
719 0 : m_currentObserver.m_email,
720 0 : m_currentObserver.m_institution } );
721 :
722 0 : return 0;
723 0 : }
724 :
725 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_operators )( const pcf::IndiProperty &ipRecv )
726 : {
727 :
728 3 : 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 0 : std::string newEmail = "";
732 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
733 : {
734 0 : if( !ipRecv.find( it->second.m_sanitizedEmail ) )
735 0 : continue;
736 :
737 0 : if( ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
738 : {
739 0 : if( newEmail != "" )
740 : {
741 0 : log<text_log>( "More than one operator selected", logPrio::LOG_ERROR );
742 0 : return -1;
743 : }
744 :
745 0 : newEmail = it->first;
746 : }
747 : }
748 :
749 0 : if( newEmail == "" )
750 : {
751 0 : std::cerr << "nothing\n";
752 0 : return 0;
753 : }
754 :
755 : {
756 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
757 :
758 0 : m_currentOperator = m_observers[newEmail];
759 :
760 0 : for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
761 : {
762 0 : if( it->first == m_currentOperator.m_sanitizedEmail )
763 : {
764 0 : updateSwitchIfChanged(
765 0 : m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
766 : }
767 : else
768 : {
769 0 : updateSwitchIfChanged(
770 0 : m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
771 : }
772 : }
773 0 : }
774 :
775 0 : log<logger::ao_operator>( { m_currentOperator.m_fullName,
776 0 : m_currentOperator.m_pfoa,
777 0 : m_currentOperator.m_email,
778 0 : m_currentOperator.m_institution } );
779 :
780 0 : return 0;
781 0 : }
782 :
783 0 : std::string observerCtrl::timeStampAsISO8601( const std::chrono::time_point<std::chrono::system_clock> &tp )
784 : {
785 : // Convert to time_t for whole seconds
786 0 : std::time_t t = std::chrono::system_clock::to_time_t( tp );
787 0 : std::tm tm = *std::gmtime( &t );
788 :
789 : // Get nanoseconds
790 0 : auto duration = tp.time_since_epoch();
791 0 : auto seconds = std::chrono::duration_cast<std::chrono::seconds>( duration );
792 0 : auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>( duration - seconds ).count();
793 :
794 : // Format ISO 8601 with nanoseconds
795 0 : std::stringstream ss;
796 0 : ss << std::put_time( &tm, "%Y-%m-%dT%H:%M:%S" );
797 0 : ss << '.' << std::setw( 9 ) << std::setfill( '0' ) << nanos << "Z"; // Z for UTC
798 :
799 0 : return ss.str();
800 0 : }
801 :
802 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsName )( const pcf::IndiProperty &ipRecv )
803 : {
804 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsName, ipRecv );
805 :
806 0 : std::string target;
807 :
808 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
809 :
810 0 : if( indiTargetUpdate( m_indiP_obsName, target, ipRecv, true ) < 0 )
811 : {
812 0 : log<software_error>( { __FILE__, __LINE__ } );
813 0 : return -1;
814 : }
815 :
816 0 : m_obsName = target;
817 :
818 0 : return 0;
819 0 : }
820 :
821 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observing )( const pcf::IndiProperty &ipRecv )
822 : {
823 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_observing, ipRecv );
824 :
825 0 : if( !ipRecv.find( "toggle" ) )
826 : {
827 0 : return 0;
828 : }
829 :
830 0 : recordObserver( true );
831 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
832 : {
833 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
834 0 : startObserving();
835 0 : updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
836 0 : }
837 0 : else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
838 : {
839 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
840 0 : stopObserving();
841 0 : updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
842 0 : }
843 :
844 0 : return 0;
845 : }
846 :
847 0 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsDuration )( const pcf::IndiProperty &ipRecv )
848 : {
849 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsDuration, ipRecv );
850 :
851 0 : return indiTargetUpdate( m_indiP_obsDuration, m_obsDuration, ipRecv, false );
852 : }
853 :
854 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_sws )( const pcf::IndiProperty &ipRecv )
855 : {
856 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_sws, ipRecv );
857 :
858 0 : if( m_observing == true )
859 : {
860 0 : log<text_log>( { "Can't change stream writers while observing" }, logPrio::LOG_WARNING );
861 0 : return 0;
862 : }
863 :
864 0 : for( size_t n = 0; n < m_streamWriters.size(); ++n )
865 : {
866 0 : if( !ipRecv.find( m_streamWriters[n] ) )
867 : {
868 0 : continue;
869 : }
870 :
871 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
872 0 : updateSwitchIfChanged( m_indiP_sws, m_streamWriters[n], ipRecv[m_streamWriters[n]].getSwitchState() );
873 0 : }
874 :
875 0 : return 0;
876 : }
877 :
878 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_userlog )( const pcf::IndiProperty &ipRecv )
879 : {
880 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_userlog, ipRecv );
881 :
882 0 : std::string email;
883 0 : std::string message;
884 :
885 0 : timespecX ts{};
886 :
887 0 : if( ipRecv.find( "email" ) )
888 : {
889 0 : email = ipRecv["email"].get();
890 : }
891 :
892 0 : if( ipRecv.find( "message" ) )
893 : {
894 0 : message = ipRecv["message"].get();
895 : }
896 :
897 0 : if( message == "" )
898 : {
899 0 : return 0;
900 : }
901 :
902 0 : if( email == "" )
903 : {
904 0 : email = m_currentObserver.m_email;
905 : }
906 :
907 0 : if( ipRecv.find( "time_s" ) )
908 : {
909 0 : ts.time_s = ipRecv["time_s"].get<flatlogs::secT>();
910 : }
911 :
912 0 : if( ipRecv.find( "time_ns" ) )
913 : {
914 0 : ts.time_ns = ipRecv["time_ns"].get<flatlogs::nanosecT>();
915 : }
916 :
917 0 : if( ts.time_s != 0 )
918 : {
919 0 : m_log.template log<user_log>( ts, { email, message }, logPrio::LOG_INFO );
920 : }
921 : else
922 : {
923 0 : log<user_log>( { email, message } );
924 : }
925 :
926 0 : return 0;
927 0 : }
928 :
929 3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_resetTarget )( const pcf::IndiProperty &ipRecv )
930 : {
931 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_resetTarget, ipRecv );
932 :
933 0 : if( !ipRecv.find( "request" ) )
934 : {
935 0 : return 0;
936 : }
937 :
938 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
939 : {
940 0 : if( m_observing )
941 : {
942 0 : m_tgtStartTime = m_obsStartTime;
943 0 : m_tgtStartParang = m_obsStartParang;
944 : }
945 : else
946 : {
947 0 : m_newTargetBlock = true;
948 : }
949 : }
950 :
951 0 : return 0;
952 : }
953 :
954 6 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_target )( const pcf::IndiProperty &ipRecv )
955 : {
956 6 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_target, ipRecv );
957 :
958 3 : std::string target;
959 3 : if( indiTargetUpdate( m_indiP_target, target, ipRecv ) < 0 )
960 : {
961 1 : return log<software_error, -1>( { __FILE__, __LINE__ } );
962 : }
963 :
964 2 : if( target != m_target )
965 : {
966 2 : if( m_observing )
967 : {
968 1 : return log<text_log,-2>( "Can not change target while observing", logPrio::LOG_ERROR );
969 : }
970 :
971 1 : m_newTargetBlock = true;
972 :
973 1 : m_target = target;
974 :
975 1 : log<text_log>( "Target updated by observer to " + m_target, logPrio::LOG_NOTICE );
976 :
977 1 : std::unique_lock<std::mutex> lock( m_indiMutex );
978 6 : updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
979 1 : }
980 :
981 : // Set this regardless so this can be a reset
982 1 : m_newPointing = false;
983 2 : updateSwitchIfChanged(m_indiP_newPointing, "set", pcf::IndiElement::Off);
984 :
985 1 : return 0;
986 4 : }
987 :
988 8 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_tcsTarget )( const pcf::IndiProperty &ipRecv )
989 : {
990 8 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tcsTarget, ipRecv );
991 :
992 10 : if( !ipRecv.find( "request" ) )
993 : {
994 1 : return -1;
995 : }
996 :
997 8 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
998 : {
999 2 : if( m_target != m_catObj )
1000 : {
1001 2 : if( m_observing )
1002 : {
1003 1 : return log<text_log,-2>( "Can not change target while observing", logPrio::LOG_ERROR );
1004 : }
1005 :
1006 1 : m_newTargetBlock = true;
1007 :
1008 1 : m_target = m_catObj;
1009 :
1010 1 : log<text_log>( "Target updated by observer to TCS target: " + m_target, logPrio::LOG_NOTICE );
1011 6 : 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 1 : m_newPointing = false;
1016 3 : updateSwitchIfChanged(m_indiP_newPointing, "set", pcf::IndiElement::Off);
1017 : }
1018 :
1019 3 : return 0;
1020 1 : }
1021 :
1022 6 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catalog )( const pcf::IndiProperty &ipRecv )
1023 : {
1024 6 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_catalog, ipRecv );
1025 :
1026 6 : if( !ipRecv.find( "object" ) )
1027 : {
1028 1 : return -1;
1029 : }
1030 :
1031 2 : std::string object = ipRecv["object"].get();
1032 :
1033 2 : if( object != m_catObj )
1034 : {
1035 2 : m_catObj = object;
1036 :
1037 2 : m_newPointing = true;
1038 4 : updateSwitchIfChanged(m_indiP_newPointing, "set", pcf::IndiElement::On);
1039 :
1040 : // Always log change in name (different from RA and DEC)
1041 2 : log<text_log>( "TCS target updated to " + m_catObj, logPrio::LOG_NOTICE );
1042 : }
1043 :
1044 2 : return 0;
1045 2 : }
1046 :
1047 0 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catdata )( const pcf::IndiProperty &ipRecv )
1048 : {
1049 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_catdata, ipRecv );
1050 :
1051 0 : bool change = false;
1052 0 : if( ipRecv.find( "ra" ) )
1053 : {
1054 0 : std::string ra = ipRecv["ra"].get();
1055 :
1056 0 : if( ra != m_catRA )
1057 : {
1058 0 : m_catRA = ra;
1059 0 : change = true;
1060 : }
1061 0 : }
1062 :
1063 0 : if( ipRecv.find( "dec" ) )
1064 : {
1065 0 : std::string dec = ipRecv["dec"].get();
1066 :
1067 0 : if( dec != m_catDec )
1068 : {
1069 0 : m_catDec = dec;
1070 0 : change = true;
1071 : }
1072 0 : }
1073 :
1074 0 : if( change ) // Only log if not already new
1075 : {
1076 0 : m_newPointing = true;
1077 0 : updateSwitchIfChanged(m_indiP_newPointing, "set", pcf::IndiElement::On);
1078 0 : log<text_log>( "Pointing change. Probable target change.", logPrio::LOG_NOTICE );
1079 : }
1080 :
1081 0 : return 0;
1082 : }
1083 :
1084 3 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
1085 : {
1086 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_teldata, ipRecv );
1087 :
1088 0 : if( ipRecv.find( "pa" ) )
1089 : {
1090 0 : m_parang = ipRecv["pa"].get<double>();
1091 : }
1092 :
1093 0 : return 0;
1094 : }
1095 :
1096 3 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_labMode )( const pcf::IndiProperty &ipRecv )
1097 : {
1098 3 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_labMode, ipRecv );
1099 :
1100 0 : if( ipRecv.find( "toggle" ) )
1101 : {
1102 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1103 : {
1104 0 : m_labMode = true;
1105 : }
1106 : else
1107 : {
1108 0 : m_labMode = false;
1109 : }
1110 : }
1111 :
1112 0 : std::cerr << "got labmode: " << m_labMode << '\n';
1113 0 : return 0;
1114 : }
1115 :
1116 0 : inline int observerCtrl::checkRecordTimes()
1117 : {
1118 0 : return telemeter<observerCtrl>::checkRecordTimes( telem_observer() );
1119 : }
1120 :
1121 0 : inline int observerCtrl::recordTelem( const telem_observer * )
1122 : {
1123 0 : return recordObserver( true );
1124 : }
1125 :
1126 0 : inline int observerCtrl::recordObserver( bool force )
1127 : {
1128 0 : static std::string last_email;
1129 0 : static std::string last_obsName;
1130 : static bool last_observing;
1131 0 : static std::string last_target;
1132 0 : static std::string last_operator;
1133 :
1134 0 : if( last_email != m_currentObserver.m_email || last_obsName != m_obsName || last_observing != m_observing ||
1135 0 : last_target != m_target || last_operator != m_currentOperator.m_email || force )
1136 : {
1137 0 : telem<telem_observer>(
1138 0 : { m_currentObserver.m_email, m_obsName, m_observing, m_target, m_currentOperator.m_email } );
1139 :
1140 0 : last_email = m_currentObserver.m_email;
1141 0 : last_obsName = m_obsName;
1142 0 : last_observing = m_observing;
1143 0 : last_target = m_target;
1144 0 : last_operator = m_currentOperator.m_email;
1145 : }
1146 :
1147 0 : return 0;
1148 : }
1149 :
1150 : } // namespace app
1151 : } // namespace MagAOX
1152 :
1153 : #endif // observerCtrl_hpp
|