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