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