LCOV - code coverage report
Current view: top level - apps/observerCtrl - observerCtrl.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 14.3 % 483 69
Test Date: 2026-01-03 21:03:39 Functions: 33.3 % 42 14

            Line data    Source code
       1              : /** \file observerCtrl.hpp
       2              :  * \brief The MagAO-X Observer Controller header file
       3              :  *
       4              :  * \ingroup observerCtrl_files
       5              :  */
       6              : 
       7              : #ifndef observerCtrl_hpp
       8              : #define observerCtrl_hpp
       9              : 
      10              : #include <map>
      11              : #include <mx/math/geo.hpp>
      12              : 
      13              : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
      14              : #include "../../magaox_git_version.h"
      15              : 
      16              : /** \defgroup observerCtrl
      17              :  * \brief The MagAO-X Observer Controller application
      18              :  *
      19              :  * <a href="../handbook/operating/software/apps/observerCtrl.html">Application Documentation</a>
      20              :  *
      21              :  * \ingroup apps
      22              :  *
      23              :  */
      24              : 
      25              : /** \defgroup observerCtrl_files
      26              :  * \ingroup observerCtrl
      27              :  */
      28              : 
      29              : namespace MagAOX
      30              : {
      31              : namespace app
      32              : {
      33              : 
      34              : /// The MagAO-X Observer Controller
      35              : /**
      36              :  * \ingroup observerCtrl
      37              :  */
      38              : class observerCtrl : public MagAOXApp<true>, public dev::telemeter<observerCtrl>
      39              : {
      40              : 
      41              :     // Give the test harness access.
      42              :     friend class observerCtrl_test;
      43              : 
      44              :     friend class dev::telemeter<observerCtrl>;
      45              : 
      46              :     typedef dev::telemeter<observerCtrl> telemeterT;
      47              : 
      48              :     typedef std::chrono::time_point<std::chrono::steady_clock> timePointT;
      49              : 
      50              :     typedef std::string timeStampT;
      51              : 
      52              :     typedef std::chrono::duration<double> durationT;
      53              : 
      54              :     std::string timeStampAsISO8601( const std::chrono::time_point<std::chrono::system_clock> &tp );
      55              : 
      56              :   protected:
      57              :     /** \name Configurable Parameters
      58              :      *@{
      59              :      */
      60              : 
      61              :     std::vector<std::string> m_streamWriters; ///< The stream writers to stop and start
      62              : 
      63              :     std::string m_tcsDev{ "tcsi" };
      64              :     std::string m_catalogProp{ "catalog" };
      65              :     std::string m_objEl{ "object" };
      66              :     std::string m_catdataProp{ "catdata" };
      67              :     std::string m_raEl{ "ra" };
      68              :     std::string m_decEl{ "dec" };
      69              :     std::string m_labModeProp{ "labMode" };
      70              : 
      71              :     std::string m_teldataProp{ "teldata" };
      72              :     std::string m_parangEl{ "pa" };
      73              : 
      74              :     ///@}
      75              : 
      76              :     /// The observer specification
      77              :     struct observer
      78              :     {
      79              :         std::string m_fullName;       ///< Obsever's full name
      80              :         std::string m_pfoa;           ///< Observer's preferred form of of address
      81              :         std::string m_pronunciation;  ///< Guide for the TTS to pronounced the pfoa (defaults to pfoa)
      82              :         std::string m_email;          ///< Observer's email.  Must be unique.
      83              :         std::string m_sanitizedEmail; ///< Observer's email sanitized for use in INDI properties
      84              :         std::string m_institution;    ///< The observer's institution
      85              :     };
      86              : 
      87              :     typedef std::map<std::string, observer> observerMapT;
      88              : 
      89              :     observerMapT m_observers; ///< The observers from the configuration file
      90              : 
      91              :     observer m_currentObserver; ///< The current selected observer
      92              : 
      93              :     observer m_currentOperator; ///< The current selected observer
      94              : 
      95              :     std::string m_obsName;          ///< The name of the observation.
      96              :     double      m_obsDuration{ 0 }; ///< The desired duration of the observation.  If 0 then until stopped.
      97              : 
      98              :     bool m_observing{ false }; ///< Flag indicating whether or not we are in an observation
      99              : 
     100              :     std::string m_target;
     101              : 
     102              :     std::string m_catObj;
     103              : 
     104              :     std::string m_catRA;
     105              :     std::string m_catDec;
     106              : 
     107              :     bool m_labMode{ false }; ///< Flag tracking whether the TCS interface is in lab mode.
     108              : 
     109              :     bool m_newTargetBlock{ true }; /**< Flag to indicate that this is a new target block.  This starts out as true
     110              :                                         but becomes false on the first observation.*/
     111              : 
     112              :     bool m_newPointing{ false }; /**< Flag to indicate that a new pointing has been set by the TCS.  Triggered by
     113              :                                       changes in catalog object, RA, or Dec. */
     114              : 
     115              :     /// The start time of the current observation
     116              :     timePointT m_obsStartTime;
     117              : 
     118              :     /// The UTC start time of the current observation
     119              :     timeStampT m_obsStartTimeStamp;
     120              : 
     121              :     /// The parallactic angle at the start of the observation
     122              :     double m_obsStartParang{ 0 };
     123              : 
     124              :     /// The start time of the current target
     125              :     timePointT m_tgtStartTime;
     126              : 
     127              :     /// The UTC start time of the current target
     128              :     timeStampT m_tgtStartTimeStamp;
     129              : 
     130              :     /// The parallactic angle at the start of observing the current target
     131              :     double m_tgtStartParang{ 0 };
     132              : 
     133              :     /// The current parallactic angle
     134              :     double m_parang;
     135              : 
     136              :     /// The current target time.  Only updated while observing.
     137              :     durationT m_tgtTime;
     138              : 
     139              :     /// The current target angle. Only updated while observing.
     140              :     double m_tgtAng{ 0 };
     141              : 
     142              :   public:
     143              :     /// Default c'tor.
     144              :     observerCtrl();
     145              : 
     146              :     /// D'tor, declared and defined for noexcept.
     147           41 :     ~observerCtrl() noexcept
     148           41 :     {
     149           41 :     }
     150              : 
     151              :     virtual void setupConfig();
     152              : 
     153              :     /// Implementation of loadConfig logic, separated for testing.
     154              :     /** This is called by loadConfig().
     155              :      */
     156              :     int loadConfigImpl( mx::app::appConfigurator &_config /**< [in] an application configuration
     157              :                                                                     from which to load values*/
     158              :     );
     159              : 
     160              :     virtual void loadConfig();
     161              : 
     162              :     /// Startup function
     163              :     /**
     164              :      *
     165              :      */
     166              :     virtual int appStartup();
     167              : 
     168              :     /// Implementation of the FSM for observerCtrl.
     169              :     /**
     170              :      * \returns 0 on no critical error
     171              :      * \returns -1 on an error requiring shutdown
     172              :      */
     173              :     virtual int appLogic();
     174              : 
     175              :     /// Shutdown the app.
     176              :     /**
     177              :      *
     178              :      */
     179              :     virtual int appShutdown();
     180              : 
     181              :     void startObserving();
     182              : 
     183              :     void stopObserving();
     184              : 
     185              :     ///\name INDI
     186              :     /** @{
     187              :      */
     188              :   protected:
     189              :     pcf::IndiProperty m_indiP_observers; ///< Selection switch to allow selection of the observer
     190              :     pcf::IndiProperty m_indiP_observer;  ///< Text which contains the specifications of the current observer
     191              : 
     192              :     pcf::IndiProperty m_indiP_operators; ///< Selection switch to allow selection of the observer
     193              :     pcf::IndiProperty m_indiP_operator;  ///< Text which contains the specifications of the current observer
     194              : 
     195              :     pcf::IndiProperty m_indiP_obsName;     /**< The current observation name, used to specify the
     196              :                                                 purpose of the observation*/
     197              :     pcf::IndiProperty m_indiP_observing;   ///< Toggle switch to trigger observation
     198              :     pcf::IndiProperty m_indiP_obsDuration; ///< Number to set the desired duration of observation
     199              :     pcf::IndiProperty m_indiP_obsStart;    ///< String timestamp indicating the start for target/observation
     200              :     pcf::IndiProperty m_indiP_obsTime;     ///< Number tracking the elapsed time
     201              :     pcf::IndiProperty m_indiP_obsAngle;    ///< Number tracking the change in angle
     202              :     pcf::IndiProperty m_indiP_sws;         ///< Selection to switch which stream writers are enabled
     203              :     pcf::IndiProperty m_indiP_userlog;     ///< Text to enter a user log
     204              : 
     205              :     pcf::IndiProperty m_indiP_resetTarget; ///< Reset the target statistics
     206              : 
     207              :     pcf::IndiProperty m_indiP_target; ///< The target name, which can be overridden by the user
     208              : 
     209              :     pcf::IndiProperty m_indiP_tcsTarget; ///< Set the target to match TCS catObj
     210              : 
     211              :     pcf::IndiProperty m_indiP_catalog; ///< Catalog text data
     212              :     pcf::IndiProperty m_indiP_catdata; ///< Catalog numeric data
     213              :     pcf::IndiProperty m_indiP_teldata; ///< Telescope data (for parang)
     214              : 
     215              :     pcf::IndiProperty m_indiP_labMode; ///< Tracks whether TCS is in lab mode.
     216              : 
     217              :     pcf::IndiProperty m_indiP_newPointing; ///< Reports the status of the new pointing flag
     218              : 
     219              :   public:
     220            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_observers );
     221              : 
     222            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_operators );
     223              : 
     224            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_obsName );
     225            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_observing );
     226            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_obsDuration );
     227              : 
     228            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_sws );
     229              : 
     230            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_userlog );
     231              : 
     232            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_resetTarget );
     233              : 
     234            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_target );
     235              : 
     236            0 :     INDI_NEWCALLBACK_DECL( observerCtrl, m_indiP_tcsTarget );
     237              : 
     238            0 :     INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_catalog );
     239              : 
     240            0 :     INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_catdata );
     241              : 
     242            0 :     INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_teldata );
     243              : 
     244            0 :     INDI_SETCALLBACK_DECL( observerCtrl, m_indiP_labMode );
     245              : 
     246              :     ///@}
     247              : 
     248              :     /** \name Telemeter Interface
     249              :      *
     250              :      * @{
     251              :      */
     252              :     int checkRecordTimes();
     253              : 
     254              :     int recordTelem( const telem_observer * );
     255              : 
     256              :     int recordObserver( bool force = false );
     257              : 
     258              :     ///@}
     259              : };
     260              : 
     261          861 : observerCtrl::observerCtrl() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
     262              : {
     263           41 :     return;
     264            0 : }
     265              : 
     266            0 : void observerCtrl::setupConfig()
     267              : {
     268            0 :     config.add( "stream.writers",
     269              :                 "",
     270              :                 "stream.writers",
     271              :                 argType::Required,
     272              :                 "stream",
     273              :                 "writers",
     274              :                 false,
     275              :                 "string",
     276              :                 "The device names of the stream writers to control." );
     277              : 
     278            0 :     dev::telemeter<observerCtrl>::setupConfig( config );
     279            0 : }
     280              : 
     281            0 : int observerCtrl::loadConfigImpl( mx::app::appConfigurator &_config )
     282              : {
     283            0 :     _config( m_streamWriters, "stream.writers" );
     284              : 
     285            0 :     std::vector<std::string> sections;
     286              : 
     287            0 :     _config.unusedSections( sections );
     288              : 
     289            0 :     if( sections.size() == 0 )
     290              :     {
     291            0 :         log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
     292            0 :         return -1;
     293              :     }
     294              : 
     295            0 :     for( size_t i = 0; i < sections.size(); ++i )
     296              :     {
     297            0 :         bool pfoaSet = _config.isSetUnused( mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
     298            0 :         if( !pfoaSet )
     299            0 :             continue;
     300              : 
     301            0 :         std::string email = sections[i];
     302              : 
     303            0 :         std::string pfoa;
     304            0 :         _config.configUnused( pfoa, mx::app::iniFile::makeKey( sections[i], "pfoa" ) );
     305              : 
     306            0 :         std::string pronunciation;
     307            0 :         _config.configUnused( pronunciation, mx::app::iniFile::makeKey( sections[i], "pronunciation" ) );
     308              : 
     309            0 :         if( pronunciation == "" )
     310              :         {
     311            0 :             pronunciation = pfoa;
     312              :         }
     313              : 
     314            0 :         std::string fullName;
     315            0 :         _config.configUnused( fullName, mx::app::iniFile::makeKey( sections[i], "full_name" ) );
     316              : 
     317            0 :         std::string institution;
     318            0 :         _config.configUnused( institution, mx::app::iniFile::makeKey( sections[i], "institution" ) );
     319              : 
     320            0 :         std::string sanitizedEmail = "";
     321            0 :         for( size_t n = 0; n < email.size(); ++n )
     322              :         {
     323            0 :             if( email[n] == '@' )
     324              :             {
     325            0 :                 sanitizedEmail = sanitizedEmail + "-at-";
     326              :             }
     327            0 :             else if( email[n] == '.' )
     328              :             {
     329            0 :                 sanitizedEmail = sanitizedEmail + "-dot-";
     330              :             }
     331              :             else
     332              :             {
     333            0 :                 sanitizedEmail.push_back( email[n] );
     334              :             }
     335              :         }
     336            0 :         m_observers[email] = observer( { fullName, pfoa, pronunciation, email, sanitizedEmail, institution } );
     337            0 :     }
     338              : 
     339            0 :     return 0;
     340            0 : }
     341              : 
     342            0 : void observerCtrl::loadConfig()
     343              : {
     344            0 :     if( loadConfigImpl( config ) < 0 )
     345              :     {
     346            0 :         m_shutdown = 1;
     347            0 :         return;
     348              :     }
     349              : 
     350            0 :     if( m_observers.size() < 1 )
     351              :     {
     352            0 :         log<text_log>( "no observers found in config", logPrio::LOG_CRITICAL );
     353            0 :         m_shutdown = 1;
     354            0 :         return;
     355              :     }
     356              : 
     357            0 :     dev::telemeter<observerCtrl>::loadConfig( config );
     358              : }
     359              : 
     360            0 : int observerCtrl::appStartup()
     361              : {
     362            0 :     std::vector<std::string> sanitizedEmails;
     363            0 :     std::vector<std::string> emails;
     364            0 :     for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
     365              :     {
     366            0 :         sanitizedEmails.push_back( it->second.m_sanitizedEmail );
     367            0 :         emails.push_back( it->second.m_email );
     368              :     }
     369              : 
     370            0 :     if( createStandardIndiSelectionSw( m_indiP_observers, "observers", sanitizedEmails, emails ) < 0 )
     371              :     {
     372            0 :         log<software_critical>( { __FILE__, __LINE__ } );
     373            0 :         return -1;
     374              :     }
     375              : 
     376              :     // Set to default user of jared
     377              :     ///\todo do something else. maybe a default user is specified in the config?
     378            0 :     for( auto &it : m_observers )
     379              :     {
     380            0 :         if( it.first.find( "jrmales" ) != std::string::npos )
     381              :         {
     382            0 :             m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
     383            0 :             m_currentObserver = it.second;
     384              :         }
     385              :         else
     386              :         {
     387            0 :             m_indiP_observers[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
     388              :         }
     389              :     }
     390              : 
     391            0 :     REG_INDI_NEWPROP_NOSETUP( m_indiP_observers );
     392              : 
     393            0 :     if( createStandardIndiSelectionSw( m_indiP_operators, "operators", sanitizedEmails, emails ) < 0 )
     394              :     {
     395            0 :         log<software_critical>( { __FILE__, __LINE__ } );
     396            0 :         return -1;
     397              :     }
     398              : 
     399              :     // Set to default user of jared
     400              :     ///\todo do something else. maybe a default user is specified in the config?
     401            0 :     for( auto &it : m_observers )
     402              :     {
     403            0 :         if( it.first.find( "jrmales" ) != std::string::npos )
     404              :         {
     405            0 :             m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::On );
     406            0 :             m_currentOperator = it.second;
     407              :         }
     408              :         else
     409              :         {
     410            0 :             m_indiP_operators[it.second.m_sanitizedEmail].setSwitchState( pcf::IndiElement::Off );
     411              :         }
     412              :     }
     413              : 
     414            0 :     REG_INDI_NEWPROP_NOSETUP( m_indiP_operators );
     415              : 
     416            0 :     CREATE_REG_INDI_NEW_TEXT( m_indiP_obsName, "obs_name", "Observation Name", "Observer" );
     417              : 
     418            0 :     CREATE_REG_INDI_NEW_TOGGLESWITCH( m_indiP_observing, "obs_on" );
     419              : 
     420            0 :     CREATE_REG_INDI_NEW_NUMBERD( m_indiP_obsDuration, "obs_duration", 0, 300, 0.1, "%0.1f", "Duration", "Observer" );
     421              : 
     422            0 :     CREATE_REG_INDI_RO_NUMBER( m_indiP_obsTime, "obs_time", "Observation Time", "Observer" );
     423            0 :     indi::addNumberElement<double>( m_indiP_obsTime, "observation", 0, 14400, 0.1, "%0.1f", "Current Obs" );
     424            0 :     indi::addNumberElement<double>( m_indiP_obsTime, "target", 0, 14400, 0.1, "%0.1f", "Target" );
     425              : 
     426            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_obsStart, "obs_start", pcf::IndiProperty::Text );
     427            0 :     indi::addTextElement( m_indiP_obsStart, "observation" );
     428            0 :     indi::addTextElement( m_indiP_obsStart, "target" );
     429              : 
     430            0 :     CREATE_REG_INDI_RO_NUMBER( m_indiP_obsAngle, "obs_delta_parang", "Change in Par. Ang.", "Observer" );
     431            0 :     indi::addNumberElement<double>( m_indiP_obsAngle, "observation", 0, 360, 0.1, "%0.1f", "Current Obs" );
     432            0 :     indi::addNumberElement<double>( m_indiP_obsAngle, "target", 0, 360, 0.1, "%0.1f", "Target" );
     433              : 
     434            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_observer, "current_observer", pcf::IndiProperty::Text );
     435            0 :     indi::addTextElement( m_indiP_observer, "full_name" );
     436            0 :     indi::addTextElement( m_indiP_observer, "email" );
     437            0 :     indi::addTextElement( m_indiP_observer, "pfoa" );
     438            0 :     indi::addTextElement( m_indiP_observer, "pronunciation" );
     439            0 :     indi::addTextElement( m_indiP_observer, "institution" );
     440              : 
     441            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_operator, "current_operator", pcf::IndiProperty::Text );
     442            0 :     indi::addTextElement( m_indiP_operator, "full_name" );
     443            0 :     indi::addTextElement( m_indiP_operator, "email" );
     444            0 :     indi::addTextElement( m_indiP_operator, "pfoa" );
     445            0 :     indi::addTextElement( m_indiP_operator, "pronunciation" );
     446            0 :     indi::addTextElement( m_indiP_operator, "institution" );
     447              : 
     448            0 :     m_indiP_sws = pcf::IndiProperty( pcf::IndiProperty::Switch );
     449            0 :     m_indiP_sws.setDevice( configName() );
     450            0 :     m_indiP_sws.setName( "writers" );
     451            0 :     m_indiP_sws.setPerm( pcf::IndiProperty::ReadWrite );
     452            0 :     m_indiP_sws.setState( pcf::IndiProperty::Idle );
     453            0 :     m_indiP_sws.setRule( pcf::IndiProperty::AnyOfMany );
     454              : 
     455            0 :     for( size_t n = 0; n < m_streamWriters.size(); ++n )
     456              :     {
     457            0 :         m_indiP_sws.add( pcf::IndiElement( m_streamWriters[n], pcf::IndiElement::Off ) );
     458              :     }
     459              : 
     460            0 :     REG_INDI_NEWPROP_NOSETUP( m_indiP_sws );
     461              : 
     462            0 :     m_indiP_userlog = pcf::IndiProperty( pcf::IndiProperty::Text );
     463            0 :     m_indiP_userlog.setDevice( configName() );
     464            0 :     m_indiP_userlog.setName( "user_log" );
     465            0 :     m_indiP_userlog.setPerm( pcf::IndiProperty::ReadWrite );
     466            0 :     m_indiP_userlog.setState( pcf::IndiProperty::Idle );
     467            0 :     m_indiP_userlog.add( pcf::IndiElement( "email" ) );
     468            0 :     m_indiP_userlog.add( pcf::IndiElement( "message" ) );
     469            0 :     m_indiP_userlog.add( pcf::IndiElement( "time_s" ) );
     470            0 :     m_indiP_userlog.add( pcf::IndiElement( "time_ns" ) );
     471              : 
     472            0 :     REG_INDI_NEWPROP_NOSETUP( m_indiP_userlog );
     473              : 
     474            0 :     CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_resetTarget, "target_reset" );
     475              : 
     476            0 :     CREATE_REG_INDI_NEW_TEXT( m_indiP_target, "target", "Target", "Observer" );
     477              : 
     478            0 :     CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_tcsTarget, "target_load_from_tcs" );
     479              : 
     480            0 :     REG_INDI_SETPROP( m_indiP_catalog, m_tcsDev, m_catalogProp );
     481            0 :     REG_INDI_SETPROP( m_indiP_catdata, m_tcsDev, m_catdataProp );
     482            0 :     REG_INDI_SETPROP( m_indiP_teldata, m_tcsDev, m_teldataProp );
     483            0 :     REG_INDI_SETPROP( m_indiP_labMode, m_tcsDev, m_labModeProp );
     484              : 
     485              :     //The new pointing flag
     486            0 :     if( registerIndiPropertyNew( m_indiP_newPointing,
     487              :                                  "new_pointing",
     488            0 :                                  pcf::IndiProperty::Switch,
     489            0 :                                  pcf::IndiProperty::ReadOnly,
     490            0 :                                  pcf::IndiProperty::Idle,
     491            0 :                                  pcf::IndiProperty::AtMostOne,
     492            0 :                                  nullptr ) < 0 )
     493              :     {
     494            0 :         return log<software_critical, -1>( { __FILE__, __LINE__ } );
     495              :     }
     496            0 :     m_indiP_newPointing.add(pcf::IndiElement("set"));
     497            0 :     if(m_newPointing)
     498              :     {
     499            0 :         m_indiP_newPointing["set"].setSwitchState(pcf::IndiElement::On);
     500              :     }
     501              :     else
     502              :     {
     503            0 :         m_indiP_newPointing["set"].setSwitchState(pcf::IndiElement::Off);
     504              :     }
     505              : 
     506            0 :     TELEMETER_APP_STARTUP;
     507              : 
     508            0 :     state( stateCodes::READY );
     509            0 :     return 0;
     510            0 : }
     511              : 
     512            0 : int observerCtrl::appLogic()
     513              : {
     514              : 
     515            0 :     std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
     516              : 
     517            0 :     if( lock.owns_lock() )
     518              :     {
     519            0 :         updatesIfChanged<std::string>( m_indiP_observer,
     520              :                                        { "full_name", "email", "pfoa", "pronunciation", "institution" },
     521            0 :                                        { m_currentObserver.m_fullName,
     522            0 :                                          m_currentObserver.m_email,
     523            0 :                                          m_currentObserver.m_pfoa,
     524            0 :                                          m_currentObserver.m_pronunciation,
     525            0 :                                          m_currentObserver.m_institution } );
     526              : 
     527            0 :         for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
     528              :         {
     529            0 :             if( it->first == m_currentObserver.m_email )
     530              :             {
     531            0 :                 updateSwitchIfChanged(
     532            0 :                     m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
     533              :             }
     534              :             else
     535              :             {
     536            0 :                 updateSwitchIfChanged(
     537            0 :                     m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
     538              :             }
     539              :         }
     540              : 
     541            0 :         updatesIfChanged<std::string>( m_indiP_operator,
     542              :                                        { "full_name", "email", "pfoa", "pronunciation", "institution" },
     543            0 :                                        { m_currentOperator.m_fullName,
     544            0 :                                          m_currentOperator.m_email,
     545            0 :                                          m_currentOperator.m_pfoa,
     546            0 :                                          m_currentOperator.m_pronunciation,
     547            0 :                                          m_currentOperator.m_institution } );
     548              : 
     549            0 :         for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
     550              :         {
     551            0 :             if( it->first == m_currentOperator.m_email )
     552              :             {
     553            0 :                 updateSwitchIfChanged(
     554            0 :                     m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
     555              :             }
     556              :             else
     557              :             {
     558            0 :                 updateSwitchIfChanged(
     559            0 :                     m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
     560              :             }
     561              :         }
     562              : 
     563            0 :         updatesIfChanged<std::string>( m_indiP_obsName, { "current", "target" }, { m_obsName, m_obsName } );
     564              : 
     565            0 :         updatesIfChanged<double>( m_indiP_obsDuration, { "current", "target" }, { m_obsDuration, m_obsDuration } );
     566              : 
     567            0 :         if( m_observing )
     568              :         {
     569            0 :             timePointT                          ct      = std::chrono::steady_clock::now();
     570            0 :             const std::chrono::duration<double> obstime = ct - m_obsStartTime;
     571            0 :             m_tgtTime                                   = ct - m_tgtStartTime;
     572              : 
     573            0 :             double obsang = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_obsStartParang );
     574            0 :             m_tgtAng      = mx::math::angleDiff<mx::math::degreesT<double>>( m_parang, m_tgtStartParang );
     575              : 
     576            0 :             updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
     577            0 :             updatesIfChanged<double>(
     578            0 :                 m_indiP_obsTime, { "observation", "target" }, { obstime.count(), m_tgtTime.count() } );
     579              : 
     580            0 :             updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { obsang, m_tgtAng } );
     581            0 :             updatesIfChanged<std::string>(
     582            0 :                 m_indiP_obsStart, { "observation", "target" }, { m_obsStartTimeStamp, m_tgtStartTimeStamp } );
     583              : 
     584            0 :             if( m_obsDuration > 0.0 && obstime.count() > m_obsDuration )
     585              :             {
     586            0 :                 stopObserving();
     587              :             }
     588              :         }
     589              :         else
     590              :         {
     591            0 :             updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
     592            0 :             updatesIfChanged<std::string>( m_indiP_obsStart, { "observation", "target" }, { "", m_tgtStartTimeStamp } );
     593            0 :             updatesIfChanged<double>( m_indiP_obsTime, { "observation", "target" }, { 0.0, m_tgtTime.count() } );
     594            0 :             updatesIfChanged<double>( m_indiP_obsAngle, { "observation", "target" }, { 0.0, m_tgtAng } );
     595              :         }
     596              :     }
     597              : 
     598            0 :     TELEMETER_APP_LOGIC;
     599              : 
     600            0 :     return 0;
     601            0 : }
     602              : 
     603            0 : int observerCtrl::appShutdown()
     604              : {
     605            0 :     TELEMETER_APP_SHUTDOWN;
     606              : 
     607            0 :     return 0;
     608              : }
     609              : 
     610            0 : void observerCtrl::startObserving()
     611              : {
     612            0 :     for( size_t n = 0; n < m_streamWriters.size(); ++n )
     613              :     {
     614            0 :         if( m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On )
     615              :         {
     616            0 :             pcf::IndiProperty ip( pcf::IndiProperty::Switch );
     617              : 
     618            0 :             ip.setDevice( m_streamWriters[n] + "-sw" );
     619            0 :             ip.setName( "writing" );
     620            0 :             ip.add( pcf::IndiElement( "toggle" ) );
     621            0 :             ip["toggle"].setSwitchState( pcf::IndiElement::On );
     622              : 
     623            0 :             sendNewProperty( ip );
     624            0 :         }
     625              :     }
     626              : 
     627            0 :     mx::sys::sleep( 1 );
     628              : 
     629            0 :     m_obsStartTime      = std::chrono::steady_clock::now();
     630            0 :     m_obsStartTimeStamp = timeStampAsISO8601( std::chrono::system_clock::now() );
     631            0 :     m_obsStartParang    = m_parang;
     632              : 
     633            0 :     if( m_newTargetBlock || m_labMode ) // We always reset if in lab mode
     634              :     {
     635            0 :         m_tgtStartTime      = m_obsStartTime;
     636            0 :         m_tgtStartTimeStamp = m_obsStartTimeStamp;
     637            0 :         m_tgtStartParang    = m_obsStartParang;
     638              : 
     639            0 :         m_newTargetBlock = false;
     640              :     }
     641              : 
     642            0 :     m_observing = true;
     643            0 :     recordObserver( true );
     644            0 : }
     645              : 
     646            0 : void observerCtrl::stopObserving()
     647              : {
     648            0 :     m_observing = false;
     649            0 :     recordObserver( true );
     650              : 
     651            0 :     for( size_t n = 0; n < m_streamWriters.size(); ++n )
     652              :     {
     653            0 :         if( m_indiP_sws[m_streamWriters[n]].getSwitchState() == pcf::IndiElement::On )
     654              :         {
     655            0 :             pcf::IndiProperty ip( pcf::IndiProperty::Switch );
     656              : 
     657            0 :             ip.setDevice( m_streamWriters[n] + "-sw" );
     658            0 :             ip.setName( "writing" );
     659            0 :             ip.add( pcf::IndiElement( "toggle" ) );
     660            0 :             ip["toggle"].setSwitchState( pcf::IndiElement::Off );
     661              : 
     662            0 :             sendNewProperty( ip );
     663            0 :         }
     664              :     }
     665            0 : }
     666              : 
     667            3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observers )( const pcf::IndiProperty &ipRecv )
     668              : {
     669              : 
     670            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_observers, ipRecv );
     671              : 
     672              :     // look for selected mode switch which matches a known mode.  Make sure only one is selected.
     673            0 :     std::string newEmail = "";
     674            0 :     for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
     675              :     {
     676            0 :         if( !ipRecv.find( it->second.m_sanitizedEmail ) )
     677            0 :             continue;
     678              : 
     679            0 :         if( ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
     680              :         {
     681            0 :             if( newEmail != "" )
     682              :             {
     683            0 :                 log<text_log>( "More than one observer selected", logPrio::LOG_ERROR );
     684            0 :                 return -1;
     685              :             }
     686              : 
     687            0 :             newEmail = it->first;
     688              :         }
     689              :     }
     690              : 
     691            0 :     if( newEmail == "" )
     692              :     {
     693            0 :         std::cerr << "nothing\n";
     694            0 :         return 0;
     695              :     }
     696              : 
     697              :     {
     698            0 :         std::unique_lock<std::mutex> lock( m_indiMutex );
     699              : 
     700            0 :         m_currentObserver = m_observers[newEmail];
     701              : 
     702            0 :         for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
     703              :         {
     704            0 :             if( it->first == m_currentObserver.m_sanitizedEmail )
     705              :             {
     706            0 :                 updateSwitchIfChanged(
     707            0 :                     m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
     708              :             }
     709              :             else
     710              :             {
     711            0 :                 updateSwitchIfChanged(
     712            0 :                     m_indiP_observers, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
     713              :             }
     714              :         }
     715            0 :     }
     716              : 
     717            0 :     log<logger::observer>( { m_currentObserver.m_fullName,
     718            0 :                              m_currentObserver.m_pfoa,
     719            0 :                              m_currentObserver.m_email,
     720            0 :                              m_currentObserver.m_institution } );
     721              : 
     722            0 :     return 0;
     723            0 : }
     724              : 
     725            3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_operators )( const pcf::IndiProperty &ipRecv )
     726              : {
     727              : 
     728            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_operators, ipRecv );
     729              : 
     730              :     // look for selected mode switch which matches a known mode.  Make sure only one is selected.
     731            0 :     std::string newEmail = "";
     732            0 :     for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
     733              :     {
     734            0 :         if( !ipRecv.find( it->second.m_sanitizedEmail ) )
     735            0 :             continue;
     736              : 
     737            0 :         if( ipRecv[it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
     738              :         {
     739            0 :             if( newEmail != "" )
     740              :             {
     741            0 :                 log<text_log>( "More than one operator selected", logPrio::LOG_ERROR );
     742            0 :                 return -1;
     743              :             }
     744              : 
     745            0 :             newEmail = it->first;
     746              :         }
     747              :     }
     748              : 
     749            0 :     if( newEmail == "" )
     750              :     {
     751            0 :         std::cerr << "nothing\n";
     752            0 :         return 0;
     753              :     }
     754              : 
     755              :     {
     756            0 :         std::unique_lock<std::mutex> lock( m_indiMutex );
     757              : 
     758            0 :         m_currentOperator = m_observers[newEmail];
     759              : 
     760            0 :         for( auto it = m_observers.begin(); it != m_observers.end(); ++it )
     761              :         {
     762            0 :             if( it->first == m_currentOperator.m_sanitizedEmail )
     763              :             {
     764            0 :                 updateSwitchIfChanged(
     765            0 :                     m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::On, INDI_IDLE );
     766              :             }
     767              :             else
     768              :             {
     769            0 :                 updateSwitchIfChanged(
     770            0 :                     m_indiP_operators, it->second.m_sanitizedEmail, pcf::IndiElement::Off, INDI_IDLE );
     771              :             }
     772              :         }
     773            0 :     }
     774              : 
     775            0 :     log<logger::ao_operator>( { m_currentOperator.m_fullName,
     776            0 :                                 m_currentOperator.m_pfoa,
     777            0 :                                 m_currentOperator.m_email,
     778            0 :                                 m_currentOperator.m_institution } );
     779              : 
     780            0 :     return 0;
     781            0 : }
     782              : 
     783            0 : std::string observerCtrl::timeStampAsISO8601( const std::chrono::time_point<std::chrono::system_clock> &tp )
     784              : {
     785              :     // Convert to time_t for whole seconds
     786            0 :     std::time_t t  = std::chrono::system_clock::to_time_t( tp );
     787            0 :     std::tm     tm = *std::gmtime( &t );
     788              : 
     789              :     // Get nanoseconds
     790            0 :     auto duration = tp.time_since_epoch();
     791            0 :     auto seconds  = std::chrono::duration_cast<std::chrono::seconds>( duration );
     792            0 :     auto nanos    = std::chrono::duration_cast<std::chrono::nanoseconds>( duration - seconds ).count();
     793              : 
     794              :     // Format ISO 8601 with nanoseconds
     795            0 :     std::stringstream ss;
     796            0 :     ss << std::put_time( &tm, "%Y-%m-%dT%H:%M:%S" );
     797            0 :     ss << '.' << std::setw( 9 ) << std::setfill( '0' ) << nanos << "Z"; // Z for UTC
     798              : 
     799            0 :     return ss.str();
     800            0 : }
     801              : 
     802            3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsName )( const pcf::IndiProperty &ipRecv )
     803              : {
     804            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsName, ipRecv );
     805              : 
     806            0 :     std::string target;
     807              : 
     808            0 :     std::unique_lock<std::mutex> lock( m_indiMutex );
     809              : 
     810            0 :     if( indiTargetUpdate( m_indiP_obsName, target, ipRecv, true ) < 0 )
     811              :     {
     812            0 :         log<software_error>( { __FILE__, __LINE__ } );
     813            0 :         return -1;
     814              :     }
     815              : 
     816            0 :     m_obsName = target;
     817              : 
     818            0 :     return 0;
     819            0 : }
     820              : 
     821            3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_observing )( const pcf::IndiProperty &ipRecv )
     822              : {
     823            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_observing, ipRecv );
     824              : 
     825            0 :     if( !ipRecv.find( "toggle" ) )
     826              :     {
     827            0 :         return 0;
     828              :     }
     829              : 
     830            0 :     recordObserver( true );
     831            0 :     if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
     832              :     {
     833            0 :         std::unique_lock<std::mutex> lock( m_indiMutex );
     834            0 :         startObserving();
     835            0 :         updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::On, INDI_OK );
     836            0 :     }
     837            0 :     else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
     838              :     {
     839            0 :         std::unique_lock<std::mutex> lock( m_indiMutex );
     840            0 :         stopObserving();
     841            0 :         updateSwitchIfChanged( m_indiP_observing, "toggle", pcf::IndiElement::Off, INDI_IDLE );
     842            0 :     }
     843              : 
     844            0 :     return 0;
     845              : }
     846              : 
     847            0 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_obsDuration )( const pcf::IndiProperty &ipRecv )
     848              : {
     849            0 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_obsDuration, ipRecv );
     850              : 
     851            0 :     return indiTargetUpdate( m_indiP_obsDuration, m_obsDuration, ipRecv, false );
     852              : }
     853              : 
     854            3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_sws )( const pcf::IndiProperty &ipRecv )
     855              : {
     856            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_sws, ipRecv );
     857              : 
     858            0 :     if( m_observing == true )
     859              :     {
     860            0 :         log<text_log>( { "Can't change stream writers while observing" }, logPrio::LOG_WARNING );
     861            0 :         return 0;
     862              :     }
     863              : 
     864            0 :     for( size_t n = 0; n < m_streamWriters.size(); ++n )
     865              :     {
     866            0 :         if( !ipRecv.find( m_streamWriters[n] ) )
     867              :         {
     868            0 :             continue;
     869              :         }
     870              : 
     871            0 :         std::unique_lock<std::mutex> lock( m_indiMutex );
     872            0 :         updateSwitchIfChanged( m_indiP_sws, m_streamWriters[n], ipRecv[m_streamWriters[n]].getSwitchState() );
     873            0 :     }
     874              : 
     875            0 :     return 0;
     876              : }
     877              : 
     878            3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_userlog )( const pcf::IndiProperty &ipRecv )
     879              : {
     880            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_userlog, ipRecv );
     881              : 
     882            0 :     std::string email;
     883            0 :     std::string message;
     884              : 
     885            0 :     timespecX ts{};
     886              : 
     887            0 :     if( ipRecv.find( "email" ) )
     888              :     {
     889            0 :         email = ipRecv["email"].get();
     890              :     }
     891              : 
     892            0 :     if( ipRecv.find( "message" ) )
     893              :     {
     894            0 :         message = ipRecv["message"].get();
     895              :     }
     896              : 
     897            0 :     if( message == "" )
     898              :     {
     899            0 :         return 0;
     900              :     }
     901              : 
     902            0 :     if( email == "" )
     903              :     {
     904            0 :         email = m_currentObserver.m_email;
     905              :     }
     906              : 
     907            0 :     if( ipRecv.find( "time_s" ) )
     908              :     {
     909            0 :         ts.time_s = ipRecv["time_s"].get<flatlogs::secT>();
     910              :     }
     911              : 
     912            0 :     if( ipRecv.find( "time_ns" ) )
     913              :     {
     914            0 :         ts.time_ns = ipRecv["time_ns"].get<flatlogs::nanosecT>();
     915              :     }
     916              : 
     917            0 :     if( ts.time_s != 0 )
     918              :     {
     919            0 :         m_log.template log<user_log>( ts, { email, message }, logPrio::LOG_INFO );
     920              :     }
     921              :     else
     922              :     {
     923            0 :         log<user_log>( { email, message } );
     924              :     }
     925              : 
     926            0 :     return 0;
     927            0 : }
     928              : 
     929            3 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_resetTarget )( const pcf::IndiProperty &ipRecv )
     930              : {
     931            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_resetTarget, ipRecv );
     932              : 
     933            0 :     if( !ipRecv.find( "request" ) )
     934              :     {
     935            0 :         return 0;
     936              :     }
     937              : 
     938            0 :     if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
     939              :     {
     940            0 :         if( m_observing )
     941              :         {
     942            0 :             m_tgtStartTime   = m_obsStartTime;
     943            0 :             m_tgtStartParang = m_obsStartParang;
     944              :         }
     945              :         else
     946              :         {
     947            0 :             m_newTargetBlock = true;
     948              :         }
     949              :     }
     950              : 
     951            0 :     return 0;
     952              : }
     953              : 
     954            6 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_target )( const pcf::IndiProperty &ipRecv )
     955              : {
     956            6 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_target, ipRecv );
     957              : 
     958            3 :     std::string target;
     959            3 :     if( indiTargetUpdate( m_indiP_target, target, ipRecv ) < 0 )
     960              :     {
     961            1 :         return log<software_error, -1>( { __FILE__, __LINE__ } );
     962              :     }
     963              : 
     964            2 :     if( target != m_target )
     965              :     {
     966            2 :         if( m_observing )
     967              :         {
     968            1 :             return log<text_log,-2>( "Can not change target while observing", logPrio::LOG_ERROR );
     969              :         }
     970              : 
     971            1 :         m_newTargetBlock = true;
     972              : 
     973            1 :         m_target = target;
     974              : 
     975            1 :         log<text_log>( "Target updated by observer to " + m_target, logPrio::LOG_NOTICE );
     976              : 
     977            1 :         std::unique_lock<std::mutex> lock( m_indiMutex );
     978            6 :         updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
     979            1 :     }
     980              : 
     981              :     // Set this regardless so this can be a reset
     982            1 :     m_newPointing = false;
     983            2 :     updateSwitchIfChanged(m_indiP_newPointing, "set", pcf::IndiElement::Off);
     984              : 
     985            1 :     return 0;
     986            4 : }
     987              : 
     988            8 : INDI_NEWCALLBACK_DEFN( observerCtrl, m_indiP_tcsTarget )( const pcf::IndiProperty &ipRecv )
     989              : {
     990            8 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tcsTarget, ipRecv );
     991              : 
     992           10 :     if( !ipRecv.find( "request" ) )
     993              :     {
     994            1 :         return -1;
     995              :     }
     996              : 
     997            8 :     if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
     998              :     {
     999            2 :         if( m_target != m_catObj )
    1000              :         {
    1001            2 :             if( m_observing )
    1002              :             {
    1003            1 :                 return log<text_log,-2>( "Can not change target while observing", logPrio::LOG_ERROR );
    1004              :             }
    1005              : 
    1006            1 :             m_newTargetBlock = true;
    1007              : 
    1008            1 :             m_target = m_catObj;
    1009              : 
    1010            1 :             log<text_log>( "Target updated by observer to TCS target: " + m_target, logPrio::LOG_NOTICE );
    1011            6 :             updatesIfChanged<std::string>( m_indiP_target, { "current", "target" }, { m_target, m_target } );
    1012              :         }
    1013              : 
    1014              :         // We always update this to allow this to be a reset
    1015            1 :         m_newPointing = false;
    1016            3 :         updateSwitchIfChanged(m_indiP_newPointing, "set", pcf::IndiElement::Off);
    1017              :     }
    1018              : 
    1019            3 :     return 0;
    1020            1 : }
    1021              : 
    1022            6 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catalog )( const pcf::IndiProperty &ipRecv )
    1023              : {
    1024            6 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_catalog, ipRecv );
    1025              : 
    1026            6 :     if( !ipRecv.find( "object" ) )
    1027              :     {
    1028            1 :         return -1;
    1029              :     }
    1030              : 
    1031            2 :     std::string object = ipRecv["object"].get();
    1032              : 
    1033            2 :     if( object != m_catObj )
    1034              :     {
    1035            2 :         m_catObj = object;
    1036              : 
    1037            2 :         m_newPointing = true;
    1038            4 :         updateSwitchIfChanged(m_indiP_newPointing, "set", pcf::IndiElement::On);
    1039              : 
    1040              :         // Always log change in name (different from RA and DEC)
    1041            2 :         log<text_log>( "TCS target updated to " + m_catObj, logPrio::LOG_NOTICE );
    1042              :     }
    1043              : 
    1044            2 :     return 0;
    1045            2 : }
    1046              : 
    1047            0 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_catdata )( const pcf::IndiProperty &ipRecv )
    1048              : {
    1049            0 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_catdata, ipRecv );
    1050              : 
    1051            0 :     bool change = false;
    1052            0 :     if( ipRecv.find( "ra" ) )
    1053              :     {
    1054            0 :         std::string ra = ipRecv["ra"].get();
    1055              : 
    1056            0 :         if( ra != m_catRA )
    1057              :         {
    1058            0 :             m_catRA = ra;
    1059            0 :             change  = true;
    1060              :         }
    1061            0 :     }
    1062              : 
    1063            0 :     if( ipRecv.find( "dec" ) )
    1064              :     {
    1065            0 :         std::string dec = ipRecv["dec"].get();
    1066              : 
    1067            0 :         if( dec != m_catDec )
    1068              :         {
    1069            0 :             m_catDec = dec;
    1070            0 :             change   = true;
    1071              :         }
    1072            0 :     }
    1073              : 
    1074            0 :     if( change ) // Only log if not already new
    1075              :     {
    1076            0 :         m_newPointing = true;
    1077            0 :         updateSwitchIfChanged(m_indiP_newPointing, "set", pcf::IndiElement::On);
    1078            0 :         log<text_log>( "Pointing change.  Probable target change.", logPrio::LOG_NOTICE );
    1079              :     }
    1080              : 
    1081            0 :     return 0;
    1082              : }
    1083              : 
    1084            3 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_teldata )( const pcf::IndiProperty &ipRecv )
    1085              : {
    1086            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_teldata, ipRecv );
    1087              : 
    1088            0 :     if( ipRecv.find( "pa" ) )
    1089              :     {
    1090            0 :         m_parang = ipRecv["pa"].get<double>();
    1091              :     }
    1092              : 
    1093            0 :     return 0;
    1094              : }
    1095              : 
    1096            3 : INDI_SETCALLBACK_DEFN( observerCtrl, m_indiP_labMode )( const pcf::IndiProperty &ipRecv )
    1097              : {
    1098            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_labMode, ipRecv );
    1099              : 
    1100            0 :     if( ipRecv.find( "toggle" ) )
    1101              :     {
    1102            0 :         if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
    1103              :         {
    1104            0 :             m_labMode = true;
    1105              :         }
    1106              :         else
    1107              :         {
    1108            0 :             m_labMode = false;
    1109              :         }
    1110              :     }
    1111              : 
    1112            0 :     std::cerr << "got labmode: " << m_labMode << '\n';
    1113            0 :     return 0;
    1114              : }
    1115              : 
    1116            0 : inline int observerCtrl::checkRecordTimes()
    1117              : {
    1118            0 :     return telemeter<observerCtrl>::checkRecordTimes( telem_observer() );
    1119              : }
    1120              : 
    1121            0 : inline int observerCtrl::recordTelem( const telem_observer * )
    1122              : {
    1123            0 :     return recordObserver( true );
    1124              : }
    1125              : 
    1126            0 : inline int observerCtrl::recordObserver( bool force )
    1127              : {
    1128            0 :     static std::string last_email;
    1129            0 :     static std::string last_obsName;
    1130              :     static bool        last_observing;
    1131            0 :     static std::string last_target;
    1132            0 :     static std::string last_operator;
    1133              : 
    1134            0 :     if( last_email != m_currentObserver.m_email || last_obsName != m_obsName || last_observing != m_observing ||
    1135            0 :         last_target != m_target || last_operator != m_currentOperator.m_email || force )
    1136              :     {
    1137            0 :         telem<telem_observer>(
    1138            0 :             { m_currentObserver.m_email, m_obsName, m_observing, m_target, m_currentOperator.m_email } );
    1139              : 
    1140            0 :         last_email     = m_currentObserver.m_email;
    1141            0 :         last_obsName   = m_obsName;
    1142            0 :         last_observing = m_observing;
    1143            0 :         last_target    = m_target;
    1144            0 :         last_operator  = m_currentOperator.m_email;
    1145              :     }
    1146              : 
    1147            0 :     return 0;
    1148              : }
    1149              : 
    1150              : } // namespace app
    1151              : } // namespace MagAOX
    1152              : 
    1153              : #endif // observerCtrl_hpp
        

Generated by: LCOV version 2.0-1