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
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
28namespace MagAOX
29{
30namespace app
31{
32
33/// The MagAO-X Observer Controller
34/**
35 * \ingroup observerCtrl
36 */
37class 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
45protected:
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
75public:
76 /// Default c'tor.
78
79 /// D'tor, declared and defined for noexcept.
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 */
118protected:
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
130public:
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
157observerCtrl::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
170int 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 {
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
251 {
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 {
274 return -1;
275 }
276
277 createStandardIndiText( m_indiP_obsName, "obs_name", "Observation Name", "Observer");
279 {
281 return -1;
282 }
283
284 createStandardIndiToggleSw( m_indiP_observing, "obs_on", "Observation On", "Observer");
286 {
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 {
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 {
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 {
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 {
362 return 0;
363 }
364
365 return 0;
366
367}
368
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
390 }
391 }
392
393 mx::sys::sleep(1);
394
395 m_observing = true;
397}
398
400{
401 m_observing = false;
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
416 }
417 }
418
419
420}
421
422INDI_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
466INDI_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
485INDI_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
508INDI_NEWCALLBACK_DEFN(observerCtrl, m_indiP_sws)(const pcf::IndiProperty &ipRecv)
509{
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
528INDI_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
579inline
584
585inline
587{
588 return recordObserver(true);
589}
590
591inline
593{
594 static std::string last_email;
595 static std::string last_obsName;
596 static bool last_observing;
597
599 {
601
605 }
606
607 return 0;
608}
609
610inline
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.
stateCodes::stateCodeT state()
Get the current state code.
int registerIndiPropertyNew(pcf::IndiProperty &prop, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for.
int 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.
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 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.
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
virtual int appShutdown()
Shutdown the app.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
std::map< std::string, observer > observerMapT
pcf::IndiProperty m_indiP_sws
#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 INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
uint32_t secT
The type used for seconds.
Definition logDefs.hpp:29
@ READY
The device is ready for operation, but is not operating.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
#define INDI_IDLE
Definition indiUtils.hpp:28
#define INDI_OK
Definition indiUtils.hpp:29
const pcf::IndiProperty & ipRecv
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:24
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:69
int appShutdown()
Perform telemeter application shutdown.
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Software 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