Line data Source code
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>
53 : class stdMotionStage
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 : */
159 : int whilePowerOff();
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 0 : derivedT & derived()
257 : {
258 0 : return *static_cast<derivedT *>(this);
259 : }
260 : };
261 :
262 : template<class derivedT>
263 48 : stdMotionStage<derivedT>::~stdMotionStage() noexcept
264 : {
265 48 : return;
266 48 : }
267 :
268 :
269 :
270 : template<class derivedT>
271 0 : int stdMotionStage<derivedT>::setupConfig(mx::app::appConfigurator & config)
272 : {
273 : static_cast<void>(config);
274 :
275 0 : config.add("stage.powerOnHome", "", "stage.powerOnHome", argType::Required, "stage", "powerOnHome", false, "bool", "If true, home at startup/power-on. Default=false.");
276 :
277 0 : config.add("stage.homePreset", "", "stage.homePreset", argType::Required, "stage", "homePreset", false, "int", "If >=0, this preset number is moved to after homing.");
278 :
279 0 : 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 0 : 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 0 : return 0;
283 : }
284 :
285 : template<class derivedT>
286 0 : int stdMotionStage<derivedT>::loadConfig(mx::app::appConfigurator & config)
287 : {
288 0 : config(m_powerOnHome, "stage.powerOnHome");
289 0 : config(m_homePreset, "stage.homePreset");
290 :
291 0 : config(m_presetNames, m_presetNotation + "s.names");
292 :
293 0 : if(m_defaultPositions)
294 : {
295 0 : m_presetPositions.resize(m_presetNames.size(), 0);
296 0 : for(size_t n=0;n<m_presetPositions.size();++n) m_presetPositions[n] = n+1;
297 : }
298 :
299 0 : config(m_presetPositions, m_presetNotation + "s.positions");
300 :
301 0 : if(m_defaultPositions)
302 : {
303 0 : for(size_t n=0;n<m_presetPositions.size();++n) if(m_presetPositions[n] == 0) m_presetPositions[n] = n+1;
304 : }
305 :
306 0 : return 0;
307 : }
308 :
309 :
310 :
311 : template<class derivedT>
312 0 : int stdMotionStage<derivedT>::appStartup()
313 : {
314 0 : double step = 0.0;
315 0 : std::string format = "%.4f";
316 0 : if(!m_fractionalPresets)
317 : {
318 0 : step = 1.0;
319 0 : format = "%d";
320 : }
321 :
322 0 : derived().createStandardIndiNumber( m_indiP_preset, m_presetNotation, 1.0, (double) m_presetNames.size(), step, format);
323 0 : m_indiP_preset["current"].set(0);
324 0 : m_indiP_preset["target"].set(0);
325 0 : if( derived().registerIndiPropertyNew( m_indiP_preset, st_newCallBack_stdMotionStage) < 0)
326 : {
327 : #ifndef STDFILTERWHEEL_TEST_NOLOG
328 0 : derivedT::template log<software_error>({__FILE__,__LINE__});
329 : #endif
330 0 : return -1;
331 : }
332 :
333 0 : if(derived().createStandardIndiSelectionSw( m_indiP_presetName, m_presetNotation + "Name", m_presetNames) < 0)
334 : {
335 0 : derivedT::template log<software_critical>({__FILE__, __LINE__});
336 0 : return -1;
337 : }
338 0 : if( derived().registerIndiPropertyNew( m_indiP_presetName, st_newCallBack_stdMotionStage) < 0)
339 : {
340 : #ifndef STDFILTERWHEEL_TEST_NOLOG
341 0 : derivedT::template log<software_error>({__FILE__,__LINE__});
342 : #endif
343 0 : return -1;
344 : }
345 :
346 0 : derived().createStandardIndiRequestSw( m_indiP_home, "home");
347 0 : if( derived().registerIndiPropertyNew( m_indiP_home, st_newCallBack_stdMotionStage) < 0)
348 : {
349 : #ifndef STDFILTERWHEEL_TEST_NOLOG
350 0 : derivedT::template log<software_error>({__FILE__,__LINE__});
351 : #endif
352 0 : return -1;
353 : }
354 :
355 0 : derived().createStandardIndiRequestSw( m_indiP_stop, "stop");
356 0 : if( derived().registerIndiPropertyNew( m_indiP_stop, st_newCallBack_stdMotionStage) < 0)
357 : {
358 : #ifndef STDFILTERWHEEL_TEST_NOLOG
359 0 : derivedT::template log<software_error>({__FILE__,__LINE__});
360 : #endif
361 0 : return -1;
362 : }
363 :
364 0 : return 0;
365 0 : }
366 :
367 : template<class derivedT>
368 0 : int stdMotionStage<derivedT>::appLogic()
369 : {
370 0 : return 0;
371 :
372 : }
373 :
374 : template<class derivedT>
375 0 : int stdMotionStage<derivedT>::onPowerOff()
376 : {
377 0 : m_moving = -2;
378 0 : m_preset = 0;
379 0 : m_preset_target = 0;
380 :
381 0 : if( !derived().m_indiDriver ) return 0;
382 :
383 0 : return 0;
384 : }
385 :
386 : template<class derivedT>
387 0 : int stdMotionStage<derivedT>::whilePowerOff()
388 : {
389 0 : if( !derived().m_indiDriver ) return 0;
390 :
391 0 : return 0;
392 : }
393 :
394 : template<class derivedT>
395 : int stdMotionStage<derivedT>::appShutdown()
396 : {
397 : return 0;
398 : }
399 :
400 :
401 : template<class derivedT>
402 0 : int stdMotionStage<derivedT>::st_newCallBack_stdMotionStage( void * app,
403 : const pcf::IndiProperty &ipRecv
404 : )
405 : {
406 0 : std::string name = ipRecv.getName();
407 0 : derivedT * _app = static_cast<derivedT *>(app);
408 :
409 0 : if(name == "stop") return _app->newCallBack_m_indiP_stop(ipRecv); //Check this first to make sure it
410 0 : if(name == "home") return _app->newCallBack_m_indiP_home(ipRecv);
411 0 : if(name == _app->m_presetNotation) return _app->newCallBack_m_indiP_preset(ipRecv);
412 0 : if(name == _app->m_presetNotation + "Name") return _app->newCallBack_m_indiP_presetName (ipRecv);
413 :
414 0 : return -1;
415 0 : }
416 :
417 : template<class derivedT>
418 6 : int stdMotionStage<derivedT>::newCallBack_m_indiP_preset ( const pcf::IndiProperty &ipRecv )
419 : {
420 6 : INDI_VALIDATE_CALLBACK_PROPS_DERIVED(m_indiP_preset, ipRecv);
421 :
422 : float target;
423 :
424 0 : if( derived().indiTargetUpdate( m_indiP_preset, target, ipRecv, true) < 0)
425 : {
426 0 : derivedT::template log<software_error>({__FILE__,__LINE__});
427 0 : return -1;
428 : }
429 :
430 0 : m_preset_target = target;
431 :
432 0 : std::lock_guard<std::mutex> guard(derived().m_indiMutex);
433 0 : m_movingState = 0; //this is not a preset move
434 0 : return derived().moveTo(target);
435 :
436 0 : }
437 :
438 : template<class derivedT>
439 6 : int stdMotionStage<derivedT>::newCallBack_m_indiP_presetName( const pcf::IndiProperty &ipRecv )
440 : {
441 6 : INDI_VALIDATE_CALLBACK_PROPS_DERIVED(m_indiP_presetName, ipRecv);
442 :
443 0 : std::string newName = "";
444 0 : int newn = -1;
445 :
446 : size_t i;
447 0 : for(i=0; i< m_presetNames.size(); ++i)
448 : {
449 0 : if(!ipRecv.find(m_presetNames[i])) continue;
450 :
451 0 : if(ipRecv[m_presetNames[i]].getSwitchState() == pcf::IndiElement::On)
452 : {
453 0 : if(newName != "")
454 : {
455 0 : derivedT::template log<text_log>("More than one " + m_presetNotation + " selected", logPrio::LOG_ERROR);
456 0 : return -1;
457 : }
458 :
459 0 : newName = m_presetNames[i];
460 0 : newn = i;
461 : }
462 : }
463 :
464 0 : if(newName == "" || newn < 0)
465 : {
466 0 : return 0; //This is just an reset of current probably
467 : }
468 :
469 0 : std::lock_guard<std::mutex> guard(derived().m_indiMutex);
470 :
471 0 : m_preset_target = m_presetPositions[newn];
472 0 : derived().updateIfChanged(m_indiP_preset, "target", m_preset_target, INDI_BUSY);
473 :
474 0 : m_movingState = 1; //This is a preset move
475 0 : return derived().moveTo(m_preset_target);
476 :
477 0 : }
478 :
479 : template<class derivedT>
480 6 : int stdMotionStage<derivedT>::newCallBack_m_indiP_home( const pcf::IndiProperty &ipRecv )
481 : {
482 6 : INDI_VALIDATE_CALLBACK_PROPS_DERIVED(m_indiP_home, ipRecv);
483 :
484 0 : if(!ipRecv.find("request")) return 0;
485 :
486 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
487 : {
488 0 : indi::updateSwitchIfChanged(m_indiP_home, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY);
489 :
490 0 : std::lock_guard<std::mutex> guard(derived().m_indiMutex);
491 0 : m_movingState = 0;
492 0 : return derived().startHoming();
493 0 : }
494 0 : return 0;
495 : }
496 :
497 : template<class derivedT>
498 6 : int stdMotionStage<derivedT>::newCallBack_m_indiP_stop( const pcf::IndiProperty &ipRecv )
499 : {
500 6 : INDI_VALIDATE_CALLBACK_PROPS_DERIVED(m_indiP_stop, ipRecv);
501 :
502 0 : if(ipRecv.getName() != m_indiP_stop.getName())
503 : {
504 0 : derivedT::template log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
505 0 : return -1;
506 : }
507 :
508 0 : if(!ipRecv.find("request")) return 0;
509 :
510 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
511 : {
512 0 : indi::updateSwitchIfChanged(m_indiP_stop, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY);
513 :
514 : //-->do not lock mutex!
515 0 : m_movingState = 0;
516 0 : return derived().stop();
517 : }
518 0 : return 0;
519 : }
520 :
521 : template<class derivedT>
522 0 : int stdMotionStage<derivedT>::updateINDI()
523 : {
524 0 : if( !derived().m_indiDriver ) return 0;
525 :
526 0 : int n = derived().presetNumber();
527 :
528 0 : size_t nn = n;
529 :
530 : //Check for changes and update the filterNames
531 0 : bool changed = false;
532 :
533 : static int8_t last_moving = -1; //Initialize so we always update first time through.
534 :
535 0 : if(last_moving != m_moving)
536 : {
537 0 : changed = true;
538 0 : last_moving = m_moving;
539 : }
540 :
541 0 : for(size_t i =0; i < m_presetNames.size(); ++i)
542 : {
543 0 : if( i == nn )
544 : {
545 0 : if(m_indiP_presetName[m_presetNames[i]] != pcf::IndiElement::On)
546 : {
547 0 : changed = true;
548 0 : m_indiP_presetName[m_presetNames[i]] = pcf::IndiElement::On;
549 : }
550 : }
551 : else
552 : {
553 0 : if(m_indiP_presetName[m_presetNames[i]] != pcf::IndiElement::Off)
554 : {
555 0 : changed = true;
556 0 : m_indiP_presetName[m_presetNames[i]] = pcf::IndiElement::Off;
557 : }
558 : }
559 : }
560 0 : if(changed)
561 : {
562 0 : if(m_moving > 0)
563 : {
564 0 : m_indiP_presetName.setState(INDI_BUSY);
565 : }
566 : else
567 : {
568 0 : m_indiP_presetName.setState(INDI_IDLE);
569 : }
570 :
571 0 : m_indiP_presetName.setTimeStamp(pcf::TimeStamp());
572 0 : derived().m_indiDriver->sendSetProperty(m_indiP_presetName);
573 : }
574 :
575 :
576 :
577 0 : if(m_moving && m_movingState < 1)
578 : {
579 0 : indi::updateIfChanged(m_indiP_preset, "current", m_preset, derived().m_indiDriver,INDI_BUSY);
580 0 : indi::updateIfChanged(m_indiP_preset, "target", m_preset_target, derived().m_indiDriver,INDI_BUSY);
581 : }
582 : else
583 : {
584 0 : indi::updateIfChanged(m_indiP_preset, "current", m_preset, derived().m_indiDriver,INDI_IDLE);
585 0 : indi::updateIfChanged(m_indiP_preset, "target", m_preset_target, derived().m_indiDriver,INDI_IDLE);
586 : }
587 :
588 0 : return recordStage();
589 : }
590 :
591 : template<class derivedT>
592 0 : int stdMotionStage<derivedT>::recordStage(bool force)
593 : {
594 0 : static int8_t last_moving = m_moving + 100; //guarantee first run
595 : static float last_preset;
596 0 : static std::string last_presetName;
597 :
598 0 : size_t n = derived().presetNumber();
599 :
600 0 : std::string presetName;
601 0 : if(n < m_presetNames.size()) presetName = m_presetNames[n];
602 :
603 0 : if( m_moving != last_moving || m_preset != last_preset || presetName != last_presetName || force)
604 : {
605 0 : derived().template telem<telem_stage>({m_moving, m_preset, presetName});
606 0 : last_moving = m_moving;
607 0 : last_preset = m_preset;
608 0 : last_presetName = presetName;
609 :
610 : }
611 :
612 :
613 0 : return 0;
614 0 : }
615 :
616 :
617 : } //namespace dev
618 : } //namespace app
619 : } //namespace MagAOX
620 :
621 : #endif //stdMotionStage_hpp
|