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