LCOV - code coverage report
Current view: top level - libMagAOX/app - MagAOXApp.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 73.6 % 1110 817
Test Date: 2026-04-15 19:34:29 Functions: 89.3 % 84 75

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

Generated by: LCOV version 2.0-1