API
stdMotionStage.hpp
Go to the documentation of this file.
1 /** \file stdMotionStage.hpp
2  * \brief Standard motion stage interface
3  *
4  * \author Jared R. Males (jaredmales@gmail.com)
5  *
6  * \ingroup app_files
7  */
8 
9 #ifndef stdMotionStage_hpp
10 #define stdMotionStage_hpp
11 
12 
13 namespace MagAOX
14 {
15 namespace app
16 {
17 namespace dev
18 {
19 
20 
21 /// MagAO-X standard motion stage interface
22 /** Implements the standard interface to a MagAO-X motion stage.
23  * This includes the mcbl filter wheels, the zaber stages.
24  *
25  * The required interface to be implemented in derivedT is
26  * \code
27 
28  int stop(); //Note that the INDI mutex will not be locked on this call
29 
30  int startHoming(); //INDI mutex will be locked on this call.
31 
32  float presetNumber();
33 
34  int moveTo(float); //INDI mutex will be locked on this call.
35  \endcode
36  *
37  * In addition the derived class is responsible for setting m_moving and m_preset. m_preset_target should also be set if the wheel
38  * is moved via a low-level position command.
39  *
40  * The derived class `derivedT` must be a MagAOXApp<true>, and should declare this class a friend like so:
41  \code
42  friend class dev::stdMotionStage<derivedT>;
43  \endcode
44  *
45  *
46  *
47  * Calls to this class's `setupConfig`, `loadConfig`, `appStartup`, `appLogic`, `appShutdown`
48  * `onPowerOff`, and `whilePowerOff`, must be placed in the derived class's functions of the same name.
49  *
50  * \ingroup appdev
51  */
52 template<class derivedT>
54 {
55 protected:
56 
57  /** \name Configurable Parameters
58  * @{
59  */
60 
61  bool m_powerOnHome {false}; ///< If true, then the motor is homed at startup (by this software or actual power on)
62 
63  int m_homePreset {-1}; ///< If >=0, this preset position is moved to after homing
64 
65  std::vector<std::string> m_presetNames; ///< The names of each position on the stage.
66 
67  std::vector<float> m_presetPositions; ///< The positions, in arbitrary units, of each preset. If 0, then the integer position number (starting from 1) is used to calculate.
68 
69  ///@}
70 
71  std::string m_presetNotation {"preset"}; ///< Notation used to refer to a preset, should be singular, as in "preset" or "filter".
72 
73  bool m_fractionalPresets {true}; ///< Flag to set in constructor determining if fractional presets are allowed. Used for INDI/GUIs.
74 
75  bool m_defaultPositions {true}; ///< Flag controlling whether the default preset positions (the vector index) are set in loadConfig.
76 
77  int8_t m_moving {0}; ///< Whether or not the stage is moving. -2 means powered off, -1 means not homed, 0 means not moving, 1 means moving, 2 means homing.
78  int8_t m_movingState {0}; ///< Used to track the type of command. If > 1 this is a command to move to a preset. If 0 then it is a move to an arbitrary position.
79 
80  float m_preset {0}; ///< The current numerical preset position [1.0 is index 0 in the preset name vector]
81  float m_preset_target {0}; ///< The target numerical preset position [1.0 is index 0 in the preset name vector]
82 
83 public:
84 
85  ///Destructor
86  ~stdMotionStage() noexcept;
87 
88  /// Setup the configuration system
89  /**
90  * This should be called in `derivedT::setupConfig` as
91  * \code
92  stdMotionStage<derivedT>::setupConfig(config);
93  \endcode
94  * with appropriate error checking.
95  */
96  int setupConfig(mx::app::appConfigurator & config /**< [out] the derived classes configurator*/);
97 
98  /// load the configuration system results
99  /**
100  * This should be called in `derivedT::loadConfig` as
101  * \code
102  stdMotionStage<derivedT>::loadConfig(config);
103  \endcode
104  * with appropriate error checking.
105  */
106  int loadConfig(mx::app::appConfigurator & config /**< [in] the derived classes configurator*/);
107 
108  /// Startup function
109  /**
110  * This should be called in `derivedT::appStartup` as
111  * \code
112  stdMotionStage<derivedT>::appStartup();
113  \endcode
114  * with appropriate error checking.
115  *
116  * \returns 0 on success
117  * \returns -1 on error, which is logged.
118  */
119  int appStartup();
120 
121  /// Application logic
122  /** Checks the stdMotionStage thread
123  *
124  * This should be called from the derived's appLogic() as in
125  * \code
126  stdMotionStage<derivedT>::appLogic();
127  \endcode
128  * with appropriate error checking.
129  *
130  * \returns 0 on success
131  * \returns -1 on error, which is logged.
132  */
133  int appLogic();
134 
135  /// Actions on power off
136  /**
137  * This should be called from the derived's onPowerOff() as in
138  * \code
139  stdMotionStage<derivedT>::onPowerOff();
140  \endcode
141  * with appropriate error checking.
142  *
143  * \returns 0 on success
144  * \returns -1 on error, which is logged.
145  */
146  int onPowerOff();
147 
148  /// Actions while powered off
149  /**
150  * This should be called from the derived's whilePowerOff() as in
151  * \code
152  stdMotionStage<derivedT>::whilePowerOff();
153  \endcode
154  * with appropriate error checking.
155  *
156  * \returns 0 on success
157  * \returns -1 on error, which is logged.
158  */
160 
161  /// Application the shutdown
162  /** Shuts down the stdMotionStage thread
163  *
164  * \code
165  stdMotionStage<derivedT>::appShutdown();
166  \endcode
167  * with appropriate error checking.
168  *
169  * \returns 0 on success
170  * \returns -1 on error, which is logged.
171  */
172  int appShutdown();
173 
174 protected:
175 
176 
177  /** \name INDI
178  *
179  *@{
180  */
181 protected:
182  //declare our properties
183 
184  ///The position of the stage in presets
185  pcf::IndiProperty m_indiP_preset;
186 
187  ///The name of the nearest preset for this position
188  pcf::IndiProperty m_indiP_presetName;
189 
190  ///Command the stage to home. .
191  pcf::IndiProperty m_indiP_home;
192 
193  ///Command the stage to halt.
194  pcf::IndiProperty m_indiP_stop;
195 
196 public:
197 
198  /// The static callback function to be registered for stdMotionStage properties
199  /** Dispatches to the relevant handler
200  *
201  * \returns 0 on success.
202  * \returns -1 on error.
203  */
204  static int st_newCallBack_stdMotionStage( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
205  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
206  );
207 
208  /// Callback to process a NEW preset position request
209  /**
210  * \returns 0 on success.
211  * \returns -1 on error.
212  */
213  int newCallBack_m_indiP_preset( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
214 
215  /// Callback to process a NEW preset name request
216  /**
217  * \returns 0 on success.
218  * \returns -1 on error.
219  */
220  int newCallBack_m_indiP_presetName( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
221 
222  /// Callback to process a NEW home request switch toggle
223  /**
224  * \returns 0 on success.
225  * \returns -1 on error.
226  */
227  int newCallBack_m_indiP_home( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
228 
229  /// Callback to process a NEW stop request switch toggle
230  /**
231  * \returns 0 on success.
232  * \returns -1 on error.
233  */
234  int newCallBack_m_indiP_stop( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
235 
236  /// Update the INDI properties for this device controller
237  /** You should call this once per main loop.
238  * It is not called automatically.
239  *
240  * \returns 0 on success.
241  * \returns -1 on error.
242  */
243  int updateINDI();
244 
245  ///@}
246 
247  /** \name Telemeter Interface
248  * @{
249  */
250 
251  int recordStage( bool force = false );
252 
253  ///@}
254 
255 private:
256  derivedT & derived()
257  {
258  return *static_cast<derivedT *>(this);
259  }
260 };
261 
262 template<class derivedT>
264 {
265  return;
266 }
267 
268 
269 
270 template<class derivedT>
271 int stdMotionStage<derivedT>::setupConfig(mx::app::appConfigurator & config)
272 {
273  static_cast<void>(config);
274 
275  config.add("stage.powerOnHome", "", "stage.powerOnHome", argType::Required, "stage", "powerOnHome", false, "bool", "If true, home at startup/power-on. Default=false.");
276 
277  config.add("stage.homePreset", "", "stage.homePreset", argType::Required, "stage", "homePreset", false, "int", "If >=0, this preset number is moved to after homing.");
278 
279  config.add(m_presetNotation + "s.names", "", m_presetNotation + "s.names", argType::Required, m_presetNotation+"s", "names", false, "vector<string>", "The names of the " + m_presetNotation+ "s.");
280  config.add(m_presetNotation + "s.positions", "", m_presetNotation + "s.positions", argType::Required, m_presetNotation+"s", "positions", false, "vector<float>", "The positions of the " + m_presetNotation + "s. If omitted or 0 then order is used.");
281 
282  return 0;
283 }
284 
285 template<class derivedT>
286 int stdMotionStage<derivedT>::loadConfig(mx::app::appConfigurator & config)
287 {
288  config(m_powerOnHome, "stage.powerOnHome");
289  config(m_homePreset, "stage.homePreset");
290 
291  config(m_presetNames, m_presetNotation + "s.names");
292 
293  if(m_defaultPositions)
294  {
295  m_presetPositions.resize(m_presetNames.size(), 0);
296  for(size_t n=0;n<m_presetPositions.size();++n) m_presetPositions[n] = n+1;
297  }
298 
299  config(m_presetPositions, m_presetNotation + "s.positions");
300 
301  if(m_defaultPositions)
302  {
303  for(size_t n=0;n<m_presetPositions.size();++n) if(m_presetPositions[n] == 0) m_presetPositions[n] = n+1;
304  }
305 
306  return 0;
307 }
308 
309 
310 
311 template<class derivedT>
313 {
314  double step = 0.0;
315  std::string format = "%.4f";
316  if(!m_fractionalPresets)
317  {
318  step = 1.0;
319  format = "%d";
320  }
321 
322  derived().createStandardIndiNumber( m_indiP_preset, m_presetNotation, 1.0, (double) m_presetNames.size(), step, format);
323  m_indiP_preset["current"].set(0);
324  m_indiP_preset["target"].set(0);
325  if( derived().registerIndiPropertyNew( m_indiP_preset, st_newCallBack_stdMotionStage) < 0)
326  {
327  #ifndef STDFILTERWHEEL_TEST_NOLOG
328  derivedT::template log<software_error>({__FILE__,__LINE__});
329  #endif
330  return -1;
331  }
332 
333  if(derived().createStandardIndiSelectionSw( m_indiP_presetName, m_presetNotation + "Name", m_presetNames) < 0)
334  {
335  derivedT::template log<software_critical>({__FILE__, __LINE__});
336  return -1;
337  }
338  if( derived().registerIndiPropertyNew( m_indiP_presetName, st_newCallBack_stdMotionStage) < 0)
339  {
340  #ifndef STDFILTERWHEEL_TEST_NOLOG
341  derivedT::template log<software_error>({__FILE__,__LINE__});
342  #endif
343  return -1;
344  }
345 
346  derived().createStandardIndiRequestSw( m_indiP_home, "home");
347  if( derived().registerIndiPropertyNew( m_indiP_home, st_newCallBack_stdMotionStage) < 0)
348  {
349  #ifndef STDFILTERWHEEL_TEST_NOLOG
350  derivedT::template log<software_error>({__FILE__,__LINE__});
351  #endif
352  return -1;
353  }
354 
355  derived().createStandardIndiRequestSw( m_indiP_stop, "stop");
356  if( derived().registerIndiPropertyNew( m_indiP_stop, st_newCallBack_stdMotionStage) < 0)
357  {
358  #ifndef STDFILTERWHEEL_TEST_NOLOG
359  derivedT::template log<software_error>({__FILE__,__LINE__});
360  #endif
361  return -1;
362  }
363 
364  return 0;
365 }
366 
367 template<class derivedT>
369 {
370  return 0;
371 
372 }
373 
374 template<class derivedT>
376 {
377  m_moving = -2;
378  m_preset = 0;
379  m_preset_target = 0;
380 
381  if( !derived().m_indiDriver ) return 0;
382 
383  return 0;
384 }
385 
386 template<class derivedT>
388 {
389  if( !derived().m_indiDriver ) return 0;
390 
391  return 0;
392 }
393 
394 template<class derivedT>
396 {
397  return 0;
398 }
399 
400 
401 template<class derivedT>
403  const pcf::IndiProperty &ipRecv
404  )
405 {
406  std::string name = ipRecv.getName();
407  derivedT * _app = static_cast<derivedT *>(app);
408 
409  if(name == "stop") return _app->newCallBack_m_indiP_stop(ipRecv); //Check this first to make sure it
410  if(name == "home") return _app->newCallBack_m_indiP_home(ipRecv);
411  if(name == _app->m_presetNotation) return _app->newCallBack_m_indiP_preset(ipRecv);
412  if(name == _app->m_presetNotation + "Name") return _app->newCallBack_m_indiP_presetName (ipRecv);
413 
414  return -1;
415 }
416 
417 template<class derivedT>
419 {
421 
422  float target;
423 
424  if( derived().indiTargetUpdate( m_indiP_preset, target, ipRecv, true) < 0)
425  {
426  derivedT::template log<software_error>({__FILE__,__LINE__});
427  return -1;
428  }
429 
430  m_preset_target = target;
431 
432  std::lock_guard<std::mutex> guard(derived().m_indiMutex);
433  m_movingState = 0; //this is not a preset move
434  return derived().moveTo(target);
435 
436 }
437 
438 template<class derivedT>
440 {
441  INDI_VALIDATE_CALLBACK_PROPS_DERIVED(m_indiP_presetName, ipRecv);
442 
443  std::string newName = "";
444  int newn = -1;
445 
446  size_t i;
447  for(i=0; i< m_presetNames.size(); ++i)
448  {
449  if(!ipRecv.find(m_presetNames[i])) continue;
450 
451  if(ipRecv[m_presetNames[i]].getSwitchState() == pcf::IndiElement::On)
452  {
453  if(newName != "")
454  {
455  derivedT::template log<text_log>("More than one " + m_presetNotation + " selected", logPrio::LOG_ERROR);
456  return -1;
457  }
458 
459  newName = m_presetNames[i];
460  newn = i;
461  }
462  }
463 
464  if(newName == "" || newn < 0)
465  {
466  return 0; //This is just an reset of current probably
467  }
468 
469  std::lock_guard<std::mutex> guard(derived().m_indiMutex);
470 
471  m_preset_target = m_presetPositions[newn];
472  derived().updateIfChanged(m_indiP_preset, "target", m_preset_target, INDI_BUSY);
473 
474  m_movingState = 1; //This is a preset move
475  return derived().moveTo(m_preset_target);
476 
477 }
478 
479 template<class derivedT>
481 {
483 
484  if(!ipRecv.find("request")) return 0;
485 
486  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
487  {
488  indi::updateSwitchIfChanged(m_indiP_home, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY);
489 
490  std::lock_guard<std::mutex> guard(derived().m_indiMutex);
491  m_movingState = 0;
492  return derived().startHoming();
493  }
494  return 0;
495 }
496 
497 template<class derivedT>
499 {
501 
502  if(ipRecv.getName() != m_indiP_stop.getName())
503  {
504  derivedT::template log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
505  return -1;
506  }
507 
508  if(!ipRecv.find("request")) return 0;
509 
510  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
511  {
512  indi::updateSwitchIfChanged(m_indiP_stop, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY);
513 
514  //-->do not lock mutex!
515  m_movingState = 0;
516  return derived().stop();
517  }
518  return 0;
519 }
520 
521 template<class derivedT>
523 {
524  if( !derived().m_indiDriver ) return 0;
525 
526  int n = derived().presetNumber();
527 
528  size_t nn = n;
529 
530  //Check for changes and update the filterNames
531  bool changed = false;
532 
533  static int8_t last_moving = -1; //Initialize so we always update first time through.
534 
535  if(last_moving != m_moving)
536  {
537  changed = true;
538  last_moving = m_moving;
539  }
540 
541  for(size_t i =0; i < m_presetNames.size(); ++i)
542  {
543  if( i == nn )
544  {
545  if(m_indiP_presetName[m_presetNames[i]] != pcf::IndiElement::On)
546  {
547  changed = true;
548  m_indiP_presetName[m_presetNames[i]] = pcf::IndiElement::On;
549  }
550  }
551  else
552  {
553  if(m_indiP_presetName[m_presetNames[i]] != pcf::IndiElement::Off)
554  {
555  changed = true;
556  m_indiP_presetName[m_presetNames[i]] = pcf::IndiElement::Off;
557  }
558  }
559  }
560  if(changed)
561  {
562  if(m_moving > 0)
563  {
564  m_indiP_presetName.setState(INDI_BUSY);
565  }
566  else
567  {
568  m_indiP_presetName.setState(INDI_IDLE);
569  }
570 
571  m_indiP_presetName.setTimeStamp(pcf::TimeStamp());
572  derived().m_indiDriver->sendSetProperty(m_indiP_presetName);
573  }
574 
575 
576 
577  if(m_moving && m_movingState < 1)
578  {
579  indi::updateIfChanged(m_indiP_preset, "current", m_preset, derived().m_indiDriver,INDI_BUSY);
580  indi::updateIfChanged(m_indiP_preset, "target", m_preset_target, derived().m_indiDriver,INDI_BUSY);
581  }
582  else
583  {
584  indi::updateIfChanged(m_indiP_preset, "current", m_preset, derived().m_indiDriver,INDI_IDLE);
585  indi::updateIfChanged(m_indiP_preset, "target", m_preset_target, derived().m_indiDriver,INDI_IDLE);
586  }
587 
588  return recordStage();
589 }
590 
591 template<class derivedT>
593 {
594  static int8_t last_moving = m_moving + 100; //guarantee first run
595  static float last_preset;
596  static std::string last_presetName;
597 
598  size_t n = derived().presetNumber();
599 
600  std::string presetName;
601  if(n < m_presetNames.size()) presetName = m_presetNames[n];
602 
603  if( m_moving != last_moving || m_preset != last_preset || presetName != last_presetName || force)
604  {
605  derived().template telem<telem_stage>({m_moving, m_preset, presetName});
606  last_moving = m_moving;
607  last_preset = m_preset;
608  last_presetName = presetName;
609 
610  }
611 
612 
613  return 0;
614 }
615 
616 
617 } //namespace dev
618 } //namespace app
619 } //namespace MagAOX
620 
621 #endif //stdMotionStage_hpp
MagAO-X standard motion stage interface.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
int onPowerOff()
Actions on power off.
std::vector< std::string > m_presetNames
The names of each position on the stage.
bool m_powerOnHome
If true, then the motor is homed at startup (by this software or actual power on)
std::string m_presetNotation
Notation used to refer to a preset, should be singular, as in "preset" or "filter".
float m_preset_target
The target numerical preset position [1.0 is index 0 in the preset name vector].
pcf::IndiProperty m_indiP_home
Command the stage to home. .
int newCallBack_m_indiP_stop(const pcf::IndiProperty &ipRecv)
Callback to process a NEW stop request switch toggle.
bool m_defaultPositions
Flag controlling whether the default preset positions (the vector index) are set in loadConfig.
int newCallBack_m_indiP_home(const pcf::IndiProperty &ipRecv)
Callback to process a NEW home request switch toggle.
int updateINDI()
Update the INDI properties for this device controller.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
pcf::IndiProperty m_indiP_presetName
The name of the nearest preset for this position.
int whilePowerOff()
Actions while powered off.
~stdMotionStage() noexcept
Destructor.
int newCallBack_m_indiP_presetName(const pcf::IndiProperty &ipRecv)
Callback to process a NEW preset name request.
int m_homePreset
If >=0, this preset position is moved to after homing.
int8_t m_moving
Whether or not the stage is moving. -2 means powered off, -1 means not homed, 0 means not moving,...
pcf::IndiProperty m_indiP_stop
Command the stage to halt.
pcf::IndiProperty m_indiP_preset
The position of the stage in presets.
float m_preset
The current numerical preset position [1.0 is index 0 in the preset name vector].
static int st_newCallBack_stdMotionStage(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for stdMotionStage properties.
bool m_fractionalPresets
Flag to set in constructor determining if fractional presets are allowed. Used for INDI/GUIs.
int appStartup()
Startup function.
std::vector< float > m_presetPositions
The positions, in arbitrary units, of each preset. If 0, then the integer position number (starting f...
int recordStage(bool force=false)
int appLogic()
Application logic.
int8_t m_movingState
Used to track the type of command. If > 1 this is a command to move to a preset. If 0 then it is a mo...
int appShutdown()
Application the shutdown.
int newCallBack_m_indiP_preset(const pcf::IndiProperty &ipRecv)
Callback to process a NEW preset position request.
#define INDI_VALIDATE_CALLBACK_PROPS_DERIVED(prop1, prop2)
Standard check for matching INDI properties in a callback in a CRTP base class.
Definition: indiMacros.hpp:196
#define INDI_IDLE
Definition: indiUtils.hpp:28
#define INDI_BUSY
Definition: indiUtils.hpp:30
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
Definition: indiUtils.hpp:95
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
Definition: indiUtils.hpp:206
const pcf::IndiProperty & ipRecv
Definition: dm.hpp:24
constexpr static logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
Definition: logPriority.hpp:40