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