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 int splitResponse( int & address,
38  std::string & response,
39  const std::string & fullResponse
40  )
41 {
42  size_t carrot = fullResponse.find('>');
43 
44  if(carrot == std::string::npos)
45  {
46  address = 1;
47  response = fullResponse;
48  return 0;
49  }
50 
51  if(carrot == 0)
52  {
53  address = 0;
54  response = "";
55  return -1;
56  }
57 
58  if(carrot == fullResponse.size()-1)
59  {
60  address = 0;
61  response = "";
62  return -2;
63  }
64 
65  try
66  {
67  address = std::stoi( fullResponse.substr(0,carrot));
68  response = fullResponse.substr(carrot+1);
69  }
70  catch(...)
71  {
72  address = 0;
73  response = "";
74  return -3;
75  }
76 
77  return 0;
78 
79 }
80 
81 /** MagAO-X application to control a multi-channel Newport Picomotor Controller.
82  *
83  * \todo need to recognize signals in tty polls and not return errors, etc.
84  * \todo need to implement an onDisconnect() to update values to unknown indicators.
85  * \todo need a frequency-dependent max amp facility.
86  * \todo convert to ioDevice
87  * \todo need telnet device, with optional username/password.
88  *
89  */
90 class picoMotorCtrl : public MagAOXApp<>, public dev::ioDevice, public dev::telemeter<picoMotorCtrl>
91 {
92 
93  friend class dev::telemeter<picoMotorCtrl>;
94 
96 
97  typedef long posT;
98 
99  struct motorChannel
100  {
101  picoMotorCtrl * m_parent {nullptr}; ///< A pointer to this for thread starting.
102 
103  std::string m_name; ///< The name of this channel, from the config section
104 
105  int m_address {1}; ///< The controller address, default is 1
106 
107  int m_channel {-1}; ///< The number of this channel, where the motor is plugged in
108 
109  int m_type {3}; ///< The motor type of this channel, default is 3
110 
111  std::vector<std::string> m_presetNames;
112  std::vector<posT> m_presetPositions;
113 
114  posT m_currCounts {0}; ///< The current counts, the cumulative position
115 
116  bool m_doMove {false}; ///< Flag indicating that a move is requested.
117  bool m_moving {false}; ///< Flag to indicate that we are actually moving
118 
119  pcf::IndiProperty m_property;
120  pcf::IndiProperty m_indiP_presetName;
121 
122  std::thread * m_thread {nullptr}; ///< Thread for managing this channel. A pointer to allow copying, but must be deleted in d'tor of parent.
123 
124  bool m_threadInit {true}; ///< Thread initialization flag.
125 
126  pid_t m_threadID {0}; ///< The ID of the thread.
127 
128  pcf::IndiProperty m_threadProp; ///< The property to hold the thread details.
129 
130  motorChannel( picoMotorCtrl * p /**< [in] The parent point to set */) : m_parent(p)
131  {
132  m_thread = new std::thread;
133  }
134 
135  motorChannel( picoMotorCtrl * p, ///< [in] The parent point to set
136  const std::string & n, ///< [in] The name of this channel
137  int add, ///< [in] The controller address
138  int ch, ///< [in] The number of this channel
139  int type ///< [in] The motor type of this channel
140  ) : m_parent(p), m_name(n), m_address(add), m_channel(ch), m_type(type)
141  {
142  m_thread = new std::thread;
143  }
144 
145  };
146 
147  typedef std::map<std::string, motorChannel> channelMapT;
148 
149  /** \name Configurable Parameters
150  * @{
151  */
152 
153  std::string m_deviceAddr; ///< The device address
154  std::string m_devicePort {"23"}; ///< The device port
155 
156  int m_nChannels {4}; ///< The number of motor channels total on the hardware. Number of attached motors inferred from config.
157 
158  ///@}
159 
160  std::vector<int> m_addresses; ///< The unique controller addresses.
161 
162  channelMapT m_channels; ///< Map of motor names to channel.
163 
164  tty::telnetConn m_telnetConn; ///< The telnet connection manager
165 
166  ///Mutex for locking telnet communications.
167  std::mutex m_telnetMutex;
168 
169  public:
170 
171  /// Default c'tor.
172  picoMotorCtrl();
173 
174  /// D'tor, declared and defined for noexcept.
175  ~picoMotorCtrl() noexcept;
176 
177  /// Setup the configuration system (called by MagAOXApp::setup())
178  virtual void setupConfig();
179 
180  /// Implementation of loadConfig logic, separated for testing.
181  /** This is called by loadConfig().
182  */
183  int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
184 
185  /// load the configuration system results (called by MagAOXApp::setup())
186  virtual void loadConfig();
187 
188  /// Startup functions
189  /** Setsup the INDI vars.
190  *
191  */
192  virtual int appStartup();
193 
194  /// Implementation of the FSM
195  /**
196  * \returns 0 on no critical error
197  * \returns -1 on an error requiring shutdown
198  */
199  virtual int appLogic();
200 
201  /// Implementation of the on-power-off FSM logic
202  virtual int onPowerOff();
203 
204  /// Implementation of the while-powered-off FSM
205  virtual int whilePowerOff();
206 
207  /// Do any needed shutdown tasks.
208  virtual int appShutdown();
209 
210  /// Read the current channel counts from disk at startup
211  /** Reads the counts from the file with the specified name in this apps sys directory.
212  * Returns the file contents as a posT.
213  */
214  posT readChannelCounts(const std::string & chName);
215 
216  int writeChannelCounts( const std::string & chName,
217  posT counts
218  );
219 
220  /// Channel thread starter function
221  static void channelThreadStart( motorChannel * mc /**< [in] the channel to start controlling */);
222 
223  /// Channel thread execution function
224  /** Runs until m_shutdown is true.
225  */
226  void channelThreadExec( motorChannel * mc );
227 
228 /** \name INDI
229  * @{
230  */
231 protected:
232 
233  //declare our properties
234  std::vector<pcf::IndiProperty> m_indiP_counts;
235 
236 
237 public:
238  /// The static callback function to be registered for relative position requests
239  /** Dispatches to the handler, which then signals the relavent thread.
240  *
241  * \returns 0 on success.
242  * \returns -1 on error.
243  */
244  static int st_newCallBack_picopos( void * app, ///< [in] a pointer to this, will be static_cast-ed to this
245  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
246  );
247 
248  /// The handler function for relative position requests, called by the static callback
249  /** Signals the relavent thread.
250  *
251  * \returns 0 on success.
252  * \returns -1 on error.
253  */
254  int newCallBack_picopos( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
255 
256  /// The static callback function to be registered for position presets
257  /** Dispatches to the handler, which then signals the relavent thread.
258  *
259  * \returns 0 on success.
260  * \returns -1 on error.
261  */
262  static int st_newCallBack_presetName( void * app, ///< [in] a pointer to this, will be static_cast-ed to this
263  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
264  );
265 
266  /// The handler function for position presets, called by the static callback
267  /** Signals the relavent thread.
268  *
269  * \returns 0 on success.
270  * \returns -1 on error.
271  */
272  int newCallBack_presetName( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
273  ///@}
274 
275  /** \name Telemeter Interface
276  *
277  * @{
278  */
279  int checkRecordTimes();
280 
281  int recordTelem( const telem_pico * );
282 
283  int recordPico( bool force = false );
284  ///@}
285 
286 };
287 
288 picoMotorCtrl::picoMotorCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
289 {
290  m_powerMgtEnabled = true;
291  m_telnetConn.m_prompt = "\r\n";
292  return;
293 }
294 
296 {
297  //Wait for each channel thread to exit, then delete it.
298  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
299  {
300  if(it->second.m_thread != nullptr)
301  {
302  if(it->second.m_thread->joinable()) it->second.m_thread->join();
303  delete it->second.m_thread;
304  }
305  }
306 }
307 
308 
310 {
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.");
313 
315 
316  TELEMETER_SETUP_CONFIG( config );
317 
318 }
319 
320 #define PICOMOTORCTRL_E_NOMOTORS (-5)
321 #define PICOMOTORCTRL_E_BADCHANNEL (-6)
322 #define PICOMOTORCTRL_E_DUPMOTOR (-7)
323 #define PICOMOTORCTRL_E_INDIREG (-20)
324 
325 int picoMotorCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
326 {
327  //Standard config parsing
328  _config(m_deviceAddr, "device.address");
329  _config(m_nChannels, "device.nChannels");
330 
331 
332  // Parse the unused config options to look for motors
333  std::vector<std::string> sections;
334 
335  _config.unusedSections(sections);
336 
337  if( sections.size() == 0 )
338  {
339  log<text_log>("No motors found in config.", logPrio::LOG_CRITICAL);
340 
342  }
343 
344  //Now see if any unused sections have a channel keyword
345  for(size_t i=0;i<sections.size(); ++i)
346  {
347  int channel = -1;
348  _config.configUnused(channel, mx::app::iniFile::makeKey(sections[i], "channel" ) );
349  if( channel == -1 )
350  {
351  //not a channel
352  continue;
353  }
354 
355  if(channel < 1 || channel > m_nChannels)
356  {
357  log<text_log>("Bad channel specificiation: " + sections[i] + " channel: " + std::to_string(channel), logPrio::LOG_CRITICAL);
358 
360  }
361 
362  int address = 1;
363  _config.configUnused(address, mx::app::iniFile::makeKey(sections[i], "address" ) );
364 
365  if(address < 1)
366  {
367  log<text_log>("Bad channel specificiation: " + sections[i] + " address: " + std::to_string(address), logPrio::LOG_CRITICAL);
368 
370  }
371 
372  int type = 3;
373  _config.configUnused(type, mx::app::iniFile::makeKey(sections[i], "type" ) );
374 
375  if(address < 1)
376  {
377  log<text_log>("Bad motor type specificiation: " + sections[i] + " type: " + std::to_string(type), logPrio::LOG_CRITICAL);
378 
380  }
381 
382  //Ok, valid channel. Insert into map and check for duplicates.
383  std::pair<channelMapT::iterator, bool> insert = m_channels.insert(std::pair<std::string, motorChannel>(sections[i], motorChannel(this,sections[i], address, channel, type)));
384 
385  if(insert.second == false)
386  {
387  log<text_log>("Duplicate motor specificiation: " + sections[i] + " " + std::to_string(channel), logPrio::LOG_CRITICAL);
389  }
390  else
391  {
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" ));
394  }
395 
396  ///\todo extend to include address
397  log<pico_channel>({sections[i], (uint8_t) channel});
398 
399  bool found = false;
400  for(size_t n = 0; n < m_addresses.size(); ++n)
401  {
402  if(address == m_addresses[n])
403  {
404  found = true;
405  break;
406  }
407  }
408 
409  if(!found)
410  {
411  m_addresses.push_back(address);
412  }
413  }
414 
415  TELEMETER_LOAD_CONFIG( config );
416 
417  return 0;
418 }
419 
421 {
422  if( loadConfigImpl(config) < 0)
423  {
424  log<text_log>("Error during config", logPrio::LOG_CRITICAL);
425  m_shutdown = true;
426  }
427 
428  if(dev::ioDevice::loadConfig(config) < 0)
429  {
430  log<text_log>("Error during ioDevice config", logPrio::LOG_CRITICAL);
431  m_shutdown = true;
432  }
433 
434 
435 
436 }
437 
439 {
440  ///\todo read state from disk to get current counts.
441 
442  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
443  {
444  it->second.m_currCounts = readChannelCounts(it->second.m_name);
445 
446 
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);
450  it->second.m_property.setState(INDI_IDLE);
451 
452  if( registerIndiPropertyNew( it->second.m_property, st_newCallBack_picopos) < 0)
453  {
454  #ifndef PICOMOTORCTRL_TEST_NOLOG
455  log<software_error>({__FILE__,__LINE__});
456  #endif
458  }
459 
460  if(it->second.m_presetNames.size() > 0)
461  {
462  if(createStandardIndiSelectionSw( it->second.m_indiP_presetName, it->first, it->second.m_presetNames) < 0)
463  {
464  log<software_critical>({__FILE__, __LINE__});
465  return -1;
466  }
467  if( registerIndiPropertyNew( it->second.m_indiP_presetName, st_newCallBack_presetName) < 0)
468  {
469  log<software_error>({__FILE__,__LINE__});
470  return -1;
471  }
472  }
473 
474  //Here we start each channel thread, with 0 R/T prio.
475  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);
476  }
477 
478  //Install empty signal handler for USR1, which is used to interrupt sleeps in the channel threads.
479  struct sigaction act;
480  sigset_t set;
481 
482  act.sa_sigaction = &sigUsr1Handler;
483  act.sa_flags = SA_SIGINFO;
484  sigemptyset(&set);
485  act.sa_mask = set;
486 
487  errno = 0;
488  if( sigaction(SIGUSR1, &act, 0) < 0 )
489  {
490  std::string logss = "Setting handler for SIGUSR1 failed. Errno says: ";
491  logss += strerror(errno);
492 
493  log<software_error>({__FILE__, __LINE__, errno, 0, logss});
494 
495  return -1;
496  }
497 
499 
500  return 0;
501 }
502 
504 {
505  if( state() == stateCodes::POWERON)
506  {
507  if(!powerOnWaitElapsed()) return 0;
508 
510  }
511 
513  {
515 
516  if(rv == 0)
517  {
520  }
521  else
522  {
523  if(powerState() != 1 || powerStateTarget() != 1) return 0;
524 
525  if(!stateLogged())
526  {
527  log<text_log>("Failed to connect on " + m_deviceAddr + ":" + m_devicePort);
528  }
529 
530  return 0;
531  }
532 
533  }
534 
536  {
537 
538  std::unique_lock<std::mutex> lock(m_telnetMutex);
539 
540  //First check the address 1 controller
541  int rv = m_telnetConn.write("*IDN?\r\n", m_writeTimeout);
542  if(rv != TTY_E_NOERROR)
543  {
544  if(powerState() != 1 || powerStateTarget() != 1) return 0;
545  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
547  return 0;
548  }
549 
550  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
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  if(m_telnetConn.m_strRead.find("New_Focus") != std::string::npos)
560  {
561  log<text_log>("Connected to " + m_telnetConn.m_strRead + " at address 1");
562  }
563  else
564  {
565  if(powerState() != 1 || powerStateTarget() != 1) return 0;
566  log<software_error>({__FILE__, __LINE__, "wrong response to IDN query at address 1"});
568  return 0;
569  }
570 
571  //Now do a controller scan
572  rv = m_telnetConn.write("SC1\r\n", m_writeTimeout); //Will adjust addresses.
573  if(rv != TTY_E_NOERROR)
574  {
575  if(powerState() != 1 || powerStateTarget() != 1) return 0;
576  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
578  return 0;
579  }
580 
581  //And now do a motor scan
582  rv = m_telnetConn.write("MC\r\n", m_writeTimeout);
583  if(rv != TTY_E_NOERROR)
584  {
585  if(powerState() != 1 || powerStateTarget() != 1) return 0;
586  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
588  return 0;
589  }
590 
591  sleep(2); //Give time for controller scan to finish
592 
593  for(size_t n = 0; n < m_addresses.size(); ++n)
594  {
595  if(m_addresses[n] == 1) continue; //already done.
596 
597  std::string addprefix = std::to_string(m_addresses[n]) + ">";
598 
599  int rv = m_telnetConn.write(addprefix + "*IDN?\r\n", m_writeTimeout);
600  if(rv != TTY_E_NOERROR)
601  {
602  if(powerState() != 1 || powerStateTarget() != 1) return 0;
603  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
605  return 0;
606  }
607 
608  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
609  if(rv != TTY_E_NOERROR)
610  {
611  if(powerState() != 1 || powerStateTarget() != 1) return 0;
612  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
614  return 0;
615  }
616 
617  int add;
618  std::string resp;
619 
620  rv = splitResponse(add, resp, m_telnetConn.m_strRead);
621  if(rv != 0)
622  {
623  if(powerState() != 1 || powerStateTarget() != 1) return 0;
624  log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
626  return 0;
627  }
628 
629  if(add != m_addresses[n])
630  {
631  if(powerState() != 1 || powerStateTarget() != 1) return 0;
632  log<software_error>({__FILE__, __LINE__, "address did not match in response"});
634  return 0;
635  }
636 
637  if(resp.find("New_Focus") != std::string::npos)
638  {
639  log<text_log>("Connected to " + resp + " at address " + std::to_string(m_addresses[n]));
640  }
641  else
642  {
643  if(powerState() != 1 || powerStateTarget() != 1) return 0;
644  log<software_error>({__FILE__, __LINE__, "wrong response to IDN query at address " + std::to_string(m_addresses[n])});
646  return 0;
647  }
648 
649  //Now do a motor scan
650  rv = m_telnetConn.write(addprefix + "MC\r\n", m_writeTimeout);
651  if(rv != TTY_E_NOERROR)
652  {
653  if(powerState() != 1 || powerStateTarget() != 1) return 0;
654  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
656  return 0;
657  }
658  }
659 
660  sleep(2); //This is to give time for motor scans to finish
661 
662  //Now check for each motor attached
663  for(auto it=m_channels.begin(); it!=m_channels.end();++it)
664  {
665  std::string query = std::to_string(it->second.m_address) + ">" + std::to_string(it->second.m_channel) + "QM?";
666 
667  rv = m_telnetConn.write(query + "\r\n", m_writeTimeout);
668  if(rv != TTY_E_NOERROR)
669  {
670  if(powerState() != 1 || powerStateTarget() != 1) return 0;
671  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
673  return 0;
674  }
675 
676  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
677  if(rv != TTY_E_NOERROR)
678  {
679  if(powerState() != 1 || powerStateTarget() != 1) return 0;
680  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
682  return 0;
683  }
684 
685  int add;
686  std::string resp;
687 
688  rv = splitResponse(add, resp, m_telnetConn.m_strRead);
689  if(rv != 0)
690  {
691  if(powerState() != 1 || powerStateTarget() != 1) return 0;
692  log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
694  return 0;
695  }
696 
697  if(add != it->second.m_address)
698  {
699  if(powerState() != 1 || powerStateTarget() != 1) return 0;
700  log<software_error>({__FILE__, __LINE__, "address did not match in response"});
702  return 0;
703  }
704 
705  int moType = std::stoi(resp);
706  if(moType == 0)
707  {
708  if(powerState() != 1 || powerStateTarget() != 1) return 0;
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);
711  return -1;
712  }
713  else if (moType != it->second.m_type)
714  {
715  if(powerState() != 1 || powerStateTarget() != 1) return 0;
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);
720 
721  log<text_log>(msg, logPrio::LOG_CRITICAL);
723  return -1;
724  }
725  }
726 
728 
729  return 0;
730  }
731 
733  {
734  //check connection
735  {
736  std::unique_lock<std::mutex> lock(m_telnetMutex);
737 
738  int rv = m_telnetConn.write("*IDN?\r\n", m_writeTimeout);
739  if(rv != TTY_E_NOERROR)
740  {
741  if(powerState() != 1 || powerStateTarget() != 1) return 0;
742  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
744  return 0;
745  }
746 
747  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
748  if(rv != TTY_E_NOERROR)
749  {
750  if(powerState() != 1 || powerStateTarget() != 1) return 0;
751  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
753  return 0;
754  }
755 
756  if(m_telnetConn.m_strRead.find("New_Focus") == std::string::npos)
757  {
758  if(powerState() != 1 || powerStateTarget() != 1) return 0;
759 
760  log<software_error>({__FILE__, __LINE__, "wrong response to IDN query"});
762  return 0;
763  }
764  }
765 
766  //Now check state of motors
767  bool anymoving = false;
768 
769  //This is where we'd check for moving
770  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
771  {
772  std::unique_lock<std::mutex> lock(m_telnetMutex);
773 
774  std::string query = std::to_string(it->second.m_address) + ">" + std::to_string(it->second.m_channel) + "MD?";
775 
776  int rv = m_telnetConn.write(query + "\r\n", m_writeTimeout);
777  if(rv != TTY_E_NOERROR)
778  {
779  if(powerState() != 1 || powerStateTarget() != 1) return 0;
780  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
782  return 0;
783  }
784 
785  rv = m_telnetConn.read("\r\n", m_readTimeout, true);
786  if(rv != TTY_E_NOERROR)
787  {
788  if(powerState() != 1 || powerStateTarget() != 1) return 0;
789  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
791  return 0;
792  }
793 
794  int add;
795  std::string resp;
796 
797  rv = splitResponse(add, resp, m_telnetConn.m_strRead);
798  if(rv != 0)
799  {
800  if(powerState() != 1 || powerStateTarget() != 1) return 0;
801  log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
803  return 0;
804  }
805 
806  if(add != it->second.m_address)
807  {
808  if(powerState() != 1 || powerStateTarget() != 1) return 0;
809  log<software_error>({__FILE__, __LINE__, "address did not match in response"});
811  return 0;
812  }
813 
814  //The check for moving here. With power off detection
815  if(std::stoi(resp) == 0)
816  {
817  anymoving = true;
818  it->second.m_moving = true;
819  }
820  else
821  {
822  it->second.m_moving = false;
823  }
824 
825  if(it->second.m_moving == false && it->second.m_doMove == true)
826  {
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;
830  recordPico(true);
831  }
832  }
833 
834  if(anymoving == false) state(stateCodes::READY);
836 
837  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
838  {
839  std::unique_lock<std::mutex> lock(m_indiMutex);
840  if(it->second.m_moving) updateIfChanged(it->second.m_property, "current", it->second.m_currCounts, INDI_BUSY);
841  else updateIfChanged(it->second.m_property, "current", it->second.m_currCounts, INDI_IDLE);
842 
843  for(size_t n=0; n < it->second.m_presetNames.size(); ++n)
844  {
845  bool changed = false;
846  if( it->second.m_currCounts == it->second.m_presetPositions[n])
847  {
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;
850  }
851  else
852  {
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;
855  }
856 
857  if(changed) m_indiDriver->sendSetProperty(it->second.m_indiP_presetName);
858  }
859 
860  if(writeChannelCounts(it->second.m_name, it->second.m_currCounts) < 0)
861  {
862  log<software_error>({__FILE__, __LINE__});
863  }
864  }
865 
867 
868  return 0;
869  }
870 
871 
872  return 0;
873 }
874 
876 {
877  return 0;
878 }
879 
881 {
882  return 0;
883 }
884 
886 {
887  //Shutdown and join the threads
888  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
889  {
890  if(it->second.m_thread->joinable())
891  {
892  pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
893  try
894  {
895  it->second.m_thread->join(); //this will throw if it was already joined
896  }
897  catch(...)
898  {
899  }
900  }
901  }
902 
904 
905  return 0;
906 }
907 
909 {
910  std::string statusDir = sysPath;
911  statusDir += "/";
912  statusDir += m_configName;
913 
914  std::string fileName = statusDir + "/" + chName;
915 
916  std::ifstream posIn;
917  posIn.open( fileName );
918 
919  if(!posIn.good())
920  {
921  log<text_log>("no position file for " + chName + " found. initializing to 0.");
922  return 0;
923  }
924 
925  long pos;
926  posIn >> pos;
927 
928  posIn.close();
929 
930  log<text_log>("initializing " + chName + " to " + std::to_string(pos));
931 
932  return pos;
933 }
934 
935 int picoMotorCtrl::writeChannelCounts( const std::string & chName,
936  posT counts
937  )
938 {
939  std::string statusDir = sysPath;
940  statusDir += "/";
941  statusDir += m_configName;
942 
943  std::string fileName = statusDir + "/" + chName;
944 
945  elevatedPrivileges ep(this);
946 
947  std::ofstream posOut;
948  posOut.open( fileName );
949 
950  if(!posOut.good())
951  {
952  log<text_log>("could not open counts file for " + chName + " -- can not store position.", logPrio::LOG_ERROR);
953  return -1;
954  }
955 
956  posOut << counts;
957 
958  posOut.close();
959 
960  return 0;
961 }
962 
964 {
965  mc->m_parent->channelThreadExec(mc);
966 }
967 
969 {
970  //Get the thread PID immediately so the caller can return.
971  mc->m_threadID = syscall(SYS_gettid);
972 
973  //Wait for initialization to complete.
974  while( mc->m_threadInit == true && m_shutdown == 0)
975  {
976  sleep(1);
977  }
978 
979  //Now begin checking for state change request.
980  while(!m_shutdown)
981  {
982  //If told to move and not moving, start a move
983  if(mc->m_doMove && !mc->m_moving && (state() == stateCodes::READY || state() == stateCodes::OPERATING))
984  {
985  long dr = mc->m_property["target"].get<long>() - mc->m_currCounts;
986 
987  recordPico(true);
988  std::unique_lock<std::mutex> lock(m_telnetMutex);
990  mc->m_moving = true;
991  log<text_log>("moving " + mc->m_name + " by " + std::to_string(dr) + " counts");
992 
993  std::string comm = std::to_string(mc->m_address) + ">" + std::to_string(mc->m_channel) + "PR" + std::to_string(dr);
994 
995  int rv = m_telnetConn.write(comm + "\r\n", m_writeTimeout);
996  if(rv != TTY_E_NOERROR)
997  {
998  if(powerState() != 1 || powerStateTarget() != 1) //about to get POWEROFF
999  {
1000  sleep(1);
1001  continue;
1002  }
1003  log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
1005  }
1006  }
1007  else if( !(state() == stateCodes::READY || state() == stateCodes::OPERATING))
1008  {
1009  mc->m_doMove = false; //In case a move is requested when not able to move
1010  }
1011 
1012  sleep(1);
1013  }
1014 
1015 
1016 }
1017 
1018 
1020  const pcf::IndiProperty &ipRecv
1021  )
1022 {
1023  return static_cast<picoMotorCtrl*>(app)->newCallBack_picopos(ipRecv);
1024 }
1025 
1026 int picoMotorCtrl::newCallBack_picopos( const pcf::IndiProperty &ipRecv )
1027 {
1028 
1029  //Search for the channel
1030  std::string propName = ipRecv.getName();
1031  size_t nend = propName.rfind("_pos");
1032 
1033  if(nend == std::string::npos)
1034  {
1035  log<software_error>({__FILE__, __LINE__, "Channel without _pos received"});
1036  return -1;
1037  }
1038 
1039  std::string chName = propName.substr(0, nend);
1040  channelMapT::iterator it = m_channels.find(chName);
1041 
1042  if(it == m_channels.end())
1043  {
1044  log<software_error>({__FILE__, __LINE__, "Unknown channel name received"});
1045  return -1;
1046  }
1047 
1048  if(it->second.m_doMove == true)
1049  {
1050  log<text_log>("channel " + it->second.m_name + " is already moving", logPrio::LOG_WARNING);
1051  return 0;
1052  }
1053 
1054  //Set the target element, and the doMove flag, and then signal the thread.
1055  {//scope for mutex
1056  std::unique_lock<std::mutex> lock(m_indiMutex);
1057 
1058  long counts; //not actually used
1059  if(indiTargetUpdate( it->second.m_property, counts, ipRecv, true) < 0)
1060  {
1061  return log<software_error,-1>({__FILE__,__LINE__});
1062  }
1063  }
1064 
1065  it->second.m_doMove= true;
1066 
1067  pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
1068 
1069  return 0;
1070 }
1071 
1073  const pcf::IndiProperty &ipRecv
1074  )
1075 {
1076  return static_cast<picoMotorCtrl*>(app)->newCallBack_presetName (ipRecv);
1077 }
1078 
1079 int picoMotorCtrl::newCallBack_presetName( const pcf::IndiProperty &ipRecv )
1080 {
1081  channelMapT::iterator it = m_channels.find(ipRecv.getName());
1082 
1083  if(it == m_channels.end())
1084  {
1085  log<software_error>({__FILE__, __LINE__, "Unknown channel name received"});
1086  return -1;
1087  }
1088 
1089  if(it->second.m_doMove == true)
1090  {
1091  log<text_log>("channel " + it->second.m_name + " is already moving", logPrio::LOG_WARNING);
1092  return 0;
1093  }
1094 
1095  long counts = -1e10;
1096 
1097  size_t i;
1098  for(i=0; i< it->second.m_presetNames.size(); ++i)
1099  {
1100  if(!ipRecv.find(it->second.m_presetNames[i])) continue;
1101 
1102  if(ipRecv[it->second.m_presetNames[i]].getSwitchState() == pcf::IndiElement::On)
1103  {
1104  if(counts != -1e10)
1105  {
1106  log<text_log>("More than one preset selected", logPrio::LOG_ERROR);
1107  return -1;
1108  }
1109 
1110  counts = it->second.m_presetPositions[i];
1111  std::cerr << "selected: " << it->second.m_presetNames[i] << " " << counts << "\n";
1112  }
1113  }
1114 
1115  //Set the target element, and the doMove flag, and then signal the thread.
1116  {//scope for mutex
1117  std::unique_lock<std::mutex> lock(m_indiMutex);
1118 
1119  it->second.m_property["target"].set(counts);
1120  }
1121 
1122  it->second.m_doMove= true;
1123 
1124  pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
1125 
1126  return 0;
1127 }
1128 
1130 {
1132 }
1133 
1135 {
1136  return recordPico(true);
1137 }
1138 
1139 int picoMotorCtrl::recordPico( bool force )
1140 {
1141  static std::vector<int64_t> lastpos(m_nChannels, std::numeric_limits<long>::max());
1142 
1143  bool changed = false;
1144  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
1145  {
1146  if(it->second.m_currCounts != lastpos[it->second.m_channel-1]) changed = true;
1147  }
1148 
1149  if( changed || force )
1150  {
1151  for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
1152  {
1153  lastpos[it->second.m_channel-1] = it->second.m_currCounts;
1154  }
1155 
1156  telem<telem_pico>(lastpos);
1157  }
1158 
1159  return 0;
1160 }
1161 
1162 } //namespace app
1163 } //namespace MagAOX
1164 
1165 #endif //picoMotorCtrl_hpp
Internal class to manage setuid privilege escalation with RAII.
Definition: MagAOXApp.hpp:324
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:73
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:3120
std::string m_configName
The name of the configuration file (minus .conf).
Definition: MagAOXApp.hpp:83
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2297
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:2470
int powerState()
Returns the current power state.
Definition: MagAOXApp.hpp:3414
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:100
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
Definition: MagAOXApp.hpp:542
int powerStateTarget()
Returns the target power state.
Definition: MagAOXApp.hpp:3423
int stateLogged()
Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change,...
Definition: MagAOXApp.hpp:2361
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1804
bool powerOnWaitElapsed()
This method tests whether the power on wait time has elapsed.
Definition: MagAOXApp.hpp:3397
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:545
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:2603
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:2157
std::string sysPath
The path to the system directory, for PID file, etc.
Definition: MagAOXApp.hpp:91
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:3197
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.
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:55
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
Definition: stateCodes.hpp:42
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
Definition: stateCodes.hpp:43
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:56
@ CONNECTED
The application has connected to the device or service.
Definition: stateCodes.hpp:50
@ NOTCONNECTED
The application is not connected to the device or service.
Definition: stateCodes.hpp:49
@ POWERON
The device power is on.
Definition: stateCodes.hpp:48
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()
std::stringstream msg
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.
Definition: MagAOXApp.cpp:42
const pcf::IndiProperty & ipRecv
Definition: MagAOXApp.hpp:3434
std::unique_lock< std::mutex > lock(m_indiMutex)
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 base class which saves telemetry.
Definition: telemeter.hpp:69
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:281
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.
motorChannel(picoMotorCtrl *p, const std::string &n, int add, int ch, int type)
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.
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 TELEMETER_APP_LOGIC
Call telemeter::appLogic with error checking.
Definition: telemeter.hpp:339
#define TELEMETER_LOAD_CONFIG(cfig)
Call telemeter::loadConfig with error checking.
Definition: telemeter.hpp:325
#define TELEMETER_APP_STARTUP
Call telemeter::appStartup with error checking.
Definition: telemeter.hpp:332
#define TELEMETER_SETUP_CONFIG(cfig)
Call telemeter::setupConfig with error checking.
Definition: telemeter.hpp:314
#define TELEMETER_APP_SHUTDOWN
Call telemeter::appShutdown with error checking.
Definition: telemeter.hpp:346
#define TTY_E_NOERROR
Definition: ttyErrors.hpp:15