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