7 #ifndef picoMotorCtrl_hpp
8 #define picoMotorCtrl_hpp
12 #include "../../libMagAOX/libMagAOX.hpp"
13 #include "../../magaox_git_version.h"
38 std::string & response,
39 const std::string & fullResponse
42 size_t carrot = fullResponse.find(
'>');
44 if(carrot == std::string::npos)
47 response = fullResponse;
58 if(carrot == fullResponse.size()-1)
67 address = std::stoi( fullResponse.substr(0,carrot));
68 response = fullResponse.substr(carrot+1);
136 const std::string & n,
245 const pcf::IndiProperty &
ipRecv
263 const pcf::IndiProperty &
ipRecv
300 if(
it->second.m_thread !=
nullptr)
302 if(
it->second.m_thread->joinable())
it->second.m_thread->join();
303 delete it->second.m_thread;
311 config.add(
"device.address",
"",
"device.address", argType::Required,
"device",
"address",
false,
"string",
"The controller IP address.");
312 config.add(
"device.nChannels",
"",
"device.nChannels", argType::Required,
"device",
"nChannels",
false,
"int",
"Number of motoro channels. Default is 4.");
320 #define PICOMOTORCTRL_E_NOMOTORS (-5)
321 #define PICOMOTORCTRL_E_BADCHANNEL (-6)
322 #define PICOMOTORCTRL_E_DUPMOTOR (-7)
323 #define PICOMOTORCTRL_E_INDIREG (-20)
333 std::vector<std::string> sections;
335 _config.unusedSections(sections);
337 if( sections.size() == 0 )
345 for(
size_t i=0;i<sections.size(); ++i)
348 _config.configUnused(channel, mx::app::iniFile::makeKey(sections[i],
"channel" ) );
357 log<text_log>(
"Bad channel specificiation: " + sections[i] +
" channel: " + std::to_string(channel),
logPrio::LOG_CRITICAL);
363 _config.configUnused(address, mx::app::iniFile::makeKey(sections[i],
"address" ) );
367 log<text_log>(
"Bad channel specificiation: " + sections[i] +
" address: " + std::to_string(address),
logPrio::LOG_CRITICAL);
373 _config.configUnused(type, mx::app::iniFile::makeKey(sections[i],
"type" ) );
377 log<text_log>(
"Bad motor type specificiation: " + sections[i] +
" type: " + std::to_string(type),
logPrio::LOG_CRITICAL);
383 std::pair<channelMapT::iterator, bool> insert =
m_channels.insert(std::pair<std::string, motorChannel>(sections[i],
motorChannel(
this,sections[i], address, channel, type)));
385 if(insert.second ==
false)
387 log<text_log>(
"Duplicate motor specificiation: " + sections[i] +
" " + std::to_string(channel),
logPrio::LOG_CRITICAL);
392 _config.configUnused(insert.first->second.m_presetNames, mx::app::iniFile::makeKey(sections[i],
"names" ));
393 _config.configUnused(insert.first->second.m_presetPositions, mx::app::iniFile::makeKey(sections[i],
"positions" ));
397 log<pico_channel>({sections[i], (uint8_t) channel});
447 createStandardIndiNumber(
it->second.m_property,
it->first+
"_pos", std::numeric_limits<posT>::lowest(), std::numeric_limits<posT>::max(),
static_cast<posT>(1),
"%d",
"Position",
it->first);
448 it->second.m_property[
"current"].set(
it->second.m_currCounts);
449 it->second.m_property[
"target"].set(
it->second.m_currCounts);
454 #ifndef PICOMOTORCTRL_TEST_NOLOG
455 log<software_error>({__FILE__,__LINE__});
460 if(
it->second.m_presetNames.size() > 0)
464 log<software_critical>({__FILE__, __LINE__});
469 log<software_error>({__FILE__,__LINE__});
479 struct sigaction act;
483 act.sa_flags = SA_SIGINFO;
488 if( sigaction(SIGUSR1, &act, 0) < 0 )
490 std::string logss =
"Setting handler for SIGUSR1 failed. Errno says: ";
491 logss += strerror(errno);
493 log<software_error>({__FILE__, __LINE__, errno, 0, logss});
566 log<software_error>({__FILE__, __LINE__,
"wrong response to IDN query at address 1"});
597 std::string addprefix = std::to_string(
m_addresses[n]) +
">";
624 log<software_error>({__FILE__, __LINE__,
"splitResponse returned " + std::to_string(rv)});
632 log<software_error>({__FILE__, __LINE__,
"address did not match in response"});
637 if(resp.find(
"New_Focus") != std::string::npos)
639 log<text_log>(
"Connected to " + resp +
" at address " + std::to_string(
m_addresses[n]));
644 log<software_error>({__FILE__, __LINE__,
"wrong response to IDN query at address " + std::to_string(
m_addresses[n])});
665 std::string query = std::to_string(
it->second.m_address) +
">" + std::to_string(
it->second.m_channel) +
"QM?";
692 log<software_error>({__FILE__, __LINE__,
"splitResponse returned " + std::to_string(rv)});
697 if(add !=
it->second.m_address)
700 log<software_error>({__FILE__, __LINE__,
"address did not match in response"});
705 int moType = std::stoi(resp);
709 log<text_log>(
"No motor connected on channel " + std::to_string(
it->second.m_address) +
"." + std::to_string(
it->second.m_channel) +
" [" +
it->second.m_name +
"]",
logPrio::LOG_CRITICAL);
713 else if (moType !=
it->second.m_type)
716 std::string
msg =
"Wrong motor type connected on channel " + std::to_string(
it->second.m_address) +
".";
717 msg += std::to_string(
it->second.m_channel) +
" [" +
it->second.m_name +
"] ";
718 msg +=
"expected " + std::to_string(
it->second.m_type) +
" ";
719 msg +=
"got " + std::to_string(moType);
760 log<software_error>({__FILE__, __LINE__,
"wrong response to IDN query"});
767 bool anymoving =
false;
774 std::string query = std::to_string(
it->second.m_address) +
">" + std::to_string(
it->second.m_channel) +
"MD?";
801 log<software_error>({__FILE__, __LINE__,
"splitResponse returned " + std::to_string(rv)});
806 if(add !=
it->second.m_address)
809 log<software_error>({__FILE__, __LINE__,
"address did not match in response"});
815 if(std::stoi(resp) == 0)
818 it->second.m_moving =
true;
822 it->second.m_moving =
false;
825 if(
it->second.m_moving ==
false &&
it->second.m_doMove ==
true)
827 it->second.m_currCounts =
it->second.m_property[
"target"].get<
long>();
828 log<text_log>(
"moved " +
it->second.m_name +
" to " + std::to_string(
it->second.m_currCounts) +
" counts");
829 it->second.m_doMove =
false;
843 for(
size_t n=0; n <
it->second.m_presetNames.size(); ++n)
845 bool changed =
false;
846 if(
it->second.m_currCounts ==
it->second.m_presetPositions[n])
848 if(
it->second.m_indiP_presetName[
it->second.m_presetNames[n]] == pcf::IndiElement::Off) changed =
true;
849 it->second.m_indiP_presetName[
it->second.m_presetNames[n]] = pcf::IndiElement::On;
853 if(
it->second.m_indiP_presetName[
it->second.m_presetNames[n]] == pcf::IndiElement::On) changed =
true;
854 it->second.m_indiP_presetName[
it->second.m_presetNames[n]] = pcf::IndiElement::Off;
857 if(changed)
m_indiDriver->sendSetProperty(
it->second.m_indiP_presetName);
862 log<software_error>({__FILE__, __LINE__});
890 if(
it->second.m_thread->joinable())
892 pthread_kill(
it->second.m_thread->native_handle(), SIGUSR1);
895 it->second.m_thread->join();
910 std::string statusDir =
sysPath;
914 std::string fileName = statusDir +
"/" + chName;
917 posIn.open( fileName );
921 log<text_log>(
"no position file for " + chName +
" found. initializing to 0.");
930 log<text_log>(
"initializing " + chName +
" to " + std::to_string(pos));
939 std::string statusDir =
sysPath;
943 std::string fileName = statusDir +
"/" + chName;
947 std::ofstream posOut;
948 posOut.open( fileName );
952 log<text_log>(
"could not open counts file for " + chName +
" -- can not store position.",
logPrio::LOG_ERROR);
991 log<text_log>(
"moving " + mc->
m_name +
" by " + std::to_string(dr) +
" counts");
993 std::string comm = std::to_string(mc->
m_address) +
">" + std::to_string(mc->
m_channel) +
"PR" + std::to_string(dr);
1020 const pcf::IndiProperty &
ipRecv
1030 std::string propName =
ipRecv.getName();
1031 size_t nend = propName.rfind(
"_pos");
1033 if(nend == std::string::npos)
1035 log<software_error>({__FILE__, __LINE__,
"Channel without _pos received"});
1039 std::string chName = propName.substr(0, nend);
1044 log<software_error>({__FILE__, __LINE__,
"Unknown channel name received"});
1048 if(
it->second.m_doMove ==
true)
1065 it->second.m_doMove=
true;
1067 pthread_kill(
it->second.m_thread->native_handle(), SIGUSR1);
1073 const pcf::IndiProperty &
ipRecv
1085 log<software_error>({__FILE__, __LINE__,
"Unknown channel name received"});
1089 if(
it->second.m_doMove ==
true)
1095 long counts = -1e10;
1098 for(i=0; i<
it->second.m_presetNames.size(); ++i)
1100 if(!
ipRecv.find(
it->second.m_presetNames[i]))
continue;
1102 if(
ipRecv[
it->second.m_presetNames[i]].getSwitchState() == pcf::IndiElement::On)
1110 counts =
it->second.m_presetPositions[i];
1111 std::cerr <<
"selected: " <<
it->second.m_presetNames[i] <<
" " << counts <<
"\n";
1119 it->second.m_property[
"target"].set(counts);
1122 it->second.m_doMove=
true;
1124 pthread_kill(
it->second.m_thread->native_handle(), SIGUSR1);
1141 static std::vector<int64_t> lastpos(
m_nChannels, std::numeric_limits<long>::max());
1143 bool changed =
false;
1146 if(
it->second.m_currCounts != lastpos[
it->second.m_channel-1]) changed =
true;
1149 if( changed || force )
1153 lastpos[
it->second.m_channel-1] =
it->second.m_currCounts;
1156 telem<telem_pico>(lastpos);
Internal class to manage setuid privilege escalation with RAII.
The base-class for MagAO-X applications.
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
std::string m_configName
The name of the configuration file (minus .conf).
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 createStandardIndiNumber(pcf::IndiProperty &prop, const std::string &name, const T &min, const T &max, const T &step, const std::string &format, const std::string &label="", const std::string &group="")
Create a standard R/W INDI Number property with target and current elements.
int powerState()
Returns the current power state.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
int powerStateTarget()
Returns the target power state.
int stateLogged()
Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change,...
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
bool powerOnWaitElapsed()
This method tests whether the power on wait time has elapsed.
std::mutex m_indiMutex
Mutex for locking INDI communications.
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 threadStart(std::thread &thrd, bool &thrdInit, pid_t &tpid, pcf::IndiProperty &thProp, int thrdPrio, const std::string &cpuset, const std::string &thrdName, thisPtr *thrdThis, Function &&thrdStart)
Start a thread, using this class's privileges to set priority, etc.
std::string sysPath
The path to the system directory, for PID file, etc.
int indiTargetUpdate(pcf::IndiProperty &localProperty, T &localTarget, const pcf::IndiProperty &remoteProperty, bool setBusy=true)
Get the target element value from an new property.
int newCallBack_picopos(const pcf::IndiProperty &ipRecv)
The handler function for relative position requests, called by the static callback.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
int m_nChannels
The number of motor channels total on the hardware. Number of attached motors inferred from config.
virtual int onPowerOff()
Implementation of the on-power-off FSM logic.
tty::telnetConn m_telnetConn
The telnet connection manager.
static int st_newCallBack_presetName(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for position presets.
std::map< std::string, motorChannel > channelMapT
~picoMotorCtrl() noexcept
D'tor, declared and defined for noexcept.
static void channelThreadStart(motorChannel *mc)
Channel thread starter function.
posT readChannelCounts(const std::string &chName)
Read the current channel counts from disk at startup.
std::string m_deviceAddr
The device address.
virtual int appStartup()
Startup functions.
virtual void loadConfig()
load the configuration system results (called by MagAOXApp::setup())
virtual int appLogic()
Implementation of the FSM.
int recordPico(bool force=false)
dev::telemeter< picoMotorCtrl > telemeterT
virtual int whilePowerOff()
Implementation of the while-powered-off FSM.
std::vector< int > m_addresses
The unique controller addresses.
int recordTelem(const telem_pico *)
static int st_newCallBack_picopos(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for relative position requests.
picoMotorCtrl()
Default c'tor.
std::string m_devicePort
The device port.
virtual int appShutdown()
Do any needed shutdown tasks.
std::mutex m_telnetMutex
Mutex for locking telnet communications.
int writeChannelCounts(const std::string &chName, posT counts)
virtual void setupConfig()
Setup the configuration system (called by MagAOXApp::setup())
void channelThreadExec(motorChannel *mc)
Channel thread execution function.
int newCallBack_presetName(const pcf::IndiProperty &ipRecv)
The handler function for position presets, called by the static callback.
channelMapT m_channels
Map of motor names to channel.
std::vector< pcf::IndiProperty > m_indiP_counts
@ OPERATING
The device is operating, other than homing.
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
@ READY
The device is ready for operation, but is not operating.
@ CONNECTED
The application has connected to the device or service.
@ NOTCONNECTED
The application is not connected to the device or service.
@ POWERON
The device power is on.
std::string ttyErrorString(int ec)
Get a text explanation of a TTY_E_ error code.
int splitResponse(int &address, std::string &response, const std::string &fullResponse)
void sigUsr1Handler(int signum, siginfo_t *siginf, void *ucont)
Empty signal handler. SIGUSR1 is used to interrupt sleep in various threads.
const pcf::IndiProperty & ipRecv
std::unique_lock< std::mutex > lock(m_indiMutex)
constexpr static logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
constexpr static logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
constexpr static logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
#define PICOMOTORCTRL_E_INDIREG
#define PICOMOTORCTRL_E_NOMOTORS
#define PICOMOTORCTRL_E_BADCHANNEL
#define PICOMOTORCTRL_E_DUPMOTOR
An input/output capable device.
unsigned m_writeTimeout
The write timeout [msec].
int loadConfig(mx::app::appConfigurator &config)
Load the device section from an application configurator.
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for the device section.
unsigned m_readTimeout
The read timeout [msec].
A device base class which saves telemetry.
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
picoMotorCtrl * m_parent
A pointer to this for thread starting.
int m_address
The controller address, default is 1.
int m_channel
The number of this channel, where the motor is plugged in.
std::vector< std::string > m_presetNames
bool m_doMove
Flag indicating that a move is requested.
pid_t m_threadID
The ID of the thread.
pcf::IndiProperty m_threadProp
The property to hold the thread details.
posT m_currCounts
The current counts, the cumulative position.
bool m_threadInit
Thread initialization flag.
pcf::IndiProperty m_indiP_presetName
std::vector< posT > m_presetPositions
pcf::IndiProperty m_property
motorChannel(picoMotorCtrl *p, const std::string &n, int add, int ch, int type)
motorChannel(picoMotorCtrl *p)
std::string m_name
The name of this channel, from the config section.
bool m_moving
Flag to indicate that we are actually moving.
std::thread * m_thread
Thread for managing this channel. A pointer to allow copying, but must be deleted in d'tor of parent.
int m_type
The motor type of this channel, default is 3.
Log entry recording CPU temperatures.
A Telnet connection manager, wrapping libtelnet.
int write(const std::string &buffWrite, int timeoutWrite)
Write to a telnet connection.
std::string m_strRead
The accumulated string read from the device.
int noLogin()
Set flags as if we're logged in, used when device doesn't require it.
std::string m_prompt
The device's prompt, used for detecting end of transmission.
int read(const std::string &eot, int timeoutRead, bool clear=true)
Read from a telnet connection, until end-of-transmission string is read.
int connect(const std::string &host, const std::string &port)
Connect to the device.
#define TELEMETER_APP_LOGIC
Call telemeter::appLogic with error checking.
#define TELEMETER_LOAD_CONFIG(cfig)
Call telemeter::loadConfig with error checking.
#define TELEMETER_APP_STARTUP
Call telemeter::appStartup with error checking.
#define TELEMETER_SETUP_CONFIG(cfig)
Call telemeter::setupConfig with error checking.
#define TELEMETER_APP_SHUTDOWN
Call telemeter::appShutdown with error checking.