API
 
Loading...
Searching...
No Matches
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
13namespace MagAOX
14{
15namespace app
16{
17namespace 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 */
52template<class derivedT>
54{
55protected:
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
83public:
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 */
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 */
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 */
173
174protected:
175
176
177 /** \name INDI
178 *
179 *@{
180 */
181protected:
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
196public:
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 */
244
245 ///@}
246
247 /** \name Telemeter Interface
248 * @{
249 */
250
251 int recordStage( bool force = false );
252
253 ///@}
254
255private:
256 derivedT & derived()
257 {
258 return *static_cast<derivedT *>(this);
259 }
260};
261
262template<class derivedT>
264{
265 return;
266}
267
268
269
270template<class derivedT>
271int 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
285template<class derivedT>
286int 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
311template<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
367template<class derivedT>
369{
370 return 0;
371
372}
373
374template<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
386template<class derivedT>
388{
389 if( !derived().m_indiDriver ) return 0;
390
391 return 0;
392}
393
394template<class derivedT>
396{
397 return 0;
398}
399
400
401template<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
417template<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
438template<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
479template<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
497template<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
521template<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
591template<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 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.
#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.
const pcf::IndiProperty & ipRecv
Definition dm.hpp:24