7#ifndef observerCtrl_hpp
8#define observerCtrl_hpp
11#include <mx/math/geo.hpp>
13#include "../../libMagAOX/libMagAOX.hpp"
14#include "../../magaox_git_version.h"
48 typedef std::chrono::time_point<std::chrono::steady_clock>
timePointT;
272 config.add(
"stream.writers",
280 "The device names of the stream writers to control." );
318 std::string fullName;
319 _config.configUnused( fullName, mx::app::iniFile::makeKey(
sections[
i],
"full_name" ) );
325 for(
size_t n = 0;
n < email.size(); ++
n )
327 if( email[
n] ==
'@' )
331 else if( email[
n] ==
'.' )
367 std::vector<std::string>
emails;
371 emails.push_back(
it->second.m_email );
384 if(
it.first.find(
"jrmales" ) != std::string::npos )
407 if(
it.first.find(
"jrmales" ) != std::string::npos )
427 indi::addNumberElement<double>(
m_indiP_obsTime,
"observation", 0, 14400, 0.1,
"%0.1f",
"Current Obs" );
428 indi::addNumberElement<double>(
m_indiP_obsTime,
"target", 0, 14400, 0.1,
"%0.1f",
"Target" );
435 indi::addNumberElement<double>(
m_indiP_obsAngle,
"observation", 0, 360, 0.1,
"%0.1f",
"Current Obs" );
436 indi::addNumberElement<double>(
m_indiP_obsAngle,
"target", 0, 360, 0.1,
"%0.1f",
"Target" );
452 m_indiP_sws = pcf::IndiProperty( pcf::IndiProperty::Switch );
455 m_indiP_sws.setPerm( pcf::IndiProperty::ReadWrite );
457 m_indiP_sws.setRule( pcf::IndiProperty::AnyOfMany );
502 if(
lock.owns_lock() )
505 {
"full_name",
"email",
"pfoa",
"pronunciation",
"institution" },
527 {
"full_name",
"email",
"pfoa",
"pronunciation",
"institution" },
601 pcf::IndiProperty
ip( pcf::IndiProperty::Switch );
604 ip.setName(
"writing" );
605 ip.add( pcf::IndiElement(
"toggle" ) );
606 ip[
"toggle"].setSwitchState( pcf::IndiElement::On );
640 pcf::IndiProperty
ip( pcf::IndiProperty::Switch );
643 ip.setName(
"writing" );
644 ip.add( pcf::IndiElement(
"toggle" ) );
645 ip[
"toggle"].setSwitchState( pcf::IndiElement::Off );
658 std::string newEmail =
"";
659 for(
auto it = m_observers.begin();
it != m_observers.end(); ++
it )
661 if( !
ipRecv.find(
it->second.m_sanitizedEmail ) )
664 if(
ipRecv[
it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
672 newEmail =
it->first;
678 std::cerr <<
"nothing\n";
683 std::unique_lock<std::mutex>
lock( m_indiMutex );
685 m_currentObserver = m_observers[newEmail];
687 for(
auto it = m_observers.begin();
it != m_observers.end(); ++
it )
689 if(
it->first == m_currentObserver.m_sanitizedEmail )
691 updateSwitchIfChanged(
692 m_indiP_observers,
it->second.m_sanitizedEmail, pcf::IndiElement::On,
INDI_IDLE );
696 updateSwitchIfChanged(
697 m_indiP_observers,
it->second.m_sanitizedEmail, pcf::IndiElement::Off,
INDI_IDLE );
702 log<logger::observer>( { m_currentObserver.m_fullName,
703 m_currentObserver.m_pfoa,
704 m_currentObserver.m_email,
705 m_currentObserver.m_institution } );
716 std::string newEmail =
"";
717 for(
auto it = m_observers.begin();
it != m_observers.end(); ++
it )
719 if( !
ipRecv.find(
it->second.m_sanitizedEmail ) )
722 if(
ipRecv[
it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
730 newEmail =
it->first;
736 std::cerr <<
"nothing\n";
741 std::unique_lock<std::mutex>
lock( m_indiMutex );
743 m_currentOperator = m_observers[newEmail];
745 for(
auto it = m_observers.begin();
it != m_observers.end(); ++
it )
747 if(
it->first == m_currentOperator.m_sanitizedEmail )
749 updateSwitchIfChanged(
750 m_indiP_operators,
it->second.m_sanitizedEmail, pcf::IndiElement::On,
INDI_IDLE );
754 updateSwitchIfChanged(
755 m_indiP_operators,
it->second.m_sanitizedEmail, pcf::IndiElement::Off,
INDI_IDLE );
760 log<logger::ao_operator>( { m_currentOperator.m_fullName,
761 m_currentOperator.m_pfoa,
762 m_currentOperator.m_email,
763 m_currentOperator.m_institution } );
771 std::time_t
t = std::chrono::system_clock::to_time_t(
tp );
772 std::tm
tm = *std::gmtime( &
t );
775 auto duration =
tp.time_since_epoch();
776 auto seconds = std::chrono::duration_cast<std::chrono::seconds>( duration );
777 auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>( duration -
seconds ).count();
780 std::stringstream
ss;
781 ss << std::put_time( &
tm,
"%Y-%m-%dT%H:%M:%S" );
782 ss <<
'.' << std::setw( 9 ) << std::setfill(
'0' ) <<
nanos <<
"Z";
793 std::unique_lock<std::mutex>
lock( m_indiMutex );
795 if( indiTargetUpdate( m_indiP_obsName, target,
ipRecv,
true ) < 0 )
797 log<software_error>( { __FILE__, __LINE__ } );
810 if( !
ipRecv.find(
"toggle" ) )
815 recordObserver(
true );
816 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
818 std::unique_lock<std::mutex>
lock( m_indiMutex );
820 updateSwitchIfChanged( m_indiP_observing,
"toggle", pcf::IndiElement::On,
INDI_OK );
822 else if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
824 std::unique_lock<std::mutex>
lock( m_indiMutex );
826 updateSwitchIfChanged( m_indiP_observing,
"toggle", pcf::IndiElement::Off,
INDI_IDLE );
836 return indiTargetUpdate( m_indiP_obsDuration, m_obsDuration,
ipRecv,
false );
843 if( m_observing ==
true )
849 for(
size_t n = 0; n < m_streamWriters.size(); ++n )
851 if( !
ipRecv.find( m_streamWriters[n] ) )
856 std::unique_lock<std::mutex>
lock( m_indiMutex );
857 updateSwitchIfChanged( m_indiP_sws, m_streamWriters[n],
ipRecv[m_streamWriters[n]].getSwitchState() );
872 if(
ipRecv.find(
"email" ) )
874 email =
ipRecv[
"email"].get();
877 if(
ipRecv.find(
"message" ) )
879 message =
ipRecv[
"message"].get();
889 email = m_currentObserver.m_email;
892 if(
ipRecv.find(
"time_s" ) )
897 if(
ipRecv.find(
"time_ns" ) )
908 log<user_log>( { email, message } );
918 if( !
ipRecv.find(
"request" ) )
923 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
927 m_tgtStartTime = m_obsStartTime;
928 m_tgtStartParang = m_obsStartParang;
932 m_newTargetBlock =
true;
944 if( indiTargetUpdate( m_indiP_target, target,
ipRecv ) < 0 )
949 if( target != m_target )
953 m_tgtStartTime = m_obsStartTime;
954 m_tgtStartParang = m_obsStartParang;
958 m_newTargetBlock =
true;
966 std::unique_lock<std::mutex>
lock( m_indiMutex );
967 updatesIfChanged<std::string>( m_indiP_target, {
"current",
"target" }, { m_target, m_target } );
977 if( !
ipRecv.find(
"request" ) )
982 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
984 if( m_target != m_catObj )
988 m_tgtStartTime = m_obsStartTime;
989 m_tgtStartParang = m_obsStartParang;
993 m_newTargetBlock =
true;
999 log<text_log>(
"Target updated by observer to TCS target: " + m_target,
logPrio::LOG_NOTICE );
1000 updatesIfChanged<std::string>( m_indiP_target, {
"current",
"target" }, { m_target, m_target } );
1011 if( !
ipRecv.find(
"object" ) )
1016 std::string
object =
ipRecv[
"object"].get();
1018 if(
object != m_catObj )
1038 bool change =
false;
1039 if(
ipRecv.find(
"ra" ) )
1041 std::string ra =
ipRecv[
"ra"].get();
1059 if(
ipRecv.find(
"dec" ) )
1061 std::string dec =
ipRecv[
"dec"].get();
1063 if( dec != m_catDec )
1091 if(
ipRecv.find(
"pa" ) )
1093 m_parang =
ipRecv[
"pa"].get<
double>();
1103 if(
ipRecv.find(
"toggle" ) )
1105 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1115 std::cerr <<
"got labmode: " << m_labMode <<
'\n';
1123 if(
ipRecv.find(
"toggle" ) )
1125 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
The base-class for MagAO-X applications.
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
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.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
std::mutex m_indiMutex
Mutex for locking INDI communications.
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.
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)
The MagAO-X Observer Controller.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_loop)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_resetTarget)
pcf::IndiProperty m_indiP_observer
Text which contains the specifications of the current observer.
std::string m_pronunciation
Guide for the TTS to pronounced the pfoa (defaults to pfoa)
std::chrono::time_point< std::chrono::steady_clock > timePointT
pcf::IndiProperty m_indiP_obsName
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_obsDuration)
double m_parang
The current parallactic angle.
int recordTelem(const telem_observer *)
timePointT m_obsStartTime
The start time of the current observation.
pcf::IndiProperty m_indiP_resetTarget
Reset the target statistics.
std::string m_sanitizedEmail
Observer's email sanitized for use in INDI properties.
pcf::IndiProperty m_indiP_tcsTarget
Set the target to match TCS catObj.
pcf::IndiProperty m_indiP_obsTime
Number tracking the elapsed time.
pcf::IndiProperty m_indiP_obsAngle
Number tracking the change in angle.
std::string m_labModeProp
durationT m_tgtTime
The current target time. Only updated while observing.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_operators)
virtual int appLogic()
Implementation of the FSM for observerCtrl.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_labMode)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_observing)
std::string m_pfoa
Observer's preferred forma of address.
pcf::IndiProperty m_indiP_observers
Selection switch to allow selection of the observer.
int recordObserver(bool force=false)
std::string m_institution
The observer's institution.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_sws)
pcf::IndiProperty m_indiP_obsStart
String timestamp indicating the start for target/observation.
double m_obsDuration
The desired duration of the observation. If 0 then until stopped.
pcf::IndiProperty m_indiP_labMode
Tracks whether TCS is in lab mode.
std::string m_catalogProp
~observerCtrl() noexcept
D'tor, declared and defined for noexcept.
double m_obsStartParang
The parallactic angle at the start of the observation.
std::string m_catdataProp
pcf::IndiProperty m_indiP_catalog
Catalog text data.
std::chrono::duration< double > durationT
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_obsName)
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_observers)
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_catalog)
std::string m_loopStateProp
virtual void loadConfig()
dev::telemeter< observerCtrl > telemeterT
pcf::IndiProperty m_indiP_operator
Text which contains the specifications of the current observer.
std::vector< std::string > m_streamWriters
The stream writers to stop and start.
pcf::IndiProperty m_indiP_obsDuration
Number to set the desired duration of observation.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_teldata)
std::string m_fullName
Obsever's full name.
pcf::IndiProperty m_indiP_loop
Tracks the loop state.
pcf::IndiProperty m_indiP_target
The target name, which can be overridden by the user.
timeStampT m_obsStartTimeStamp
The UTC start time of the current observation.
pcf::IndiProperty m_indiP_operators
Selection switch to allow selection of the observer.
std::string m_teldataProp
virtual int appStartup()
Startup function.
bool m_observing
Flag indicating whether or not we are in an observation.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_userlog)
double m_tgtStartParang
The parallactic angle at the start of observing the current target.
pcf::IndiProperty m_indiP_userlog
Text to enter a user log.
std::string timeStampAsISO8601(const std::chrono::time_point< std::chrono::system_clock > &tp)
pcf::IndiProperty m_indiP_observing
Toggle switch to trigger observation.
observerCtrl()
Default c'tor.
pcf::IndiProperty m_indiP_catdata
Catalog numeric data.
INDI_SETCALLBACK_DECL(observerCtrl, m_indiP_catdata)
timePointT m_tgtStartTime
The start time of the current target.
double m_tgtAng
The current target angle. Only updated while observing.
virtual int appShutdown()
Shutdown the app.
bool m_labMode
Flag tracking whether the TCS interface is in lab mode.
bool m_loop
Flag tracking loop state. true is loop closed.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
std::string m_obsName
The name of the observation.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_tcsTarget)
std::string m_email
Observer's email. Must be unique.
observerMapT m_observers
The observers from the configuration file.
INDI_NEWCALLBACK_DECL(observerCtrl, m_indiP_target)
friend class observerCtrl_test
observer m_currentObserver
The current selected observer.
timeStampT m_tgtStartTimeStamp
The UTC start time of the current target.
observer m_currentOperator
The current selected observer.
std::map< std::string, observer > observerMapT
pcf::IndiProperty m_indiP_sws
Selection to switch which stream writers are enabled.
pcf::IndiProperty m_indiP_teldata
Telescope data (for parang)
virtual void setupConfig()
The observer specification.
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
#define CREATE_REG_INDI_NEW_TOGGLESWITCH(prop, name)
Create and register a NEW INDI property as a standard toggle switch, using the standard callback name...
#define CREATE_REG_INDI_NEW_REQUESTSWITCH(prop, name)
Create and register a NEW INDI property as a standard request switch, using the standard callback nam...
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
#define CREATE_REG_INDI_NEW_NUMBERD(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as double, using the standard callback n...
#define REG_INDI_NEWPROP_NOSETUP(prop)
Register a NEW INDI property with the class, using the standard callback name.
#define CREATE_REG_INDI_NEW_TEXT(prop, name, label, group)
Create and register a NEW INDI property as a standard text, using the standard callback name.
#define CREATE_REG_INDI_RO_NUMBER(prop, name, label, group)
Create and register a RO INDI property as a number, using the standard callback name.
uint32_t secT
The type used for seconds.
@ READY
The device is ready for operation, but is not operating.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
int addTextElement(pcf::IndiProperty &prop, const std::string &name, const std::string &label="")
Add a standard INDI Text element.
const pcf::IndiProperty & ipRecv
std::unique_lock< std::mutex > lock(m_indiMutex)
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.
uint32_t nanosecT
The type used for nanoseconds.
A device base class which saves telemetry.
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Log entry recording the build-time git state.
A fixed-width timespec structure.
secT time_s
Time since the Unix epoch.
#define TELEMETER_APP_LOGIC
Call telemeter::appLogic with error checking.
#define TELEMETER_APP_STARTUP
Call telemeter::appStartup with error checking.
#define TELEMETER_APP_SHUTDOWN
Call telemeter::appShutdown with error checking.