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