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