API
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 
12 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
13 #include "../../magaox_git_version.h"
14 
15 /** \defgroup observerCtrl
16  * \brief The MagAO-X Observer Controller application
17  *
18  * <a href="../handbook/operating/software/apps/observerCtrl.html">Application Documentation</a>
19  *
20  * \ingroup apps
21  *
22  */
23 
24 /** \defgroup observerCtrl_files
25  * \ingroup observerCtrl
26  */
27 
28 namespace MagAOX
29 {
30 namespace app
31 {
32 
33 /// The MagAO-X Observer Controller
34 /**
35  * \ingroup observerCtrl
36  */
37 class observerCtrl : public MagAOXApp<true>, public dev::telemeter<observerCtrl>
38 {
39 
40  //Give the test harness access.
41  friend class observerCtrl_test;
42 
43  friend class dev::telemeter<observerCtrl>;
44 
45 protected:
46 
47  /** \name Configurable Parameters
48  *@{
49  */
50 
51  std::vector<std::string> m_streamWriters; ///< The stream writers to stop and start
52 
53  ///@}
54 
55  struct observer
56  {
57  std::string m_fullName;
58  std::string m_pfoa;
59  std::string m_email;
60  std::string m_sanitizedEmail;
61  std::string m_institution;
62  };
63 
64  typedef std::map<std::string, observer> observerMapT;
65 
67 
69 
70  std::string m_obsName;
71  bool m_observing {false};
72 
73 
74 
75 public:
76  /// Default c'tor.
77  observerCtrl();
78 
79  /// D'tor, declared and defined for noexcept.
80  ~observerCtrl() noexcept
81  {}
82 
83  virtual void setupConfig();
84 
85  /// Implementation of loadConfig logic, separated for testing.
86  /** This is called by loadConfig().
87  */
88  int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
89 
90  virtual void loadConfig();
91 
92  /// Startup function
93  /**
94  *
95  */
96  virtual int appStartup();
97 
98  /// Implementation of the FSM for observerCtrl.
99  /**
100  * \returns 0 on no critical error
101  * \returns -1 on an error requiring shutdown
102  */
103  virtual int appLogic();
104 
105  /// Shutdown the app.
106  /**
107  *
108  */
109  virtual int appShutdown();
110 
111  void startObserving();
112 
113  void stopObserving();
114 
115  ///\name INDI
116  /** @{
117  */
118 protected:
119  pcf::IndiProperty m_indiP_observers;
120  pcf::IndiProperty m_indiP_observer;
121 
122  pcf::IndiProperty m_indiP_obsName;
123  pcf::IndiProperty m_indiP_observing;
124  pcf::IndiProperty m_indiP_obslog;
125 
126  pcf::IndiProperty m_indiP_sws;
127 
128  pcf::IndiProperty m_indiP_userlog;
129 
130 public:
132 
135 
137 
139 
140  ///@}
141 
142  /** \name Telemeter Interface
143  *
144  * @{
145  */
146  int checkRecordTimes();
147 
148  int recordTelem( const telem_observer * );
149 
150  int recordObserver(bool force = false);
151 
152  int recordObserverNow();
153 
154  ///@}
155 };
156 
157 observerCtrl::observerCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
158 {
159 
160  return;
161 }
162 
164 {
165  config.add("stream.writers", "", "stream.writers", argType::Required, "stream", "writers", false, "string", "The device names of the stream writers to control.");
166 
168 }
169 
170 int observerCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
171 {
172  _config(m_streamWriters, "stream.writers");
173 
174  std::vector<std::string> sections;
175 
176  _config.unusedSections(sections);
177 
178  if( sections.size() == 0 )
179  {
180  log<text_log>("no observers found in config", logPrio::LOG_CRITICAL);
181  return -1;
182  }
183 
184  for(size_t i=0; i< sections.size(); ++i)
185  {
186  bool pfoaSet = _config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "pfoa" ));
187  if( !pfoaSet ) continue;
188 
189  std::string email = sections[i];
190 
191  std::string pfoa;
192  _config.configUnused(pfoa, mx::app::iniFile::makeKey(sections[i], "pfoa" ));
193 
194  std::string fullName;
195  _config.configUnused(fullName, mx::app::iniFile::makeKey(sections[i], "full_name" ));
196 
197  std::string institution;
198  _config.configUnused(institution, mx::app::iniFile::makeKey(sections[i], "institution" ));
199 
200  std::string sanitizedEmail = "";
201  for(size_t n = 0; n < email.size(); ++n)
202  {
203  if(email[n] == '@')
204  {
205  sanitizedEmail = sanitizedEmail + "-at-";
206  }
207  else if(email[n] == '.')
208  {
209  sanitizedEmail = sanitizedEmail + "-dot-";
210  }
211  else
212  {
213  sanitizedEmail.push_back(email[n]);
214  }
215  }
216  m_observers[email] = observer({fullName, pfoa, email, sanitizedEmail, institution});
217  }
218 
219  return 0;
220 }
221 
223 {
224  if(loadConfigImpl(config) < 0)
225  {
226  m_shutdown = 1;
227  return;
228  }
229 
230  if(m_observers.size() < 1)
231  {
232  log<text_log>("no observers found in config", logPrio::LOG_CRITICAL);
233  m_shutdown = 1;
234  return;
235  }
236 
238 }
239 
241 {
242  std::vector<std::string> sanitizedEmails;
243  std::vector<std::string> emails;
244  for(auto it = m_observers.begin(); it!=m_observers.end(); ++it)
245  {
246  sanitizedEmails.push_back(it->second.m_sanitizedEmail);
247  emails.push_back(it->second.m_email);
248  }
249 
250  if(createStandardIndiSelectionSw( m_indiP_observers, "observers", sanitizedEmails, emails) < 0)
251  {
252  log<software_critical>({__FILE__, __LINE__});
253  return -1;
254  }
255 
256  //Set to default user of jared
257  ///\todo do something else. maybe a defautl user is specified in the config?
258  for(auto & it : m_observers)
259  {
260  if(it.first.find("jrmales") != std::string::npos)
261  {
262  m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState(pcf::IndiElement::On);
263  m_currentObserver = it.second;
264  }
265  else
266  {
267  m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState(pcf::IndiElement::Off);
268  }
269  }
270 
272  {
273  log<software_critical>({__FILE__, __LINE__});
274  return -1;
275  }
276 
277  createStandardIndiText( m_indiP_obsName, "obs_name", "Observation Name", "Observer");
279  {
280  log<software_critical>({__FILE__, __LINE__});
281  return -1;
282  }
283 
284  createStandardIndiToggleSw( m_indiP_observing, "obs_on", "Observation On", "Observer");
286  {
287  log<software_critical>({__FILE__, __LINE__});
288  return -1;
289  }
290 
291  REG_INDI_NEWPROP_NOCB(m_indiP_observer, "current_observer", pcf::IndiProperty::Text);
292  m_indiP_observer.add(pcf::IndiElement("full_name"));
293  m_indiP_observer.add(pcf::IndiElement("email"));
294  m_indiP_observer.add(pcf::IndiElement("pfoa"));
295  m_indiP_observer.add(pcf::IndiElement("institution"));
296 
297  m_indiP_sws = pcf::IndiProperty(pcf::IndiProperty::Switch);
298  m_indiP_sws.setDevice(configName());
299  m_indiP_sws.setName("writers");
300  m_indiP_sws.setPerm(pcf::IndiProperty::ReadWrite);
301  m_indiP_sws.setState(pcf::IndiProperty::Idle);
302  m_indiP_sws.setRule(pcf::IndiProperty::AnyOfMany);
303 
304  for(size_t n =0; n < m_streamWriters.size(); ++n)
305  {
306  m_indiP_sws.add(pcf::IndiElement(m_streamWriters[n], pcf::IndiElement::Off));
307  }
308 
310  {
311  log<software_critical>({__FILE__, __LINE__});
312  return -1;
313  }
314 
315  m_indiP_userlog = pcf::IndiProperty(pcf::IndiProperty::Text);
316  m_indiP_userlog.setDevice(configName());
317  m_indiP_userlog.setName("user_log");
318  m_indiP_userlog.setPerm(pcf::IndiProperty::ReadWrite);
319  m_indiP_userlog.setState(pcf::IndiProperty::Idle);
320  m_indiP_userlog.add(pcf::IndiElement("email"));
321  m_indiP_userlog.add(pcf::IndiElement("message"));
322  m_indiP_userlog.add(pcf::IndiElement("time_s"));
323  m_indiP_userlog.add(pcf::IndiElement("time_ns"));
325  {
326  log<software_critical>({__FILE__, __LINE__});
327  return -1;
328  }
329 
331  {
332  return log<software_error,-1>({__FILE__,__LINE__});
333  }
334 
336  return 0;
337 }
338 
340 {
341 
342  std::unique_lock<std::mutex> lock(m_indiMutex, std::try_to_lock);
343 
344  if(lock.owns_lock())
345  {
346  updateIfChanged<std::string>(m_indiP_observer, {"full_name","email","pfoa","institution"},{m_currentObserver.m_fullName, m_currentObserver.m_email, m_currentObserver.m_pfoa, m_currentObserver.m_institution});
347 
348 
349  for(auto it = m_observers.begin();it!=m_observers.end();++it)
350  {
351  if(it->first == m_currentObserver.m_email) updateSwitchIfChanged(m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE);
352  else updateSwitchIfChanged(m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE);
353  }
354 
357  }
358 
360  {
361  log<software_error>({__FILE__, __LINE__});
362  return 0;
363  }
364 
365  return 0;
366 
367 }
368 
370 {
372 
373  return 0;
374 }
375 
377 {
378  for(size_t n=0; n < m_streamWriters.size(); ++n)
379  {
380  if(m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On)
381  {
382  pcf::IndiProperty ip(pcf::IndiProperty::Switch);
383 
384  ip.setDevice(m_streamWriters[n] + "-sw");
385  ip.setName("writing");
386  ip.add(pcf::IndiElement("toggle"));
387  ip["toggle"].setSwitchState(pcf::IndiElement::On);
388 
389  sendNewProperty(ip);
390  }
391  }
392 
393  mx::sys::sleep(1);
394 
395  m_observing = true;
396  recordObserver();
397 }
398 
400 {
401  m_observing = false;
402  recordObserver();
403 
404  for(size_t n=0; n < m_streamWriters.size(); ++n)
405  {
406  if(m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On)
407  {
408  pcf::IndiProperty ip(pcf::IndiProperty::Switch);
409 
410  ip.setDevice(m_streamWriters[n] + "-sw");
411  ip.setName("writing");
412  ip.add(pcf::IndiElement("toggle"));
413  ip["toggle"].setSwitchState(pcf::IndiElement::Off);
414 
415  sendNewProperty(ip);
416  }
417  }
418 
419 
420 }
421 
422 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_observers)(const pcf::IndiProperty &ipRecv)
423 {
424 
425  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_observers, ipRecv);
426 
427  //look for selected mode switch which matches a known mode. Make sure only one is selected.
428  std::string newEmail = "";
429  for(auto it=m_observers.begin(); it != m_observers.end(); ++it)
430  {
431  if(!ipRecv.find(it->second.m_sanitizedEmail)) continue;
432 
433  if(ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On)
434  {
435  if(newEmail != "")
436  {
437  log<text_log>("More than one observer selected", logPrio::LOG_ERROR);
438  return -1;
439  }
440 
441  newEmail = it->first;
442  }
443  }
444 
445  if(newEmail == "")
446  {
447  std::cerr << "nothing\n";
448  return 0;
449  }
450 
451  std::unique_lock<std::mutex> lock(m_indiMutex);
452 
453  m_currentObserver = m_observers[newEmail];
454 
455  for(auto it = m_observers.begin();it!=m_observers.end();++it)
456  {
457  if(it->first == m_currentObserver.m_sanitizedEmail) updateSwitchIfChanged(m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE);
458  else updateSwitchIfChanged(m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE);
459  }
460 
461  log<logger::observer>({m_currentObserver.m_fullName,m_currentObserver.m_pfoa, m_currentObserver.m_email, m_currentObserver.m_institution});
462 
463  return 0;
464 }
465 
466 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_obsName)(const pcf::IndiProperty &ipRecv)
467 {
468  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_obsName, ipRecv);
469 
470  std::string target;
471 
472  std::unique_lock<std::mutex> lock(m_indiMutex);
473 
474  if( indiTargetUpdate( m_indiP_obsName, target, ipRecv, true) < 0)
475  {
476  log<software_error>({__FILE__,__LINE__});
477  return -1;
478  }
479 
480  m_obsName = target;
481 
482  return 0;
483 }
484 
485 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_observing)(const pcf::IndiProperty &ipRecv)
486 {
487  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_observing, ipRecv);
488 
489  if(!ipRecv.find("toggle")) return 0;
490 
491  std::unique_lock<std::mutex> lock(m_indiMutex);
492 
493  recordObserver(true);
494  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
495  {
496  startObserving();
497  updateSwitchIfChanged(m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK);
498  }
499  else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
500  {
501  stopObserving();
502  updateSwitchIfChanged(m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE);
503  }
504 
505  return 0;
506 }
507 
508 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_sws)(const pcf::IndiProperty &ipRecv)
509 {
510  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_sws, ipRecv);
511 
512  if(m_observing == true)
513  {
514  log<text_log>({"Can't change stream writers while observing"}, logPrio::LOG_WARNING);
515  return 0;
516  }
517 
518  for(size_t n =0; n < m_streamWriters.size(); ++n)
519  {
520  if(!ipRecv.find(m_streamWriters[n])) continue;
521 
522  updateSwitchIfChanged(m_indiP_sws, m_streamWriters[n], ipRecv[m_streamWriters[n]].getSwitchState());
523  }
524 
525  return 0;
526 }
527 
528 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_userlog)(const pcf::IndiProperty &ipRecv)
529 {
530  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_userlog, ipRecv);
531 
532  std::string email;
533  std::string message;
534 
535  timespecX ts {};
536 
537  if(ipRecv.find("email"))
538  {
539  email = ipRecv["email"].get();
540  }
541 
542  if(ipRecv.find("message"))
543  {
544  message = ipRecv["message"].get();
545  }
546 
547  if(message == "")
548  {
549  return 0;
550  }
551 
552  if(email == "")
553  {
554  email = m_currentObserver.m_email;
555  }
556 
557  if(ipRecv.find("time_s"))
558  {
559  ts.time_s = ipRecv["time_s"].get<flatlogs::secT>();
560  }
561 
562  if(ipRecv.find("time_ns"))
563  {
564  ts.time_ns = ipRecv["time_ns"].get<flatlogs::nanosecT>();
565  }
566 
567  if(ts.time_s != 0)
568  {
569  m_log.template log<user_log>(ts, {email, message}, logPrio::LOG_INFO);
570  }
571  else
572  {
573  log<user_log>({email, message});
574  }
575 
576  return 0;
577 }
578 
579 inline
581 {
583 }
584 
585 inline
587 {
588  return recordObserver(true);
589 }
590 
591 inline
593 {
594  static std::string last_email;
595  static std::string last_obsName;
596  static bool last_observing;
597 
598  if( last_email != m_currentObserver.m_email || last_obsName != m_obsName || last_observing != m_observing || force)
599  {
600  telem<telem_observer>({m_currentObserver.m_email, m_obsName, m_observing});
601 
602  last_email = m_currentObserver.m_email;
603  last_obsName = m_obsName;
604  last_observing = m_observing;
605  }
606 
607  return 0;
608 }
609 
610 inline
612 {
613  telem<telem_observer>({m_currentObserver.m_email, m_obsName, m_observing});
614  return 0;
615 }
616 
617 } //namespace app
618 } //namespace MagAOX
619 
620 #endif //observerCtrl_hpp
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:73
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
Definition: MagAOXApp.hpp:3120
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2297
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 createStandardIndiToggleSw(pcf::IndiProperty &prop, const std::string &name, const std::string &label="", const std::string &group="")
Create a standard R/W INDI switch with a single toggle element.
Definition: MagAOXApp.hpp:2543
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:100
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.
Definition: MagAOXApp.hpp:3144
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1804
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:545
std::string configName()
Get the config name.
Definition: MagAOXApp.hpp:3477
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.
Definition: MagAOXApp.hpp:2603
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)
Definition: MagAOXApp.hpp:3268
int createStandardIndiText(pcf::IndiProperty &prop, const std::string &propName, const std::string &label="", const std::string &group="")
Create a standard R/W INDI Text property with target and current elements.
Definition: MagAOXApp.hpp:2406
The MagAO-X Observer Controller.
pcf::IndiProperty m_indiP_observer
pcf::IndiProperty m_indiP_obsName
int recordTelem(const telem_observer *)
virtual int appLogic()
Implementation of the FSM for observerCtrl.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_observing)
pcf::IndiProperty m_indiP_observers
int recordObserver(bool force=false)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_sws)
~observerCtrl() noexcept
D'tor, declared and defined for noexcept.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_obsName)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_observers)
std::vector< std::string > m_streamWriters
The stream writers to stop and start.
virtual int appStartup()
Startup function.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_userlog)
pcf::IndiProperty m_indiP_userlog
pcf::IndiProperty m_indiP_observing
pcf::IndiProperty m_indiP_obslog
observerCtrl()
Default c'tor.
virtual int appShutdown()
Shutdown the app.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
friend class observerCtrl_test
std::map< std::string, observer > observerMapT
pcf::IndiProperty m_indiP_sws
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
Definition: indiMacros.hpp:248
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
Definition: indiMacros.hpp:208
uint32_t secT
The type used for seconds.
Definition: logDefs.hpp:29
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:56
#define INDI_IDLE
Definition: indiUtils.hpp:28
#define INDI_OK
Definition: indiUtils.hpp:29
std::ostream & cerr()
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
Definition: indiUtils.hpp:212
INDI_VALIDATE_CALLBACK_PROPS(function, ipRecv)
const pcf::IndiProperty & ipRecv
Definition: MagAOXApp.hpp:3434
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition: dm.hpp:24
constexpr static logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
Definition: logPriority.hpp:37
constexpr static logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
Definition: logPriority.hpp:40
constexpr static logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
Definition: logPriority.hpp:49
constexpr static logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
Definition: logPriority.hpp:43
uint32_t nanosecT
The type used for nanoseconds.
Definition: logDefs.hpp:34
A device base class which saves telemetry.
Definition: telemeter.hpp:69
int appShutdown()
Perform telemeter application shutdown.
Definition: telemeter.hpp:274
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Definition: telemeter.hpp:223
int appLogic()
Perform telemeter application logic.
Definition: telemeter.hpp:268
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Definition: telemeter.hpp:211
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Definition: telemeter.hpp:281
Software ERR log entry.
Log entry recording the build-time git state.
A fixed-width timespec structure.
Definition: timespecX.hpp:35