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