10#include <mx/improc/eigenImage.hpp>
11#include <mx/improc/milkImage.hpp>
12#include <mx/improc/eigenCube.hpp>
13using namespace mx::improc;
15#include "../../ImageStreamIO/pixaccess.hpp"
135template<
class derivedT>
428 return *
static_cast<derivedT *
>(
this);
433template<
class derivedT>
438 derivedT::template log<software_error>({__FILE__, __LINE__,
"shmimMonitorT::setupConfig"});
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");
446 if(derived().darkShmimMonitor().setupConfig(config) < 0)
448 derivedT::template log<software_error>({__FILE__, __LINE__,
"darkShmimMonitorT::setupConfig"});
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.");
465template<
class derivedT>
470 return derivedT::template log<software_error, -1>({__FILE__, __LINE__,
"shmimMonitorT::loadConfig"});
473 m_wfsCamDevName = derived().shmimMonitor().shmimName();
474 config(m_wfsCamDevName,
"wfscam.camDevName");
477 config(m_wfsSemWait,
"wfscam.loopSemWait");
479 m_wfsSemWait_sec = floor(m_wfsSemWait);
480 m_wfsSemWait_nsec = (m_wfsSemWait - m_wfsSemWait_sec) * 1e9;
482 config(m_imageSemWait,
"wfscam.imageSemWait");
484 m_imageSemWait_sec = floor(m_imageSemWait);
485 m_imageSemWait_nsec = (m_imageSemWait - m_imageSemWait_sec) * 1e9;
487 if(derived().darkShmimMonitor().loadConfig(config) < 0)
489 return derivedT::template log<software_error, -1>({__FILE__, __LINE__,
"darkShmimMonitorT::loadConfig"});
492 config(m_dmChan,
"pokecen.dmChannel");
494 config(m_poke_x,
"pokecen.pokeX");
496 config(m_poke_y,
"pokecen.pokeY");
498 if(m_poke_x.size() == 0 || (m_poke_x.size() != m_poke_y.size()))
500 return derivedT::template log<software_error,-1>({__FILE__, __LINE__,
"invalid poke specification"});
503 config(m_poke_amp,
"pokecen.pokeAmp");
505 config(m_dmSleep,
"pokecen.dmSleep");
507 config(m_nPokeImages,
"pokecen.nPokeImages");
509 config(m_nPokeAverage,
"pokecen.nPokeAverage");
514template<
class derivedT>
519 return derivedT::template log<software_error, -1>({__FILE__,__LINE__});
522 if( derived().darkShmimMonitor().appStartup() < 0)
524 return derivedT::template log<software_error, -1>({__FILE__,__LINE__});
528 m_indiP_poke_amp[
"current"].setValue(m_poke_amp);
529 m_indiP_poke_amp[
"target"].setValue(m_poke_amp);
532 m_indiP_nPokeImages[
"current"].setValue(m_nPokeImages);
533 m_indiP_nPokeImages[
"target"].setValue(m_nPokeImages);
536 m_indiP_nPokeAverage[
"current"].setValue(m_nPokeAverage);
537 m_indiP_nPokeAverage[
"target"].setValue(m_nPokeAverage);
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});
552 if(sem_init(&m_wfsSemaphore, 0,0) < 0)
554 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno,0,
"Initializing wfs semaphore"});
557 if(sem_init(&m_imageSemaphore, 0,0) < 0)
559 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno,0,
"Initializing image semaphore"});
562 if(derived().template threadStart( m_wfsThread, m_wfsThreadInit, m_wfsThreadID, m_wfsThreadProp, m_wfsThreadPrio, m_wfsCpuset,
"wfs",
this, wfsThreadStart) < 0)
564 return derivedT::template log<software_critical,-1>({__FILE__, __LINE__});
570template<
class derivedT>
576 return derivedT::template log<software_error, -1>({__FILE__,__LINE__});
579 if( derived().darkShmimMonitor().appLogic() < 0)
581 return derivedT::template log<software_error, -1>({__FILE__,__LINE__});
588 if(pthread_tryjoin_np(m_wfsThread.native_handle(),0) == 0)
590 derivedT::template log<software_error>({__FILE__, __LINE__,
"WFS thread has exited"});
596 derivedT::template log<software_error>({__FILE__, __LINE__,
"WFS thread has exited"});
604 derived().template updateSwitchIfChanged(m_indiP_continuous,
"toggle", pcf::IndiElement::SwitchStateType::On,
INDI_OK);
608 derived().template updateSwitchIfChanged(m_indiP_continuous,
"toggle", pcf::IndiElement::SwitchStateType::Off,
INDI_IDLE);
613 derived().template updateSwitchIfChanged(m_indiP_single,
"toggle", pcf::IndiElement::SwitchStateType::On,
INDI_OK);
617 derived().template updateSwitchIfChanged(m_indiP_single,
"toggle", pcf::IndiElement::SwitchStateType::Off,
INDI_IDLE);
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);
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);
633template<
class derivedT>
638 derivedT::template log<software_error>({__FILE__, __LINE__,
"error from shmimMonitorT::appShutdown"});
641 if(derived().darkShmimMonitor().appShutdown() < 0)
643 derivedT::template log<software_error>({__FILE__, __LINE__,
"error from darkShmimMonitorT::appShutdown"});
646 if (m_wfsThread.joinable())
648 pthread_kill(m_wfsThread.native_handle(), SIGUSR1);
661template<
class derivedT>
664 static_cast<void>(dummy);
666 std::unique_lock<std::mutex>
lock(m_wfsImageMutex);
668 m_rawImage.create( derived().m_configName +
"_raw", derived().
shmimMonitor().width(), derived().
shmimMonitor().height());
670 wfsPixget = getPixPointer<float>(derived().
shmimMonitor().dataType());
674 m_dmStream.open(m_dmChan);
676 catch(
const std::exception& e)
678 return derivedT::template log<software_error,-1>({__FILE__, __LINE__, std::string(
"exception opening DM: ") + e.what()});
681 m_dmImage.resize(m_dmStream.rows(), m_dmStream.cols());
683 if(derived().darkShmimMonitor().width() == derived().shmimMonitor().width() &&
684 derived().darkShmimMonitor().height() == derived().shmimMonitor().height() )
693 if(m_pokeImage.rows() != derived().shmimMonitor().width() || m_pokeImage.cols() != derived().shmimMonitor().height())
695 m_pokeImage.create(derived().m_configName +
"_poke", derived().shmimMonitor().width(), derived().shmimMonitor().height());
698 m_pokeLocal.resize(derived().shmimMonitor().width(), derived().shmimMonitor().height());
703template<
class derivedT>
708 static_cast<void>(dummy);
710 std::unique_lock<std::mutex>
lock(m_wfsImageMutex);
712 float * data = m_rawImage().data();
713 float * darkData = m_darkImage.data();
716 uint64_t Npix = derived().shmimMonitor().width()*derived().shmimMonitor().height();
720 for(
unsigned nn=0; nn < Npix; ++nn)
722 data[nn] = wfsPixget(curr_src, nn) - darkData[nn];
727 for(
unsigned nn=0; nn < Npix; ++nn)
729 data[nn] = wfsPixget(curr_src, nn);
733 if(sem_post(&m_imageSemaphore) < 0)
735 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
743template<
class derivedT>
746 static_cast<void>(dummy);
748 std::unique_lock<std::mutex>
lock(m_wfsImageMutex);
750 m_darkImage.resize(derived().darkShmimMonitor().width(), derived().darkShmimMonitor().height());
752 darkPixget = getPixPointer<float>(derived().darkShmimMonitor().dataType());
754 if(derived().darkShmimMonitor().width() == derived().
shmimMonitor().width() &&
755 derived().darkShmimMonitor().height() == derived().
shmimMonitor().height() )
757 std::cerr <<
"dark is valid " << derived().darkShmimMonitor().width() <<
" " << derived().shmimMonitor().width() <<
" ";
758 std::cerr << derived().darkShmimMonitor().height() <<
" " << derived().shmimMonitor().height() <<
"\n";
769template<
class derivedT>
774 static_cast<void>(dummy);
776 std::unique_lock<std::mutex>
lock(m_wfsImageMutex);
778 float * darkData = m_darkImage.data();
781 uint64_t nPix = derived().darkShmimMonitor().width()*derived().darkShmimMonitor().height();
782 for(
unsigned nn=0; nn < nPix; ++nn)
784 darkData[nn] = darkPixget(curr_src, nn);
790template<
class derivedT>
796template<
class derivedT>
799 m_wfsThreadID = syscall(SYS_gettid);
802 while(m_wfsThreadInit ==
true && derived().m_shutdown == 0)
807 while(derived().m_shutdown == 0)
819 else if(m_continuous)
831 while(!m_pokeImage.valid())
833 mx::sys::milliSleep(10);
836 m_stopMeasurement =
false;
838 bool firstRun =
true;
840 while(!m_stopMeasurement && !derived().m_shutdown)
842 if( derived().runSensor(firstRun) < 0)
844 derivedT::template log<software_error>({__FILE__, __LINE__,
"runSensor returned error"});
848 if(m_stopMeasurement || derived().m_shutdown)
853 if( derived().analyzeSensor() < 0)
855 derivedT::template log<software_error>({__FILE__, __LINE__,
"runSensor returned error"});
860 derived().updateIfChanged(m_indiP_measurement,
"counter", m_counter);
861 derived().recordPokeLoop();
884template<
class derivedT>
890 if(pokeSign < 0) sign = -1;
895 for(
size_t nn = 0; nn < m_poke_x.size(); ++nn)
897 m_dmImage( m_poke_x[nn], m_poke_y[nn]) = pokeSign*m_poke_amp;
901 m_dmStream = m_dmImage;
903 mx::sys::microSleep(m_dmSleep);
911 while(!ready && !(m_stopMeasurement || derived().m_shutdown))
921 while(n < m_nPokeImages && !(m_stopMeasurement || derived().m_shutdown))
928 m_pokeLocal += sign*m_rawImage();
933 if(m_stopMeasurement || derived().m_shutdown)
936 m_dmStream = m_dmImage;
943template<
class derivedT>
948 if(!m_pokeImage.valid())
950 return derivedT::template log<software_error,-1>({__FILE__, __LINE__,
"poke image is not allocated"});
953 m_pokeLocal.setZero();
955 for(
unsigned nseq = 0; nseq < m_nPokeAverage; ++nseq)
960 rv = basicTimedPoke(+1);
964 derivedT::template log<software_error>({__FILE__, __LINE__});
972 if(m_stopMeasurement || derived().m_shutdown)
979 rv = basicTimedPoke(-1);
983 derivedT::template log<software_error>({__FILE__, __LINE__});
991 if(m_stopMeasurement || derived().m_shutdown)
999 m_pokeImage = m_pokeLocal/(2.0*m_nPokeImages*m_nPokeAverage);
1001 catch(
const std::exception& e)
1003 return derivedT::template log<software_error,-1>({__FILE__, __LINE__, e.what()});
1008 m_dmImage.setZero();
1009 m_dmStream = m_dmImage;
1014template<
class derivedT>
1021 m_indiP_measurement[
"delta_x"] = deltaX;
1022 m_indiP_measurement[
"delta_y"] = deltaY;
1027template<
class derivedT>
1034 if( derived().
template indiTargetUpdate(m_indiP_nPokeImages, target,
ipRecv,
false) < 0)
1036 return derivedT::template log<software_error,-1>({__FILE__, __LINE__});
1039 m_nPokeImages = target;
1044template<
class derivedT>
1051 if( derived().
template indiTargetUpdate(m_indiP_nPokeAverage, target,
ipRecv,
false) < 0)
1053 return derivedT::template log<software_error,-1>({__FILE__, __LINE__});
1056 m_nPokeAverage = target;
1061template<
class derivedT>
1068 if( derived().
template indiTargetUpdate(m_indiP_poke_amp, target,
ipRecv,
false) < 0)
1070 return derivedT::template log<software_error,-1>({__FILE__, __LINE__});
1073 m_poke_amp = target;
1078template<
class derivedT>
1083 if(
ipRecv.find(
"current") !=
true )
1088 m_wfsFps =
ipRecv[
"current"].get<
float>();
1093template<
class derivedT>
1098 if(
ipRecv.find(
"toggle") !=
true )
1103 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1105 if(m_measuring == 0)
1109 if(sem_post(&m_wfsSemaphore) < 0)
1111 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
1119template<
class derivedT>
1124 if(
ipRecv.find(
"toggle") !=
true )
1129 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1131 if(m_measuring == 0)
1135 if(sem_post(&m_wfsSemaphore) < 0)
1137 return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
1141 else if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
1143 if(m_measuring != 0)
1145 m_stopMeasurement =
true;
1152template<
class derivedT>
1157 if(
ipRecv.find(
"request") !=
true )
1162 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
1164 if(m_measuring != 0)
1166 m_stopMeasurement =
true;
1173template<
class derivedT>
1176 return recordPokeLoop(
true);
1179template<
class derivedT>
1182 static int measuring = -1;
1183 static float deltaX = std::numeric_limits<float>::max();
1184 static float deltaY = std::numeric_limits<float>::max();
1185 static uint64_t counter = std::numeric_limits<uint64_t>::max();
1187 if(force || (m_counter != counter) || (m_deltaX != deltaX) || (m_deltaY != deltaY) || (m_measuring != measuring))
1189 uint8_t meas = m_measuring;
1190 derived().template telem<telem_pokeloop>({meas, m_deltaX, m_deltaY, m_counter});
1192 measuring = m_measuring;
1195 counter = m_counter;
1205#define DMPOKEWFS_SETUP_CONFIG( cfig ) \
1206 if(dmPokeWFST::setupConfig(cfig) < 0) \
1208 log<software_error>({__FILE__, __LINE__, "Error from dmPokeWFST::setupConfig"}); \
1209 m_shutdown = true; \
1217#define DMPOKEWFS_LOAD_CONFIG( cfig ) \
1218 if(dmPokeWFST::loadConfig(cfig) < 0) \
1220 return log<software_error,-1>({__FILE__, __LINE__, "Error from dmPokeWFST::loadConfig"}); \
1224#define DMPOKEWFS_APP_STARTUP \
1225 if( dmPokeWFST::appStartup() < 0) \
1227 return log<software_error, -1>({__FILE__,__LINE__}); \
1231#define DMPOKEWFS_APP_LOGIC \
1232 if( dmPokeWFST::appLogic() < 0) \
1234 return log<software_error, -1>({__FILE__,__LINE__}); \
1238#define DMPOKEWFS_APP_SHUTDOWN \
1239 if(dmPokeWFST::appShutdown() < 0) \
1241 log<software_error>({__FILE__, __LINE__, "error from dmPokeWFST::appShutdown"}); \
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 &)
std::mutex m_wfsImageMutex
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)
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
#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.
static std::string indiPrefix()
static std::string configSection()
static std::string configSection()
static std::string indiPrefix()