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;
268 config.add(
"stream.writers",
276 "The device names of the stream writers to control." );
304 _config.configUnused( pfoa, mx::app::iniFile::makeKey(
sections[
i],
"pfoa" ) );
314 std::string fullName;
315 _config.configUnused( fullName, mx::app::iniFile::makeKey(
sections[
i],
"full_name" ) );
317 std::string institution;
318 _config.configUnused( institution, mx::app::iniFile::makeKey(
sections[
i],
"institution" ) );
321 for(
size_t n = 0;
n < email.size(); ++
n )
323 if( email[
n] ==
'@' )
327 else if( email[
n] ==
'.' )
363 std::vector<std::string>
emails;
367 emails.push_back(
it->second.m_email );
380 if(
it.first.find(
"jrmales" ) != std::string::npos )
403 if(
it.first.find(
"jrmales" ) != std::string::npos )
423 indi::addNumberElement<double>(
m_indiP_obsTime,
"observation", 0, 14400, 0.1,
"%0.1f",
"Current Obs" );
424 indi::addNumberElement<double>(
m_indiP_obsTime,
"target", 0, 14400, 0.1,
"%0.1f",
"Target" );
431 indi::addNumberElement<double>(
m_indiP_obsAngle,
"observation", 0, 360, 0.1,
"%0.1f",
"Current Obs" );
432 indi::addNumberElement<double>(
m_indiP_obsAngle,
"target", 0, 360, 0.1,
"%0.1f",
"Target" );
448 m_indiP_sws = pcf::IndiProperty( pcf::IndiProperty::Switch );
451 m_indiP_sws.setPerm( pcf::IndiProperty::ReadWrite );
453 m_indiP_sws.setRule( pcf::IndiProperty::AnyOfMany );
488 pcf::IndiProperty::Switch,
489 pcf::IndiProperty::ReadOnly,
490 pcf::IndiProperty::Idle,
491 pcf::IndiProperty::AtMostOne,
517 if(
lock.owns_lock() )
520 {
"full_name",
"email",
"pfoa",
"pronunciation",
"institution" },
542 {
"full_name",
"email",
"pfoa",
"pronunciation",
"institution" },
616 pcf::IndiProperty
ip( pcf::IndiProperty::Switch );
619 ip.setName(
"writing" );
620 ip.add( pcf::IndiElement(
"toggle" ) );
621 ip[
"toggle"].setSwitchState( pcf::IndiElement::On );
655 pcf::IndiProperty
ip( pcf::IndiProperty::Switch );
658 ip.setName(
"writing" );
659 ip.add( pcf::IndiElement(
"toggle" ) );
660 ip[
"toggle"].setSwitchState( pcf::IndiElement::Off );
673 std::string newEmail =
"";
674 for(
auto it = m_observers.begin();
it != m_observers.end(); ++
it )
676 if( !
ipRecv.find(
it->second.m_sanitizedEmail ) )
679 if(
ipRecv[
it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
687 newEmail =
it->first;
693 std::cerr <<
"nothing\n";
698 std::unique_lock<std::mutex>
lock( m_indiMutex );
700 m_currentObserver = m_observers[newEmail];
702 for(
auto it = m_observers.begin();
it != m_observers.end(); ++
it )
704 if(
it->first == m_currentObserver.m_sanitizedEmail )
706 updateSwitchIfChanged(
707 m_indiP_observers,
it->second.m_sanitizedEmail, pcf::IndiElement::On,
INDI_IDLE );
711 updateSwitchIfChanged(
712 m_indiP_observers,
it->second.m_sanitizedEmail, pcf::IndiElement::Off,
INDI_IDLE );
717 log<logger::observer>( { m_currentObserver.m_fullName,
718 m_currentObserver.m_pfoa,
719 m_currentObserver.m_email,
720 m_currentObserver.m_institution } );
731 std::string newEmail =
"";
732 for(
auto it = m_observers.begin();
it != m_observers.end(); ++
it )
734 if( !
ipRecv.find(
it->second.m_sanitizedEmail ) )
737 if(
ipRecv[
it->second.m_sanitizedEmail].getSwitchState() == pcf::IndiElement::On )
745 newEmail =
it->first;
751 std::cerr <<
"nothing\n";
756 std::unique_lock<std::mutex>
lock( m_indiMutex );
758 m_currentOperator = m_observers[newEmail];
760 for(
auto it = m_observers.begin();
it != m_observers.end(); ++
it )
762 if(
it->first == m_currentOperator.m_sanitizedEmail )
764 updateSwitchIfChanged(
765 m_indiP_operators,
it->second.m_sanitizedEmail, pcf::IndiElement::On,
INDI_IDLE );
769 updateSwitchIfChanged(
770 m_indiP_operators,
it->second.m_sanitizedEmail, pcf::IndiElement::Off,
INDI_IDLE );
775 log<logger::ao_operator>( { m_currentOperator.m_fullName,
776 m_currentOperator.m_pfoa,
777 m_currentOperator.m_email,
778 m_currentOperator.m_institution } );
786 std::time_t
t = std::chrono::system_clock::to_time_t(
tp );
787 std::tm
tm = *std::gmtime( &
t );
790 auto duration =
tp.time_since_epoch();
791 auto seconds = std::chrono::duration_cast<std::chrono::seconds>( duration );
792 auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>( duration -
seconds ).count();
795 std::stringstream
ss;
796 ss << std::put_time( &
tm,
"%Y-%m-%dT%H:%M:%S" );
797 ss <<
'.' << std::setw( 9 ) << std::setfill(
'0' ) <<
nanos <<
"Z";
808 std::unique_lock<std::mutex>
lock( m_indiMutex );
810 if( indiTargetUpdate( m_indiP_obsName, target,
ipRecv,
true ) < 0 )
812 log<software_error>( { __FILE__, __LINE__ } );
825 if( !
ipRecv.find(
"toggle" ) )
830 recordObserver(
true );
831 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
833 std::unique_lock<std::mutex>
lock( m_indiMutex );
835 updateSwitchIfChanged( m_indiP_observing,
"toggle", pcf::IndiElement::On,
INDI_OK );
837 else if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
839 std::unique_lock<std::mutex>
lock( m_indiMutex );
841 updateSwitchIfChanged( m_indiP_observing,
"toggle", pcf::IndiElement::Off,
INDI_IDLE );
851 return indiTargetUpdate( m_indiP_obsDuration, m_obsDuration,
ipRecv,
false );
858 if( m_observing ==
true )
864 for(
size_t n = 0; n < m_streamWriters.size(); ++n )
866 if( !
ipRecv.find( m_streamWriters[n] ) )
871 std::unique_lock<std::mutex>
lock( m_indiMutex );
872 updateSwitchIfChanged( m_indiP_sws, m_streamWriters[n],
ipRecv[m_streamWriters[n]].getSwitchState() );
887 if(
ipRecv.find(
"email" ) )
889 email =
ipRecv[
"email"].get();
892 if(
ipRecv.find(
"message" ) )
894 message =
ipRecv[
"message"].get();
904 email = m_currentObserver.m_email;
907 if(
ipRecv.find(
"time_s" ) )
912 if(
ipRecv.find(
"time_ns" ) )
923 log<user_log>( { email, message } );
933 if( !
ipRecv.find(
"request" ) )
938 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
942 m_tgtStartTime = m_obsStartTime;
943 m_tgtStartParang = m_obsStartParang;
947 m_newTargetBlock =
true;
959 if( indiTargetUpdate( m_indiP_target, target,
ipRecv ) < 0 )
964 if( target != m_target )
971 m_newTargetBlock =
true;
977 std::unique_lock<std::mutex>
lock( m_indiMutex );
978 updatesIfChanged<std::string>( m_indiP_target, {
"current",
"target" }, { m_target, m_target } );
982 m_newPointing =
false;
983 updateSwitchIfChanged(m_indiP_newPointing,
"set", pcf::IndiElement::Off);
992 if( !
ipRecv.find(
"request" ) )
997 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
999 if( m_target != m_catObj )
1006 m_newTargetBlock =
true;
1008 m_target = m_catObj;
1010 log<text_log>(
"Target updated by observer to TCS target: " + m_target,
logPrio::LOG_NOTICE );
1011 updatesIfChanged<std::string>( m_indiP_target, {
"current",
"target" }, { m_target, m_target } );
1015 m_newPointing =
false;
1016 updateSwitchIfChanged(m_indiP_newPointing,
"set", pcf::IndiElement::Off);
1026 if( !
ipRecv.find(
"object" ) )
1031 std::string
object =
ipRecv[
"object"].get();
1033 if(
object != m_catObj )
1037 m_newPointing =
true;
1038 updateSwitchIfChanged(m_indiP_newPointing,
"set", pcf::IndiElement::On);
1051 bool change =
false;
1052 if(
ipRecv.find(
"ra" ) )
1054 std::string ra =
ipRecv[
"ra"].get();
1063 if(
ipRecv.find(
"dec" ) )
1065 std::string dec =
ipRecv[
"dec"].get();
1067 if( dec != m_catDec )
1076 m_newPointing =
true;
1077 updateSwitchIfChanged(m_indiP_newPointing,
"set", pcf::IndiElement::On);
1088 if(
ipRecv.find(
"pa" ) )
1090 m_parang =
ipRecv[
"pa"].get<
double>();
1100 if(
ipRecv.find(
"toggle" ) )
1102 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1112 std::cerr <<
"got labmode: " << m_labMode <<
'\n';
The base-class for XWCTk applications.
stateCodes::stateCodeT state()
Get the current state code.
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 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_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 form of 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)
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_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.
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()
pcf::IndiProperty m_indiP_newPointing
Reports the status of the new pointing flag.
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.
Software CRITICAL log entry.
Log entry recording the build-time git state.
A simple text log, a string-type log.
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.