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