API
MagAOXApp.hpp
Go to the documentation of this file.
1 /** \file magAOXApp.hpp
2  * \brief The basic MagAO-X Application
3  * \author Jared R. Males (jaredmales@gmail.com)
4  *
5  * History:
6  * - 2017-12-24 created by JRM
7  *
8  * \ingroup app_files
9  */
10 
11 #ifndef app_MagAOXApp_hpp
12 #define app_MagAOXApp_hpp
13 
14 #include <signal.h>
15 #include <sys/stat.h>
16 #include <sys/syscall.h>
17 
18 #include <cstdlib>
19 #include <fstream>
20 #include <sstream>
21 #include <thread>
22 #include <mutex>
23 
24 #include <unordered_map>
25 
26 #include <mx/mxlib.hpp>
27 #include <mx/app/application.hpp>
28 #include <mx/sys/environment.hpp>
29 #include <mx/sys/timeUtils.hpp>
30 #include <mx/ioutils/fileUtils.hpp>
31 
32 #include "../common/environment.hpp"
33 #include "../common/paths.hpp"
34 #include "../common/defaults.hpp"
35 #include "../common/config.hpp"
36 
37 #include "../logger/logFileRaw.hpp"
38 #include "../logger/logManager.hpp"
39 
40 #include "../sys/thSetuid.hpp"
41 
42 #include "stateCodes.hpp"
43 #include "indiDriver.hpp"
44 #include "indiMacros.hpp"
45 #include "indiUtils.hpp"
46 
47 using namespace mx::app;
48 
49 using namespace MagAOX::logger;
50 
51 namespace MagAOX
52 {
53 namespace app
54 {
55 
56 /// The base-class for MagAO-X applications.
57 /**
58  * You can define a base configuration file for this class by defining
59  * \code
60  * m_configBase = "base_name";
61  * \endcode
62  * in the derived class constructor. This would be used, for instance to have a config common to
63  * all filter wheels.
64  *
65  *
66  * \todo make m_powerMgtEnabled a template parameter, and static_assert check if _useINDI== false and power management
67  * is true
68  *
69  * \ingroup magaoxapp
70  */
71 template <bool _useINDI = true>
72 class MagAOXApp : public application
73 {
74  friend class MagAOXApp_test;
75 
76  public:
77  /// The log manager type.
79 
80  protected:
81  std::string MagAOXPath; ///< The base path of the MagAO-X system.
82 
83  std::string m_configName; ///< The name of the configuration file (minus .conf).
84 
85  std::string m_configDir; ///< The path to configuration files for MagAOX.
86 
87  std::string m_configBase; ///< The name of a base config class for this app (minus .conf).
88 
89  std::string m_calibDir; ///< The path to calibration files for MagAOX.
90 
91  std::string sysPath; ///< The path to the system directory, for PID file, etc.
92 
93  std::string secretsPath; ///< Path to the secrets directory, where passwords, etc, are stored.
94 
95  unsigned long m_loopPause{ MAGAOX_default_loopPause }; /**< The time in nanoseconds to pause the main loop.
96  The appLogic() function of the derived class is called
97  every m_loopPause nanoseconds. Default is
98  1,000,000,000 ns. Config with loopPause=X.*/
99 
100  int m_shutdown{ 0 }; ///< Flag to signal it's time to shutdown. When not 0, the main loop exits.
101 
102  private:
103  /// Default c'tor is deleted.
104  MagAOXApp() = delete;
105 
106  public:
107  /// Public c'tor. Handles uid, logs git repo status, and initializes static members.
108  /**
109  * Only one MagAOXApp can be instantiated per program. Hence this c'tor will issue exit(-1)
110  * if the static self-pointer m_self is already initialized.
111  *
112  * euid is set to 'real' to ensure that the application has normal privileges unless
113  * explicitly needed.
114  *
115  * Reference: http://man7.org/linux/man-pages/man2/getresuid.2.html
116  *
117  * The git repository status is required to create a MagAOXApp. Derived classes
118  * should include the results of running `gengithead.sh` and pass the defined
119  * sha1 and modified flags.
120  *
121  */
122  MagAOXApp( const std::string &git_sha1, ///< [in] The current SHA1 hash of the git repository
123  const bool git_modified ///< [in] Whether or not the repo is modified.
124  );
125 
126  ~MagAOXApp() noexcept( true );
127 
128  /// Get the value of the shutdown flag.
129  /**
130  * \returns the current value of m_shutdown
131  */
132  int shutdown();
133 
134  /// Set the paths for config files
135  /** Replaces the mx::application defaults with the MagAO-X config system.
136  *
137  * This function parses the CL for "-n" or "--name".
138  *
139  *
140  * Do not override this unless you intend to depart from the MagAO-X standard.
141  */
142  virtual void setDefaults( int argc, ///< [in] standard command line result specifying number of arguments in argv
143  char **argv ///< [in] standard command line result containing the arguments.
144  );
145 
146  /// The basic MagAO-X configuration setup method. Should not normally be overridden.
147  /** This method sets up the config system with the standard MagAO-X key=value pairs.
148  *
149  * Though it is virtual, it should not normally be overridden unless you need
150  * to depart from the MagAO-X standard.
151  *
152  * Setting up app specific config goes in setupConfig() implemented in the derived class.
153  */
154  virtual void setupBasicConfig();
155 
156  /// The basic MagAO-X configuration processing method. Should not normally be overridden.
157  /** This method processes the standard MagAO-X key=value pairs.
158  *
159  * Though it is virtual, it should not normally be overridden unless you need
160  * to depart from the MagAO-X standard.
161  *
162  * Processing of app specific config goes in loadConfig() implemented by the derived class.
163  */
164  virtual void loadBasicConfig();
165 
166  /// Check for unused and unrecognized config options and settings.
167  /** Logs the unused targets as warnings. Unrecognized and unused options are logged as critical, and m_shutdown is
168  * set. Any command line argument (not an option) will also be critical and cause shutdown.
169  */
170  virtual void checkConfig();
171 
172  /// The execute method implementing the standard main loop. Should not normally be overridden.
173  /** Performs final startup steps. That is:
174  * - Verifies correct effective user-id by comparison to logs directory.
175  * - PID locking lockPID()
176  * - log thread startup by logThreadStart()
177  * - signal handling installation by setSigTermHandler()
178  * - appStartup() is called
179  * - INDI communications started by startINDI()
180  * - power state is checked, pausing if unknown (if being managed)
181  *
182  * Errors in the above steps will cause a process exit.
183  *
184  * Then commences the main event loop.
185  * Conditions on entry to the main loop:
186  * - PID locked
187  * - Log thread running
188  * - Signal handling installed
189  * - appStartup successful
190  * - INDI communications started successfully (if being used)
191  * - power state known (if being managed)
192  *
193  * In the event loop, the power state is checked (if being managed). If power is off, then onPowerOff is called.
194  * If power is on, or power is not managed, appLogic is called. These methods are implemented in derived classes,
195  * and are called every m_loopPause interval.
196  *
197  * If an error is returned by either onPowerOff or appLogic, or a signal is handled, then the shutdown is managed.
198  * This includes shutting down INDI, calling appShutdown, and unlocking the PID. The log thread will shutdown.
199  */
200  virtual int execute();
201 
202  /** \name Pure Virtual Functions
203  * Derived applications must implement these.
204  * @{
205  */
206 
207  /// Any tasks to perform prior to the main event loop go here.
208  /** This is called after signal handling is installed. FSM state is
209  * stateCodes::INITIALIZED when this is called.
210  *
211  * Set m_shutdown = 1 on any fatal errors here.
212  */
213  virtual int appStartup() = 0;
214 
215  /// This is where derived applications implement their main FSM logic.
216  /** This will be called every m_loopPause nanoseconds until the application terminates.
217  *
218  * FSM state will be whatever it is on exti from appStartup.
219  *
220  * Should return -1 on an any unrecoverable errors which will caues app to terminate. Could also set m_shutdown=1.
221  * Return 0 on success, or at least intent to continue.
222  *
223  */
224  virtual int appLogic() = 0;
225 
226  /// Any tasks to perform after main loop exit go here.
227  /** Should be able to handle case where appStartup and/or appLogic have not run.
228  */
229  virtual int appShutdown() = 0;
230 
231  ///@} -- Pure Virtual Functions
232 
233  /** \name Logging
234  * @{
235  */
236  public:
237  static logManagerT m_log;
238 
239  /// Make a log entry
240  /** Wrapper for logManager::log
241  *
242  * \tparam logT the log entry type
243  * \tparam retval the value returned by this method.
244  *
245  */
246  template <typename logT, int retval = 0>
247  static int log( const typename logT::messageT &msg, ///< [in] the message to log
248  logPrioT level = logPrio::LOG_DEFAULT /**< [in] [optional] the log level. The default
249  is used if not specified.*/
250  );
251 
252  /// Make a log entry
253  /** Wrapper for logManager::log
254  *
255  * \tparam logT the log entry type
256  * \tparam retval the value returned by this method.
257  *
258  */
259  template <typename logT, int retval = 0>
260  static int log( logPrioT level = logPrio::LOG_DEFAULT /**< [in] [optional] the log level. The default is
261  used if not specified.*/
262  );
263 
264  /// Handle a log message from the logging system
265  /** This is a callback from the logManager, and is called when the log thread is processing log entries.
266  *
267  * Decides whether to display to stderr and whether to send via INDI.
268  */
269  void logMessage( bufferPtrT &b );
270 
271  protected:
272  /// Callback for config system logging.
273  /** Called by appConfigurator each time a value is set using the config() operator.
274  * You never need to call this directly.
275  */
276  static void configLog( const std::string &name, ///< [in] The name of the config value
277  const int &code, ///< [in] numeric code specifying the type
278  const std::string &value, ///< [in] the value read by the config system
279  const std::string &source ///< [in] the source of the value.
280  );
281 
282  ///@} -- logging
283 
284  /** \name Signal Handling
285  * @{libMagAOX/logger/types/software_log.hpp
286  */
287  private:
288  static MagAOXApp
289  *m_self; ///< Static pointer to this (set in constructor). Used to test whether a a MagAOXApp is already
290  ///< instatiated (a fatal error) and used for getting out of static signal handlers.
291 
292  /// Sets the handler for SIGTERM, SIGQUIT, and SIGINT.
293  int setSigTermHandler();
294 
295  /// The handler called when SIGTERM, SIGQUIT, or SIGINT is received. Just a wrapper for handlerSigTerm.
296  static void _handlerSigTerm( int signum, ///< [in] specifies the signal.
297  siginfo_t *siginf, ///< [in] ignored by MagAOXApp
298  void *ucont ///< [in] ignored by MagAOXApp
299  );
300 
301  /// Handles SIGTERM, SIGQUIT, and SIGINT. Sets m_shutdown to 1 and logs the signal.
302  void handlerSigTerm( int signum, ///< [in] specifies the signal.
303  siginfo_t *siginf, ///< [in] ignored by MagAOXApp
304  void *ucont ///< [in] ignored by MagAOXApp
305  );
306 
307  ///@} -- Signal Handling
308 
309  /** \name Privilege Management
310  * @{
311  */
312  private:
313  uid_t m_euidReal; ///< The real user id of the proces (i.e. the lower privileged id of the user)
314  uid_t m_euidCalled; ///< The user id of the process as called (i.e. the higher privileged id of the owner, root if
315  ///< setuid).
316  uid_t m_suid; ///< The save-set user id of the process
317 
318  protected:
319  /// Internal class to manage setuid privilege escalation with RAII
320  /** Upon construction this elevates to the called user id, root in a setuid process.
321  * Restores privileges to real user id upon destruction (i.e. when it goes out of scope).
322  */
324  {
325  private:
327  bool m_elevated{ false };
328 
329  public:
330  explicit elevatedPrivileges( MagAOXApp *app )
331  {
332  m_app = app;
333  elevate();
334  }
335 
336  void elevate()
337  {
338  if( m_elevated )
339  return;
340 
341  m_app->setEuidCalled();
342  m_elevated = true;
343  }
344 
345  void restore()
346  {
347  if( !m_elevated )
348  return;
349 
350  m_app->setEuidReal();
351  m_elevated = false;
352  }
353 
355  {
356  restore();
357  }
358  };
359 
360  private:
361  /// Set the effective user ID to the called value, i.e. the highest possible.
362  /** If setuid is set on the file, this will be super-user privileges.
363  *
364  * Reference: http://pubs.opengroup.org/onlinepubs/009695399/functions/seteuid.html
365  *
366  * \returns 0 on success
367  * \returns -1 on error from setuid().
368  */
370 
371  /// Set the effective user ID to the real value, i.e. the file owner.
372  /**
373  * Reference: http://pubs.opengroup.org/onlinepubs/009695399/functions/seteuid.html
374  *
375  * \returns 0 on success
376  * \returns -1 on error from setuid().
377  */
378  int setEuidReal();
379 
380  ///@} -- Privilege Management
381 
382  protected:
383  /** \name PID Locking
384  *
385  * Each MagAOXApp has a PID lock file in the system directory. The app will not
386  * startup if it detects that the PID is already locked, preventing duplicates. This is
387  * based on the configured name, not the invoked name (argv[0]).
388  *
389  * @{
390  */
391 
392  std::string pidFileName; ///< The name of the PID file
393 
394  pid_t m_pid{ 0 }; ///< This process's PID
395 
396  /// Attempt to lock the PID by writing it to a file. Fails if a process is already running with the same config
397  /// name.
398  /** First checks the PID file for an existing PID. If found, interrogates /proc to determine if that process is
399  * running and if so if the command line matches. If a matching process is currently running, then this returns an
400  * error.
401  *
402  * Will not fail if a PID file exists but the stored PID does not correspond to a running process with the same
403  * command line name.
404  *
405  * Reference: https://linux.die.net/man/3/getpid
406  *
407  * \returns 0 on success.
408  * \returns -1 on any error, including creating the PID file or if this app is already running.
409  */
410  int lockPID();
411 
412  /// Remove the PID file.
413  int unlockPID();
414 
415  ///@} -- PID Locking
416 
417  /** \name cpusets
418  * The path to the cpusets mount is configured by the environment varialbe defined by MAGOX_env_cpuset
419  * in environment.hpp. This environment varialbe is normally named "CGROUPS1_CPUSET_MOUNTPOINT". If
420  * the environment variable is not set, the default defined by MAGAOX_cpusetPath in paths.hpp is used.
421  * This is normally "/opt/MagAOX/cpuset/"
422  */
423 
424  std::string m_cpusetPath{ MAGAOX_cpusetPath };
425 
426  /** \name Threads
427  *
428  * @{
429  */
430  public:
431  /// Start a thread, using this class's privileges to set priority, etc.
432  /**
433  * The thread initialization synchronizer `bool` is set to true at the beginning
434  * of this function, then is set to false once all initialization is complete. The
435  * thread exec function should wait until this is false before doing _anything_ except
436  * setting the pid. This is to avoid privilege escalation bugs.
437  *
438  * The interface of the thread start function is:
439  \code
440  static void impl::myThreadStart( impl * o )
441  {
442  o->myThreadExec(); //A member function which actually executes the thread
443  }
444  \endcode
445  * where `impl` is the derived class, and `mThreadStart` and `myThreadExec` are members
446  * of `impl`.
447  *
448  * \returns 0 on success
449  * \returns -1 on error
450  */
451  template <class thisPtr, class Function>
452  int threadStart( std::thread &thrd, /**< [out] The thread object to start executing */
453  bool &thrdInit, /**< [in/out] The thread initilization synchronizer. */
454  pid_t &tpid, /**< [in/out] The thread pid to be filled in by thrdStart
455  immediately upon call*/
456  pcf::IndiProperty &thProp, /**< [in/out] The INDI property to publish the thread details */
457  int thrdPrio, /**< [in] The r/t priority to set for this thread */
458  const std::string &cpuset, /**< [in] the cpuset to place this thread on. Ignored if "". */
459  const std::string &thrdName, /**< [in] The name of the thread (just for logging) */
460  thisPtr *thrdThis, /**< [in] The `this` pointer to pass to the thread starter function */
461  Function &&thrdStart /**< [in] The thread starting function, a static function taking a
462  `this` pointer as argument. */
463  );
464 
465  ///@} -- Threads
466 
467  /** \name Application State
468  *
469  * @{
470  */
471  private:
472  stateCodes::stateCodeT m_state{ stateCodes::UNINITIALIZED }; ///< The application's state. Never ever set this
473  ///< directly, use state(const stateCodeT & s).
474 
475  bool m_stateAlert{ false }; // Flag to control whether the FSM is in an alert state. Once set, only user
476  // acknowledgement can change this.
477 
478  bool m_gitAlert{ false }; // Flag set if there is a git modified warning to alert on.
479 
480  int m_stateLogged{ 0 }; /**< Counter and flag for use to log errors just once.
481  Never ever access directly, use stateLogged().*/
482 
483  public:
484  /// Get the current state code
485  /** \returns m_state
486  */
488 
489  /// Set the current state code
490  /*
491  * If it is a change, the state change is logged. Also resets m_stateLogged to 0.
492  *
493  * Will also update INDI if there is a change.
494  */
495  void state( const stateCodes::stateCodeT &s, ///< [in] The new application state
496  bool stateAlert = false ///< [in] [optional] flag to set the alert state of the FSM property.
497  );
498 
499  /// Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change, >0 afterwards.
500  /** This method exists to facilitate logging the reason for a state change once, but not
501  * logging it on subsequent event loops. Returns the current value upon entry, but updates
502  * before returning so that the next call returns the incremented value. Example usage:
503  * \code
504  if( connection_failed ) //some condition set this to true
505  {
506  state( stateCodes::NOTCONNECTED );
507  if(!stateLogged()) log<text_log>("Not connected");
508  }
509  \endcode
510  * In this example, the log entry is made the first time the state changes. If there are no changes to a
511  * different state in the mean time, then when the event loop gets here again and decides it is not connected,
512  * the log entry will not be made.
513  *
514  * \returns current value of m_stateLogged, that is the value before it is incremented.
515  */
516  int stateLogged();
517 
518  ///@} --Application State
519 
520  private:
521  /// Clear the FSM alert state.
522  /** This can only be done from within this class, and this
523  * should only be possible via user action via INDI.
524  */
526 
527  /** \name INDI Interface
528  *
529  * For reference: "Get" and "New" refer to properties we own. "Set" refers to properties owned by others.
530  * So we respond to GetProperties by listing our own properties, and NewProperty is a request to change
531  * a property we own. Whereas SetProperty is a notification that someone else has changed a property.
532  *
533  * @{
534  */
535  protected:
536  /// Flag controlling whether INDI is used. If false, then no INDI code executes.
537  constexpr static bool m_useINDI = _useINDI;
538 
539  ///\todo instead of making this public, provide an accessor.
540  public:
541  /// The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communications.
542  indiDriver<MagAOXApp> *m_indiDriver{ nullptr };
543 
544  /// Mutex for locking INDI communications.
545  std::mutex m_indiMutex;
546 
547  protected:
548  /// Structure to hold the call-back details for handling INDI communications.
550  {
551  pcf::IndiProperty *property{ 0 }; ///< A pointer to an INDI property.
552  int ( *callBack )( void *, const pcf::IndiProperty & ){ 0 }; /**< The function to call for a new or
553  set property.*/
554 
555  bool m_defReceived{ false }; /**< Flag indicating that a DefProperty has been received
556  after a GetProperty.*/
557  };
558 
559  public:
560  /// Value type of the indiCallBack map.
561  typedef std::pair<std::string, indiCallBack> callBackValueType;
562 
563  /// Iterator type of the indiCallBack map.
564  typedef typename std::unordered_map<std::string, indiCallBack>::iterator callBackIterator;
565 
566  /// Return type of insert on the indiCallBack map.
567  typedef std::pair<callBackIterator, bool> callBackInsertResult;
568 
569  protected:
570  /// Map to hold the NewProperty indiCallBacks for this App, with fast lookup by property name.
571  /** The key for these is the property name.
572  */
573  std::unordered_map<std::string, indiCallBack> m_indiNewCallBacks;
574 
575  /// Map to hold the SetProperty indiCallBacks for this App, with fast lookup by property name.
576  /** The key for these is device.name
577  */
578  std::unordered_map<std::string, indiCallBack> m_indiSetCallBacks;
579 
580  protected:
581  /// Flag indicating that all registered Set properties have been updated since last Get.
582  bool m_allDefsReceived{ false };
583 
584  /// Full path name of the INDI driver input FIFO.
585  std::string m_driverInName;
586 
587  /// Full path name of the INDI driver output FIFO.
588  std::string m_driverOutName;
589 
590  /// Full path name of the INDI driver control FIFO.
591  /** This is currently only used to signal restarts.
592  */
593  std::string m_driverCtrlName;
594 
595  public:
596  /// Create a standard R/W INDI Text property with target and current elements.
597  /**
598  * \returns 0 on success
599  * \returns -1 on error
600  */
601  int createStandardIndiText( pcf::IndiProperty &prop, /**< [out] the property to create and setup */
602  const std::string &propName, /**< [in] the name of the property */
603  const std::string &label = "", /**< [in] [optional] the GUI label suggestion for this
604  property */
605  const std::string &group = "" /**< [in] [optional] the group for this property */
606  );
607 
608  /// Create a standard ReadOnly INDI Text property, with at least one element.
609  /**
610  * \returns 0 on success
611  * \returns -1 on error
612  */
613  int
614  createROIndiText( pcf::IndiProperty &prop, ///< [out] the property to create and setup
615  const std::string &propName, ///< [in] the name of the property
616  const std::string &elName, ///< [in] the name of the element
617  const std::string &propLabel = "", ///< [in] [optional] the GUI label suggestion for this property
618  const std::string &propGroup = "", ///< [in] [optional] the group for this property
619  const std::string &elLabel = "" ///< [in] [optional] the GUI label suggestion for the element
620  );
621 
622  /// Create a standard R/W INDI Number property with target and current elements.
623  /**
624  * \returns 0 on success
625  * \returns -1 on error
626  */
627  template <typename T>
628  int createStandardIndiNumber( pcf::IndiProperty &prop, /**< [out] the property to create and setup */
629  const std::string &name, /**< [in] the name of the property */
630  const T &min, /**< [in] the minimum value for the elements, applied
631  to both target and current */
632  const T &max, /**< [in] the minimum value for the elements, applied
633  to both target and current */
634  const T &step, /**< [in] the step size for the elements, applied to
635  both target and current */
636  const std::string &format, /**< [in] the _ value for the elements, applied to
637  both target and current. Set to "" to use
638  the MagAO-X standard for type. */
639  const std::string &label = "", /**< [in] [optional] the GUI label suggestion for
640  this property */
641  const std::string &group = "" /**< [in] [optional] the group for this property*/
642  );
643 
644  /// Create a ReadOnly INDI Number property
645  /**
646  * \returns 0 on success
647  * \returns -1 on error
648  */
649  int createROIndiNumber( pcf::IndiProperty &prop, /**< [out] the property to create and setup */
650  const std::string &propName, /**< [in] the name of the property */
651  const std::string &propLabel = "", /**< [in] [optional] the GUI label suggestion for this
652  property */
653  const std::string &propGroup = "" /**< [in] [optional] the group for this property */
654  );
655 
656  /// Create a standard R/W INDI switch with a single toggle element.
657  /** This switch is intended to function like an on/off toggle switch.
658  *
659  * \returns 0 on success
660  * \returns -1 on error
661  */
662  int createStandardIndiToggleSw( pcf::IndiProperty &prop, /**< [out] the property to create and setup */
663  const std::string &name, /**< [in] the name of the property */
664  const std::string &label = "", /**< [in] [optional] the GUI label suggestion for
665  this property */
666  const std::string &group = "" /**< [in] [optional] the group for this property */
667  );
668 
669  /// Create a standard R/W INDI switch with a single request element.
670  /** This switch is intended to function like a momentary switch.
671  *
672  * \returns 0 on success
673  * \returns -1 on error
674  */
675  int createStandardIndiRequestSw( pcf::IndiProperty &prop, /**< [out] the property to create and setup */
676  const std::string &name, /**< [in] the name of the property */
677  const std::string &label = "", /**< [in] [optional] the GUI label suggestion
678  for this property */
679  const std::string &group = "" /**< [in] [optional] the group for this property */
680  );
681 
682  /// Create a standard R/W INDI selection (one of many) switch with vector of elements and element labels
683  /** This switch is intended to function like drop down menu.
684  *
685  * \returns 0 on success
686  * \returns -1 on error
687  */
688  int createStandardIndiSelectionSw( pcf::IndiProperty &prop, /**< [out] the property to create and setup */
689  const std::string &name, /**< [in] the name of the property, */
690  const std::vector<std::string> &elements, /**< [in] the element names to give to
691  the switches */
692  const std::vector<std::string> &elementLabels, /**< [in] the element labels to
693  give to the switches */
694  const std::string &label = "", /**< [in] [optional] the GUI label suggestion for
695  this property */
696  const std::string &group = "" /**< [in] [optional] the group for this property */
697  );
698 
699  /// Create a standard R/W INDI selection (one of many) switch with vector of elements using the element strings as
700  /// their own labels
701  /** This switch is intended to function like drop down menu.
702  *
703  * \returns 0 on success
704  * \returns -1 on error
705  */
706  int createStandardIndiSelectionSw( pcf::IndiProperty &prop, ///< [out] the property to create and setup
707  const std::string &name, ///< [in] the name of the property,
708  const std::vector<std::string> &elements, /**< [in] the element names to give to
709  the switches */
710  const std::string &label = "", /**< [in] [optional] the GUI label suggestion for
711  this property */
712  const std::string &group = "" ///< [in] [optional] the group for this property
713  );
714 
715  /// Register an INDI property which is read only.
716  /** This version requires the property be fully set up.
717  *
718  * \returns 0 on success.
719  * \returns -1 on error.
720  *
721  */
722  int registerIndiPropertyReadOnly( pcf::IndiProperty &prop /**< [in] the property to register, must be
723  completely setup */ );
724 
725  /// Register an INDI property which is read only.
726  /** This verison sets up the INDI property according to the arguments.
727  *
728  * \returns 0 on success.
729  * \returns -1 on error.
730  *
731  */
732  int registerIndiPropertyReadOnly( pcf::IndiProperty &prop, /**< [out] the property to
733  register, will
734  be configured */
735  const std::string &propName, /**< [in] the name of the
736  property */
737  const pcf::IndiProperty::Type &propType, /**< [in] the type of the
738  property */
739  const pcf::IndiProperty::PropertyPermType &propPerm, /**< [in] the permissions
740  of the property
741  */
742  const pcf::IndiProperty::PropertyStateType &propState /**< [in] the state of the
743  property */
744  );
745 
746  /// Register an INDI property which is exposed for others to request a New Property for.
747  /** In this version the supplied IndiProperty must be fully set up before passing in.
748  *
749  * \returns 0 on success.
750  * \returns -1 on error.
751  *
752  */
753  int registerIndiPropertyNew( pcf::IndiProperty &prop, /**< [in] the property to register,
754  must be fully set up */
755  int ( * )( void *, const pcf::IndiProperty & ) /**< [in] the callback for changing
756  the property */
757  );
758 
759  /// Register an INDI property which is exposed for others to request a New Property for.
760  /** This verison sets up the INDI property according to the arguments.
761  *
762  * \returns 0 on success.
763  * \returns -1 on error.
764  *
765  */
767  pcf::IndiProperty &prop, ///< [out] the property to register
768  const std::string &propName, ///< [in] the name of the property
769  const pcf::IndiProperty::Type &propType, ///< [in] the type of the property
770  const pcf::IndiProperty::PropertyPermType &propPerm, ///< [in] the permissions of the property
771  const pcf::IndiProperty::PropertyStateType &propState, ///< [in] the state of the property
772  int ( * )( void *, const pcf::IndiProperty & ) ///< [in] the callback for changing the property
773  );
774 
775  /// Register an INDI property which is exposed for others to request a New Property for, with a switch rule
776  /** This verison sets up the INDI property according to the arguments.
777  *
778  * \returns 0 on success.
779  * \returns -1 on error.
780  *
781  */
783  pcf::IndiProperty &prop, ///< [out] the property to register
784  const std::string &propName, ///< [in] the name of the property
785  const pcf::IndiProperty::Type &propType, ///< [in] the type of the property
786  const pcf::IndiProperty::PropertyPermType &propPerm, ///< [in] the permissions of the property
787  const pcf::IndiProperty::PropertyStateType &propState, ///< [in] the state of the property
788  const pcf::IndiProperty::SwitchRuleType &propRule, ///< [in] the switch rule type
789  int ( * )( void *, const pcf::IndiProperty & ) ///< [in] the callback for changing the property
790  );
791 
792  /// Register an INDI property which is monitored for updates from others.
793  /**
794  *
795  * \returns 0 on success.
796  * \returns -1 on error.
797  *
798  */
800  pcf::IndiProperty &prop, ///< [out] the property to register
801  const std::string &devName, ///< [in] the device which owns this property
802  const std::string &propName, ///< [in] the name of the property
803  int ( * )( void *, const pcf::IndiProperty & ) ///< [in] the callback for processing the property change
804  );
805 
806  protected:
807  /// Create the INDI FIFOs
808  /** Changes permissions to max available and creates the
809  * FIFOs at the configured path.
810  */
812 
813  /// Start INDI Communications
814  /**
815  * \returns 0 on success
816  * \returns -1 on error. This is fatal.
817  */
818  int startINDI();
819 
820  public:
821  void sendGetPropertySetList( bool all = false );
822 
823  /// Handler for the DEF INDI properties notification
824  /** Uses the properties registered in m_indiSetCallBacks to process the notification. This is called by
825  * m_indiDriver's indiDriver::handleDefProperties.
826  */
827  void handleDefProperty( const pcf::IndiProperty &ipRecv /**< [in] The property being sent. */ );
828 
829  /// Handler for the get INDI properties request
830  /** Uses the properties registered in m_indiCallBacks to respond to the request. This is called by
831  * m_indiDriver's indiDriver::handleGetProperties.
832  */
833  void handleGetProperties( const pcf::IndiProperty &ipRecv /**< [in] The property being requested. */ );
834 
835  /// Handler for the new INDI property request
836  /** Uses the properties registered in m_indiCallBacks to respond to the request, looking up the callback for this
837  * property and calling it.
838  *
839  * This is called by m_indiDriver's indiDriver::handleGetProperties.
840  *
841  * \todo handle errors, are they FATAL?
842  */
843  void handleNewProperty( const pcf::IndiProperty &ipRecv /**< [in] The property being changed. */ );
844 
845  /// Handler for the set INDI property request
846  /**
847  *
848  * This is called by m_indiDriver's indiDriver::handleSetProperties.
849  *
850  * \todo handle errors, are they FATAL?
851  */
852  void handleSetProperty( const pcf::IndiProperty &ipRecv /**< [in] The property being changed. */ );
853 
854  protected:
855  /// Update an INDI property element value if it has changed.
856  /** Will only peform a SetProperty if the new element value has changed
857  * compared to the stored value, or if the property state has changed.
858  *
859  * This comparison is done in the true
860  * type of the value.
861  *
862  * For a property with multiple elements, you should use the vector version to minimize network traffic.
863  */
864  template <typename T>
865  void updateIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
866  const std::string &el, ///< [in] The element name
867  const T &newVal, ///< [in] the new value
868  pcf::IndiProperty::PropertyStateType ipState = pcf::IndiProperty::Ok );
869 
870  /// Update an INDI property element value if it has changed.
871  /** Will only peform a SetProperty if the new element value has changed
872  * compared to the stored value, or if the property state has changed.
873  *
874  * This comparison is done in the true
875  * type of the value.
876  *
877  * This is a specialization for `const char *` to `std::string`.
878  *
879  * For a property with multiple elements, you should use the vector version to minimize network traffic.
880  * \overload
881  */
882  void updateIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
883  const std::string &el, ///< [in] The element name
884  const char *newVal, ///< [in] the new value
885  pcf::IndiProperty::PropertyStateType ipState = pcf::IndiProperty::Ok );
886 
887  /// Update an INDI switch element value if it has changed.
888  /** Will only peform a SetProperty if the new element switch state has changed, or the propery state
889  * has changed.
890  *
891  */
892  void
893  updateSwitchIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
894  const std::string &el, ///< [in] The element name
895  const pcf::IndiElement::SwitchStateType &newVal, ///< [in] the new value
896  pcf::IndiProperty::PropertyStateType ipState = pcf::IndiProperty::Ok );
897 
898  /// Update an INDI property if values have changed.
899  /** Will only peform a SetProperty if at least one value has changed
900  * compared to the stored value, or if the property state has changed.
901  *
902  * Constructs the element names for each value as elX where X is the index of the vector.
903  *
904  * This comparison is done in the true
905  * type of the value.
906  *
907  *
908  * \overload
909  */
910  template <typename T>
912  pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
913  const std::string &el, ///< [in] Beginning of each element name
914  const std::vector<T> &newVals, ///< [in] the new values
915  pcf::IndiProperty::PropertyStateType ipState = pcf::IndiProperty::Ok ///< [in] [optional] the new state
916  );
917 
918  /// Update an INDI property if values have changed.
919  /** Will only peform a SetProperty if at least one value has changed
920  * compared to the stored value, or if the property state has changed.
921  *
922  * This comparison is done in the true
923  * type of the value.
924  *
925  * \overload
926  */
927  template <typename T>
928  void updateIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
929  const std::vector<std::string> &els, ///< [in] String vector of element names
930  const std::vector<T> &newVals, ///< [in] the new values
931  pcf::IndiProperty::PropertyStateType newState =
932  pcf::IndiProperty::Ok ///< [in] [optional] The state of the property
933  );
934 
935  /// Get the target element value from an new property
936  /**
937  * \returns 0 on success
938  * \returns -1 on error
939  */
940  template <typename T>
941  int indiTargetUpdate( pcf::IndiProperty &localProperty, ///< [out] The local property to update
942  T &localTarget, ///< [out] The local value to update
943  const pcf::IndiProperty &remoteProperty, ///< [in] the new property received
944  bool setBusy = true ///< [in] [optional] set property to busy if true
945  );
946 
947  /// Send a newProperty command to another device (using the INDI Client interface)
948  /** Copies the input IndiProperty, then updates the element with the new value.
949  *
950  * \returns 0 on success.
951  * \returns -1 on an errory.
952  */
953  template <typename T>
954  int sendNewProperty( const pcf::IndiProperty &ipSend, ///< [in] The property to send a "new" INDI command for
955  const std::string &el, ///< [in] The element of the property to change
956  const T &newVal ///< [in] The value to request for the element.
957  );
958  /// Send a newProperty command to another device (using the INDI Client interface)
959  /**
960  *
961  * \returns 0 on success.
962  * \returns -1 on an error, which will be logged
963  */
964  int sendNewProperty( const pcf::IndiProperty &ipSend /**< [in] The property to send a "new" INDI command for */ );
965 
966  /// Send a new property commmand for a standard toggle switch
967  /**
968  * \returns 0 on success
969  * \returns -1 on an error, which will be logged.
970  */
971  int sendNewStandardIndiToggle( const std::string &device, ///< [in] The device name
972  const std::string &property, ///< [in] The property name
973  bool onoff ///< [in] Switch state to send: true = on, false = off
974  );
975 
976  /// indi Property to report the application state.
977  pcf::IndiProperty m_indiP_state;
978 
979  /// indi Property to clear an FSM alert.
980  pcf::IndiProperty m_indiP_clearFSMAlert;
981 
982  /// The static callback function to be registered for requesting to clear the FSM alert
983  /**
984  * \returns 0 on success.
985  * \returns -1 on error.
986  */
987  static int st_newCallBack_clearFSMAlert( void *app, /**< [in] a pointer to this, will be
988  static_cast-ed to MagAOXApp. */
989  const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with
990  the new property request. */
991  );
992 
993  /// The callback called by the static version, to actually process the FSM Alert Clear request.
994  /**
995  * \returns 0 on success.
996  * \returns -1 on error.
997  */
998  int newCallBack_clearFSMAlert( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with
999  the new property request.*/ );
1000 
1001  ///@} --INDI Interface
1002 
1003  /** \name Power Management
1004  * For devices which have remote power management (e.g. from one of the PDUs) we implement
1005  * a standard power state monitoring and management component for the FSM. This needs to be enabled
1006  * in the derived app constructor. To stay enabled, m_powerDevice and m_powerChannel must be
1007  * not empty strings after the configuration. These could be set in the derived app defaults.
1008  *
1009  * If power management is enabled, then while power is off, appLogic will not be called.
1010  * Instead a parrallel set of virtual functions is called, onPowerOff (to allow apps to
1011  * perform cleanup) and whilePowerOff (to allow apps to keep variables updated, etc).
1012  * Note that these could merely call appLogic if desired.
1013  *
1014  */
1015  protected:
1016  bool m_powerMgtEnabled{ false }; ///< Flag controls whether power mgt is used. Set this in the constructor of a
1017  ///< derived app. If true, then if after loadConfig the powerDevice and
1018  ///< powerChannel are empty, then the app will exit with a critical error.
1019 
1020  /* Configurables . . . */
1021  std::string m_powerDevice; ///< The INDI device name of the power controller
1022  std::string m_powerChannel; ///< The INDI property name of the channel controlling this device's power.
1023  std::string m_powerElement{ "state" }; ///< The INDI element name to monitor for this device's power state.
1024  std::string m_powerTargetElement{ "target" }; ///< The INDI element name to monitor for this device's power state.
1025 
1026  unsigned long m_powerOnWait{ 0 }; ///< Time in sec to wait for device to boot after power on.
1027 
1028  /* Power on waiting counter . . . */
1029  int m_powerOnCounter{ -1 }; ///< Counts numer of loops after power on, implements delay for device bootup. If -1,
1030  ///< then device was NOT powered off on app startup.
1031 
1032  /* Power state . . . */
1033  int m_powerState{ -1 }; ///< Current power state, 1=On, 0=Off, -1=Unk.
1034  int m_powerTargetState{ -1 }; ///< Current target power state, 1=On, 0=Off, -1=Unk.
1035 
1036  pcf::IndiProperty m_indiP_powerChannel; ///< INDI property used to communicate power state.
1037 
1038  /// This method is called when the change to poweroff is detected.
1039  /**
1040  * \returns 0 on success.
1041  * \returns -1 on any error which means the app should exit.
1042  */
1043  virtual int onPowerOff();
1044 
1045  /// This method is called while the power is off, once per FSM loop.
1046  /**
1047  * \returns 0 on success.
1048  * \returns -1 on any error which means the app should exit.
1049  */
1050  virtual int whilePowerOff();
1051 
1052  /// This method tests whether the power on wait time has elapsed.
1053  /** You would call this once per appLogic loop while in state POWERON. While false, you would return 0.
1054  * Once it becomes true, take post-power-on actions and go on with life.
1055  *
1056  * \returns true if the time since POWERON is greater than the power-on wait, or if power management is not enabled
1057  * \returns false otherwise
1058  */
1060 
1061  public:
1062  /// Returns the current power state.
1063  /** If power management is not enabled, this always returns 1=On.
1064  *
1065  * \returns -1 if power state is unknown
1066  * \returns 0 if power is off
1067  * \returns 1 if power is on or m_powerMgtEnabled==false
1068  */
1069  int powerState();
1070 
1071  /// Returns the target power state.
1072  /** If power management is not enabled, this always returns 1=On.
1073  *
1074  * \returns -1 if target power state is unknown
1075  * \returns 0 if target power state is off
1076  * \returns 1 if target power is on or m_powerMgtEnabled==false
1077  */
1079 
1080  INDI_SETCALLBACK_DECL( MagAOXApp, m_indiP_powerChannel );
1081 
1082  ///@} Power Management
1083 
1084  public:
1085  /** \name Member Accessors
1086  *
1087  * @{
1088  */
1089 
1090  /// Get the config name
1091  /**
1092  * \returns the current value of m_configName
1093  */
1094  std::string configName();
1095 
1096  /// Get the config directory
1097  /**
1098  * \returns the current value of m_configDir
1099  */
1100  std::string configDir();
1101 
1102  /// Get the INDI input FIFO file name
1103  /**
1104  * \returns the current value of m_driverInName
1105  */
1106  std::string driverInName();
1107 
1108  /// Get the INDI output FIFO file name
1109  /**
1110  * \returns the current value of m_driverOutName
1111  */
1112  std::string driverOutName();
1113 
1114  /// Get the INDI control FIFO file name
1115  /**
1116  * \returns the current value of m_driverCtrlName
1117  */
1118  std::string driverCtrlName();
1119 
1120  ///@} --Member Accessors
1121 };
1122 
1123 // Set self pointer to null so app starts up uninitialized.
1124 template <bool _useINDI>
1126 
1127 // Define the logger
1128 template <bool _useINDI>
1130 
1131 template <bool _useINDI>
1132 MagAOXApp<_useINDI>::MagAOXApp( const std::string &git_sha1, const bool git_modified )
1133 {
1134  if( m_self != nullptr )
1135  {
1136  std::cerr << "Attempt to instantiate 2nd MagAOXApp. Exiting immediately.\n";
1137  exit( -1 );
1138  }
1139 
1140  m_self = this;
1141 
1142  // Get the uids of this process.
1143  getresuid( &m_euidReal, &m_euidCalled, &m_suid );
1144  setEuidReal(); // immediately step down to unpriveleged uid.
1145 
1146  m_log.parent( this );
1147 
1148  // Set up config logging
1149  config.m_sources = true;
1150  config.configLog = configLog;
1151 
1152  // We log the current GIT status.
1154  if( git_modified )
1155  {
1156  gl = logPrio::LOG_WARNING;
1157  m_gitAlert = true;
1158  }
1159  log<git_state>( git_state::messageT( "MagAOX", git_sha1, git_modified ), gl );
1160 
1161  gl = logPrio::LOG_INFO;
1162  if( MXLIB_UNCOMP_REPO_MODIFIED )
1163  {
1164  gl = logPrio::LOG_WARNING;
1165  m_gitAlert = true;
1166  }
1167 
1168  log<git_state>( git_state::messageT( "mxlib", MXLIB_UNCOMP_CURRENT_SHA1, MXLIB_UNCOMP_REPO_MODIFIED ), gl );
1169 }
1170 
1171 template <bool _useINDI>
1173 {
1174  if( m_indiDriver )
1175  delete m_indiDriver;
1176  m_log.parent( nullptr );
1177 
1178  MagAOXApp<_useINDI>::m_self = nullptr;
1179 }
1180 
1181 template <bool _useINDI>
1183 {
1184  return m_shutdown;
1185 }
1186 
1187 template <bool _useINDI>
1189  char **argv ) // virtual
1190 {
1191  std::string tmpstr;
1192 
1193  tmpstr = mx::sys::getEnv( MAGAOX_env_path );
1194  if( tmpstr != "" )
1195  {
1196  MagAOXPath = tmpstr;
1197  }
1198  else
1199  {
1200  MagAOXPath = MAGAOX_path;
1201  }
1202 
1203  // Set the config path relative to MagAOXPath
1204  tmpstr = mx::sys::getEnv( MAGAOX_env_config );
1205  if( tmpstr == "" )
1206  {
1207  tmpstr = MAGAOX_configRelPath;
1208  }
1209  m_configDir = MagAOXPath + "/" + tmpstr;
1210  m_configPathGlobal = m_configDir + "/magaox.conf";
1211 
1212  // Set the calib path relative to MagAOXPath
1213  tmpstr = mx::sys::getEnv( MAGAOX_env_calib );
1214  if( tmpstr == "" )
1215  {
1216  tmpstr = MAGAOX_calibRelPath;
1217  }
1218  m_calibDir = MagAOXPath + "/" + tmpstr;
1219 
1220  // Setup default log path
1221  tmpstr = MagAOXPath + "/" + MAGAOX_logRelPath;
1222 
1223  m_log.logPath( tmpstr );
1224 
1225  // Setup default sys path
1226  tmpstr = MagAOXPath + "/" + MAGAOX_sysRelPath;
1227  sysPath = tmpstr;
1228 
1229  // Setup default secrets path
1230  tmpstr = MagAOXPath + "/" + MAGAOX_secretsRelPath;
1231  secretsPath = tmpstr;
1232 
1233  // Setup default cpuset path
1234  tmpstr = mx::sys::getEnv( MAGAOX_env_cpuset );
1235  if( tmpstr != "" )
1236  {
1237  m_cpusetPath = tmpstr;
1238  }
1239  // else we stick with the default
1240 
1241  if( m_configBase != "" )
1242  {
1243  // We use mx::application's configPathUser for this components base config file
1244  m_configPathUser = m_configDir + "/" + m_configBase + ".conf";
1245  }
1246 
1247  // Parse CL just to get the "name".
1248  config.add( "name",
1249  "n",
1250  "name",
1251  argType::Required,
1252  "",
1253  "",
1254  true,
1255  "string",
1256  "The name of the application and its device name in INDI (if used), specifies the config file in the XWC config directory." );
1257 
1258  config.parseCommandLine( argc, argv, "name" );
1259  config( m_configName, "name" );
1260 
1261  if( m_configName == "" )
1262  {
1263  m_configName = mx::ioutils::pathStem( invokedName );
1264  if(!doHelp)
1265  {
1266  log<text_log>( "Configuration Error: Application name (-n --name) not set." );
1267  doHelp = true;
1268  }
1269  }
1270 
1271  // We use mx::application's configPathLocal for this component's config file
1272  m_configPathLocal = m_configDir + "/" + m_configName + ".conf";
1273 
1274  // Now we can setup common INDI properties
1275  if( registerIndiPropertyNew(
1276  m_indiP_state, "fsm", pcf::IndiProperty::Text, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle, 0 ) <
1277  0 )
1278  {
1279  log<software_error>( { __FILE__, __LINE__, "failed to register read only fsm_state property" } );
1280  }
1281 
1282  m_indiP_state.add( pcf::IndiElement( "state" ) );
1283 
1284  createStandardIndiRequestSw( m_indiP_clearFSMAlert, "fsm_clear_alert", "Clear FSM Alert", "FSM" );
1285  if( registerIndiPropertyNew( m_indiP_clearFSMAlert, st_newCallBack_clearFSMAlert ) < 0 )
1286  {
1287  log<software_error>( { __FILE__, __LINE__, "failed to register new fsm_alert property" } );
1288  }
1289 
1290  return;
1291 }
1292 
1293 template <bool _useINDI>
1295 {
1296  // Validate config
1297  config.add( "config.validate",
1298  "",
1299  "config.validate",
1300  argType::True,
1301  "",
1302  "",
1303  false,
1304  "bool",
1305  "Validate the configuration. App will exit after loading the configuration, but before entering the event loop. Errors from configuratin processing will be shown. Always safe to run." );
1306 
1307  // App stuff
1308  config.add( "loopPause",
1309  "p",
1310  "loopPause",
1311  argType::Required,
1312  "",
1313  "loopPause",
1314  false,
1315  "unsigned long",
1316  "The main loop pause time in ns" );
1317 
1318  config.add(
1319  "ignore_git", "", "ignore-git", argType::True, "", "", false, "bool", "set to true to ignore git status to prevent the fsm_alert" );
1320 
1321  // Logger Stuff
1322  m_log.setupConfig( config );
1323 
1324  if( m_powerMgtEnabled )
1325  {
1326  if( _useINDI == false )
1327  {
1328  // If this condition obtains, we should not go on because it means we'll never leave power off!!!
1329  log<software_critical>( { __FILE__, __LINE__, "power management is enabled but we are not using INDI" } );
1330  m_shutdown = true;
1331  }
1332 
1333  // Power Management
1334  config.add( "power.device",
1335  "",
1336  "power.device",
1337  argType::Required,
1338  "power",
1339  "device",
1340  false,
1341  "string",
1342  "Device controlling power for this app's device (INDI name)." );
1343  config.add( "power.channel",
1344  "",
1345  "power.channel",
1346  argType::Required,
1347  "power",
1348  "channel",
1349  false,
1350  "string",
1351  "Channel on device for this app's device (INDI name)." );
1352  config.add( "power.element",
1353  "",
1354  "power.element",
1355  argType::Required,
1356  "power",
1357  "element",
1358  false,
1359  "string",
1360  "INDI power state element name. Default is \"state\", only need to specify if different." );
1361  config.add( "power.targetElement",
1362  "",
1363  "power.targetElement",
1364  argType::Required,
1365  "power",
1366  "targetElement",
1367  false,
1368  "string",
1369  "INDI power target element name. Default is \"target\", only need to specify if different." );
1370  config.add( "power.powerOnWait",
1371  "",
1372  "power.powerOnWait",
1373  argType::Required,
1374  "power",
1375  "powerOnWait",
1376  false,
1377  "int",
1378  "Time after power-on to wait before continuing [sec]. Default is 0 sec, max is 3600 sec." );
1379  }
1380 }
1381 
1382 template <bool _useINDI>
1384 {
1385  //--------- Ignore Git State --------//
1386  bool ig{ false };
1387  config( ig, "ignore_git" );
1388 
1389  if( !ig && m_gitAlert )
1390  {
1391  m_stateAlert = true;
1392  }
1393 
1394  //--------- Config Validation Mode --------//
1395  if(config.isSet("config.validate"))
1396  {
1397  m_configOnly = true; //m_configOnly is from mx::application
1398  }
1399 
1400  //---------- Setup the logger ----------//
1401  m_log.logName( m_configName );
1402  m_log.loadConfig( config );
1403 
1404  //--------- Loop Pause Time --------//
1405  config( m_loopPause, "loopPause" );
1406 
1407  //--------Power Management --------//
1408  if( m_powerMgtEnabled )
1409  {
1410  config( m_powerDevice, "power.device" );
1411  config( m_powerChannel, "power.channel" );
1412  config( m_powerElement, "power.element" );
1413  config( m_powerTargetElement, "power.targetElement" );
1414 
1415  if( m_powerDevice != "" && m_powerChannel != "" )
1416  {
1417  log<text_log>( "enabling power management: " + m_powerDevice + "." + m_powerChannel + "." + m_powerElement +
1418  "/" + m_powerTargetElement );
1419  if( registerIndiPropertySet(
1420  m_indiP_powerChannel, m_powerDevice, m_powerChannel, INDI_SETCALLBACK( m_indiP_powerChannel ) ) <
1421  0 )
1422  {
1423  log<software_error>( { __FILE__, __LINE__, "failed to register set property" } );
1424  }
1425  }
1426  else
1427  {
1428  log<text_log>( "power management not configured!", logPrio::LOG_CRITICAL );
1429  m_shutdown = true;
1430  }
1431 
1432  config( m_powerOnWait, "power.powerOnWait" );
1433  if( m_powerOnWait > 3600 )
1434  {
1435  log<text_log>( "powerOnWait longer than 1 hour. Setting to 0.", logPrio::LOG_ERROR );
1436  }
1437  }
1438 }
1439 
1440 template <bool _useINDI>
1442 {
1443  // This checks for unused but valid config options and arguments, and logs them.
1444  // This will catch options we aren't actually using but are configured(debugging).
1445  for( auto it = config.m_targets.begin(); it != config.m_targets.end(); ++it )
1446  {
1447  if( it->second.used == false )
1448  {
1449  std::string msg = it->second.name;
1450  if( config.m_sources && it->second.sources.size() > 0 )
1451  {
1452  msg += " [" + it->second.sources[0] + "]";
1453  }
1454  log<text_log>( "Unused config target: " + msg, logPrio::LOG_WARNING );
1455  }
1456  }
1457 
1458  // This checks for invalid/unknown config options and arguments, and logs them.
1459  // This diagnosis problems in the config file
1460  if( config.m_unusedConfigs.size() > 0 )
1461  {
1462  for( auto it = config.m_unusedConfigs.begin(); it != config.m_unusedConfigs.end(); ++it )
1463  {
1464  if( it->second.used == true )
1465  {
1466  continue;
1467  }
1468 
1469  std::string msg = it->second.name;
1470  if( config.m_sources && it->second.sources.size() > 0 )
1471  {
1472  msg += " [" + it->second.sources[0] + "]";
1473  }
1474  log<text_log>( "Unrecognized config setting: " + msg, logPrio::LOG_CRITICAL );
1475 
1476  m_shutdown = true;
1477  }
1478  }
1479 
1480  //MagAO-X does not use non-option CLI arguments. Presence probably points to a typo (missing - or --).
1481  if( config.nonOptions.size() > 0 )
1482  {
1483  for( size_t n = 0; n < config.nonOptions.size(); ++n )
1484  {
1485  log<text_log>( "Unrecognized command line argument: " + config.nonOptions[n], logPrio::LOG_CRITICAL );
1486  }
1487  m_shutdown = true;
1488  }
1489 
1490  if(m_configOnly) //validation mode
1491  {
1492  if(m_shutdown == true)
1493  {
1494  std::cerr << "\nThere were configuration errors.\n\n";
1495  }
1496  else
1497  {
1498  std::cerr << "\nConfiguration is valid.\n\n";
1499  }
1500  }
1501  else if(m_shutdown == true)
1502  {
1503  doHelp = true; //Causes mx::application to print help and exit.
1504  }
1505 }
1506 
1507 template <bool _useINDI>
1509 {
1510 //----------------------------------------//
1511 // Check user
1512 //----------------------------------------//
1513 #ifndef XWC_DISABLE_USER_CHECK
1514  struct stat logstat;
1515 
1516  if( stat( m_log.logPath().c_str(), &logstat ) < 0 )
1517  {
1518  state( stateCodes::FAILURE );
1519  std::cerr << "\nCRITICAL: Can not stat the log path.\n\n";
1520  return -1;
1521  }
1522 
1523  if( logstat.st_uid != geteuid() )
1524  {
1525  state( stateCodes::FAILURE );
1526  std::cerr << "\nCRITICAL: You are running this app as the wrong user.\n\n";
1527  return -1;
1528  }
1529 #endif
1530 
1531  //----------------------------------------//
1532  // Get the PID Lock
1533  //----------------------------------------//
1534  if( lockPID() < 0 )
1535  {
1536  state( stateCodes::FAILURE );
1537 
1538  // We don't log this, because it won't be logged anyway.
1539  std::cerr << "\nCRITICAL: Failed to lock PID. Exiting.\n\n";
1540 
1541  // Return immediately, not safe to go on.
1542  return -1;
1543  }
1544 
1545  /* ***************************** */
1546  /* start logging */
1547  /* ***************************** */
1548  m_log.logThreadStart(); // no return type
1549 
1550  // Give up to 2 secs to make sure log thread has time to get started and try to open a file.
1551  int w = 0;
1552  while( m_log.logThreadRunning() == false && w < 20 )
1553  {
1554  // Sleep for 100 msec
1555  std::this_thread::sleep_for( std::chrono::duration<unsigned long, std::nano>( 100000000 ) );
1556  ++w;
1557  }
1558 
1559  if( m_log.logThreadRunning() == false )
1560  {
1561  state( stateCodes::FAILURE );
1562 
1563  // We don't log this, because it won't be logged anyway.
1564  std::cerr << "\nCRITICAL: log thread not running. Exiting.\n\n";
1565 
1566  m_shutdown = 1; //just in case, though this should not have an effect yet.
1567 
1568  if( unlockPID() < 0 )
1569  {
1570  log<software_error>( { __FILE__, __LINE__, "error from unlockPID()" } );
1571  }
1572 
1573  return -1;
1574  }
1575 
1576  /* ***************************** */
1577  /* signal handling */
1578  /* ***************************** */
1579  if( m_shutdown == 0 )
1580  {
1581  if( setSigTermHandler() < 0 )
1582  {
1583  state( stateCodes::FAILURE );
1584 
1585  log<software_critical>( { __FILE__, __LINE__, "error from setSigTermHandler()" } );
1586 
1587  m_shutdown = 1; //just in case, though this should not have an effect yet.
1588 
1589  if( unlockPID() < 0 )
1590  {
1591  log<software_error>( { __FILE__, __LINE__, "error from unlockPID()" } );
1592  }
1593 
1594  return -1;
1595  }
1596  }
1597 
1598  /* ***************************** */
1599  /* appStartup() */
1600  /* ***************************** */
1601  if( m_shutdown == 0 )
1602  {
1603  state( stateCodes::INITIALIZED );
1604 
1605  if( appStartup() < 0 )
1606  {
1607  state( stateCodes::FAILURE );
1608 
1609  log<software_critical>( { __FILE__, __LINE__, "error from appStartup()" } );
1610 
1611  m_shutdown = 1; //just in case, though this should not have an effect yet.
1612 
1613  if( unlockPID() < 0 )
1614  {
1615  log<software_error>( { __FILE__, __LINE__, "error from unlockPID()" } );
1616  }
1617 
1618  return -1;
1619  }
1620  }
1621 
1622  //====Begin INDI Communications
1623  if( m_useINDI && m_shutdown == 0 ) // if we're using INDI and not already dead, that is
1624  {
1625  if( startINDI() < 0 )
1626  {
1627  state( stateCodes::FAILURE );
1628 
1629  log<software_critical>( { __FILE__, __LINE__, "INDI failed to start." } );
1630 
1631  m_shutdown = 1; //have to set so that child event loops know to exit
1632 
1633  // Have to call appShutdown since appStartup was called
1634  if( appShutdown() < 0 )
1635  {
1636  log<software_error>( { __FILE__, __LINE__, "error from appShutdown()" } );
1637  }
1638 
1639  if( unlockPID() < 0 )
1640  {
1641  log<software_error>( { __FILE__, __LINE__, "error from unlockPID()" } );
1642  }
1643 
1644  return -1;
1645  }
1646  }
1647 
1648  // We have to wait for power status to become available
1649  if( m_powerMgtEnabled && m_shutdown == 0 )
1650  {
1651  int nwaits = 0;
1652  while( m_powerState < 0 && !m_shutdown )
1653  {
1654  sleep( 1 );
1655  if( m_powerState < 0 )
1656  {
1657  if( !stateLogged() )
1658  log<text_log>( "waiting for power state" );
1659  }
1660 
1661  ++nwaits;
1662  if( nwaits == 30 )
1663  {
1664  log<text_log>( "stalled waiting for power state", logPrio::LOG_ERROR );
1665  state( stateCodes::ERROR );
1666  }
1667  }
1668 
1669  if( m_powerState > 0 )
1670  {
1671  state( stateCodes::POWERON );
1672  }
1673  else
1674  {
1675  m_powerOnCounter = 0;
1676  state( stateCodes::POWEROFF );
1677  if( onPowerOff() < 0 )
1678  {
1679  log<software_error>( { __FILE__, __LINE__, "error from onPowerOff()" } );
1680  m_shutdown = 1;
1681  }
1682  }
1683  }
1684 
1685  // This is the main event loop.
1686  /* Conditions on entry:
1687  * -- PID locked
1688  * -- Log thread running
1689  * -- Signal handling installed
1690  * -- appStartup() successful
1691  * -- INDI communications started successfully (if being used)
1692  * -- power state known (if being managed)
1693  */
1694  while( m_shutdown == 0 )
1695  {
1696  // First check power state.
1697  if( m_powerMgtEnabled )
1698  {
1699  if( state() == stateCodes::POWEROFF )
1700  {
1701  if( m_powerState == 1 )
1702  {
1703  m_powerOnCounter = 0;
1704  state( stateCodes::POWERON );
1705  }
1706  }
1707  else // Any other state
1708  {
1709  if( m_powerState == 0 )
1710  {
1711  state( stateCodes::POWEROFF );
1712  if( onPowerOff() < 0 )
1713  {
1714  log<software_error>( { __FILE__, __LINE__, "error from onPowerOff()" } );
1715  m_shutdown = 1;
1716  continue;
1717  }
1718  }
1719  // We don't do anything if m_powerState is -1, which is a startup condition.
1720  }
1721  }
1722 
1723  // Only run appLogic if power is on, or we are not managing power.
1724  if( !m_powerMgtEnabled || m_powerState > 0 )
1725  {
1726  if( appLogic() < 0 )
1727  {
1728  log<software_error>( { __FILE__, __LINE__, "error from appLogic()" } );
1729  m_shutdown = 1;
1730  continue;
1731  }
1732  }
1733  else if( m_powerState == 0 )
1734  {
1735  if( whilePowerOff() < 0 )
1736  {
1737  log<software_error>( { __FILE__, __LINE__, "error from whilePowerOff()" } );
1738  m_shutdown = 1;
1739  continue;
1740  }
1741  }
1742 
1743  /** \todo Need a heartbeat update here.
1744  */
1745 
1746  if( m_useINDI )
1747  {
1748  // Checkup on the INDI properties we're monitoring.
1749  // This will make sure we are up-to-date if indiserver restarts without us.
1750  // And handles cases where we miss a Def becuase the other driver wasn't started up
1751  // when we sent our Get.
1752  sendGetPropertySetList( false ); // Only does anything if it needs to be done.
1753  }
1754 
1755  // This is purely to make sure INDI is up to date in case
1756  // mutex was locked on last attempt.
1757  state( state() );
1758 
1759  // Pause loop unless shutdown is set
1760  if( m_shutdown == 0 )
1761  {
1762  std::this_thread::sleep_for( std::chrono::duration<unsigned long, std::nano>( m_loopPause ) );
1763  }
1764  }
1765 
1766  if( appShutdown() < 0 )
1767  {
1768  log<software_error>( { __FILE__, __LINE__, "error from appShutdown()" } );
1769  }
1770 
1771  state( stateCodes::SHUTDOWN );
1772 
1773  // Stop INDI communications
1774  if( m_indiDriver != nullptr )
1775  {
1776  pcf::IndiProperty ipSend;
1777  ipSend.setDevice( m_configName );
1778  try
1779  {
1780  m_indiDriver->sendDelProperty( ipSend );
1781  }
1782  catch( const std::exception &e )
1783  {
1784  log<software_error>(
1785  { __FILE__, __LINE__, std::string( "exception caught from sendDelProperty: " ) + e.what() } );
1786  }
1787 
1788  m_indiDriver->quitProcess();
1789  m_indiDriver->deactivate();
1790  log<indidriver_stop>();
1791  }
1792 
1793  if( unlockPID() < 0 )
1794  {
1795  log<software_error>( { __FILE__, __LINE__, "error from unlockPID()" } );
1796  }
1797 
1798  sleep( 1 );
1799  return 0;
1800 }
1801 
1802 template <bool _useINDI>
1803 template <typename logT, int retval>
1804 int MagAOXApp<_useINDI>::log( const typename logT::messageT &msg, logPrioT level )
1805 {
1806  m_log.template log<logT>( msg, level );
1807  return retval;
1808 }
1809 
1810 template <bool _useINDI>
1811 template <typename logT, int retval>
1813 {
1814  m_log.template log<logT>( level );
1815  return retval;
1816 }
1817 
1818 template <bool _useINDI>
1820 {
1821  if( logHeader::logLevel( b ) <= logPrio::LOG_NOTICE )
1822  {
1823  logStdFormat( std::cerr, b );
1824  std::cerr << "\n";
1825  }
1826 
1827  if( logHeader::logLevel( b ) < logPrio::LOG_ERROR )
1828  {
1829  state( m_state, true ); // For anything worse than error, we set the FSM state to alert
1830  }
1831 
1832  if( _useINDI && m_indiDriver )
1833  {
1834  pcf::IndiProperty msg;
1835  msg.setDevice( m_configName );
1836 
1837  std::stringstream logstdf;
1838  logMinStdFormat( logstdf, b );
1839 
1840  msg.setMessage( logstdf.str() );
1841 
1842  // Set the INDI prop timespec to match the log entry
1843  timespecX ts = logHeader::timespec( b );
1844  timeval tv;
1845  tv.tv_sec = ts.time_s;
1846  tv.tv_usec = (long int)( ( (double)ts.time_ns ) / 1e3 );
1847 
1848  msg.setTimeStamp( pcf::TimeStamp( tv ) );
1849 
1850  try
1851  {
1852  m_indiDriver->sendMessage( msg );
1853  }
1854  catch( const std::exception &e )
1855  {
1856  log<software_error>(
1857  { __FILE__, __LINE__, std::string( "exception caught from sendMessage: " ) + e.what() } );
1858  }
1859  }
1860 }
1861 
1862 template <bool _useINDI>
1863 void MagAOXApp<_useINDI>::configLog( const std::string &name,
1864  const int &code,
1865  const std::string &value,
1866  const std::string &source )
1867 {
1868  m_log.template log<config_log>( { name, code, value, source } );
1869 }
1870 
1871 template <bool _useINDI>
1873 {
1874  struct sigaction act;
1875  sigset_t set;
1876 
1877  act.sa_sigaction = &MagAOXApp<_useINDI>::_handlerSigTerm;
1878  act.sa_flags = SA_SIGINFO;
1879  sigemptyset( &set );
1880  act.sa_mask = set;
1881 
1882  errno = 0;
1883  if( sigaction( SIGTERM, &act, 0 ) < 0 )
1884  {
1885  std::string logss = "Setting handler for SIGTERM failed. Errno says: ";
1886  logss += strerror( errno );
1887 
1888  log<software_error>( { __FILE__, __LINE__, errno, 0, logss } );
1889 
1890  return -1;
1891  }
1892 
1893  errno = 0;
1894  if( sigaction( SIGQUIT, &act, 0 ) < 0 )
1895  {
1896  std::string logss = "Setting handler for SIGQUIT failed. Errno says: ";
1897  logss += strerror( errno );
1898 
1899  log<software_error>( { __FILE__, __LINE__, errno, 0, logss } );
1900 
1901  return -1;
1902  }
1903 
1904  errno = 0;
1905  if( sigaction( SIGINT, &act, 0 ) < 0 )
1906  {
1907  std::string logss = "Setting handler for SIGINT failed. Errno says: ";
1908  logss += strerror( errno );
1909 
1910  log<software_error>( { __FILE__, __LINE__, errno, 0, logss } );
1911 
1912  return -1;
1913  }
1914 
1915  log<text_log>( "Installed SIGTERM/SIGQUIT/SIGINT signal handler.", logPrio::LOG_DEBUG );
1916 
1917  return 0;
1918 }
1919 
1920 template <bool _useINDI>
1921 void MagAOXApp<_useINDI>::_handlerSigTerm( int signum, siginfo_t *siginf, void *ucont )
1922 {
1923  m_self->handlerSigTerm( signum, siginf, ucont );
1924 }
1925 
1926 template <bool _useINDI>
1928  siginfo_t *siginf __attribute__( ( unused ) ),
1929  void *ucont __attribute__( ( unused ) ) )
1930 {
1931  m_shutdown = 1;
1932 
1933  std::string signame;
1934  switch( signum )
1935  {
1936  case SIGTERM:
1937  signame = "SIGTERM";
1938  break;
1939  case SIGINT:
1940  signame = "SIGINT";
1941  break;
1942  case SIGQUIT:
1943  signame = "SIGQUIT";
1944  break;
1945  default:
1946  signame = "OTHER";
1947  }
1948 
1949  std::string logss = "Caught signal ";
1950  logss += signame;
1951  logss += ". Shutting down.";
1952 
1953  std::cerr << "\n" << logss << std::endl;
1954  log<text_log>( logss );
1955 }
1956 
1957 /// Empty signal handler. SIGUSR1 is used to interrupt sleep in various threads.
1958 void sigUsr1Handler( int signum, siginfo_t *siginf, void *ucont );
1959 
1960 template <bool _useINDI>
1962 {
1963  errno = 0;
1964  if( sys::th_seteuid( m_euidCalled ) < 0 )
1965  {
1966  std::string logss = "Setting effective user id to euidCalled (";
1967  logss += mx::ioutils::convertToString<int>( m_euidCalled );
1968  logss += ") failed. Errno says: ";
1969  logss += strerror( errno );
1970 
1971  log<software_error>( { __FILE__, __LINE__, errno, 0, logss } );
1972 
1973  return -1;
1974  }
1975 
1976  return 0;
1977 }
1978 
1979 template <bool _useINDI>
1981 {
1982  errno = 0;
1983  if( sys::th_seteuid( m_euidReal ) < 0 )
1984  {
1985  std::string logss = "Setting effective user id to euidReal (";
1986  logss += mx::ioutils::convertToString<int>( m_euidReal );
1987  logss += ") failed. Errno says: ";
1988  logss += strerror( errno );
1989 
1990  log<software_error>( { __FILE__, __LINE__, errno, 0, logss } );
1991 
1992  return -1;
1993  }
1994 
1995  return 0;
1996 }
1997 
1998 template <bool _useINDI>
2000 {
2001  m_pid = getpid();
2002 
2003  std::string statusDir = sysPath;
2004 
2005  // Get the maximum privileges available
2006  elevatedPrivileges elPriv( this );
2007 
2008  // Create statusDir root with read/write/search permissions for owner and group, and with read/search permissions
2009  // for others.
2010  errno = 0;
2011  if( mkdir( statusDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) < 0 )
2012  {
2013  if( errno != EEXIST )
2014  {
2015  std::stringstream logss;
2016  logss << "Failed to create root of statusDir (" << statusDir << "). Errno says: " << strerror( errno );
2017  log<software_critical>( { __FILE__, __LINE__, errno, 0, logss.str() } );
2018  return -1;
2019  }
2020  }
2021 
2022  statusDir += "/";
2023  statusDir += m_configName;
2024 
2025  pidFileName = statusDir + "/pid";
2026 
2027  // Create statusDir with read/write/search permissions for owner and group, and with read/search permissions for
2028  // others.
2029  errno = 0;
2030  if( mkdir( statusDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) < 0 )
2031  {
2032  if( errno != EEXIST )
2033  {
2034  std::stringstream logss;
2035  logss << "Failed to create statusDir (" << statusDir << "). Errno says: " << strerror( errno );
2036  log<software_critical>( { __FILE__, __LINE__, errno, 0, logss.str() } );
2037  return -1;
2038  }
2039 
2040  // If here, then we need to check the pid file.
2041 
2042  std::ifstream pidIn;
2043  pidIn.open( pidFileName );
2044 
2045  if( pidIn.good() ) // PID file exists, now read its contents and compare to proc/<pid>/cmdline
2046  {
2047  // Read PID from file
2048  pid_t testPid;
2049  pidIn >> testPid;
2050  pidIn.close();
2051 
2052  // Get command line used to start this process from /proc
2053  std::stringstream procN;
2054  procN << "/proc/" << testPid << "/cmdline";
2055 
2056  std::ifstream procIn;
2057  std::string pidCmdLine;
2058 
2059  try
2060  {
2061  procIn.open( procN.str() );
2062  if( procIn.good() )
2063  procIn >> pidCmdLine;
2064  procIn.close();
2065  }
2066  catch( ... )
2067  {
2068  log<software_critical>( { __FILE__, __LINE__, 0, 0, "exception caught testing /proc/pid" } );
2069  return -1;
2070  }
2071 
2072  // If pidCmdLine == "" at this point we just allow the rest of the
2073  // logic to run...
2074 
2075  // Search for invokedName in command line.
2076  size_t invokedPos = pidCmdLine.find( invokedName );
2077 
2078  // If invokedName found, then we check for configName.
2079  size_t configPos = std::string::npos;
2080  if( invokedPos != std::string::npos )
2081  configPos = pidCmdLine.find( m_configName );
2082 
2083  // Check if PID is already locked by this program+config combo:
2084  if( invokedPos != std::string::npos && configPos != std::string::npos )
2085  {
2086  // This means that this app already exists for this config, and we need to die.
2087  std::stringstream logss;
2088  logss << "PID already locked (" << testPid << "). Time to die.";
2089  std::cerr << logss.str() << std::endl;
2090 
2091  log<text_log>( logss.str(), logPrio::LOG_CRITICAL );
2092 
2093  return -1;
2094  }
2095  }
2096  else
2097  {
2098  // No PID File so we should just go on.
2099  pidIn.close();
2100  }
2101  }
2102 
2103  // Now write current PID to file and go on with life.
2104  std::ofstream pidOut;
2105  pidOut.open( pidFileName );
2106 
2107  if( !pidOut.good() )
2108  {
2109  log<software_critical>( { __FILE__, __LINE__, errno, 0, "could not open pid file for writing." } );
2110  // euidReal();
2111  return -1;
2112  }
2113 
2114  pidOut << m_pid;
2115 
2116  pidOut.close();
2117 
2118  std::stringstream logss;
2119  logss << "PID (" << m_pid << ") locked.";
2120  log<text_log>( logss.str() );
2121 
2122  // Go back to regular privileges
2123  /*if( euidReal() < 0 )
2124  {
2125  log<software_error>({__FILE__, __LINE__, 0, 0, "Seeting euid to real failed."});
2126  return -1;
2127  }*/
2128 
2129  return 0;
2130 }
2131 
2132 template <bool _useINDI>
2134 {
2135  { // scope for elPriv
2136 
2137  // Get the maximum privileges available
2138  elevatedPrivileges elPriv( this );
2139 
2140  if( ::remove( pidFileName.c_str() ) < 0 )
2141  {
2142  log<software_error>(
2143  { __FILE__, __LINE__, errno, 0, std::string( "Failed to remove PID file: " ) + strerror( errno ) } );
2144  return -1;
2145  }
2146  }
2147 
2148  std::stringstream logss;
2149  logss << "PID (" << m_pid << ") unlocked.";
2150  log<text_log>( logss.str() );
2151 
2152  return 0;
2153 }
2154 
2155 template <bool _useINDI>
2156 template <class thisPtr, class Function>
2157 int MagAOXApp<_useINDI>::threadStart( std::thread &thrd,
2158  bool &thrdInit,
2159  pid_t &tpid,
2160  pcf::IndiProperty &thProp,
2161  int thrdPrio,
2162  const std::string &cpuset,
2163  const std::string &thrdName,
2164  thisPtr *thrdThis,
2165  Function &&thrdStart )
2166 {
2167  thrdInit = true;
2168 
2169  tpid = 0;
2170 
2171  try
2172  {
2173  thrd = std::thread( thrdStart, thrdThis );
2174  }
2175  catch( const std::exception &e )
2176  {
2177  log<software_error>(
2178  { __FILE__, __LINE__, std::string( "Exception on " + thrdName + " thread start: " ) + e.what() } );
2179  return -1;
2180  }
2181  catch( ... )
2182  {
2183  log<software_error>( { __FILE__, __LINE__, "Unkown exception on " + thrdName + " thread start" } );
2184  return -1;
2185  }
2186 
2187  if( !thrd.joinable() )
2188  {
2189  log<software_error>( { __FILE__, __LINE__, thrdName + " thread did not start" } );
2190  return -1;
2191  }
2192 
2193  // Now set the RT priority.
2194 
2195  if( thrdPrio < 0 )
2196  thrdPrio = 0;
2197  if( thrdPrio > 99 )
2198  thrdPrio = 99;
2199 
2200  sched_param sp;
2201  sp.sched_priority = thrdPrio;
2202 
2203  int rv = 0;
2204 
2205  { // scope for elPriv
2206  // Get the maximum privileges available
2207  elevatedPrivileges elPriv( this );
2208 
2209  // We set return value based on result from sched_setscheduler
2210  // But we make sure to restore privileges no matter what happens.
2211  errno = 0;
2212  if( thrdPrio > 0 )
2213  rv = pthread_setschedparam( thrd.native_handle(), MAGAOX_RT_SCHED_POLICY, &sp );
2214  else
2215  rv = pthread_setschedparam( thrd.native_handle(), SCHED_OTHER, &sp );
2216  }
2217 
2218  if( rv < 0 )
2219  {
2220  log<software_error>(
2221  { __FILE__,
2222  __LINE__,
2223  errno,
2224  "Setting " + thrdName + " thread scheduler priority to " + std::to_string( thrdPrio ) + " failed." } );
2225  }
2226  else
2227  {
2228  log<text_log>( thrdName + " thread scheduler priority set to " + std::to_string( thrdPrio ) );
2229  }
2230 
2231  // Wait for tpid to be filled in, but only for one total second.
2232  if( tpid == 0 )
2233  {
2234  for( int i = 0; i < 10; ++i )
2235  {
2236  mx::sys::milliSleep( 100 );
2237  if( tpid != 0 )
2238  break;
2239  }
2240  }
2241 
2242  if( tpid == 0 )
2243  {
2244  return log<software_error, -1>( { __FILE__, __LINE__, errno, "tpid for " + thrdName + " not set." } );
2245  }
2246  else
2247  {
2248  log<text_log>( thrdName + " thread pid is " + std::to_string( tpid ) );
2249 
2250  if( _useINDI )
2251  {
2252  thProp = pcf::IndiProperty( pcf::IndiProperty::Number );
2253  thProp.setDevice( configName() );
2254  thProp.setName( std::string( "th-" ) + thrdName );
2255  thProp.setPerm( pcf::IndiProperty::ReadOnly );
2256  thProp.setState( pcf::IndiProperty::Idle );
2257  thProp.add( pcf::IndiElement( "pid" ) );
2258  thProp["pid"] = tpid;
2259  thProp.add( pcf::IndiElement( "prio" ) );
2260  thProp["prio"] = thrdPrio;
2261  registerIndiPropertyReadOnly( thProp );
2262  }
2263 
2264  if( cpuset != "" )
2265  {
2266  elevatedPrivileges ep( this );
2267  std::string cpuFile = m_cpusetPath;
2268  cpuFile += "/" + cpuset;
2269  cpuFile += "/tasks";
2270  int wfd = open( cpuFile.c_str(), O_WRONLY );
2271  if( wfd < 0 )
2272  {
2273  return log<software_error, -1>( { __FILE__, __LINE__, errno, "error from open for " + cpuFile } );
2274  }
2275 
2276  char pids[16];
2277  snprintf( pids, sizeof( pids ), "%d", tpid );
2278 
2279  int w = write( wfd, pids, strnlen( pids, sizeof( pids ) ) );
2280  if( w != (int)strlen( pids ) )
2281  {
2282  return log<software_error, -1>( { __FILE__, __LINE__, errno, "error on write" } );
2283  }
2284 
2285  close( wfd );
2286 
2287  log<text_log>( "moved " + thrdName + " to cpuset " + cpuset, logPrio::LOG_NOTICE );
2288  }
2289  }
2290 
2291  thrdInit = false;
2292 
2293  return 0;
2294 }
2295 
2296 template <bool _useINDI>
2298 {
2299  return m_state;
2300 }
2301 
2302 template <bool _useINDI>
2303 void MagAOXApp<_useINDI>::state( const stateCodes::stateCodeT &s, bool stateAlert )
2304 {
2305  // Only log anything if it's a change
2306  if( m_state != s )
2307  {
2309  if( s == stateCodes::ERROR )
2310  lvl = logPrio::LOG_ERROR;
2311  if( s == stateCodes::FAILURE )
2312  lvl = logPrio::LOG_CRITICAL;
2313 
2314  log<state_change>( { m_state, s }, lvl );
2315 
2316  m_state = s;
2317  m_stateLogged = 0;
2318  }
2319 
2320  if( m_stateAlert != stateAlert && stateAlert == true )
2321  {
2322  m_stateAlert = stateAlert;
2323  log<text_log>( "FSM alert set", logPrio::LOG_WARNING );
2324  }
2325 
2326  // Check to make sure INDI is up to date
2327  std::unique_lock<std::mutex> lock( m_indiMutex,
2328  std::try_to_lock ); // Lock the mutex before conducting INDI communications.
2329 
2330  // Note this is called every execute loop to make sure we update eventually
2331  if( lock.owns_lock() )
2332  {
2333  ///\todo move this to a function in stateCodes
2334  pcf::IndiProperty::PropertyStateType stst = INDI_IDLE;
2335 
2336  // If it's already in the "ALERT" state, then this can't take it out of it.
2337  if( m_stateAlert == true )
2338  {
2339  stst = INDI_ALERT;
2340  }
2341  else
2342  {
2343  if( m_state == stateCodes::READY )
2344  stst = INDI_OK;
2345  else if( m_state == stateCodes::OPERATING || m_state == stateCodes::HOMING ||
2346  m_state == stateCodes::CONFIGURING )
2347  stst = INDI_BUSY;
2348  else if( m_state < stateCodes::NODEVICE )
2349  stst = INDI_ALERT;
2350  else if( m_state <= stateCodes::LOGGEDIN )
2351  stst = INDI_IDLE;
2352  else if( m_state == stateCodes::NOTHOMED || m_state == stateCodes::SHUTDOWN )
2353  stst = INDI_IDLE;
2354  }
2355 
2356  updateIfChanged( m_indiP_state, "state", stateCodes::codeText( m_state ), stst );
2357  }
2358 }
2359 
2360 template <bool _useINDI>
2362 {
2363  if( m_stateLogged > 0 )
2364  {
2365  ++m_stateLogged;
2366  return m_stateLogged - 1;
2367  }
2368  else
2369  {
2370  m_stateLogged = 1;
2371  return 0;
2372  }
2373 }
2374 
2375 template <bool _useINDI>
2377 {
2378  if( m_stateAlert == false )
2379  return 0;
2380  m_stateAlert = false;
2381  log<text_log>( "FSM alert cleared", logPrio::LOG_WARNING );
2382 
2383  pcf::IndiProperty::PropertyStateType stst = INDI_IDLE;
2384 
2385  if( m_state == stateCodes::READY )
2386  stst = INDI_OK;
2387  else if( m_state == stateCodes::OPERATING || m_state == stateCodes::HOMING || m_state == stateCodes::CONFIGURING )
2388  stst = INDI_BUSY;
2389  else if( m_state < stateCodes::NODEVICE )
2390  stst = INDI_ALERT;
2391  else if( m_state <= stateCodes::LOGGEDIN )
2392  stst = INDI_IDLE;
2393  else if( m_state == stateCodes::NOTHOMED || m_state == stateCodes::SHUTDOWN )
2394  stst = INDI_IDLE;
2395 
2396  updateIfChanged( m_indiP_state, "state", stateCodes::codeText( m_state ), stst );
2397 
2398  return 0;
2399 }
2400 
2401 /*-------------------------------------------------------------------------------------*/
2402 /* INDI Support */
2403 /*-------------------------------------------------------------------------------------*/
2404 
2405 template <bool _useINDI>
2406 int MagAOXApp<_useINDI>::createStandardIndiText( pcf::IndiProperty &prop,
2407  const std::string &propName,
2408  const std::string &label,
2409  const std::string &group )
2410 {
2411  prop = pcf::IndiProperty( pcf::IndiProperty::Text );
2412  prop.setDevice( configName() );
2413  prop.setName( propName );
2414  prop.setPerm( pcf::IndiProperty::ReadWrite );
2415  prop.setState( pcf::IndiProperty::Idle );
2416  prop.add( pcf::IndiElement( "current" ) );
2417  prop.add( pcf::IndiElement( "target" ) );
2418 
2419  // Don't set "" just in case libcommon does something with defaults
2420  if( label != "" )
2421  {
2422  prop.setLabel( label );
2423  }
2424 
2425  if( group != "" )
2426  {
2427  prop.setGroup( group );
2428  }
2429 
2430  return 0;
2431 }
2432 
2433 template <bool _useINDI>
2434 int MagAOXApp<_useINDI>::createROIndiText( pcf::IndiProperty &prop,
2435  const std::string &propName,
2436  const std::string &elName,
2437  const std::string &propLabel,
2438  const std::string &propGroup,
2439  const std::string &elLabel )
2440 {
2441  prop = pcf::IndiProperty( pcf::IndiProperty::Text );
2442  prop.setDevice( configName() );
2443  prop.setName( propName );
2444  prop.setPerm( pcf::IndiProperty::ReadOnly );
2445  prop.setState( pcf::IndiProperty::Idle );
2446 
2447  // Don't set "" just in case libcommon does something with defaults
2448  if( propLabel != "" )
2449  {
2450  prop.setLabel( propLabel );
2451  }
2452 
2453  if( propGroup != "" )
2454  {
2455  prop.setGroup( propGroup );
2456  }
2457 
2458  prop.add( pcf::IndiElement( elName ) );
2459 
2460  if( elLabel != "" )
2461  {
2462  prop[elName].setLabel( elLabel );
2463  }
2464 
2465  return 0;
2466 }
2467 
2468 template <bool _useINDI>
2469 template <typename T>
2471  const std::string &name,
2472  const T &min,
2473  const T &max,
2474  const T &step,
2475  const std::string &format,
2476  const std::string &label,
2477  const std::string &group )
2478 {
2479  prop = pcf::IndiProperty( pcf::IndiProperty::Number );
2480  prop.setDevice( configName() );
2481  prop.setName( name );
2482  prop.setPerm( pcf::IndiProperty::ReadWrite );
2483  prop.setState( pcf::IndiProperty::Idle );
2484  prop.add( pcf::IndiElement( "current" ) );
2485  prop["current"].setMin( min );
2486  prop["current"].setMax( max );
2487  prop["current"].setStep( step );
2488  if( format != "" ) // don't override defaults
2489  {
2490  prop["current"].setFormat( format );
2491  }
2492 
2493  prop.add( pcf::IndiElement( "target" ) );
2494  prop["target"].setMin( min );
2495  prop["target"].setMax( max );
2496  prop["target"].setStep( step );
2497  if( format != "" ) // don't override defaults
2498  {
2499  prop["target"].setFormat( format );
2500  }
2501 
2502  // Don't set "" just in case libcommon does something with defaults
2503  if( label != "" )
2504  {
2505  prop.setLabel( label );
2506  }
2507 
2508  if( group != "" )
2509  {
2510  prop.setGroup( group );
2511  }
2512 
2513  return 0;
2514 }
2515 
2516 template <bool _useINDI>
2517 int MagAOXApp<_useINDI>::createROIndiNumber( pcf::IndiProperty &prop,
2518  const std::string &propName,
2519  const std::string &propLabel,
2520  const std::string &propGroup )
2521 {
2522  prop = pcf::IndiProperty( pcf::IndiProperty::Number );
2523  prop.setDevice( configName() );
2524  prop.setName( propName );
2525  prop.setPerm( pcf::IndiProperty::ReadOnly );
2526  prop.setState( pcf::IndiProperty::Idle );
2527 
2528  // Don't set "" just in case libcommon does something with defaults
2529  if( propLabel != "" )
2530  {
2531  prop.setLabel( propLabel );
2532  }
2533 
2534  if( propGroup != "" )
2535  {
2536  prop.setGroup( propGroup );
2537  }
2538 
2539  return 0;
2540 }
2541 
2542 template <bool _useINDI>
2544  const std::string &name,
2545  const std::string &label,
2546  const std::string &group )
2547 {
2548  prop = pcf::IndiProperty( pcf::IndiProperty::Switch );
2549  prop.setDevice( configName() );
2550  prop.setName( name );
2551  prop.setPerm( pcf::IndiProperty::ReadWrite );
2552  prop.setState( pcf::IndiProperty::Idle );
2553  prop.setRule( pcf::IndiProperty::AtMostOne );
2554 
2555  // Add the toggle element initialized to Off
2556  prop.add( pcf::IndiElement( "toggle", pcf::IndiElement::Off ) );
2557 
2558  // Don't set "" just in case libcommon does something with defaults
2559  if( label != "" )
2560  {
2561  prop.setLabel( label );
2562  }
2563 
2564  if( group != "" )
2565  {
2566  prop.setGroup( group );
2567  }
2568 
2569  return 0;
2570 }
2571 
2572 template <bool _useINDI>
2574  const std::string &name,
2575  const std::string &label,
2576  const std::string &group )
2577 {
2578  prop = pcf::IndiProperty( pcf::IndiProperty::Switch );
2579  prop.setDevice( configName() );
2580  prop.setName( name );
2581  prop.setPerm( pcf::IndiProperty::ReadWrite );
2582  prop.setState( pcf::IndiProperty::Idle );
2583  prop.setRule( pcf::IndiProperty::AtMostOne );
2584 
2585  // Add the toggle element initialized to Off
2586  prop.add( pcf::IndiElement( "request", pcf::IndiElement::Off ) );
2587 
2588  // Don't set "" just in case libcommon does something with defaults
2589  if( label != "" )
2590  {
2591  prop.setLabel( label );
2592  }
2593 
2594  if( group != "" )
2595  {
2596  prop.setGroup( group );
2597  }
2598 
2599  return 0;
2600 }
2601 
2602 template <bool _useINDI>
2604  const std::string &name,
2605  const std::vector<std::string> &elements,
2606  const std::vector<std::string> &elementLabels,
2607  const std::string &label,
2608  const std::string &group )
2609 {
2610  if( elements.size() == 0 )
2611  {
2612  return log<software_error, -1>( { __FILE__, __LINE__, "elements vector has zero size" } );
2613  }
2614 
2615  prop = pcf::IndiProperty( pcf::IndiProperty::Switch );
2616  prop.setDevice( configName() );
2617  prop.setName( name );
2618  prop.setPerm( pcf::IndiProperty::ReadWrite );
2619  prop.setState( pcf::IndiProperty::Idle );
2620  prop.setRule( pcf::IndiProperty::OneOfMany );
2621 
2622  // Add the toggle element initialized to Off
2623  for( size_t n = 0; n < elements.size(); ++n )
2624  {
2625  pcf::IndiElement elem = pcf::IndiElement( elements[n], pcf::IndiElement::Off );
2626  elem.setLabel( elementLabels[n] );
2627  prop.add( elem );
2628  }
2629 
2630  // Don't set "" just in case libcommon does something with defaults
2631  if( label != "" )
2632  {
2633  prop.setLabel( label );
2634  }
2635 
2636  if( group != "" )
2637  {
2638  prop.setGroup( group );
2639  }
2640 
2641  return 0;
2642 }
2643 
2644 template <bool _useINDI>
2646  const std::string &name,
2647  const std::vector<std::string> &elements,
2648  const std::string &label,
2649  const std::string &group )
2650 {
2651  return createStandardIndiSelectionSw( prop, name, elements, elements, label, group );
2652 }
2653 
2654 template <bool _useINDI>
2656 {
2657  if( !m_useINDI )
2658  return 0;
2659 
2660  callBackInsertResult result =
2661  m_indiNewCallBacks.insert( callBackValueType( prop.createUniqueKey(), { &prop, nullptr } ) );
2662 
2663  try
2664  {
2665  if( !result.second )
2666  {
2667  return log<software_error, -1>(
2668  { __FILE__, __LINE__, "failed to insert INDI property: " + prop.createUniqueKey() } );
2669  }
2670  }
2671  catch( std::exception &e )
2672  {
2673  return log<software_error, -1>( { __FILE__, __LINE__, std::string( "Exception caught: " ) + e.what() } );
2674  }
2675  catch( ... )
2676  {
2677  return log<software_error, -1>( { __FILE__, __LINE__, "Unknown exception caught." } );
2678  }
2679 
2680  return 0;
2681 }
2682 
2683 template <bool _useINDI>
2685  const std::string &propName,
2686  const pcf::IndiProperty::Type &propType,
2687  const pcf::IndiProperty::PropertyPermType &propPerm,
2688  const pcf::IndiProperty::PropertyStateType &propState )
2689 {
2690  if( !m_useINDI )
2691  return 0;
2692 
2693  prop = pcf::IndiProperty( propType );
2694  prop.setDevice( m_configName );
2695  prop.setName( propName );
2696  prop.setPerm( propPerm );
2697  prop.setState( propState );
2698 
2699  callBackInsertResult result = m_indiNewCallBacks.insert( callBackValueType( propName, { &prop, nullptr } ) );
2700 
2701  try
2702  {
2703  if( !result.second )
2704  {
2705  return log<software_error, -1>(
2706  { __FILE__, __LINE__, "failed to insert INDI property: " + prop.createUniqueKey() } );
2707  }
2708  }
2709  catch( std::exception &e )
2710  {
2711  return log<software_error, -1>( { __FILE__, __LINE__, std::string( "Exception caught: " ) + e.what() } );
2712  }
2713  catch( ... )
2714  {
2715  return log<software_error, -1>( { __FILE__, __LINE__, "Unknown exception caught." } );
2716  }
2717  return 0;
2718 }
2719 
2720 template <bool _useINDI>
2721 int MagAOXApp<_useINDI>::registerIndiPropertyNew( pcf::IndiProperty &prop,
2722  int ( *callBack )( void *, const pcf::IndiProperty &ipRecv ) )
2723 {
2724  if( !m_useINDI )
2725  return 0;
2726 
2727  try
2728  {
2729  callBackInsertResult result =
2730  m_indiNewCallBacks.insert( callBackValueType( prop.createUniqueKey(), { &prop, callBack } ) );
2731 
2732  if( !result.second )
2733  {
2734  return log<software_error, -1>(
2735  { __FILE__, __LINE__, "failed to insert INDI property: " + prop.createUniqueKey() } );
2736  }
2737  }
2738  catch( std::exception &e )
2739  {
2740  return log<software_error, -1>( { __FILE__, __LINE__, std::string( "Exception caught: " ) + e.what() } );
2741  }
2742  catch( ... )
2743  {
2744  return log<software_error, -1>( { __FILE__, __LINE__, "Unknown exception caught." } );
2745  }
2746 
2747  return 0;
2748 }
2749 
2750 template <bool _useINDI>
2751 int MagAOXApp<_useINDI>::registerIndiPropertyNew( pcf::IndiProperty &prop,
2752  const std::string &propName,
2753  const pcf::IndiProperty::Type &propType,
2754  const pcf::IndiProperty::PropertyPermType &propPerm,
2755  const pcf::IndiProperty::PropertyStateType &propState,
2756  int ( *callBack )( void *, const pcf::IndiProperty &ipRecv ) )
2757 {
2758  if( !m_useINDI )
2759  return 0;
2760 
2761  prop = pcf::IndiProperty( propType );
2762  prop.setDevice( m_configName );
2763  prop.setName( propName );
2764  prop.setPerm( propPerm );
2765  prop.setState( propState );
2766 
2767  return registerIndiPropertyNew( prop, callBack );
2768 }
2769 
2770 template <bool _useINDI>
2771 int MagAOXApp<_useINDI>::registerIndiPropertyNew( pcf::IndiProperty &prop,
2772  const std::string &propName,
2773  const pcf::IndiProperty::Type &propType,
2774  const pcf::IndiProperty::PropertyPermType &propPerm,
2775  const pcf::IndiProperty::PropertyStateType &propState,
2776  const pcf::IndiProperty::SwitchRuleType &propRule,
2777  int ( *callBack )( void *, const pcf::IndiProperty &ipRecv ) )
2778 {
2779  if( !m_useINDI )
2780  return 0;
2781 
2782  prop = pcf::IndiProperty( propType );
2783  prop.setDevice( m_configName );
2784  prop.setName( propName );
2785  prop.setPerm( propPerm );
2786  prop.setState( propState );
2787  prop.setRule( propRule );
2788  return registerIndiPropertyNew( prop, callBack );
2789 }
2790 
2791 template <bool _useINDI>
2793  const std::string &devName,
2794  const std::string &propName,
2795  int ( *callBack )( void *, const pcf::IndiProperty &ipRecv ) )
2796 {
2797  if( !m_useINDI )
2798  return 0;
2799 
2800  prop = pcf::IndiProperty();
2801  prop.setDevice( devName );
2802  prop.setName( propName );
2803 
2804  callBackInsertResult result =
2805  m_indiSetCallBacks.insert( callBackValueType( prop.createUniqueKey(), { &prop, callBack } ) );
2806 
2807  try
2808  {
2809  if( !result.second )
2810  {
2811  return log<software_error, -1>(
2812  { __FILE__, __LINE__, "failed to insert INDI property: " + prop.createUniqueKey() } );
2813  }
2814  }
2815  catch( std::exception &e )
2816  {
2817  return log<software_error, -1>( { __FILE__, __LINE__, std::string( "Exception caught: " ) + e.what() } );
2818  }
2819  catch( ... )
2820  {
2821  return log<software_error, -1>( { __FILE__, __LINE__, "Unknown exception caught." } );
2822  }
2823 
2824  return 0;
2825 }
2826 
2827 template <bool _useINDI>
2829 {
2830  if( !m_useINDI )
2831  return 0;
2832 
2833  ///\todo make driver FIFO path full configurable.
2834  std::string driverFIFOPath = MAGAOX_path;
2835  driverFIFOPath += "/";
2836  driverFIFOPath += MAGAOX_driverFIFORelPath;
2837 
2838  m_driverInName = driverFIFOPath + "/" + configName() + ".in";
2839  m_driverOutName = driverFIFOPath + "/" + configName() + ".out";
2840  m_driverCtrlName = driverFIFOPath + "/" + configName() + ".ctrl";
2841 
2842  // Get max permissions
2843  elevatedPrivileges elPriv( this );
2844 
2845  // Clear the file mode creation mask so mkfifo does what we want. Don't forget to restore it.
2846  mode_t prev = umask( 0 );
2847 
2848  errno = 0;
2849  if( mkfifo( m_driverInName.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP ) != 0 )
2850  {
2851  if( errno != EEXIST )
2852  {
2853  umask( prev );
2854  log<software_critical>( { __FILE__, __LINE__, errno, 0, "mkfifo failed" } );
2855  log<text_log>( "Failed to create input FIFO.", logPrio::LOG_CRITICAL );
2856  return -1;
2857  }
2858  }
2859 
2860  errno = 0;
2861  if( mkfifo( m_driverOutName.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP ) != 0 )
2862  {
2863  if( errno != EEXIST )
2864  {
2865  umask( prev );
2866  // euidReal();
2867  log<software_critical>( { __FILE__, __LINE__, errno, 0, "mkfifo failed" } );
2868  log<text_log>( "Failed to create ouput FIFO.", logPrio::LOG_CRITICAL );
2869  return -1;
2870  }
2871  }
2872 
2873  errno = 0;
2874  if( mkfifo( m_driverCtrlName.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP ) != 0 )
2875  {
2876  if( errno != EEXIST )
2877  {
2878  umask( prev );
2879  // euidReal();
2880  log<software_critical>( { __FILE__, __LINE__, errno, 0, "mkfifo failed" } );
2881  log<text_log>( "Failed to create ouput FIFO.", logPrio::LOG_CRITICAL );
2882  return -1;
2883  }
2884  }
2885 
2886  umask( prev );
2887  // euidReal();
2888  return 0;
2889 }
2890 
2891 template <bool _useINDI>
2893 {
2894  if( !m_useINDI )
2895  return 0;
2896 
2897  //===== Create the FIFOs for INDI communications ====
2898  if( createINDIFIFOS() < 0 )
2899  {
2900  return -1;
2901  }
2902 
2903  //======= Instantiate the indiDriver
2904  try
2905  {
2906  if( m_indiDriver != nullptr )
2907  {
2908  m_indiDriver->quitProcess();
2909  m_indiDriver->deactivate();
2910  log<indidriver_stop>();
2911  delete m_indiDriver;
2912  m_indiDriver = nullptr;
2913  }
2914 
2915  m_indiDriver = new indiDriver<MagAOXApp>( this, m_configName, "0", "0" );
2916  }
2917  catch( ... )
2918  {
2919  log<software_critical>( { __FILE__, __LINE__, 0, 0, "INDI Driver construction exception." } );
2920  return -1;
2921  }
2922 
2923  // Check for INDI failure
2924  if( m_indiDriver == nullptr )
2925  {
2926  log<software_critical>( { __FILE__, __LINE__, 0, 0, "INDI Driver construction failed." } );
2927  return -1;
2928  }
2929 
2930  // Check for INDI failure to open the FIFOs
2931  if( m_indiDriver->good() == false )
2932  {
2933  log<software_critical>( { __FILE__, __LINE__, 0, 0, "INDI Driver failed to open FIFOs." } );
2934  delete m_indiDriver;
2935  m_indiDriver = nullptr;
2936  return -1;
2937  }
2938 
2939  //======= Now we start talkin'
2940  m_indiDriver->activate();
2941  log<indidriver_start>();
2942 
2943  sendGetPropertySetList( true );
2944 
2945  return 0;
2946 }
2947 
2948 template <bool _useINDI>
2950 {
2951  // Unless forced by all, we only do anything if allDefs are not received yet
2952  if( !all && m_allDefsReceived )
2953  return;
2954 
2955  callBackIterator it = m_indiSetCallBacks.begin();
2956 
2957  int nowFalse = 0;
2958  while( it != m_indiSetCallBacks.end() )
2959  {
2960  if( all || it->second.m_defReceived == false )
2961  {
2962  if( it->second.property )
2963  {
2964  try
2965  {
2966  m_indiDriver->sendGetProperties( *( it->second.property ) );
2967  }
2968  catch( const std::exception &e )
2969  {
2970  log<software_error>( { __FILE__,
2971  __LINE__,
2972  "exception caught from sendGetProperties for " +
2973  it->second.property->getName() + ": " + e.what() } );
2974  }
2975  }
2976 
2977  it->second.m_defReceived = false;
2978  ++nowFalse;
2979  }
2980  ++it;
2981  }
2982  if( nowFalse != 0 )
2983  m_allDefsReceived = false;
2984  if( nowFalse == 0 )
2985  m_allDefsReceived = true;
2986 }
2987 
2988 template <bool _useINDI>
2989 void MagAOXApp<_useINDI>::handleDefProperty( const pcf::IndiProperty &ipRecv )
2990 {
2991  handleSetProperty( ipRecv ); // We have the same response to both Def and Set.
2992 }
2993 
2994 template <bool _useINDI>
2995 void MagAOXApp<_useINDI>::handleGetProperties( const pcf::IndiProperty &ipRecv )
2996 {
2997  if( !m_useINDI )
2998  return;
2999  if( m_indiDriver == nullptr )
3000  return;
3001 
3002  // Ignore if not our device
3003  if( ipRecv.hasValidDevice() && ipRecv.getDevice() != m_indiDriver->getName() )
3004  {
3005  return;
3006  }
3007 
3008  // Send all properties if requested.
3009  if( !ipRecv.hasValidName() )
3010  {
3011  callBackIterator it = m_indiNewCallBacks.begin();
3012 
3013  while( it != m_indiNewCallBacks.end() )
3014  {
3015  if( it->second.property )
3016  {
3017  try
3018  {
3019  m_indiDriver->sendDefProperty( *( it->second.property ) );
3020  }
3021  catch( const std::exception &e )
3022  {
3023  log<software_error>( { __FILE__,
3024  __LINE__,
3025  "exception caught from sendDefProperty for " +
3026  it->second.property->getName() + ": " + e.what() } );
3027  }
3028  }
3029  ++it;
3030  }
3031 
3032  // This is a possible INDI server restart, so we re-register for all notifications.
3033  sendGetPropertySetList( true );
3034 
3035  return;
3036  }
3037 
3038  // Check if we actually have this.
3039  if( m_indiNewCallBacks.count( ipRecv.createUniqueKey() ) == 0 )
3040  {
3041  return;
3042  }
3043 
3044  // Otherwise send just the requested property, if property is not null
3045  if( m_indiNewCallBacks[ipRecv.createUniqueKey()].property )
3046  {
3047  try
3048  {
3049  m_indiDriver->sendDefProperty( *( m_indiNewCallBacks[ipRecv.createUniqueKey()].property ) );
3050  }
3051  catch( const std::exception &e )
3052  {
3053  log<software_error>( { __FILE__,
3054  __LINE__,
3055  "exception caught from sendDefProperty for " +
3056  m_indiNewCallBacks[ipRecv.createUniqueKey()].property->getName() + ": " +
3057  e.what() } );
3058  }
3059  }
3060  return;
3061 }
3062 
3063 template <bool _useINDI>
3064 void MagAOXApp<_useINDI>::handleNewProperty( const pcf::IndiProperty &ipRecv )
3065 {
3066  if( !m_useINDI )
3067  return;
3068  if( m_indiDriver == nullptr )
3069  return;
3070 
3071  // Check if this is a valid name for us.
3072  if( m_indiNewCallBacks.count( ipRecv.createUniqueKey() ) == 0 )
3073  {
3074  log<software_debug>( { __FILE__, __LINE__, "invalid NewProperty request for " + ipRecv.createUniqueKey() } );
3075  return;
3076  }
3077 
3078  int ( *callBack )( void *, const pcf::IndiProperty & ) = m_indiNewCallBacks[ipRecv.createUniqueKey()].callBack;
3079 
3080  if( callBack )
3081  callBack( this, ipRecv );
3082 
3083  log<software_debug>( { __FILE__, __LINE__, "NewProperty callback null for " + ipRecv.createUniqueKey() } );
3084 
3085  return;
3086 }
3087 
3088 template <bool _useINDI>
3089 void MagAOXApp<_useINDI>::handleSetProperty( const pcf::IndiProperty &ipRecv )
3090 {
3091  if( !m_useINDI )
3092  return;
3093  if( m_indiDriver == nullptr )
3094  return;
3095 
3096  std::string key = ipRecv.createUniqueKey();
3097 
3098  // Check if this is valid
3099  if( m_indiSetCallBacks.count( key ) > 0 )
3100  {
3101  m_indiSetCallBacks[key].m_defReceived = true; // record that we got this Def/Set
3102 
3103  // And call the callback
3104  int ( *callBack )( void *, const pcf::IndiProperty & ) = m_indiSetCallBacks[key].callBack;
3105  if( callBack )
3106  callBack( this, ipRecv );
3107 
3108  ///\todo log an error here because callBack should not be null
3109  }
3110  else
3111  {
3112  ///\todo log invalid SetProperty request.
3113  }
3114 
3115  return;
3116 }
3117 
3118 template <bool _useINDI>
3119 template <typename T>
3120 void MagAOXApp<_useINDI>::updateIfChanged( pcf::IndiProperty &p,
3121  const std::string &el,
3122  const T &newVal,
3123  pcf::IndiProperty::PropertyStateType ipState )
3124 {
3125  if( !_useINDI )
3126  return;
3127 
3128  if( !m_indiDriver )
3129  return;
3130 
3131  indi::updateIfChanged( p, el, newVal, m_indiDriver, ipState );
3132 }
3133 
3134 template <bool _useINDI>
3135 void MagAOXApp<_useINDI>::updateIfChanged( pcf::IndiProperty &p,
3136  const std::string &el,
3137  const char *newVal,
3138  pcf::IndiProperty::PropertyStateType ipState )
3139 {
3140  updateIfChanged<std::string>( p, el, std::string( newVal ), ipState );
3141 }
3142 
3143 template <bool _useINDI>
3145  const std::string &el,
3146  const pcf::IndiElement::SwitchStateType &newVal,
3147  pcf::IndiProperty::PropertyStateType ipState )
3148 {
3149  if( !_useINDI )
3150  return;
3151 
3152  if( !m_indiDriver )
3153  return;
3154 
3155  indi::updateSwitchIfChanged( p, el, newVal, m_indiDriver, ipState );
3156 }
3157 
3158 template <bool _useINDI>
3159 template <typename T>
3160 void MagAOXApp<_useINDI>::updateIfChanged( pcf::IndiProperty &p,
3161  const std::string &el,
3162  const std::vector<T> &newVals,
3163  pcf::IndiProperty::PropertyStateType ipState )
3164 {
3165  if( !_useINDI )
3166  return;
3167 
3168  if( !m_indiDriver )
3169  return;
3170 
3171  std::vector<std::string> descriptors( newVals.size(), el );
3172  for( size_t index = 0; index < newVals.size(); ++index )
3173  {
3174  descriptors[index] += std::to_string( index );
3175  }
3176  indi::updateIfChanged( p, descriptors, newVals, m_indiDriver );
3177 }
3178 
3179 template <bool _useINDI>
3180 template <typename T>
3181 void MagAOXApp<_useINDI>::updateIfChanged( pcf::IndiProperty &p,
3182  const std::vector<std::string> &els,
3183  const std::vector<T> &newVals,
3184  pcf::IndiProperty::PropertyStateType newState )
3185 {
3186  if( !_useINDI )
3187  return;
3188 
3189  if( !m_indiDriver )
3190  return;
3191 
3192  indi::updateIfChanged( p, els, newVals, m_indiDriver, newState );
3193 }
3194 
3195 template <bool _useINDI>
3196 template <typename T>
3197 int MagAOXApp<_useINDI>::indiTargetUpdate( pcf::IndiProperty &localProperty,
3198  T &localTarget,
3199  const pcf::IndiProperty &remoteProperty,
3200  bool setBusy )
3201 {
3202  if( remoteProperty.createUniqueKey() != localProperty.createUniqueKey() )
3203  {
3204  return log<text_log, -1>( "INDI property names do not match", logPrio::LOG_ERROR );
3205  }
3206 
3207  if( !( remoteProperty.find( "target" ) || remoteProperty.find( "current" ) ) )
3208  {
3209  return log<text_log, -1>( "no target or current element in INDI property", logPrio::LOG_ERROR );
3210  }
3211 
3212  bool set = false;
3213 
3214  if( remoteProperty.find( "target" ) )
3215  {
3216  localTarget = remoteProperty["target"].get<T>();
3217  set = true;
3218  }
3219 
3220  if( !set )
3221  {
3222  if( remoteProperty.find( "current" ) )
3223  {
3224  localTarget = remoteProperty["current"].get<T>();
3225  set = true;
3226  }
3227  }
3228 
3229  if( !set )
3230  {
3231  return log<text_log, -1>( "no non-empty value found in INDI property", logPrio::LOG_ERROR );
3232  }
3233 
3234  if( setBusy )
3235  {
3236  updateIfChanged( localProperty, "target", localTarget, INDI_BUSY );
3237  }
3238  else
3239  {
3240  updateIfChanged( localProperty, "target", localTarget );
3241  }
3242 
3243  return 0;
3244 }
3245 
3246 /// \todo move propType to an INDI utils file, and document.
3247 
3248 template <typename T>
3249 pcf::IndiProperty::Type propType()
3250 {
3251  return pcf::IndiProperty::Unknown;
3252 }
3253 
3254 template <>
3255 pcf::IndiProperty::Type propType<char *>();
3256 
3257 template <>
3258 pcf::IndiProperty::Type propType<std::string>();
3259 
3260 template <>
3261 pcf::IndiProperty::Type propType<int>();
3262 
3263 template <>
3264 pcf::IndiProperty::Type propType<double>();
3265 
3266 template <bool _useINDI>
3267 template <typename T>
3268 int MagAOXApp<_useINDI>::sendNewProperty( const pcf::IndiProperty &ipSend, const std::string &el, const T &newVal )
3269 {
3270  if( !_useINDI )
3271  return 0;
3272 
3273  if( !m_indiDriver )
3274  {
3275  log<software_error>( { __FILE__, __LINE__, "INDI communications not initialized." } );
3276  return -1;
3277  }
3278  pcf::IndiProperty ipToSend = ipSend;
3279 
3280  try
3281  {
3282  ipToSend[el].setValue( newVal );
3283  }
3284  catch( ... )
3285  {
3286  log<software_error>(
3287  { __FILE__, __LINE__, "Exception caught setting " + ipSend.createUniqueKey() + "." + el } );
3288  return -1;
3289  }
3290 
3291  int rv = m_indiDriver->sendNewProperty( ipToSend );
3292  if( rv < 0 )
3293  {
3294  log<software_error>( { __FILE__, __LINE__ } );
3295  return -1;
3296  }
3297 
3298  return 0;
3299 }
3300 
3301 template <bool _useINDI>
3302 int MagAOXApp<_useINDI>::sendNewProperty( const pcf::IndiProperty &ipSend )
3303 {
3304  if( !_useINDI )
3305  return 0;
3306 
3307  if( !m_indiDriver )
3308  {
3309  return log<software_error, -1>( { __FILE__, __LINE__, "INDI communications not initialized." } );
3310  }
3311 
3312  if( m_indiDriver->sendNewProperty( ipSend ) < 0 )
3313  {
3314  return log<software_error, -1>( { __FILE__, __LINE__ } );
3315  }
3316 
3317  return 0;
3318 }
3319 
3320 template <bool _useINDI>
3321 int MagAOXApp<_useINDI>::sendNewStandardIndiToggle( const std::string &device, const std::string &property, bool onoff )
3322 {
3323  if( !_useINDI )
3324  return 0;
3325 
3326  pcf::IndiProperty ipSend( pcf::IndiProperty::Switch );
3327 
3328  try
3329  {
3330  ipSend.setDevice( device );
3331  ipSend.setName( property );
3332  ipSend.add( pcf::IndiElement( "toggle" ) );
3333  }
3334  catch( std::exception &e )
3335  {
3336  return log<software_error, -1>( { __FILE__, __LINE__, std::string( "exception: " ) + e.what() } );
3337  }
3338 
3339  if( onoff == false )
3340  {
3341  ipSend["toggle"].setSwitchState( pcf::IndiElement::Off );
3342  }
3343  else
3344  {
3345  ipSend["toggle"].setSwitchState( pcf::IndiElement::On );
3346  }
3347 
3348  if( sendNewProperty( ipSend ) < 0 )
3349  {
3350  return log<software_error, -1>(
3351  { __FILE__, __LINE__, "sendNewProperty failed for " + device + "." + property } );
3352  }
3353 
3354  return 0;
3355 }
3356 
3357 template <bool _useINDI>
3358 int MagAOXApp<_useINDI>::st_newCallBack_clearFSMAlert( void *app, const pcf::IndiProperty &ipRecv )
3359 {
3360  return static_cast<MagAOXApp<_useINDI> *>( app )->newCallBack_clearFSMAlert( ipRecv );
3361 }
3362 
3363 template <bool _useINDI>
3365 {
3366 
3367  if( ipRecv.createUniqueKey() != m_indiP_clearFSMAlert.createUniqueKey() )
3368  {
3369  return log<software_error, -1>( { __FILE__, __LINE__, "wrong indi property received" } );
3370  }
3371 
3372  if( !ipRecv.find( "request" ) )
3373  return 0;
3374 
3375  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3376  {
3377  clearFSMAlert();
3378  updateSwitchIfChanged( m_indiP_clearFSMAlert, "request", pcf::IndiElement::Off, INDI_IDLE );
3379  }
3380 
3381  return 0;
3382 }
3383 
3384 template <bool _useINDI>
3386 {
3387  return 0;
3388 }
3389 
3390 template <bool _useINDI>
3392 {
3393  return 0;
3394 }
3395 
3396 template <bool _useINDI>
3398 {
3399  if( !m_powerMgtEnabled || m_powerOnWait == 0 || m_powerOnCounter < 0 )
3400  return true;
3401 
3402  if( m_powerOnCounter * m_loopPause > ( (double)m_powerOnWait ) * 1e9 )
3403  {
3404  return true;
3405  }
3406  else
3407  {
3408  ++m_powerOnCounter;
3409  return false;
3410  }
3411 }
3412 
3413 template <bool _useINDI>
3415 {
3416  if( !m_powerMgtEnabled )
3417  return 1;
3418 
3419  return m_powerState;
3420 }
3421 
3422 template <bool _useINDI>
3424 {
3425  if( !m_powerMgtEnabled )
3426  return 1;
3427 
3428  return m_powerTargetState;
3429 }
3430 
3431 template <bool _useINDI>
3432 INDI_SETCALLBACK_DEFN( MagAOXApp<_useINDI>, m_indiP_powerChannel )
3433 ( const pcf::IndiProperty &ipRecv )
3434 {
3435  std::string ps;
3436 
3437  if( ipRecv.find( m_powerElement ) )
3438  {
3439  ps = ipRecv[m_powerElement].get<std::string>();
3440 
3441  if( ps == "On" )
3442  {
3443  m_powerState = 1;
3444  }
3445  else if( ps == "Off" )
3446  {
3447  m_powerState = 0;
3448  }
3449  else
3450  {
3451  m_powerState = -1;
3452  }
3453  }
3454 
3455  if( ipRecv.find( m_powerTargetElement ) )
3456  {
3457  ps = ipRecv[m_powerTargetElement].get<std::string>();
3458 
3459  if( ps == "On" )
3460  {
3461  m_powerTargetState = 1;
3462  }
3463  else if( ps == "Off" )
3464  {
3465  m_powerTargetState = 0;
3466  }
3467  else
3468  {
3469  m_powerTargetState = -1;
3470  }
3471  }
3472 
3473  return 0;
3474 }
3475 
3476 template <bool _useINDI>
3478 {
3479  return m_configName;
3480 }
3481 
3482 template <bool _useINDI>
3484 {
3485  return m_configDir;
3486 }
3487 
3488 template <bool _useINDI>
3490 {
3491  return m_driverInName;
3492 }
3493 
3494 template <bool _useINDI>
3496 {
3497  return m_driverOutName;
3498 }
3499 
3500 template <bool _useINDI>
3502 {
3503  return m_driverCtrlName;
3504 }
3505 
3506 extern template class MagAOXApp<true>;
3507 extern template class MagAOXApp<false>;
3508 
3509 } // namespace app
3510 } // namespace MagAOX
3511 
3512 /// Error handling wrapper for the threadStart function of the XWCApp
3513 /** This should be placed in appLogic for each thread managed by an MagAOXApp.
3514  * On error, this will cause the app to shutdown.
3515  * \see MagAOXApp::threadStart
3516  *
3517  * \param thrdSt [out] (std::thread) The thread object to start executing
3518  * \param thrdInit [in/out] (bool) The thread initilization synchronizer.
3519  * \param thrdId [in/out] (pid_t) The thread pid to be filled in by thrdStart immediately upon call
3520  * \param thrdProp [in/out](pcf::IndiProperty) The INDI property to publish the thread details
3521  * \param thrdPrio [in] (int) The r/t priority to set for this thread
3522  * \param thrdCpuset [in] (const std::string &) the cpuset to place this thread on. Ignored if "".
3523  * \param thrdName [in] (const std::string &) The name of the thread (just for logging)
3524  * \param thrdStart [in] (function) The thread starting function, a static function taking a `this` pointer as
3525  * argument.
3526  */
3527 #define XWCAPP_THREAD_START( thrdSt, thrdInit, thrdId, thrdProp, thrdPrio, thrdCpuset, thrdName, thrdStart ) \
3528  if( threadStart( thrdSt, thrdInit, thrdId, thrdProp, thrdPrio, thrdCpuset, thrdName, this, thrdStart ) < 0 ) \
3529  { \
3530  log<software_error>( { __FILE__, __LINE__, "error from threadStart for " #thrdName } ); \
3531  return -1; \
3532  }
3533 
3534 /// Error handling wrapper for checking on thread status with tryjoin
3535 /** This should be placed in appLogic for each thread managed by an MagAOXApp. If the
3536  * thread has exited or otherwise causes an error the app will exit.
3537  *
3538  * \param thrdSt [in] (std::thread) The thread object to start executing
3539  * \param thrdName [in] (const std::string &) The name of the thread (just for logging)
3540  */
3541 #define XWCAPP_THREAD_CHECK( thrdSt, thrdName ) \
3542  try \
3543  { \
3544  if( pthread_tryjoin_np( thrdSt.native_handle(), 0 ) == 0 ) \
3545  { \
3546  log<software_error>( { __FILE__, __LINE__, #thrdName " thread has exited" } ); \
3547  return -1; \
3548  } \
3549  } \
3550  catch( ... ) \
3551  { \
3552  log<software_error>( { __FILE__, __LINE__, #thrdName " thread has exited" } ); \
3553  return -1; \
3554  }
3555 
3556 /// Error handlng wrapper for stopping a thread
3557 /** This should be placed in appShutdown for each thread managed by an MagAOXApp.
3558  *
3559  * \param thrdSt [in] (std::thread) The thread object to start executing
3560  */
3561 #define XWCAPP_THREAD_STOP( thrdSt ) \
3562  if( thrdSt.joinable() ) \
3563  { \
3564  pthread_kill( thrdSt.native_handle(), SIGUSR1 ); \
3565  try \
3566  { \
3567  thrdSt.join(); \
3568  } \
3569  catch( ... ) \
3570  { \
3571  } \
3572  }
3573 
3574 #endif // app_MagAOXApp_hpp
Internal class to manage setuid privilege escalation with RAII.
Definition: MagAOXApp.hpp:324
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:73
void handleDefProperty(const pcf::IndiProperty &ipRecv)
Handler for the DEF INDI properties notification.
Definition: MagAOXApp.hpp:2989
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
void handleSetProperty(const pcf::IndiProperty &ipRecv)
Handler for the set INDI property request.
Definition: MagAOXApp.hpp:3089
int sendNewStandardIndiToggle(const std::string &device, const std::string &property, bool onoff)
Send a new property commmand for a standard toggle switch.
Definition: MagAOXApp.hpp:3321
std::string m_configName
The name of the configuration file (minus .conf).
Definition: MagAOXApp.hpp:83
virtual int onPowerOff()
This method is called when the change to poweroff is detected.
Definition: MagAOXApp.hpp:3385
int createStandardIndiRequestSw(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 request element.
Definition: MagAOXApp.hpp:2573
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2297
int registerIndiPropertyNew(pcf::IndiProperty &prop, const std::string &propName, const pcf::IndiProperty::Type &propType, const pcf::IndiProperty::PropertyPermType &propPerm, const pcf::IndiProperty::PropertyStateType &propState, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for.
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
void handlerSigTerm(int signum, siginfo_t *siginf, void *ucont)
Handles SIGTERM, SIGQUIT, and SIGINT. Sets m_shutdown to 1 and logs the signal.
Definition: MagAOXApp.hpp:1927
std::string driverCtrlName()
Get the INDI control FIFO file name.
Definition: MagAOXApp.hpp:3501
int createStandardIndiNumber(pcf::IndiProperty &prop, const std::string &name, const T &min, const T &max, const T &step, const std::string &format, const std::string &label="", const std::string &group="")
Create a standard R/W INDI Number property with target and current elements.
Definition: MagAOXApp.hpp:2470
INDI_SETCALLBACK_DECL(MagAOXApp, m_indiP_powerChannel)
int powerState()
Returns the current power state.
Definition: MagAOXApp.hpp:3414
std::string pidFileName
The name of the PID file.
Definition: MagAOXApp.hpp:392
void sendGetPropertySetList(bool all=false)
Definition: MagAOXApp.hpp:2949
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const char *newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
Definition: MagAOXApp.hpp:3135
std::unordered_map< std::string, indiCallBack > m_indiNewCallBacks
Map to hold the NewProperty indiCallBacks for this App, with fast lookup by property name.
Definition: MagAOXApp.hpp:573
int setEuidReal()
Set the effective user ID to the real value, i.e. the file owner.
Definition: MagAOXApp.hpp:1980
void updateIfChanged(pcf::IndiProperty &p, const std::vector< std::string > &els, const std::vector< T > &newVals, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update an INDI property if values have changed.
Definition: MagAOXApp.hpp:3181
pcf::IndiProperty m_indiP_powerChannel
INDI property used to communicate power state.
Definition: MagAOXApp.hpp:1036
int createINDIFIFOS()
Create the INDI FIFOs.
Definition: MagAOXApp.hpp:2828
int createStandardIndiSelectionSw(pcf::IndiProperty &prop, const std::string &name, const std::vector< std::string > &elements, const std::string &label="", const std::string &group="")
Definition: MagAOXApp.hpp:2645
std::string m_powerDevice
The INDI device name of the power controller.
Definition: MagAOXApp.hpp:1021
int sendNewProperty(const pcf::IndiProperty &ipSend)
Send a newProperty command to another device (using the INDI Client interface)
Definition: MagAOXApp.hpp:3302
void state(const stateCodes::stateCodeT &s, bool stateAlert=false)
Set the current state code.
Definition: MagAOXApp.hpp:2303
void handleNewProperty(const pcf::IndiProperty &ipRecv)
Handler for the new INDI property request.
Definition: MagAOXApp.hpp:3064
int powerStateTarget()
Returns the target power state.
Definition: MagAOXApp.hpp:3423
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
std::pair< std::string, indiCallBack > callBackValueType
Value type of the indiCallBack map.
Definition: MagAOXApp.hpp:561
std::string m_calibDir
The path to calibration files for MagAOX.
Definition: MagAOXApp.hpp:89
std::string configDir()
Get the config directory.
Definition: MagAOXApp.hpp:3483
static int st_newCallBack_clearFSMAlert(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for requesting to clear the FSM alert.
Definition: MagAOXApp.hpp:3358
int stateLogged()
Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change,...
Definition: MagAOXApp.hpp:2361
int newCallBack_clearFSMAlert(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the FSM Alert Clear request.
Definition: MagAOXApp.hpp:3364
MagAOXApp(const std::string &git_sha1, const bool git_modified)
Public c'tor. Handles uid, logs git repo status, and initializes static members.
Definition: MagAOXApp.hpp:1132
void handleGetProperties(const pcf::IndiProperty &ipRecv)
Handler for the get INDI properties request.
Definition: MagAOXApp.hpp:2995
std::string m_configDir
The path to configuration files for MagAOX.
Definition: MagAOXApp.hpp:85
MagAOXApp()=delete
Default c'tor is deleted.
int createROIndiNumber(pcf::IndiProperty &prop, const std::string &propName, const std::string &propLabel="", const std::string &propGroup="")
Create a ReadOnly INDI Number property.
Definition: MagAOXApp.hpp:2517
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop)
Register an INDI property which is read only.
Definition: MagAOXApp.hpp:2655
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const std::vector< T > &newVals, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property if values have changed.
Definition: MagAOXApp.hpp:3160
std::string driverOutName()
Get the INDI output FIFO file name.
Definition: MagAOXApp.hpp:3495
std::pair< callBackIterator, bool > callBackInsertResult
Return type of insert on the indiCallBack map.
Definition: MagAOXApp.hpp:567
pcf::IndiProperty m_indiP_clearFSMAlert
indi Property to clear an FSM alert.
Definition: MagAOXApp.hpp:980
virtual int whilePowerOff()
This method is called while the power is off, once per FSM loop.
Definition: MagAOXApp.hpp:3391
bool powerOnWaitElapsed()
This method tests whether the power on wait time has elapsed.
Definition: MagAOXApp.hpp:3397
logger::logManager< MagAOXApp< _useINDI >, logFileRaw > logManagerT
The log manager type.
Definition: MagAOXApp.hpp:78
std::string m_powerChannel
The INDI property name of the channel controlling this device's power.
Definition: MagAOXApp.hpp:1022
int createROIndiText(pcf::IndiProperty &prop, const std::string &propName, const std::string &elName, const std::string &propLabel="", const std::string &propGroup="", const std::string &elLabel="")
Create a standard ReadOnly INDI Text property, with at least one element.
Definition: MagAOXApp.hpp:2434
std::string secretsPath
Path to the secrets directory, where passwords, etc, are stored.
Definition: MagAOXApp.hpp:93
int clearFSMAlert()
Clear the FSM alert state.
Definition: MagAOXApp.hpp:2376
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:545
std::string m_configBase
The name of a base config class for this app (minus .conf).
Definition: MagAOXApp.hpp:87
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
std::string m_driverOutName
Full path name of the INDI driver output FIFO.
Definition: MagAOXApp.hpp:588
int threadStart(std::thread &thrd, bool &thrdInit, pid_t &tpid, pcf::IndiProperty &thProp, int thrdPrio, const std::string &cpuset, const std::string &thrdName, thisPtr *thrdThis, Function &&thrdStart)
Start a thread, using this class's privileges to set priority, etc.
Definition: MagAOXApp.hpp:2157
std::string sysPath
The path to the system directory, for PID file, etc.
Definition: MagAOXApp.hpp:91
int startINDI()
Start INDI Communications.
Definition: MagAOXApp.hpp:2892
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
~MagAOXApp() noexcept(true)
Definition: MagAOXApp.hpp:1172
int registerIndiPropertySet(pcf::IndiProperty &prop, const std::string &devName, const std::string &propName, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is monitored for updates from others.
Definition: MagAOXApp.hpp:2792
int registerIndiPropertyNew(pcf::IndiProperty &prop, const std::string &propName, const pcf::IndiProperty::Type &propType, const pcf::IndiProperty::PropertyPermType &propPerm, const pcf::IndiProperty::PropertyStateType &propState, const pcf::IndiProperty::SwitchRuleType &propRule, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for,...
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop, const std::string &propName, const pcf::IndiProperty::Type &propType, const pcf::IndiProperty::PropertyPermType &propPerm, const pcf::IndiProperty::PropertyStateType &propState)
Register an INDI property which is read only.
Definition: MagAOXApp.hpp:2684
std::string driverInName()
Get the INDI input FIFO file name.
Definition: MagAOXApp.hpp:3489
int indiTargetUpdate(pcf::IndiProperty &localProperty, T &localTarget, const pcf::IndiProperty &remoteProperty, bool setBusy=true)
Get the target element value from an new property.
Definition: MagAOXApp.hpp:3197
int unlockPID()
Remove the PID file.
Definition: MagAOXApp.hpp:2133
std::string MagAOXPath
The base path of the MagAO-X system.
Definition: MagAOXApp.hpp:81
std::string m_driverCtrlName
Full path name of the INDI driver control FIFO.
Definition: MagAOXApp.hpp:593
pcf::IndiProperty m_indiP_state
indi Property to report the application state.
Definition: MagAOXApp.hpp:977
int setEuidCalled()
Set the effective user ID to the called value, i.e. the highest possible.
Definition: MagAOXApp.hpp:1961
std::unordered_map< std::string, indiCallBack > m_indiSetCallBacks
Map to hold the SetProperty indiCallBacks for this App, with fast lookup by property name.
Definition: MagAOXApp.hpp:578
std::unordered_map< std::string, indiCallBack >::iterator callBackIterator
Iterator type of the indiCallBack map.
Definition: MagAOXApp.hpp:564
std::string m_driverInName
Full path name of the INDI driver input FIFO.
Definition: MagAOXApp.hpp:585
A class to manage raw binary log files.
Definition: logFileRaw.hpp:42
#define MAGAOX_RT_SCHED_POLICY
The real-time scheduling policy.
Definition: config.hpp:21
#define MAGAOX_default_loopPause
The default application loopPause.
Definition: defaults.hpp:49
#define MAGAOX_calibRelPath
The relative path to the calibration files.
Definition: paths.hpp:36
#define MAGAOX_driverFIFORelPath
The relative path to the INDI driver FIFOs.
Definition: paths.hpp:85
#define MAGAOX_configRelPath
The relative path to the configuration files.
Definition: paths.hpp:29
#define MAGAOX_logRelPath
The relative path to the log directory.
Definition: paths.hpp:50
#define MAGAOX_sysRelPath
The relative path to the system directory.
Definition: paths.hpp:64
#define MAGAOX_path
The path to the MagAO-X system files.
Definition: paths.hpp:22
#define MAGAOX_secretsRelPath
The relative path to the secrets directory. Used for storing passwords, etc.
Definition: paths.hpp:71
#define MAGAOX_cpusetPath
The absolute path the the cpuset mount point.
Definition: paths.hpp:99
#define MAGAOX_env_path
Environment variable setting the MagAO-X path.
Definition: environment.hpp:20
#define MAGAOX_env_calib
Environment variable setting the relative calib path.
Definition: environment.hpp:30
#define MAGAOX_env_config
Environment variable setting the relative config path.
Definition: environment.hpp:25
#define MAGAOX_env_cpuset
Environment variable setting the relative calib path.
Definition: environment.hpp:35
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
Definition: indiMacros.hpp:118
#define INDI_SETCALLBACK(prop)
Get the name of the static callback wrapper for a set property.
Definition: indiMacros.hpp:217
int8_t logPrioT
The type of the log priority code.
Definition: logDefs.hpp:21
std::shared_ptr< char > bufferPtrT
The log entry buffer smart pointer.
Definition: logHeader.hpp:58
int th_seteuid(uid_t euid)
Sets the effective user id of the calling thread, rather than the whole process.
Definition: thSetuid.cpp:18
MagAO-X INDI Driver Wrapper.
Macros for INDI.
MagAO-X INDI Utilities.
#define INDI_IDLE
Definition: indiUtils.hpp:28
#define INDI_BUSY
Definition: indiUtils.hpp:30
#define INDI_ALERT
Definition: indiUtils.hpp:31
#define INDI_OK
Definition: indiUtils.hpp:29
GeneratorWrapper< T > value(T &&value)
Definition: catch.hpp:4001
std::ostream & cerr()
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &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:95
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
pcf::IndiProperty::Type propType< int >()
Definition: MagAOXApp.cpp:31
std::stringstream msg
pcf::IndiProperty::Type propType< char * >()
Definition: MagAOXApp.cpp:19
void sigUsr1Handler(int signum, siginfo_t *siginf, void *ucont)
Empty signal handler. SIGUSR1 is used to interrupt sleep in various threads.
Definition: MagAOXApp.cpp:42
pcf::IndiProperty::Type propType< double >()
Definition: MagAOXApp.cpp:37
const pcf::IndiProperty & ipRecv
Definition: MagAOXApp.hpp:3434
pcf::IndiProperty::Type propType()
Definition: MagAOXApp.hpp:3249
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition: dm.hpp:24
constexpr static logPrioT LOG_DEBUG
Used for debugging.
Definition: logPriority.hpp:52
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
constexpr static logPrioT LOG_DEFAULT
Used to denote "use the default level for this log type".
Definition: logPriority.hpp:61
constexpr static logPrioT LOG_NOTICE
A normal but significant condition.
Definition: logPriority.hpp:46
MagAO-X Application States.
Structure to hold the call-back details for handling INDI communications.
Definition: MagAOXApp.hpp:550
int16_t stateCodeT
The type of the state code.
Definition: stateCodes.hpp:35
The type of the input message.
Definition: git_state.hpp:36
The standard MagAOX log manager, used for both process logs and telemetry streams.
Definition: logManager.hpp:74
Software ERR log entry.
A simple text log, a string-type log.
Definition: text_log.hpp:24
A fixed-width timespec structure.
Definition: timespecX.hpp:35
nanosecT time_ns
Nanoseconds.
Definition: timespecX.hpp:37
secT time_s
Time since the Unix epoch.
Definition: timespecX.hpp:36