LCOV - code coverage report
Current view: top level - libMagAOX/app - MagAOXApp.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 72.4 % 1046 757
Test Date: 2026-01-03 21:03:39 Functions: 86.4 % 81 70

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

Generated by: LCOV version 2.0-1