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