API
 
Loading...
Searching...
No Matches
dmPokeWFS.hpp
Go to the documentation of this file.
1/** \file dmPokeWFS.hpp
2 * \brief The MagAO-X DM Poke Centering header file
3 *
4 * \ingroup dmPokeWFS_files
5 */
6
7#ifndef dmPokeWFS_hpp
8#define dmPokeWFS_hpp
9
10#include <mx/improc/eigenImage.hpp>
11#include <mx/improc/milkImage.hpp>
12#include <mx/improc/eigenCube.hpp>
13using namespace mx::improc;
14
15#include "semUtilsDerived.hpp"
16
17#include "../../ImageStreamIO/pixaccess.hpp"
18
19/** \defgroup dmPokeWFS
20 * \brief The MagAO-X device to coordinate poking a deformable mirror's actuators and synchronize reads of a camera image.
21 *
22 * <a href="../handbook/operating/software/apps/dmPokeWFS.html">Application Documentation</a>
23 *
24 * \ingroup apps
25 *
26 */
27
28
29/** \defgroup dmPokeWFS_files
30 * \ingroup dmPokeWFS
31 */
32
33namespace MagAOX
34{
35namespace app
36{
37namespace dev
38{
39
40
41
42/// A base class to coordinate poking a deformable mirror's actuators and synchronizedreads of a camera image.
43/** CRTP class `derivedT` has the following requirements:
44 *
45 * - Must be derived from MagAOXApp<true>
46 *
47 * - Must be derived from `dev::shmimMonitor<DERIVEDNAME, dev::dmPokeWFS<DERIVEDNAME>::wfsShmimT>` (replace DERIVEDNAME with derivedT class name)
48 *
49 * - Must be derived from `dev::shmimMonitor<DERIVEDNAME, dev::dmPokeWFS<DERIVEDNAME>::darkShmimT>` (replace DERIVEDNAME with derivedT class name)
50 *
51 * - Must contain the following friend declarations (replace DERIVEDNAME with derivedT class name):
52 * \code
53 * friend class dev::shmimMonitor<DERIVEDNAME, dev::dmPokeWFS<DERIVEDNAME>::wfsShmimT>;
54 * friend class dev::shmimMonitor<DERIVEDNAME, dev::dmPokeWFS<DERIVEDNAME>::darkShmimT>;
55 * friend class dev::dmPokeWFS<DERIVEDNAME>
56 * \endcode
57 *
58 * - Must contain the following typedefs (replace DERIVEDNAME with derivedT class name):
59 * \code
60 * typedef dev::shmimMonitor<DERIVEDNAME, dev::dmPokeWFS<DERIVEDNAME>::wfsShmimT> shmimMonitorT;
61 * typedef dev::shmimMonitor<DERIVEDNAME, dev::dmPokeWFS<DERIVEDNAME>::darkShmimT> darkShmimMonitorT;
62 * typedef dev::dmPokeWFS<DERIVEDNAME> dmPokeWFST;
63 *
64 * \endcode
65 * - Must provide the following interfaces:
66 * \code
67 * shmimMonitorT & shmimMonitor()
68 * {
69 * return *static_cast<shmimMonitorT *>(this);
70 * }
71 *
72 * darkShmimMonitorT & darkShmimMonitor()
73 * {
74 * return *static_cast<darkShmimMonitorT *>(this);
75 * }
76 * \endcode
77 *
78 * - If derivedT has additional shmimMonitor parents, you will need to include these lines in the class
79 * declaration:
80 * \code
81 * using dmPokeWFST::allocate;
82 * using dmPokeWFST::processImage;
83 * \endcode
84 *
85 * - Must provide the following interface:
86 * \code
87 * // Run the sensor steps
88 * // Coordinates the actions of poking and collecting images.
89 * // Upon completion this calls runSensor. If \p firstRun == true, one time
90 * // actions such as taking a dark can be executed.
91 * //
92 * // returns 0 on success
93 * // returns < 0 on an error
94 * int runSensor(bool firstRun ///< [in] flag indicating this is the first call. triggers taking a dark if true.
95 * );
96 * \endcode
97 *
98 * - Must provide the following interface:
99 * \code
100 * // Analyze the poke image
101 * // This analyzes the resulting poke images.
102 * //
103 * // returns 0 on success
104 * // returns < 0 on an error
105 * int analyzeSensor();
106 * \endcode
107 * At the conclusion of analyzeSensor the measured signal (e.g. deltaX and deltaY) should be updated and set in m_indiP_measurement.
108 * The function \ref updateMeasurement() can be used for this. However, the updating of the loop counter and the subsequent INDI
109 * property update is handled automatically after that.
110 *
111 * - Must be a telemeter with the following interface:
112 *
113 * - Must be derived from `dev::telemeter<DERIVEDNAME>` (replace DERIVEDNAME with derivedT class name) and meet the requirements
114 * of `dev::telemeter`
115 *
116 * - In the function `derivedT::checkRecordTimes()` required by `dev::telemeter`, the `telem_pokeloop` type must be checked.
117 * The minimum `derivedT::checkRecordTimes()` is:
118 * \code
119 * int checkRecordTimes()
120 * {
121 * return telemeterT::checkRecordTimes(telem_pokeloop());
122 * }
123 * \endcode
124 *
125 * - Must call this base class's setupConfig(), loadConfig(), appStartup(), appStartup(), and appShutdown() in the
126 * appropriate functions. For convenience the following macros are defined to provide error checking:
127 * \code
128 * DMPOKEWFS_SETUP_CONFIG( cfig )
129 * DMPOKEWFS_LOAD_CONFIG( cfig )
130 * DMPOKEWFS_APP_STARTUP
131 * DMPOKEWFS_APP_LOGIC
132 * DMPOKEWFS_APP_SHUTDOWN
133 * \endcode
134 *
135 * \ingroup appdev
136 */
137template<class derivedT>
139{
140
141public:
142
144 {
145 static std::string configSection()
146 {
147 return "wfscam";
148 };
149
150 static std::string indiPrefix()
151 {
152 return "wfscam";
153 };
154 };
155
157 {
158 static std::string configSection()
159 {
160 return "wfsdark";
161 };
162
163 static std::string indiPrefix()
164 {
165 return "wfsdark";
166 };
167 };
168
169protected:
170
171 /** \name Configurable Parameters
172 *@{
173 */
174
175 std::string m_wfsCamDevName; ///<INDI device name of the WFS camera. Default is wfscam.shmimName.
176
177 double m_wfsSemWait {1.5}; ///< The time in sec to wait on the WFS semaphore. Default 0.5 sec.
178
179 double m_imageSemWait {0.5}; ///< The time in sec to wait on the image semaphore. Default 0.5 sec.
180
181 unsigned m_nPokeImages {5}; ///< The number of images to average for the poke images. Default is 5.
182
183 unsigned m_nPokeAverage {10}; ///< The number of poke sequences to average. Default is 10.
184
185 std::string m_dmChan;
186
187 std::vector<int> m_poke_x;
188 std::vector<int> m_poke_y;
189
190 float m_poke_amp {0.0};
191
192 float m_dmSleep {10000}; ///<The time to sleep for the DM command to be applied, in microseconds. Default is 10000.
193
194 ///@}
195
196 std::mutex m_wfsImageMutex;
197
198 mx::improc::milkImage<float> m_rawImage;
199
200 mx::improc::milkImage<float> m_pokeImage;
201 mx::improc::eigenImage<float> m_pokeLocal;
202
203 float (*wfsPixget)(void *, size_t) {nullptr}; ///< Pointer to a function to extract the image data as float
204
205 float m_wfsFps {-1}; ///< The WFS camera FPS
206
207 mx::improc::eigenImage<float> m_darkImage; ///< The dark image
208
209 bool m_darkValid {false}; ///< Flag indicating if dark is valid based on its size.
210
211 float (*darkPixget)(void *, size_t) {nullptr}; ///< Pointer to a function to extract the dark image data as float
212
213 mx::improc::milkImage<float> m_dmStream;
214
215 mx::improc::eigenImage<float> m_dmImage;
216
217 float m_deltaX {0};
218 float m_deltaY {0};
219 uint64_t m_counter {0};
220
221public:
222
223 /**\name MagAOXApp Interface
224 *
225 * @{
226 */
227
228 /// Setup the configuration system
229 /**
230 * This should be called in `derivedT::setupConfig` as
231 * \code
232 dmPokeWFS<derivedT,realT>::setupConfig(config);
233 \endcode
234 * with appropriate error checking.
235 */
236 int setupConfig( mx::app::appConfigurator & config /**< [in] an application configuration to load values to*/);
237
238 /// load the configuration system results
239 /**
240 * This should be called in `derivedT::loadConfig` as
241 * \code
242 dmPokeWFS<derivedT,realT>::loadConfig(config);
243 \endcode
244 * with appropriate error checking.
245 */
246 int loadConfig( mx::app::appConfigurator & config /**< [in] an application configuration from which to load values */);
247
248 /// Startup function
249 /**
250 * This should be called in `derivedT::appStartup` as
251 * \code
252 dmPokeWFS<derivedT,realT>::appStartup();
253 \endcode
254 * with appropriate error checking.
255 *
256 * \returns 0 on success
257 * \returns -1 on error, which is logged.
258 */
260
261 /// dmPokeWFS application logic
262 /** This should be called in `derivedT::appLogic` as
263 * \code
264 dmPokeWFS<derivedT,realT>::appLogic();
265 \endcode
266 * with appropriate error checking.
267 *
268 * \returns 0 on success
269 * \returns -1 on error, which is logged.
270 */
271 int appLogic();
272
273 /// dmPokeWFS shutdown
274 /** This should be called in `derivedT::appShutdown` as
275 * \code
276 dmPokeWFS<derivedT,realT>::appShutdown();
277 \endcode
278 * with appropriate error checking.
279 *
280 * \returns 0 on success
281 * \returns -1 on error, which is logged.
282 */
284
285 ///@}
286
287 /** \name shmimMonitor Interface
288 * @{
289 */
290
291 int allocate( const wfsShmimT & /**< [in] tag to differentiate shmimMonitor parents.*/);
292
293 int processImage( void * curr_src, ///< [in] pointer to the start of the current frame
294 const wfsShmimT & ///< [in] tag to differentiate shmimMonitor parents.
295 );
296 ///@}
297
298
299 /** \name darkShmimMonitor Interface
300 * @{
301 */
302
303 int allocate( const darkShmimT & /**< [in] tag to differentiate shmimMonitor parents.*/);
304
305 int processImage( void * curr_src, ///< [in] pointer to the start of the current frame
306 const darkShmimT & ///< [in] tag to differentiate shmimMonitor parents.
307 );
308 ///@}
309
310 /** \name WFS Thread
311 * This thread coordinates the WFS process
312 *
313 * @{
314 */
315protected:
316
317 int m_wfsThreadPrio {1}; ///< Priority of the WFS thread, should normally be > 00.
318
319 std::string m_wfsCpuset; ///< The cpuset for the framegrabber thread. Ignored if empty (the default).
320
321 std::thread m_wfsThread; ///< A separate thread for the actual WFSing
322
323 bool m_wfsThreadInit {true}; ///< Synchronizer to ensure wfs thread initializes before doing dangerous things.
324
325 pid_t m_wfsThreadID {0}; ///< WFS thread PID.
326
327 pcf::IndiProperty m_wfsThreadProp; ///< The property to hold the WFS thread details.
328
329 ///Thread starter, called by wfsThreadStart on thread construction. Calls wfsThreadExec.
330 static void wfsThreadStart( dmPokeWFS * s /**< [in] a pointer to an streamWriter instance (normally this) */);
331
332 /// Execute the frame grabber main loop.
334
335 sem_t m_wfsSemaphore; ///< Semaphore used to signal the WFS thread to start WFSing.
336
337 unsigned m_wfsSemWait_sec {1}; ///< The timeout for the WFS semaphore, seconds component.
338
339 unsigned m_wfsSemWait_nsec {0}; ///< The timeoutfor the WFS semaphore, nanoseconds component.
340
341 int m_measuring {0}; ///< Status of measuring: 0 no, 1 single in progress, 2 continuous in progress.
342
343 bool m_single {false}; ///< True a single measurement is in progress.
344
345 bool m_continuous {false}; ///< True if continuous measurements are in progress.
346
347 bool m_stopMeasurement {false}; ///< Used to request that the measurement in progress stop.
348
349 ///@}
350
351 sem_t m_imageSemaphore; ///< Semaphore used to signal that an image is ready
352
353 unsigned m_imageSemWait_sec {1}; ///< The timeout for the image semaphore, seconds component.
354
355 unsigned m_imageSemWait_nsec {0}; ///< The timeout for the image semaphore, nanoseconds component.
356
357
358 /// Apply a single DM poke pattern and record the results
359 /** This accumulates m_nPokeImages*m_nPokeAverage images in m_pokeLocal, so m_pokeLocal
360 * should be zeroed before the first call to this (e.g. for a +1 poke),
361 * but not zeroed before the second call (e.g. for the -1 poke). You also need
362 * to 0 the DM after finishing a poke pair.
363 * See basicRunSensor() for how to use.
364 *
365 * \returns +1 if exit is due to shutdown or stop request
366 * \returns 0 if no error
367 * \returns -1 if an error occurs
368 */
369 int basicTimedPoke(float pokeSign /**< [in] the sign, and possibly a scaling, to apply to m_pokeAmplitude*/);
370
371 /// Run the basic +/- poke sensor steps
372 /** Coordinates the actions of poking and collecting images.
373 *
374 * This can be called from the derived class runSensor.
375 *
376 * \returns +1 if exit is due to shutdown or stop request
377 * \returns 0 if no error
378 * \returns -1 if an error occurs
379 */
381
382 int updateMeasurement( float deltaX,
383 float deltaY
384 );
385
386 /** \name INDI Interface
387 * @{
388 */
389protected:
390
391 pcf::IndiProperty m_indiP_poke_amp;
393
394 pcf::IndiProperty m_indiP_nPokeImages;
396
397 pcf::IndiProperty m_indiP_nPokeAverage;
399
400 pcf::IndiProperty m_indiP_wfsFps; ///< Property to get the FPS from the WFS camera
402
403 pcf::IndiProperty m_indiP_single; ///< Switch to start a single measurement
405
406 pcf::IndiProperty m_indiP_continuous; ///< Switch to start continuous measurement
408
409 pcf::IndiProperty m_indiP_stop; ///< Switch to request that measurement stop
411
412 pcf::IndiProperty m_indiP_measurement; ///< Property to report the delta measurement, including the loop counter.
413
414
415 ///@}
416
417 /** \name Telemeter Interface
418 * @{
419 */
420
421 int recordTelem(const telem_pokeloop *);
422
423 int recordPokeLoop(bool force = false);
424
425 ///@}
426
427private:
428 derivedT & derived()
429 {
430 return *static_cast<derivedT *>(this);
431 }
432
433};
434
435template<class derivedT>
436int dmPokeWFS<derivedT>::setupConfig(mx::app::appConfigurator & config)
437{
438 if(derived().shmimMonitor().setupConfig(config) < 0)
439 {
440 derivedT::template log<software_error>({__FILE__, __LINE__, "shmimMonitorT::setupConfig"});
441 return -1;
442 }
443
444 config.add("wfscam.camDevName", "", "wfscam.camDevName", argType::Required, "wfscam", "camDevName", false, "string", "INDI device name of the WFS camera. Default is wfscam.shmimName.");
445 config.add("wfscam.loopSemWait", "", "wfscam.loopSemWait", argType::Required, "wfscam", "loopSemWait", false, "float", "The semaphore wait time for the wfs loop start signal");
446 config.add("wfscam.imageSemWait", "", "wfscam.imageSemWait", argType::Required, "wfscam", "imageSemWait", false, "float", "The semaphore wait time for the image availability signal");
447
448 if(derived().darkShmimMonitor().setupConfig(config) < 0)
449 {
450 derivedT::template log<software_error>({__FILE__, __LINE__, "darkShmimMonitorT::setupConfig"});
451 return -1;
452 }
453
454 config.add("pokecen.dmChannel", "", "pokecen.dmChannel", argType::Required, "pokecen", "dmChannel", false, "string", "The dm channel to use for pokes, e.g. dm01disp06.");
455 config.add("pokecen.pokeX", "", "pokecen.pokeX", argType::Required, "pokecen", "pokeX", false, "vector<int>", "The x-coordinates of the actuators to poke. ");
456 config.add("pokecen.pokeY", "", "pokecen.pokeY", argType::Required, "pokecen", "pokeY", false, "vector<int>", "The y-coordinates of the actuators to poke. ");
457 config.add("pokecen.pokeAmp", "", "pokecen.pokeAmp", argType::Required, "pokecen", "pokeAmp", false, "float", "The poke amplitude, in DM command units. Default is 0.");
458 config.add("pokecen.dmSleep", "", "pokecen.dmSleep", argType::Required, "pokecen", "dmSleep", false, "float", "The time to sleep for the DM command to be applied, in microseconds. Default is 10000.");
459 config.add("pokecen.nPokeImages", "", "pokecen.nPokeImages", argType::Required, "pokecen", "nPokeImages", false, "int", "The number of poke images to average. Default 5.");
460 config.add("pokecen.nPokeAverage", "", "pokecen.nPokeAverage", argType::Required, "pokecen", "nPokeAverage", false, "int", "The number of poke sequences to average. Default 10.");
461
462
463 return 0;
464}
465
466
467template<class derivedT>
468int dmPokeWFS<derivedT>::loadConfig( mx::app::appConfigurator & config)
469{
470 if(derived().shmimMonitor().loadConfig(config) < 0)
471 {
472 return derivedT::template log<software_error, -1>({__FILE__, __LINE__, "shmimMonitorT::loadConfig"});
473 }
474
475 m_wfsCamDevName = derived().shmimMonitor().shmimName();
476 config(m_wfsCamDevName, "wfscam.camDevName");
477
478 //configure the semaphore waits
479 config(m_wfsSemWait, "wfscam.loopSemWait");
480
481 m_wfsSemWait_sec = floor(m_wfsSemWait);
482 m_wfsSemWait_nsec = (m_wfsSemWait - m_wfsSemWait_sec) * 1e9;
483
484 config(m_imageSemWait, "wfscam.imageSemWait");
485
486 m_imageSemWait_sec = floor(m_imageSemWait);
487 m_imageSemWait_nsec = (m_imageSemWait - m_imageSemWait_sec) * 1e9;
488
489 if(derived().darkShmimMonitor().loadConfig(config) < 0)
490 {
491 return derivedT::template log<software_error, -1>({__FILE__, __LINE__, "darkShmimMonitorT::loadConfig"});
492 }
493
494 config(m_dmChan, "pokecen.dmChannel");
495
496 config(m_poke_x, "pokecen.pokeX");
497
498 config(m_poke_y, "pokecen.pokeY");
499
500 if(m_poke_x.size() == 0 || (m_poke_x.size() != m_poke_y.size()))
501 {
502 return derivedT::template log<software_error,-1>({__FILE__, __LINE__, "invalid poke specification"});
503 }
504
505 config(m_poke_amp, "pokecen.pokeAmp");
506
507 config(m_dmSleep, "pokecen.dmSleep");
508
509 config(m_nPokeImages, "pokecen.nPokeImages");
510
511 config(m_nPokeAverage, "pokecen.nPokeAverage");
512
513 return 0;
514}
515
516template<class derivedT>
518{
519 if( derived().shmimMonitor().appStartup() < 0)
520 {
521 return derivedT::template log<software_error, -1>({__FILE__,__LINE__});
522 }
523
524 if( derived().darkShmimMonitor().appStartup() < 0)
525 {
526 return derivedT::template log<software_error, -1>({__FILE__,__LINE__});
527 }
528
529 CREATE_REG_INDI_NEW_NUMBERF_DERIVED(m_indiP_poke_amp, "poke_amp", -1, 1, 1e-1, "%0.01f", "", "");
530 m_indiP_poke_amp["current"].setValue(m_poke_amp);
531 m_indiP_poke_amp["target"].setValue(m_poke_amp);
532
533 CREATE_REG_INDI_NEW_NUMBERI_DERIVED(m_indiP_nPokeImages, "nPokeImages", 1, 1000, 1, "%d", "", "");
534 m_indiP_nPokeImages["current"].setValue(m_nPokeImages);
535 m_indiP_nPokeImages["target"].setValue(m_nPokeImages);
536
537 CREATE_REG_INDI_NEW_NUMBERI_DERIVED(m_indiP_nPokeAverage, "nPokeAverage", 1, 1000, 1, "%d", "", "");
538 m_indiP_nPokeAverage["current"].setValue(m_nPokeAverage);
539 m_indiP_nPokeAverage["target"].setValue(m_nPokeAverage);
540
541 REG_INDI_SETPROP_DERIVED(m_indiP_wfsFps, m_wfsCamDevName, std::string("fps"));
542
543 CREATE_REG_INDI_NEW_TOGGLESWITCH_DERIVED( m_indiP_single, "single");
544
545 CREATE_REG_INDI_NEW_TOGGLESWITCH_DERIVED( m_indiP_continuous, "continuous");
546
547 CREATE_REG_INDI_NEW_REQUESTSWITCH_DERIVED( m_indiP_stop, "stop");
548
549 derived().template registerIndiPropertyReadOnly( m_indiP_measurement, "measurement", pcf::IndiProperty::Number, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle);
550 m_indiP_measurement.add({"delta_x", 0.0});
551 m_indiP_measurement.add({"delta_y", 0.0});
552 m_indiP_measurement.add({"counter", 0});
553
554 if(sem_init(&m_wfsSemaphore, 0,0) < 0)
555 {
556 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno,0, "Initializing wfs semaphore"});
557 }
558
559 if(sem_init(&m_imageSemaphore, 0,0) < 0)
560 {
561 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno,0, "Initializing image semaphore"});
562 }
563
564 if(derived().template threadStart( m_wfsThread, m_wfsThreadInit, m_wfsThreadID, m_wfsThreadProp, m_wfsThreadPrio, m_wfsCpuset, "wfs", this, wfsThreadStart) < 0)
565 {
566 return derivedT::template log<software_critical,-1>({__FILE__, __LINE__});
567 }
568
569 return 0;
570}
571
572template<class derivedT>
574{
575
576 if( derived().shmimMonitor().appLogic() < 0)
577 {
578 return derivedT::template log<software_error, -1>({__FILE__,__LINE__});
579 }
580
581 if( derived().darkShmimMonitor().appLogic() < 0)
582 {
583 return derivedT::template log<software_error, -1>({__FILE__,__LINE__});
584 }
585
586 //first do a join check to see if other threads have exited.
587 //these will throw if the threads are really gone
588 try
589 {
590 if(pthread_tryjoin_np(m_wfsThread.native_handle(),0) == 0)
591 {
592 derivedT::template log<software_error>({__FILE__, __LINE__, "WFS thread has exited"});
593 return -1;
594 }
595 }
596 catch(...)
597 {
598 derivedT::template log<software_error>({__FILE__, __LINE__, "WFS thread has exited"});
599 return -1;
600 }
601
602 if(m_measuring > 0)
603 {
604 if(m_continuous)
605 {
606 derived().template updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::On, INDI_OK);
607 }
608 else
609 {
610 derived().template updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
611 }
612
613 if(m_single)
614 {
615 derived().template updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::On, INDI_OK);
616 }
617 else
618 {
619 derived().template updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
620 }
621 }
622 else
623 {
624 derived().template updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
625 derived().template updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
626 }
627
628 derived().template updateIfChanged( m_indiP_nPokeImages, "current", m_nPokeImages);
629 derived().template updateIfChanged( m_indiP_nPokeAverage, "current", m_nPokeAverage);
630 derived().template updateIfChanged( m_indiP_poke_amp, "current", m_poke_amp);
631
632 return 0;
633}
634
635template<class derivedT>
637{
638 if(derived().shmimMonitor().appShutdown() < 0)
639 {
640 derivedT::template log<software_error>({__FILE__, __LINE__, "error from shmimMonitorT::appShutdown"});
641 }
642
643 if(derived().darkShmimMonitor().appShutdown() < 0)
644 {
645 derivedT::template log<software_error>({__FILE__, __LINE__, "error from darkShmimMonitorT::appShutdown"});
646 }
647
648 if (m_wfsThread.joinable())
649 {
650 pthread_kill(m_wfsThread.native_handle(), SIGUSR1);
651 try
652 {
653 m_wfsThread.join(); // this will throw if it was already joined
654 }
655 catch (...)
656 {
657 }
658 }
659
660 return 0;
661}
662
663template<class derivedT>
665{
666 static_cast<void>(dummy); //be unused
667
668 std::unique_lock<std::mutex> lock(m_wfsImageMutex);
669
670 m_rawImage.create( derived().m_configName + "_raw", derived().shmimMonitor().width(), derived().shmimMonitor().height());
671
672 wfsPixget = getPixPointer<float>(derived().shmimMonitor().dataType());
673
674 try
675 {
676 m_dmStream.open(m_dmChan);
677 }
678 catch(const std::exception& e)
679 {
680 return derivedT::template log<software_error,-1>({__FILE__, __LINE__, std::string("exception opening DM: ") + e.what()});
681 }
682
683 m_dmStream.passive(true);
684
685 m_dmImage.resize(m_dmStream.rows(), m_dmStream.cols());
686
687 if(derived().darkShmimMonitor().width() == derived().shmimMonitor().width() &&
688 derived().darkShmimMonitor().height() == derived().shmimMonitor().height() )
689 {
690 m_darkValid = true;
691 }
692 else
693 {
694 m_darkValid = false;
695 }
696
697 if(m_pokeImage.rows() != derived().shmimMonitor().width() || m_pokeImage.cols() != derived().shmimMonitor().height())
698 {
699 m_pokeImage.create(derived().m_configName + "_poke", derived().shmimMonitor().width(), derived().shmimMonitor().height());
700 }
701
702 m_pokeLocal.resize(derived().shmimMonitor().width(), derived().shmimMonitor().height());
703
704 return 0;
705}
706
707template<class derivedT>
709 const wfsShmimT & dummy
710 )
711{
712 static_cast<void>(dummy); //be unused
713
714 std::unique_lock<std::mutex> lock(m_wfsImageMutex);
715
716 float * data = m_rawImage().data();
717 float * darkData = m_darkImage.data();
718
719 //Copy the data out as float no matter what type it is
720 uint64_t Npix = derived().shmimMonitor().width()*derived().shmimMonitor().height();
721
722 if(m_darkValid)
723 {
724 for(unsigned nn=0; nn < Npix; ++nn)
725 {
726 data[nn] = wfsPixget(curr_src, nn) - darkData[nn];
727 }
728 }
729 else
730 {
731 for(unsigned nn=0; nn < Npix; ++nn)
732 {
733 data[nn] = wfsPixget(curr_src, nn);
734 }
735 }
736
737 if(sem_post(&m_imageSemaphore) < 0)
738 {
739 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
740 }
741
742 return 0;
743}
744
745//--dark shmim
746
747template<class derivedT>
749{
750 static_cast<void>(dummy); //be unused
751
752 std::unique_lock<std::mutex> lock(m_wfsImageMutex);
753
754 m_darkImage.resize(derived().darkShmimMonitor().width(), derived().darkShmimMonitor().height());
755
756 darkPixget = getPixPointer<float>(derived().darkShmimMonitor().dataType());
757
758 if(derived().darkShmimMonitor().width() == derived().shmimMonitor().width() &&
759 derived().darkShmimMonitor().height() == derived().shmimMonitor().height() )
760 {
761 std::cerr << "dark is valid " << derived().darkShmimMonitor().width() << " " << derived().shmimMonitor().width() << " ";
762 std::cerr << derived().darkShmimMonitor().height() << " " << derived().shmimMonitor().height() << "\n";
763 m_darkValid = true;
764 }
765 else
766 {
767 m_darkValid = false;
768 }
769
770 return 0;
771}
772
773template<class derivedT>
775 const darkShmimT & dummy
776 )
777{
778 static_cast<void>(dummy); //be unused
779
780 std::unique_lock<std::mutex> lock(m_wfsImageMutex);
781
782 float * darkData = m_darkImage.data();
783
784 //Copy the data out as float no matter what type it is
785 uint64_t nPix = derived().darkShmimMonitor().width()*derived().darkShmimMonitor().height();
786 for(unsigned nn=0; nn < nPix; ++nn)
787 {
788 darkData[nn] = darkPixget(curr_src, nn);
789 }
790
791 return 0;
792}
793
794template<class derivedT>
799
800template<class derivedT>
802{
803 m_wfsThreadID = syscall(SYS_gettid);
804
805 //Wait fpr the thread starter to finish initializing this thread.
806 while(m_wfsThreadInit == true && derived().m_shutdown == 0)
807 {
808 sleep(1);
809 }
810
811 while(derived().m_shutdown == 0)
812 {
813 timespec ts;
814 XWC_SEM_WAIT_TS_RETVOID_DERIVED(ts, m_wfsSemWait_sec, m_wfsSemWait_nsec);
815
816 XWC_SEM_TIMEDWAIT_LOOP_DERIVED( m_wfsSemaphore, ts )
817
818 //Lock a mutex here
819 if(m_single)
820 {
821 m_measuring = 1;
822 }
823 else if(m_continuous)
824 {
825 m_measuring = 2;
826 }
827 else
828 {
829 m_measuring = 0;
830 return;
831 }
832
833 derived().template state(stateCodes::OPERATING);
834
835 while(!m_pokeImage.valid())
836 {
837 mx::sys::milliSleep(10);
838 }
839
840 m_stopMeasurement = false;
841
842 bool firstRun = true;
843
844 while(!m_stopMeasurement && !derived().m_shutdown)
845 {
846 if( derived().runSensor(firstRun) < 0)
847 {
848 derivedT::template log<software_error>({__FILE__, __LINE__, "runSensor returned error"});
849 break;
850 }
851
852 if(m_stopMeasurement || derived().m_shutdown)
853 {
854 break;
855 }
856
857 if( derived().analyzeSensor() < 0)
858 {
859 derivedT::template log<software_error>({__FILE__, __LINE__, "runSensor returned error"});
860 break;
861 }
862
863 ++m_counter;
864 derived().updateIfChanged(m_indiP_measurement, "counter", m_counter);
865 derived().recordPokeLoop();
866
867 firstRun = false;
868
869 if(m_measuring == 1)
870 {
871 break;
872 }
873 }
874
875 m_measuring = 0;
876 m_single = 0;
877 m_continuous = 0;
878
879 derived().template state(stateCodes::READY);
880
881
882 } //outer loop, will exit if derived().m_shutdown==true
883
884 return;
885
886}
887
888template<class derivedT>
890{
891 timespec ts;
892
893 int sign = 1;
894 if(pokeSign < 0) sign = -1;
895
896 //Prepare the DM image with the pokes
897 m_dmImage.setZero();
898
899 for(size_t nn = 0; nn < m_poke_x.size(); ++nn)
900 {
901 m_dmImage( m_poke_x[nn], m_poke_y[nn]) = pokeSign*m_poke_amp;
902 }
903
904 //This is where the pokes are applied to the DM
905 m_dmStream = m_dmImage;
906
907 mx::sys::microSleep(m_dmSleep);
908
909 //flush semaphore so we take the _next_ good image
910 XWC_SEM_FLUSH_DERIVED(m_imageSemaphore);
911
912 //** And wait one image to be sure we are on a whole poke**//
913 XWC_SEM_WAIT_TS_DERIVED(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
914 bool ready = false;
915 while(!ready && !(m_stopMeasurement || derived().m_shutdown))
916 {
917 XWC_SEM_TIMEDWAIT_LOOP_DERIVED( m_imageSemaphore, ts )
918 else
919 {
920 ready = true;
921 }
922 }
923
924 uint32_t n = 0;
925 while(n < m_nPokeImages && !(m_stopMeasurement || derived().m_shutdown))
926 {
927 //** Now we record the poke image **//
928 XWC_SEM_WAIT_TS_DERIVED(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
929 XWC_SEM_TIMEDWAIT_LOOP_DERIVED( m_imageSemaphore, ts )
930
931 //If here, we got an image. m_rawImage will have been updated
932 m_pokeLocal += sign*m_rawImage();
933
934 ++n;
935 }
936
937 if(m_stopMeasurement || derived().m_shutdown)
938 {
939 m_dmImage.setZero();
940 m_dmStream = m_dmImage;
941 return 1;
942 }
943
944 return 0;
945}
946
947template<class derivedT>
949{
950 int rv;
951
952 if(!m_pokeImage.valid())
953 {
954 return derivedT::template log<software_error,-1>({__FILE__, __LINE__, "poke image is not allocated"});
955 }
956
957 m_pokeLocal.setZero();
958
959 for(unsigned nseq = 0; nseq < m_nPokeAverage; ++nseq)
960 {
961
962 //************** positive POKE **********************/
963
964 rv = basicTimedPoke(+1);
965
966 if(rv < 0)
967 {
968 derivedT::template log<software_error>({__FILE__, __LINE__});
969 return rv;
970 }
971 else if (rv > 0) // shutdown
972 {
973 return rv;
974 }
975
976 if(m_stopMeasurement || derived().m_shutdown)
977 {
978 break;
979 }
980
981 //************** NEGATIVE POKE **********************/
982
983 rv = basicTimedPoke(-1);
984
985 if(rv < 0)
986 {
987 derivedT::template log<software_error>({__FILE__, __LINE__});
988 return rv;
989 }
990 else if (rv > 0) // shutdown
991 {
992 return rv;
993 }
994
995 if(m_stopMeasurement || derived().m_shutdown)
996 {
997 break;
998 }
999 }
1000
1001 try
1002 {
1003 m_pokeImage = m_pokeLocal/(2.0*m_nPokeImages*m_nPokeAverage);
1004 }
1005 catch(const std::exception& e)
1006 {
1007 return derivedT::template log<software_error,-1>({__FILE__, __LINE__, e.what()});
1008 }
1009
1010
1011
1012 m_dmImage.setZero();
1013 m_dmStream = m_dmImage;
1014
1015 return 0;
1016}
1017
1018template<class derivedT>
1020 float deltaY
1021 )
1022{
1023 m_deltaX = deltaX;
1024 m_deltaY = deltaY;
1025 m_indiP_measurement["delta_x"] = deltaX;
1026 m_indiP_measurement["delta_y"] = deltaY;
1027
1028 return 0;
1029}
1030
1031template<class derivedT>
1032INDI_NEWCALLBACK_DEFN( dmPokeWFS<derivedT>, m_indiP_nPokeImages )(const pcf::IndiProperty &ipRecv)
1033{
1034 INDI_VALIDATE_CALLBACK_PROPS_DERIVED(m_indiP_nPokeImages, ipRecv)
1035
1036 float target;
1037
1038 if( derived().template indiTargetUpdate(m_indiP_nPokeImages, target, ipRecv, false) < 0)
1039 {
1040 return derivedT::template log<software_error,-1>({__FILE__, __LINE__});
1041 }
1042
1043 m_nPokeImages = target;
1044
1045 return 0;
1046}
1047
1048template<class derivedT>
1049INDI_NEWCALLBACK_DEFN( dmPokeWFS<derivedT>, m_indiP_nPokeAverage )(const pcf::IndiProperty &ipRecv)
1050{
1051 INDI_VALIDATE_CALLBACK_PROPS_DERIVED(m_indiP_nPokeAverage, ipRecv)
1052
1053 float target;
1054
1055 if( derived().template indiTargetUpdate(m_indiP_nPokeAverage, target, ipRecv, false) < 0)
1056 {
1057 return derivedT::template log<software_error,-1>({__FILE__, __LINE__});
1058 }
1059
1060 m_nPokeAverage = target;
1061
1062 return 0;
1063}
1064
1065template<class derivedT>
1066INDI_NEWCALLBACK_DEFN( dmPokeWFS<derivedT>, m_indiP_poke_amp )(const pcf::IndiProperty &ipRecv)
1067{
1069
1070 float target;
1071
1072 if( derived().template indiTargetUpdate(m_indiP_poke_amp, target, ipRecv, false) < 0)
1073 {
1074 return derivedT::template log<software_error,-1>({__FILE__, __LINE__});
1075 }
1076
1077 m_poke_amp = target;
1078
1079 return 0;
1080}
1081
1082template<class derivedT>
1083INDI_SETCALLBACK_DEFN( dmPokeWFS<derivedT>, m_indiP_wfsFps )(const pcf::IndiProperty &ipRecv)
1084{
1086
1087 if( ipRecv.find("current") != true ) //this isn't valid
1088 {
1089 return 0;
1090 }
1091
1092 m_wfsFps = ipRecv["current"].get<float>();
1093
1094 return 0;
1095}
1096
1097template<class derivedT>
1098INDI_NEWCALLBACK_DEFN( dmPokeWFS<derivedT>, m_indiP_single )(const pcf::IndiProperty &ipRecv)
1099{
1101
1102 if( ipRecv.find("toggle") != true ) //this isn't valid
1103 {
1104 return -1;
1105 }
1106
1107 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1108 {
1109 if(m_measuring == 0)
1110 {
1111 m_continuous = 0;
1112 m_single = 1;
1113 if(sem_post(&m_wfsSemaphore) < 0)
1114 {
1115 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
1116 }
1117 }
1118 }
1119
1120 return 0;
1121}
1122
1123template<class derivedT>
1124INDI_NEWCALLBACK_DEFN( dmPokeWFS<derivedT>, m_indiP_continuous )(const pcf::IndiProperty &ipRecv)
1125{
1126 INDI_VALIDATE_CALLBACK_PROPS_DERIVED(m_indiP_continuous, ipRecv)
1127
1128 if( ipRecv.find("toggle") != true ) //this isn't valid
1129 {
1130 return -1;
1131 }
1132
1133 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1134 {
1135 if(m_measuring == 0)
1136 {
1137 m_continuous = 1;
1138 m_single = 0;
1139 if(sem_post(&m_wfsSemaphore) < 0)
1140 {
1141 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
1142 }
1143 }
1144 }
1145 else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1146 {
1147 if(m_measuring != 0)
1148 {
1149 m_stopMeasurement = true;
1150 }
1151 }
1152
1153 return 0;
1154}
1155
1156template<class derivedT>
1157INDI_NEWCALLBACK_DEFN( dmPokeWFS<derivedT>, m_indiP_stop )(const pcf::IndiProperty &ipRecv)
1158{
1160
1161 if( ipRecv.find("request") != true ) //this isn't valid
1162 {
1163 return -1;
1164 }
1165
1166 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1167 {
1168 if(m_measuring != 0)
1169 {
1170 m_stopMeasurement = true;
1171 }
1172 }
1173
1174 return 0;
1175}
1176
1177template<class derivedT>
1178int dmPokeWFS<derivedT>::recordTelem(const telem_pokeloop *)
1179{
1180 return recordPokeLoop(true);
1181}
1182
1183template<class derivedT>
1185{
1186 static int measuring = -1;
1187 static float deltaX = std::numeric_limits<float>::max();
1188 static float deltaY = std::numeric_limits<float>::max();
1189 static uint64_t counter = std::numeric_limits<uint64_t>::max();
1190
1191 if(force || (m_counter != counter) || (m_deltaX != deltaX) || (m_deltaY != deltaY) || (m_measuring != measuring))
1192 {
1193 uint8_t meas = m_measuring;
1194 derived().template telem<telem_pokeloop>({meas, m_deltaX, m_deltaY, m_counter});
1195
1196 measuring = m_measuring;
1197 deltaX = m_deltaX;
1198 deltaY = m_deltaY;
1199 counter = m_counter;
1200 }
1201
1202 return 0;
1203}
1204
1205/// Call dmPokeWFS::setupConfig with error checking
1206/**
1207 * \param cfig the application configurator
1208 */
1209#define DMPOKEWFS_SETUP_CONFIG( cfig ) \
1210 if(dmPokeWFST::setupConfig(cfig) < 0) \
1211 { \
1212 log<software_error>({__FILE__, __LINE__, "Error from dmPokeWFST::setupConfig"}); \
1213 m_shutdown = true; \
1214 return; \
1215 }
1216
1217/// Call dmPokeWFS::loadConfig with error checking
1218/** This must be inside a function that returns int, e.g. the standard loadConfigImpl.
1219 * \param cfig the application configurator
1220 */
1221#define DMPOKEWFS_LOAD_CONFIG( cfig ) \
1222 if(dmPokeWFST::loadConfig(cfig) < 0) \
1223 { \
1224 return log<software_error,-1>({__FILE__, __LINE__, "Error from dmPokeWFST::loadConfig"}); \
1225 }
1226
1227/// Call dmPokeWFS::appStartup with error checking
1228#define DMPOKEWFS_APP_STARTUP \
1229 if( dmPokeWFST::appStartup() < 0) \
1230 { \
1231 return log<software_error, -1>({__FILE__,__LINE__}); \
1232 }
1233
1234/// Call dmPokeWFS::appLogic with error checking
1235#define DMPOKEWFS_APP_LOGIC \
1236 if( dmPokeWFST::appLogic() < 0) \
1237 { \
1238 return log<software_error, -1>({__FILE__,__LINE__}); \
1239 }
1240
1241/// Call dmPokeWFS::appShutdown with error checking
1242#define DMPOKEWFS_APP_SHUTDOWN \
1243 if(dmPokeWFST::appShutdown() < 0) \
1244 { \
1245 log<software_error>({__FILE__, __LINE__, "error from dmPokeWFST::appShutdown"}); \
1246 }
1247
1248} //namespace dev
1249} //namespace app
1250} //namespace MagAOX
1251
1252#endif //dmPokeWFS_hpp
A base class to coordinate poking a deformable mirror's actuators and synchronizedreads of a camera i...
pcf::IndiProperty m_indiP_poke_amp
mx::improc::eigenImage< float > m_pokeLocal
mx::improc::milkImage< float > m_rawImage
bool m_continuous
True if continuous measurements are in progress.
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_nPokeAverage)
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_continuous)
std::string m_wfsCamDevName
INDI device name of the WFS camera. Default is wfscam.shmimName.
double m_imageSemWait
The time in sec to wait on the image semaphore. Default 0.5 sec.
void wfsThreadExec()
Execute the frame grabber main loop.
unsigned m_wfsSemWait_nsec
The timeoutfor the WFS semaphore, nanoseconds component.
int updateMeasurement(float deltaX, float deltaY)
float(* wfsPixget)(void *, size_t)
sem_t m_imageSemaphore
Semaphore used to signal that an image is ready.
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_single)
static void wfsThreadStart(dmPokeWFS *s)
Thread starter, called by wfsThreadStart on thread construction. Calls wfsThreadExec.
sem_t m_wfsSemaphore
Semaphore used to signal the WFS thread to start WFSing.
float m_wfsFps
Pointer to a function to extract the image data as float.
float m_dmSleep
The time to sleep for the DM command to be applied, in microseconds. Default is 10000.
pcf::IndiProperty m_indiP_nPokeAverage
std::vector< int > m_poke_y
int processImage(void *curr_src, const darkShmimT &)
float(* darkPixget)(void *, size_t)
mx::improc::eigenImage< float > m_dmImage
int m_wfsThreadPrio
Priority of the WFS thread, should normally be > 00.
unsigned m_nPokeImages
The number of images to average for the poke images. Default is 5.
int appShutdown()
dmPokeWFS shutdown
pcf::IndiProperty m_wfsThreadProp
The property to hold the WFS thread details.
bool m_darkValid
Flag indicating if dark is valid based on its size.
mx::improc::milkImage< float > m_pokeImage
unsigned m_wfsSemWait_sec
The timeout for the WFS semaphore, seconds component.
pcf::IndiProperty m_indiP_single
Switch to start a single measurement.
pcf::IndiProperty m_indiP_measurement
Property to report the delta measurement, including the loop counter.
int basicTimedPoke(float pokeSign)
Apply a single DM poke pattern and record the results.
int allocate(const wfsShmimT &)
unsigned m_imageSemWait_sec
The timeout for the image semaphore, seconds component.
unsigned m_imageSemWait_nsec
The timeout for the image semaphore, nanoseconds component.
mx::improc::milkImage< float > m_dmStream
Pointer to a function to extract the dark image data as float.
double m_wfsSemWait
The time in sec to wait on the WFS semaphore. Default 0.5 sec.
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_nPokeImages)
unsigned m_nPokeAverage
The number of poke sequences to average. Default is 10.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
pcf::IndiProperty m_indiP_wfsFps
Property to get the FPS from the WFS camera.
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_poke_amp)
pcf::IndiProperty m_indiP_stop
Switch to request that measurement stop.
bool m_wfsThreadInit
Synchronizer to ensure wfs thread initializes before doing dangerous things.
pcf::IndiProperty m_indiP_nPokeImages
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_stop)
pid_t m_wfsThreadID
WFS thread PID.
int basicRunSensor()
Run the basic +/- poke sensor steps.
int allocate(const darkShmimT &)
int recordPokeLoop(bool force=false)
std::thread m_wfsThread
A separate thread for the actual WFSing.
int appStartup()
Startup function.
int m_measuring
Status of measuring: 0 no, 1 single in progress, 2 continuous in progress.
bool m_stopMeasurement
Used to request that the measurement in progress stop.
INDI_SETCALLBACK_DECL(derivedT, m_indiP_wfsFps)
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
std::string m_wfsCpuset
The cpuset for the framegrabber thread. Ignored if empty (the default).
int appLogic()
dmPokeWFS application logic
bool m_single
True a single measurement is in progress.
int recordTelem(const telem_pokeloop *)
mx::improc::eigenImage< float > m_darkImage
The dark image.
int processImage(void *curr_src, const wfsShmimT &)
pcf::IndiProperty m_indiP_continuous
Switch to start continuous measurement.
std::vector< int > m_poke_x
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define CREATE_REG_INDI_NEW_NUMBERI_DERIVED(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as int, using the standard callback name...
#define CREATE_REG_INDI_NEW_REQUESTSWITCH_DERIVED(prop, name)
Create and register a NEW INDI property as a standard request switch, using the standard callback nam...
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
#define CREATE_REG_INDI_NEW_TOGGLESWITCH_DERIVED(prop, name)
Create and register a NEW INDI property as a standard toggle switch, using the standard callback name...
#define CREATE_REG_INDI_NEW_NUMBERF_DERIVED(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as float, using the standard callback na...
@ OPERATING
The device is operating, other than homing.
@ READY
The device is ready for operation, but is not operating.
#define INDI_VALIDATE_CALLBACK_PROPS_DERIVED(prop1, prop2)
Standard check for matching INDI properties in a callback in a CRTP base class.
#define REG_INDI_SETPROP_DERIVED(prop, devName, propName)
#define INDI_IDLE
Definition indiUtils.hpp:27
#define INDI_OK
Definition indiUtils.hpp:28
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:28
XWC app semaphore Utilities to use CRTP derived classes.
#define XWC_SEM_FLUSH_DERIVED(sem)
#define XWC_SEM_WAIT_TS_RETVOID_DERIVED(ts, sec, nsec)
Add the wait time to a timespec for a sem_timedwait call, with no value returned on error,...
#define XWC_SEM_TIMEDWAIT_LOOP_DERIVED(sem, ts)
Perform a sem_timedwait in the context of a standard loop in MagAO-X code using the derived class.
#define XWC_SEM_WAIT_TS_DERIVED(ts, sec, nsec)
Add the wait time to a timespec for a sem_timedwait call, with -1 returned on error.