API
picoMotorCtrl.hpp
Go to the documentation of this file.
1 /** \file picoMotorCtrl.hpp
2  * \brief The MagAO-X Pico Motor Controller header file
3  *
4  * \ingroup picoMotorCtrl_files
5  */
6 
7 #ifndef picoMotorCtrl_hpp
8 #define picoMotorCtrl_hpp
9 
10 #include <map>
11 
12 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
13 #include "../../magaox_git_version.h"
14 
15 /** \defgroup picoMotorCtrl
16  * \brief The Pico Motor Controller application.
17  *
18  * Controls a multi-channel Newport pico motor controller. Each motor gets its own thread.
19  *
20  * <a href="../handbook/operating/software/apps/picoMotorCtrl.html">Application Documentation</a>
21  *
22  * \ingroup apps
23  *
24  */
25 
26 /** \defgroup picoMotorCtrl_files
27  * \ingroup picoMotorCtrl
28  */
29 
30 
31 
32 namespace MagAOX
33 {
34 namespace app
35 {
36 
37 /** MagAO-X application to control a multi-channel Newport Picomotor Controller.
38  *
39  * \todo need to recognize signals in tty polls and not return errors, etc.
40  * \todo need to implement an onDisconnect() to update values to unknown indicators.
41  * \todo need a frequency-dependent max amp facility.
42  * \todo convert to ioDevice
43  * \todo need telnet device, with optional username/password.
44  *
45  */
46 class picoMotorCtrl : public MagAOXApp<>, public dev::ioDevice, public dev::telemeter<picoMotorCtrl>
47 {
48 
49  friend class dev::telemeter<picoMotorCtrl>;
50 
52 
53  typedef long posT;
54 
55  struct motorChannel
56  {
57  picoMotorCtrl * m_parent {nullptr}; ///< A pointer to this for thread starting.
58 
59  std::string m_name; ///< The name of this channel, from the config section
60 
61  std::vector<std::string> m_presetNames;
62  std::vector<posT> m_presetPositions;
63 
64  int m_channel {-1}; ///< The number of this channel, where the motor is plugged in
65 
66  posT m_currCounts {0}; ///< The current counts, the cumulative position
67 
68  bool m_doMove {false}; ///< Flag indicating that a move is requested.
69  bool m_moving {false}; ///< Flag to indicate that we are actually moving
70 
71  pcf::IndiProperty m_property;
72  pcf::IndiProperty m_indiP_presetName;
73 
74  std::thread * m_thread {nullptr}; ///< Thread for managing this channel. A pointer to allow copying, but must be deleted in d'tor of parent.
75 
76  bool m_threadInit {true}; ///< Thread initialization flag.
77 
78  pid_t m_threadID {0}; ///< The ID of the thread.
79 
80  pcf::IndiProperty m_threadProp; ///< The property to hold the thread details.
81 
82  motorChannel( picoMotorCtrl * p /**< [in] The parent point to set */) : m_parent(p)
83  {
84  m_thread = new std::thread;
85  }
86 
87  motorChannel( picoMotorCtrl * p, ///< [in] The parent point to set
88  const std::string & n, ///< [in] The name of this channel
89  int ch ///< [in] The number of this channel
90  ) : m_parent(p), m_name(n), m_channel(ch)
91  {
92  m_thread = new std::thread;
93  }
94 
95  };
96 
97  typedef std::map<std::string, motorChannel> channelMapT;
98 
99  /** \name Configurable Parameters
100  * @{
101  */
102 
103  std::string m_deviceAddr; ///< The device address
104  std::string m_devicePort {"23"}; ///< The device port
105 
106  int m_nChannels {4}; ///< The number of motor channels total on the hardware. Number of attached motors inferred from config.
107 
108  ///@}
109 
110  channelMapT m_channels; ///< Map of motor names to channel.
111 
112  tty::telnetConn m_telnetConn; ///< The telnet connection manager
113 
114  ///Mutex for locking telnet communications.
115  std::mutex m_telnetMutex;
116 
117  public:
118 
119  /// Default c'tor.
120  picoMotorCtrl();
121 
122  /// D'tor, declared and defined for noexcept.
123  ~picoMotorCtrl() noexcept;
124 
125  /// Setup the configuration system (called by MagAOXApp::setup())
126  virtual void setupConfig();
127 
128  /// Implementation of loadConfig logic, separated for testing.
129  /** This is called by loadConfig().
130  */
131  int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
132 
133  /// load the configuration system results (called by MagAOXApp::setup())
134  virtual void loadConfig();
135 
136  /// Startup functions
137  /** Setsup the INDI vars.
138  *
139  */
140  virtual int appStartup();
141 
142  /// Implementation of the FSM
143  /**
144  * \returns 0 on no critical error
145  * \returns -1 on an error requiring shutdown
146  */
147  virtual int appLogic();
148 
149  /// Implementation of the on-power-off FSM logic
150  virtual int onPowerOff();
151 
152  /// Implementation of the while-powered-off FSM
153  virtual int whilePowerOff();
154 
155  /// Do any needed shutdown tasks.
156  virtual int appShutdown();
157 
158  /// Read the current channel counts from disk at startup
159  /** Reads the counts from the file with the specified name in this apps sys directory.
160  * Returns the file contents as a posT.
161  */
162  posT readChannelCounts(const std::string & chName);
163 
164  int writeChannelCounts( const std::string & chName,
165  posT counts
166  );
167 
168  /// Channel thread starter function
169  static void channelThreadStart( motorChannel * mc /**< [in] the channel to start controlling */);
170 
171  /// Channel thread execution function
172  /** Runs until m_shutdown is true.
173  */
174  void channelThreadExec( motorChannel * mc );
175 
176 /** \name INDI
177  * @{
178  */
179 protected:
180 
181  //declare our properties
182  std::vector<pcf::IndiProperty> m_indiP_counts;
183 
184 
185 public:
186  /// The static callback function to be registered for relative position requests
187  /** Dispatches to the handler, which then signals the relavent thread.
188  *
189  * \returns 0 on success.
190  * \returns -1 on error.
191  */
192  static int st_newCallBack_picopos( void * app, ///< [in] a pointer to this, will be static_cast-ed to this
193  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
194  );
195 
196  /// The handler function for relative position requests, called by the static callback
197  /** Signals the relavent thread.
198  *
199  * \returns 0 on success.
200  * \returns -1 on error.
201  */
202  int newCallBack_picopos( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
203 
204  /// The static callback function to be registered for position presets
205  /** Dispatches to the handler, which then signals the relavent thread.
206  *
207  * \returns 0 on success.
208  * \returns -1 on error.
209  */
210  static int st_newCallBack_presetName( void * app, ///< [in] a pointer to this, will be static_cast-ed to this
211  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
212  );
213 
214  /// The handler function for position presets, called by the static callback
215  /** Signals the relavent thread.
216  *
217  * \returns 0 on success.
218  * \returns -1 on error.
219  */
220  int newCallBack_presetName( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
221  ///@}
222 
223  /** \name Telemeter Interface
224  *
225  * @{
226  */
227  int checkRecordTimes();
228 
229  int recordTelem( const telem_pico * );
230 
231  int recordPico( bool force = false );
232  ///@}
233 
234 };
235 
236 picoMotorCtrl::picoMotorCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
237 {
238  m_powerMgtEnabled = true;
239  m_telnetConn.m_prompt = "\r\n";
240  return;
241 }
242 
244 {
245  //Wait for each channel thread to exit, then delete it.
246  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
247  {
248  if(it->second.m_thread != nullptr)
249  {
250  if(it->second.m_thread->joinable()) it->second.m_thread->join();
251  delete it->second.m_thread;
252  }
253  }
254 }
255 
256 
258 {
259  config.add("device.address", "", "device.address", argType::Required, "device", "address", false, "string", "The controller IP address.");
260  config.add("device.nChannels", "", "device.nChannels", argType::Required, "device", "nChannels", false, "int", "Number of motoro channels. Default is 4.");
261 
263 
264  telemeterT::setupConfig(config);
265 }
266 
267 #define PICOMOTORCTRL_E_NOMOTORS (-5)
268 #define PICOMOTORCTRL_E_BADCHANNEL (-6)
269 #define PICOMOTORCTRL_E_DUPMOTOR (-7)
270 #define PICOMOTORCTRL_E_INDIREG (-20)
271 
272 int picoMotorCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
273 {
274  //Standard config parsing
275  _config(m_deviceAddr, "device.address");
276  _config(m_nChannels, "device.nChannels");
277 
278 
279  // Parse the unused config options to look for motors
280  std::vector<std::string> sections;
281 
282  _config.unusedSections(sections);
283 
284  if( sections.size() == 0 )
285  {
286  log<text_log>("No motors found in config.", logPrio::LOG_CRITICAL);
287 
289  }
290 
291  //Now see if any unused sections have a channel keyword
292  for(size_t i=0;i<sections.size(); ++i)
293  {
294  int channel = -1;
295  _config.configUnused(channel, mx::app::iniFile::makeKey(sections[i], "channel" ) );
296  if( channel == -1 )
297  {
298  //not a channel
299  continue;
300  }
301 
302  if(channel < 1 || channel > m_nChannels)
303  {
304  log<text_log>("Bad channel specificiation: " + sections[i] + " " + std::to_string(channel), logPrio::LOG_CRITICAL);
305 
307  }
308 
309  //Ok, valid channel. Insert into map and check for duplicates.
310  std::pair<channelMapT::iterator, bool> insert = m_channels.insert(std::pair<std::string, motorChannel>(sections[i], motorChannel(this,sections[i],channel)));
311 
312  if(insert.second == false)
313  {
314  log<text_log>("Duplicate motor specificiation: " + sections[i] + " " + std::to_string(channel), logPrio::LOG_CRITICAL);
316  }
317  else
318  {
319  _config.configUnused(insert.first->second.m_presetNames, mx::app::iniFile::makeKey(sections[i], "names" ));
320  _config.configUnused(insert.first->second.m_presetPositions, mx::app::iniFile::makeKey(sections[i], "positions" ));
321  }
322 
323  log<pico_channel>({sections[i], (uint8_t) channel});
324  }
325 
326  return 0;
327 }
328 
330 {
331  if( loadConfigImpl(config) < 0)
332  {
333  log<text_log>("Error during config", logPrio::LOG_CRITICAL);
334  m_shutdown = true;
335  }
336 
337  if(dev::ioDevice::loadConfig(config) < 0)
338  {
339  log<text_log>("Error during ioDevice config", logPrio::LOG_CRITICAL);
340  m_shutdown = true;
341  }
342 
343  if(telemeterT::loadConfig(config) < 0)
344  {
345  log<text_log>("Error during telemeter config", logPrio::LOG_CRITICAL);
346  m_shutdown = true;
347  }
348 }
349 
351 {
352  ///\todo read state from disk to get current counts.
353 
354  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
355  {
356  it->second.m_currCounts = readChannelCounts(it->second.m_name);
357 
358 
359  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);
360  it->second.m_property["current"].set(it->second.m_currCounts);
361  it->second.m_property["target"].set(it->second.m_currCounts);
362  it->second.m_property.setState(INDI_IDLE);
363 
364  if( registerIndiPropertyNew( it->second.m_property, st_newCallBack_picopos) < 0)
365  {
366  #ifndef PICOMOTORCTRL_TEST_NOLOG
367  log<software_error>({__FILE__,__LINE__});
368  #endif
370  }
371 
372  if(it->second.m_presetNames.size() > 0)
373  {
374  if(createStandardIndiSelectionSw( it->second.m_indiP_presetName, it->first, it->second.m_presetNames) < 0)
375  {
376  log<software_critical>({__FILE__, __LINE__});
377  return -1;
378  }
379  if( registerIndiPropertyNew( it->second.m_indiP_presetName, st_newCallBack_presetName) < 0)
380  {
381  log<software_error>({__FILE__,__LINE__});
382  return -1;
383  }
384  }
385 
386  //Here we start each channel thread, with 0 R/T prio.
387  threadStart( *it->second.m_thread, it->second.m_threadInit, it->second.m_threadID, it->second.m_threadProp, 0, "", it->second.m_name, &it->second, channelThreadStart);
388  }
389 
390  //Install empty signal handler for USR1, which is used to interrupt sleeps in the channel threads.
391  struct sigaction act;
392  sigset_t set;
393 
394  act.sa_sigaction = &sigUsr1Handler;
395  act.sa_flags = SA_SIGINFO;
396  sigemptyset(&set);
397  act.sa_mask = set;
398 
399  errno = 0;
400  if( sigaction(SIGUSR1, &act, 0) < 0 )
401  {
402  std::string logss = "Setting handler for SIGUSR1 failed. Errno says: ";
403  logss += strerror(errno);
404 
405  log<software_error>({__FILE__, __LINE__, errno, 0, logss});
406 
407  return -1;
408  }
409 
410  if(telemeterT::appStartup() < 0)
411  {
412  return log<software_error,-1>({__FILE__,__LINE__});
413  }
414 
415  return 0;
416 }
417 
419 {
420  if( state() == stateCodes::POWERON)
421  {
422  if(!powerOnWaitElapsed()) return 0;
423 
425  }
426 
428  {
430 
431  if(rv == 0)
432  {
435  }
436  else
437  {
438  if(powerState() != 1 || powerStateTarget() != 1) return 0;
439 
440  if(!stateLogged())
441  {
442  log<text_log>("Failed to connect on " + m_deviceAddr + ":" + m_devicePort);
443  }
444 
445  return 0;
446  }
447 
448  }
449 
451  {
452 
453  std::unique_lock<std::mutex> lock(m_telnetMutex);
454  int rv = m_telnetConn.write("*IDN?\r\n", m_writeTimeout);
455  if(rv != TTY_E_NOERROR)
456  {
457  if(powerState() != 1 || powerStateTarget() != 1) return 0;
458  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
460  return 0;
461  }
462 
463  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
464  if(rv != TTY_E_NOERROR)
465  {
466  if(powerState() != 1 || powerStateTarget() != 1) return 0;
467  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
469  return 0;
470  }
471 
472  if(m_telnetConn.m_strRead.find("New_Focus") != std::string::npos)
473  {
474  log<text_log>("Connected to " + m_telnetConn.m_strRead);
475  }
476  else
477  {
478  if(powerState() != 1 || powerStateTarget() != 1) return 0;
479 
480  log<software_error>({__FILE__, __LINE__, "wrong response to IDN query"});
482  return 0;
483  }
484 
485  //Do a motor scan
486  rv = m_telnetConn.write("MC\r\n", m_writeTimeout);
487  if(rv != TTY_E_NOERROR)
488  {
489  if(powerState() != 1 || powerStateTarget() != 1) return 0;
490  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
492  return 0;
493  }
494 
495  sleep(1); //wtf is this here?
496 
497  //Now check for each motor attached
498  for(auto it=m_channels.begin(); it!=m_channels.end();++it)
499  {
500  std::string query = std::to_string(it->second.m_channel) + "QM?";
501 
502  rv = m_telnetConn.write(query + "\r\n", m_writeTimeout);
503  if(rv != TTY_E_NOERROR)
504  {
505  if(powerState() != 1 || powerStateTarget() != 1) return 0;
506  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
508  return 0;
509  }
510 
511  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
512  if(rv != TTY_E_NOERROR)
513  {
514  if(powerState() != 1 || powerStateTarget() != 1) return 0;
515  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
517  return 0;
518  }
519 
520  int moType = std::stoi(m_telnetConn.m_strRead);
521  if(moType == 0)
522  {
523  if(powerState() != 1 || powerStateTarget() != 1) return 0;
524  log<text_log>("No motor connected on channel " + std::to_string(it->second.m_channel) + " [" + it->second.m_name + "]", logPrio::LOG_CRITICAL);
526  return -1;
527  }
528  else if (moType != 3)
529  {
530  if(powerState() != 1 || powerStateTarget() != 1) return 0;
531  log<text_log>("Wrong motor type connected on channel " + std::to_string(it->second.m_channel) + " [" + it->second.m_name + "]", logPrio::LOG_CRITICAL);
533  return -1;
534  }
535  }
536 
537 
539 
540 
541  return 0;
542  }
543 
545  {
546  //check connection
547  {
548  std::unique_lock<std::mutex> lock(m_telnetMutex);
549 
550  int rv = m_telnetConn.write("*IDN?\r\n", m_writeTimeout);
551  if(rv != TTY_E_NOERROR)
552  {
553  if(powerState() != 1 || powerStateTarget() != 1) return 0;
554  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
556  return 0;
557  }
558 
559  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
560  if(rv != TTY_E_NOERROR)
561  {
562  if(powerState() != 1 || powerStateTarget() != 1) return 0;
563  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
565  return 0;
566  }
567 
568  if(m_telnetConn.m_strRead.find("New_Focus") == std::string::npos)
569  {
570  if(powerState() != 1 || powerStateTarget() != 1) return 0;
571 
572  log<software_error>({__FILE__, __LINE__, "wrong response to IDN query"});
574  return 0;
575  }
576  }
577 
578  //Now check state of motors
579  bool anymoving = false;
580 
581  //This is where we'd check for moving
582  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
583  {
584  std::unique_lock<std::mutex> lock(m_telnetMutex);
585 
586  std::string query = std::to_string(it->second.m_channel) + "MD?";
587 
588  int rv = m_telnetConn.write(query + "\r\n", m_writeTimeout);
589  if(rv != TTY_E_NOERROR)
590  {
591  if(powerState() != 1 || powerStateTarget() != 1) return 0;
592  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
594  return 0;
595  }
596 
597  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
598  if(rv != TTY_E_NOERROR)
599  {
600  if(powerState() != 1 || powerStateTarget() != 1) return 0;
601  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
603  return 0;
604  }
605 
606  //The check for moving here. With power off detection
607  if(std::stoi(m_telnetConn.m_strRead) == 0)
608  {
609  anymoving = true;
610  it->second.m_moving = true;
611  }
612  else
613  {
614  it->second.m_moving = false;
615  }
616 
617  if(it->second.m_moving == false && it->second.m_doMove == true)
618  {
619  it->second.m_currCounts = it->second.m_property["target"].get<long>();
620  log<text_log>("moved " + it->second.m_name + " to " + std::to_string(it->second.m_currCounts) + " counts");
621  it->second.m_doMove = false;
622  recordPico(true);
623  }
624  }
625 
626  if(anymoving == false) state(stateCodes::READY);
628 
629  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
630  {
631  std::unique_lock<std::mutex> lock(m_indiMutex);
632  if(it->second.m_moving) updateIfChanged(it->second.m_property, "current", it->second.m_currCounts, INDI_BUSY);
633  else updateIfChanged(it->second.m_property, "current", it->second.m_currCounts, INDI_IDLE);
634 
635  for(size_t n=0; n < it->second.m_presetNames.size(); ++n)
636  {
637  bool changed = false;
638  if( it->second.m_currCounts == it->second.m_presetPositions[n])
639  {
640  if(it->second.m_indiP_presetName[it->second.m_presetNames[n]] == pcf::IndiElement::Off) changed = true;
641  it->second.m_indiP_presetName[it->second.m_presetNames[n]] = pcf::IndiElement::On;
642  }
643  else
644  {
645  if(it->second.m_indiP_presetName[it->second.m_presetNames[n]] == pcf::IndiElement::On) changed = true;
646  it->second.m_indiP_presetName[it->second.m_presetNames[n]] = pcf::IndiElement::Off;
647  }
648 
649  if(changed) m_indiDriver->sendSetProperty(it->second.m_indiP_presetName);
650  }
651 
652  if(writeChannelCounts(it->second.m_name, it->second.m_currCounts) < 0)
653  {
654  log<software_error>({__FILE__, __LINE__});
655  }
656  }
657 
658  if(telemeterT::appLogic() < 0)
659  {
660  log<software_error>({__FILE__, __LINE__});
661  return 0;
662  }
663 
664  return 0;
665  }
666 
667 
668  return 0;
669 }
670 
672 {
673  return 0;
674 }
675 
677 {
678  return 0;
679 }
680 
682 {
683  //Shutdown and join the threads
684  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
685  {
686  if(it->second.m_thread->joinable())
687  {
688  pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
689  try
690  {
691  it->second.m_thread->join(); //this will throw if it was already joined
692  }
693  catch(...)
694  {
695  }
696  }
697  }
698 
700 
701  return 0;
702 }
703 
705 {
706  std::string statusDir = sysPath;
707  statusDir += "/";
708  statusDir += m_configName;
709 
710  std::string fileName = statusDir + "/" + chName;
711 
712  std::ifstream posIn;
713  posIn.open( fileName );
714 
715  if(!posIn.good())
716  {
717  log<text_log>("no position file for " + chName + " found. initializing to 0.");
718  return 0;
719  }
720 
721  long pos;
722  posIn >> pos;
723 
724  posIn.close();
725 
726  log<text_log>("initializing " + chName + " to " + std::to_string(pos));
727 
728  return pos;
729 }
730 
731 int picoMotorCtrl::writeChannelCounts( const std::string & chName,
732  posT counts
733  )
734 {
735  std::string statusDir = sysPath;
736  statusDir += "/";
737  statusDir += m_configName;
738 
739  std::string fileName = statusDir + "/" + chName;
740 
741  elevatedPrivileges ep(this);
742 
743  std::ofstream posOut;
744  posOut.open( fileName );
745 
746  if(!posOut.good())
747  {
748  log<text_log>("could not open counts file for " + chName + " -- can not store position.", logPrio::LOG_ERROR);
749  return -1;
750  }
751 
752  posOut << counts;
753 
754  posOut.close();
755 
756  return 0;
757 }
758 
760 {
761  mc->m_parent->channelThreadExec(mc);
762 }
763 
765 {
766  //Get the thread PID immediately so the caller can return.
767  mc->m_threadID = syscall(SYS_gettid);
768 
769  //Wait for initialization to complete.
770  while( mc->m_threadInit == true && m_shutdown == 0)
771  {
772  sleep(1);
773  }
774 
775  //Now begin checking for state change request.
776  while(!m_shutdown)
777  {
778  //If told to move and not moving, start a move
779  if(mc->m_doMove && !mc->m_moving && (state() == stateCodes::READY || state() == stateCodes::OPERATING))
780  {
781  long dr = mc->m_property["target"].get<long>() - mc->m_currCounts;
782 
783  recordPico(true);
784  std::unique_lock<std::mutex> lock(m_telnetMutex);
786  mc->m_moving = true;
787  log<text_log>("moving " + mc->m_name + " by " + std::to_string(dr) + " counts");
788 
789  std::string comm = std::to_string(mc->m_channel) + "PR" + std::to_string(dr);
790 
791  int rv = m_telnetConn.write(comm + "\r\n", m_writeTimeout);
792  if(rv != TTY_E_NOERROR)
793  {
794  if(powerState() != 1 || powerStateTarget() != 1) //about to get POWEROFF
795  {
796  sleep(1);
797  continue;
798  }
799  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
801  }
802  }
803  else if( !(state() == stateCodes::READY || state() == stateCodes::OPERATING))
804  {
805  mc->m_doMove = false; //In case a move is requested when not able to move
806  }
807 
808  sleep(1);
809  }
810 
811 
812 }
813 
814 
816  const pcf::IndiProperty &ipRecv
817  )
818 {
819  return static_cast<picoMotorCtrl*>(app)->newCallBack_picopos(ipRecv);
820 }
821 
822 int picoMotorCtrl::newCallBack_picopos( const pcf::IndiProperty &ipRecv )
823 {
824 
825  //Search for the channel
826  std::string propName = ipRecv.getName();
827  size_t nend = propName.rfind("_pos");
828 
829  if(nend == std::string::npos)
830  {
831  log<software_error>({__FILE__, __LINE__, "Channel without _pos received"});
832  return -1;
833  }
834 
835  std::string chName = propName.substr(0, nend);
836  channelMapT::iterator it = m_channels.find(chName);
837 
838  if(it == m_channels.end())
839  {
840  log<software_error>({__FILE__, __LINE__, "Unknown channel name received"});
841  return -1;
842  }
843 
844  if(it->second.m_doMove == true)
845  {
846  log<text_log>("channel " + it->second.m_name + " is already moving", logPrio::LOG_WARNING);
847  return 0;
848  }
849 
850  //Set the target element, and the doMove flag, and then signal the thread.
851  {//scope for mutex
852  std::unique_lock<std::mutex> lock(m_indiMutex);
853 
854  long counts; //not actually used
855  if(indiTargetUpdate( it->second.m_property, counts, ipRecv, true) < 0)
856  {
857  return log<software_error,-1>({__FILE__,__LINE__});
858  }
859  }
860 
861  it->second.m_doMove= true;
862 
863  pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
864 
865  return 0;
866 }
867 
869  const pcf::IndiProperty &ipRecv
870  )
871 {
872  return static_cast<picoMotorCtrl*>(app)->newCallBack_presetName (ipRecv);
873 }
874 
875 int picoMotorCtrl::newCallBack_presetName( const pcf::IndiProperty &ipRecv )
876 {
877  channelMapT::iterator it = m_channels.find(ipRecv.getName());
878 
879  if(it == m_channels.end())
880  {
881  log<software_error>({__FILE__, __LINE__, "Unknown channel name received"});
882  return -1;
883  }
884 
885  if(it->second.m_doMove == true)
886  {
887  log<text_log>("channel " + it->second.m_name + " is already moving", logPrio::LOG_WARNING);
888  return 0;
889  }
890 
891  long counts = -1e10;
892 
893  size_t i;
894  for(i=0; i< it->second.m_presetNames.size(); ++i)
895  {
896  if(!ipRecv.find(it->second.m_presetNames[i])) continue;
897 
898  if(ipRecv[it->second.m_presetNames[i]].getSwitchState() == pcf::IndiElement::On)
899  {
900  if(counts != -1e10)
901  {
902  log<text_log>("More than one preset selected", logPrio::LOG_ERROR);
903  return -1;
904  }
905 
906  counts = it->second.m_presetPositions[i];
907  std::cerr << "selected: " << it->second.m_presetNames[i] << " " << counts << "\n";
908  }
909  }
910 
911  //Set the target element, and the doMove flag, and then signal the thread.
912  {//scope for mutex
913  std::unique_lock<std::mutex> lock(m_indiMutex);
914 
915  it->second.m_property["target"].set(counts);
916  }
917 
918  it->second.m_doMove= true;
919 
920  pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
921 
922  return 0;
923 }
924 
926 {
928 }
929 
931 {
932  return recordPico(true);
933 }
934 
935 int picoMotorCtrl::recordPico( bool force )
936 {
937  static std::vector<int64_t> lastpos(m_nChannels, std::numeric_limits<long>::max());
938 
939  bool changed = false;
940  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
941  {
942  if(it->second.m_currCounts != lastpos[it->second.m_channel-1]) changed = true;
943  }
944 
945  if( changed || force )
946  {
947  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
948  {
949  lastpos[it->second.m_channel-1] = it->second.m_currCounts;
950  }
951 
952  telem<telem_pico>(lastpos);
953  }
954 
955  return 0;
956 }
957 
958 } //namespace app
959 } //namespace MagAOX
960 
961 #endif //picoMotorCtrl_hpp
Internal class to manage setuid privilege escalation with RAII.
Definition: MagAOXApp.hpp:325
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:75
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.
Definition: MagAOXApp.hpp:2877
std::string m_configName
The name of the configuration file (minus .conf).
Definition: MagAOXApp.hpp:88
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2082
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.
Definition: MagAOXApp.hpp:2246
int powerState()
Returns the current power state.
Definition: MagAOXApp.hpp:3179
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:102
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
Definition: MagAOXApp.hpp:537
int powerStateTarget()
Returns the target power state.
Definition: MagAOXApp.hpp:3187
int stateLogged()
Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change,...
Definition: MagAOXApp.hpp:2140
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1590
bool powerOnWaitElapsed()
This method tests whether the power on wait time has elapsed.
Definition: MagAOXApp.hpp:3163
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:540
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.
Definition: MagAOXApp.hpp:2383
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.
Definition: MagAOXApp.hpp:1950
std::string sysPath
The path to the system directory, for PID file, etc.
Definition: MagAOXApp.hpp:96
int indiTargetUpdate(pcf::IndiProperty &localProperty, T &localTarget, const pcf::IndiProperty &remoteProperty, bool setBusy=true)
Get the target element value from an new property.
Definition: MagAOXApp.hpp:2951
bool m_powerMgtEnabled
Flag controls whether power mgt is used. Set this in the constructor of a derived app....
Definition: MagAOXApp.hpp:981
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.
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.
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.
Definition: stateCodes.hpp:50
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
Definition: stateCodes.hpp:37
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
Definition: stateCodes.hpp:38
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:51
@ CONNECTED
The application has connected to the device or service.
Definition: stateCodes.hpp:45
@ NOTCONNECTED
The application is not connected to the device or service.
Definition: stateCodes.hpp:44
@ POWERON
The device power is on.
Definition: stateCodes.hpp:43
std::string ttyErrorString(int ec)
Get a text explanation of a TTY_E_ error code.
Definition: ttyErrors.cpp:15
#define INDI_IDLE
Definition: indiUtils.hpp:28
#define INDI_BUSY
Definition: indiUtils.hpp:30
std::ostream & cerr()
const pcf::IndiProperty & ipRecv
void sigUsr1Handler(int signum, siginfo_t *siginf, void *ucont)
Empty signal handler. SIGUSR1 is used to interrupt sleep in various threads.
Definition: MagAOXApp.cpp:42
Definition: dm.hpp:24
constexpr static logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
Definition: logPriority.hpp:37
constexpr static logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
Definition: logPriority.hpp:40
constexpr static logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
Definition: logPriority.hpp:43
#define PICOMOTORCTRL_E_INDIREG
#define PICOMOTORCTRL_E_NOMOTORS
#define PICOMOTORCTRL_E_BADCHANNEL
#define PICOMOTORCTRL_E_DUPMOTOR
An input/output capable device.
Definition: ioDevice.hpp:27
unsigned m_writeTimeout
The write timeout [msec].
Definition: ioDevice.hpp:29
int loadConfig(mx::app::appConfigurator &config)
Load the device section from an application configurator.
Definition: ioDevice.cpp:28
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for the device section.
Definition: ioDevice.cpp:20
unsigned m_readTimeout
The read timeout [msec].
Definition: ioDevice.hpp:28
A device which saves telemetry.
Definition: telemeter.hpp:52
int appShutdown()
Perform telemeter application shutdown.
Definition: telemeter.hpp:259
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Definition: telemeter.hpp:208
int appLogic()
Perform telemeter application logic.
Definition: telemeter.hpp:253
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Definition: telemeter.hpp:195
int appStartup()
Starts the telemetry log thread.
Definition: telemeter.hpp:226
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Definition: telemeter.hpp:266
picoMotorCtrl * m_parent
A pointer to this for thread starting.
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.
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.
motorChannel(picoMotorCtrl *p, const std::string &n, int ch)
Software ERR log entry.
Log entry recording CPU temperatures.
Definition: telem_pico.hpp:26
A Telnet connection manager, wrapping libtelnet.
Definition: telnetConn.hpp:81
int write(const std::string &buffWrite, int timeoutWrite)
Write to a telnet connection.
Definition: telnetConn.cpp:182
std::string m_strRead
The accumulated string read from the device.
Definition: telnetConn.hpp:113
int noLogin()
Set flags as if we're logged in, used when device doesn't require it.
Definition: telnetConn.cpp:176
std::string m_prompt
The device's prompt, used for detecting end of transmission.
Definition: telnetConn.hpp:92
int read(const std::string &eot, int timeoutRead, bool clear=true)
Read from a telnet connection, until end-of-transmission string is read.
Definition: telnetConn.cpp:223
int connect(const std::string &host, const std::string &port)
Connect to the device.
Definition: telnetConn.cpp:31
#define TTY_E_NOERROR
Definition: ttyErrors.hpp:15