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 
129 public:
131 
134 
136 
137  ///@}
138 
139  /** \name Telemeter Interface
140  *
141  * @{
142  */
143  int checkRecordTimes();
144 
145  int recordTelem( const telem_observer * );
146 
147  int recordObserver(bool force = false);
148 
149  int recordObserverNow();
150 
151  ///@}
152 };
153 
154 observerCtrl::observerCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
155 {
156 
157  return;
158 }
159 
161 {
162  config.add("stream.writers", "", "stream.writers", argType::Required, "stream", "writers", false, "string", "The device names of the stream writers to control.");
163 
165 }
166 
167 int observerCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
168 {
169  _config(m_streamWriters, "stream.writers");
170 
171  std::vector<std::string> sections;
172 
173  _config.unusedSections(sections);
174 
175  if( sections.size() == 0 )
176  {
177  log<text_log>("no observers found in config", logPrio::LOG_CRITICAL);
178  return -1;
179  }
180 
181  for(size_t i=0; i< sections.size(); ++i)
182  {
183  bool pfoaSet = _config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "pfoa" ));
184  if( !pfoaSet ) continue;
185 
186  std::string email = sections[i];
187 
188  std::string pfoa;
189  _config.configUnused(pfoa, mx::app::iniFile::makeKey(sections[i], "pfoa" ));
190 
191  std::string fullName;
192  _config.configUnused(fullName, mx::app::iniFile::makeKey(sections[i], "full_name" ));
193 
194  std::string institution;
195  _config.configUnused(institution, mx::app::iniFile::makeKey(sections[i], "institution" ));
196 
197  std::string sanitizedEmail = "";
198  for(size_t n = 0; n < email.size(); ++n)
199  {
200  if(email[n] == '@') {
201  sanitizedEmail = sanitizedEmail + "-at-";
202  } else if(email[n] == '.') {
203  sanitizedEmail = sanitizedEmail + "-dot-";
204  } else {
205  sanitizedEmail.push_back(email[n]);
206  }
207  }
208  m_observers[email] = observer({fullName, pfoa, email, sanitizedEmail, institution});
209  }
210 
211  return 0;
212 }
213 
215 {
216  if(loadConfigImpl(config) < 0)
217  {
218  m_shutdown = 1;
219  return;
220  }
221 
222  if(m_observers.size() < 1)
223  {
224  log<text_log>("no observers found in config", logPrio::LOG_CRITICAL);
225  m_shutdown = 1;
226  return;
227  }
228 
230 }
231 
233 {
234  std::vector<std::string> sanitizedEmails;
235  std::vector<std::string> emails;
236  for(auto it = m_observers.begin(); it!=m_observers.end(); ++it)
237  {
238  sanitizedEmails.push_back(it->second.m_sanitizedEmail);
239  emails.push_back(it->second.m_email);
240  }
241 
242  if(createStandardIndiSelectionSw( m_indiP_observers, "observers", sanitizedEmails, emails) < 0)
243  {
244  log<software_critical>({__FILE__, __LINE__});
245  return -1;
246  }
247 
249  {
250  log<software_critical>({__FILE__, __LINE__});
251  return -1;
252  }
253 
254  createStandardIndiText( m_indiP_obsName, "obs_name", "Observation Name", "Observer");
256  {
257  log<software_critical>({__FILE__, __LINE__});
258  return -1;
259  }
260 
261  createStandardIndiToggleSw( m_indiP_observing, "obs_on", "Observation On", "Observer");
263  {
264  log<software_critical>({__FILE__, __LINE__});
265  return -1;
266  }
267 
268  REG_INDI_NEWPROP_NOCB(m_indiP_observer, "current_observer", pcf::IndiProperty::Text);
269  m_indiP_observer.add(pcf::IndiElement("full_name"));
270  m_indiP_observer.add(pcf::IndiElement("email"));
271  m_indiP_observer.add(pcf::IndiElement("pfoa"));
272  m_indiP_observer.add(pcf::IndiElement("institution"));
273 
274  m_indiP_sws = pcf::IndiProperty(pcf::IndiProperty::Switch);
275  m_indiP_sws.setDevice(configName());
276  m_indiP_sws.setName("writers");
277  m_indiP_sws.setPerm(pcf::IndiProperty::ReadWrite);
278  m_indiP_sws.setState(pcf::IndiProperty::Idle);
279  m_indiP_sws.setRule(pcf::IndiProperty::AnyOfMany);
280 
281  for(size_t n =0; n < m_streamWriters.size(); ++n)
282  {
283  m_indiP_sws.add(pcf::IndiElement(m_streamWriters[n], pcf::IndiElement::Off));
284  }
285 
287  {
288  log<software_critical>({__FILE__, __LINE__});
289  return -1;
290  }
291 
293  {
294  return log<software_error,-1>({__FILE__,__LINE__});
295  }
296 
298  return 0;
299 }
300 
302 {
303 
304  std::unique_lock<std::mutex> lock(m_indiMutex, std::try_to_lock);
305 
306  if(lock.owns_lock())
307  {
308  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});
309 
310 
311  for(auto it = m_observers.begin();it!=m_observers.end();++it)
312  {
313  if(it->first == m_currentObserver.m_email) updateSwitchIfChanged(m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE);
314  else updateSwitchIfChanged(m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE);
315  }
316 
319  }
320 
322  {
323  log<software_error>({__FILE__, __LINE__});
324  return 0;
325  }
326 
327  return 0;
328 
329 }
330 
332 {
334 
335  return 0;
336 }
337 
339 {
340  for(size_t n=0; n < m_streamWriters.size(); ++n)
341  {
342  if(m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On)
343  {
344  pcf::IndiProperty ip(pcf::IndiProperty::Switch);
345 
346  ip.setDevice(m_streamWriters[n] + "-sw");
347  ip.setName("writing");
348  ip.add(pcf::IndiElement("toggle"));
349  ip["toggle"].setSwitchState(pcf::IndiElement::On);
350 
351  sendNewProperty(ip);
352  }
353  }
354 
355  mx::sys::sleep(1);
356 
357  m_observing = true;
358  recordObserver();
359 }
360 
362 {
363  m_observing = false;
364  recordObserver();
365 
366  for(size_t n=0; n < m_streamWriters.size(); ++n)
367  {
368  if(m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On)
369  {
370  pcf::IndiProperty ip(pcf::IndiProperty::Switch);
371 
372  ip.setDevice(m_streamWriters[n] + "-sw");
373  ip.setName("writing");
374  ip.add(pcf::IndiElement("toggle"));
375  ip["toggle"].setSwitchState(pcf::IndiElement::Off);
376 
377  sendNewProperty(ip);
378  }
379  }
380 
381 
382 }
383 
384 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_observers)(const pcf::IndiProperty &ipRecv)
385 {
386 
387  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_observers, ipRecv);
388 
389  //look for selected mode switch which matches a known mode. Make sure only one is selected.
390  std::string newEmail = "";
391  for(auto it=m_observers.begin(); it != m_observers.end(); ++it)
392  {
393  if(!ipRecv.find(it->second.m_sanitizedEmail)) continue;
394 
395  if(ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On)
396  {
397  if(newEmail != "")
398  {
399  log<text_log>("More than one observer selected", logPrio::LOG_ERROR);
400  return -1;
401  }
402 
403  newEmail = it->first;
404  }
405  }
406 
407  if(newEmail == "")
408  {
409  std::cerr << "nothing\n";
410  return 0;
411  }
412 
413  std::unique_lock<std::mutex> lock(m_indiMutex);
414 
415  m_currentObserver = m_observers[newEmail];
416 
417  for(auto it = m_observers.begin();it!=m_observers.end();++it)
418  {
419  if(it->first == m_currentObserver.m_sanitizedEmail) updateSwitchIfChanged(m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE);
420  else updateSwitchIfChanged(m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE);
421  }
422 
423  log<logger::observer>({m_currentObserver.m_fullName,m_currentObserver.m_pfoa, m_currentObserver.m_email, m_currentObserver.m_institution});
424 
425  return 0;
426 }
427 
428 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_obsName)(const pcf::IndiProperty &ipRecv)
429 {
430  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_obsName, ipRecv);
431 
432  std::string target;
433 
434  std::unique_lock<std::mutex> lock(m_indiMutex);
435 
436  if( indiTargetUpdate( m_indiP_obsName, target, ipRecv, true) < 0)
437  {
438  log<software_error>({__FILE__,__LINE__});
439  return -1;
440  }
441 
442  m_obsName = target;
443 
444  return 0;
445 }
446 
447 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_observing)(const pcf::IndiProperty &ipRecv)
448 {
449  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_observing, ipRecv);
450 
451  if(!ipRecv.find("toggle")) return 0;
452 
453  std::unique_lock<std::mutex> lock(m_indiMutex);
454 
455  recordObserver(true);
456  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
457  {
458  startObserving();
459  updateSwitchIfChanged(m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK);
460  }
461  else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
462  {
463  stopObserving();
464  updateSwitchIfChanged(m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE);
465  }
466 
467  return 0;
468 }
469 
470 INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_sws)(const pcf::IndiProperty &ipRecv)
471 {
472  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_sws, ipRecv);
473 
474  if(m_observing == true)
475  {
476  log<text_log>({"Can't change stream writers while observing"}, logPrio::LOG_WARNING);
477  return 0;
478  }
479 
480  for(size_t n =0; n < m_streamWriters.size(); ++n)
481  {
482  if(!ipRecv.find(m_streamWriters[n])) continue;
483 
484  updateSwitchIfChanged(m_indiP_sws, m_streamWriters[n], ipRecv[m_streamWriters[n]].getSwitchState());
485  }
486 
487  return 0;
488 }
489 
490 inline
492 {
494 }
495 
496 inline
498 {
499  return recordObserver(true);
500 }
501 
502 inline
504 {
505  static std::string last_email;
506  static std::string last_obsName;
507  static bool last_observing;
508 
509  if( last_email != m_currentObserver.m_email || last_obsName != m_obsName || last_observing != m_observing || force)
510  {
511  telem<telem_observer>({m_currentObserver.m_email, m_obsName, m_observing});
512 
513  last_email = m_currentObserver.m_email;
514  last_obsName = m_obsName;
515  last_observing = m_observing;
516  }
517 
518  return 0;
519 }
520 
521 inline
523 {
524  telem<telem_observer>({m_currentObserver.m_email, m_obsName, m_observing});
525  return 0;
526 }
527 
528 } //namespace app
529 } //namespace MagAOX
530 
531 #endif //observerCtrl_hpp
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:75
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:2877
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2082
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:2321
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:102
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:2901
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1590
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:540
std::string configName()
Get the config name.
Definition: MagAOXApp.hpp:3241
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:2383
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:3031
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:2180
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.
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:247
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
Definition: indiMacros.hpp:207
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:51
#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:206
const pcf::IndiProperty & ipRecv
INDI_VALIDATE_CALLBACK_PROPS(function, ipRecv)
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
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_WARNING
A condition has occurred which may become an error, but the process continues.
Definition: logPriority.hpp:43
A device which saves telemetry.
Definition: telemeter.hpp:52
int appShutdown()
Perform telemeter application shutdown.
Definition: telemeter.hpp:259
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Definition: telemeter.hpp:208
int appLogic()
Perform telemeter application logic.
Definition: telemeter.hpp:253
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Definition: telemeter.hpp:195
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:266
Software ERR log entry.
Log entry recording the build-time git state.