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