7 #ifndef sparkleClock_hpp
8 #define sparkleClock_hpp
10 #include <mx/improc/eigenCube.hpp>
11 #include <mx/ioutils/fits/fitsFile.hpp>
12 #include <mx/improc/eigenImage.hpp>
13 #include <mx/ioutils/stringUtils.hpp>
14 #include <mx/sys/timeUtils.hpp>
15 #include <mx/sigproc/fourierModes.hpp>
17 #include "../../libMagAOX/libMagAOX.hpp"
18 #include "../../magaox_git_version.h"
125 mx::app::appConfigurator &_config );
229 config.add(
"dm.channelName",
237 "The name of the DM channel to write to." );
238 config.add(
"dm.triggerChannel",
246 "The name of the DM channel to trigger on." );
247 config.add(
"dm.triggerSemaphore",
249 "dm.triggerSemaphore",
255 "The semaphore to use (default 9)." );
256 config.add(
"dm.trigger",
264 "Run in trigger mode if true (default)." );
265 config.add(
"dm.triggerDelay",
273 "Delay to apply to the trigger." );
275 config.add(
"dm.separation_1",
283 "The radial separation of the first set of speckles (default 10.0)." );
284 config.add(
"dm.separation_2",
292 "The radial separation of the first set of speckles (default 20.0)." );
293 config.add(
"dm.angle",
301 "The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)." );
302 config.add(
"dm.angleOffset",
310 "The calibration offset of angle so that up on camsci1/2 is 0." );
311 config.add(
"dm.amp",
319 "The speckle amplitude on the DM (default 0.01)." );
320 config.add(
"dm.cross",
328 "If true, also apply the cross speckles rotated by 90 degrees." );
330 config.add(
"dm.frequency",
338 "The frequency to modulate at if not triggering (default 2000 Hz)." );
340 config.add(
"dm.dwell",
348 "The dwell time for each speckle, or for how many frames it is held. Default=1." );
350 config.add(
"modulator.threadPrio",
352 "modulator.threadPrio",
358 "The real-time priority of the modulator thread." );
360 config.add(
"modulator.cpuset",
368 "The cpuset to assign the modulator thread to." );
382 if( _config.isSet(
"dm.trigger" ) )
391 _config(
m_angle,
"dm.angle" );
393 _config(
m_amp,
"dm.amp" );
395 if( _config.isSet(
"dm.cross" ) )
397 _config(
m_cross,
"dm.cross" );
401 _config(
m_dwell,
"dm.dwell" );
420 m_indiP_dm.add( pcf::IndiElement(
"channel" ) );
423 createStandardIndiNumber<float>(
m_indiP_delay,
"delay", 0, 0, 1,
"%f" );
438 createStandardIndiNumber<float>(
m_indiP_angle,
"angle", 0, 0, 100,
"%f" );
446 log<software_error>( { __FILE__, __LINE__ } );
458 createStandardIndiNumber<float>(
m_indiP_amp,
"amp", -1, 0, 1,
"%f" );
463 createStandardIndiNumber<float>(
m_indiP_frequency,
"frequency", 0, 0, 10000,
"%f" );
468 createStandardIndiNumber<float>(
m_indiP_interval,
"interval", 0, 0, 10000,
"%f" );
476 log<software_error>( { __FILE__, __LINE__ } );
488 createStandardIndiNumber<int>(
m_indiP_dwell,
"dwell", 1, 100, 1,
"%d" );
493 createStandardIndiNumber<int>(
m_indiP_single,
"single", -1, 3, 1,
"%d" );
501 log<software_error>( { __FILE__, __LINE__ } );
508 log<software_error>( { __FILE__, __LINE__ } );
522 log<software_critical>( { __FILE__, __LINE__ } );
597 log<software_error>( { __FILE__, __LINE__ } );
624 mx::improc::eigenImage<realT> onesp, onespC;
630 if( nFrames != _nFramesRound )
632 std::cerr <<
"Got nFrames = " << nFrames <<
" to be evenly divisible by 4, but integer rounding would give "
634 <<
" Hz frequency\n";
641 float nPositions = nFrames / 4 / 2;
642 float angleStep = mx::math::two_pi<realT>() / nPositions;
643 for(
int radii = 0; radii < 2; radii++ )
646 for(
int pos = 0; pos < nPositions; pos++ )
648 int baseIdx = ( nPositions * radii ) + pos;
649 int thisIdx = baseIdx;
654 mx::sigproc::makeFourierMode(
m_shapes.image( thisIdx ), m, n, 1 );
659 mx::sigproc::makeFourierMode(
m_shapes.image( thisIdx ), -n, m, 1 );
668 mx::sigproc::makeFourierMode(
m_shapes.image( thisIdx ), m, n, -1 );
673 mx::sigproc::makeFourierMode(
m_shapes.image( thisIdx ), -n, m, -1 );
682 mx::fits::fitsFile<realT> ff;
683 ff.write(
"/tmp/specks.fits",
m_shapes );
730 mx::sys::milliSleep( 500 );
746 bool triggered =
false;
747 sem_t *sem =
nullptr;
775 clock_gettime( CLOCK_REALTIME, &modstart );
777 unsigned dwelled = 0;
791 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
793 log<software_critical>( { __FILE__, __LINE__, errno, 0,
"clock_gettime" } );
799 if( sem_timedwait( sem, &ts ) == 0 )
801 t0 = mx::sys::get_curr_time();
804 while( t1 - t0 < triggerDelay )
806 double dt = ( 1e8 ) * ( triggerDelay -
811 t1 = mx::sys::get_curr_time();
827 if( errno != ETIMEDOUT )
829 log<software_error>( { __FILE__, __LINE__, errno,
"sem_timedwait" } );
837 clock_gettime( CLOCK_REALTIME, &currtime );
840 ( currtime.tv_sec - modstart.tv_sec ) * 1000000000 + ( currtime.tv_nsec - modstart.tv_nsec );
848 else if( dnsec >= freqNsec || triggered )
874 modstart.tv_nsec += freqNsec;
875 if( modstart.tv_nsec >= 1000000000 )
877 modstart.tv_nsec -= 1000000000;
878 modstart.tv_sec += 1;
890 clock_gettime( CLOCK_REALTIME, &currtime );
903 log<text_log>(
"zeroed" );
909 (
const pcf::IndiProperty &
ipRecv )
911 if(
ipRecv.getName() != m_indiP_trigger.getName() )
913 log<software_error>( { __FILE__, __LINE__,
"invalid indi property received" } );
917 if( !
ipRecv.find(
"toggle" ) )
920 std::unique_lock<std::mutex>
lock( m_indiMutex );
922 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
928 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
941 if(
ipRecv.getName() != m_indiP_delay.getName() )
943 log<software_error>( { __FILE__, __LINE__,
"wrong INDI property received." } );
947 float del = -1000000000;
949 if(
ipRecv.find(
"current" ) )
951 del =
ipRecv[
"current"].get<
float>();
954 if(
ipRecv.find(
"target" ) )
956 del =
ipRecv[
"target"].get<
float>();
959 if( del == -1000000000 )
961 log<software_error>( { __FILE__, __LINE__,
"No requested delay" } );
965 std::unique_lock<std::mutex>
lock( m_indiMutex );
966 m_triggerDelay = del;
975 if(
ipRecv.getName() != m_indiP_separation_1.getName() )
977 log<software_error>( { __FILE__, __LINE__,
"wrong INDI property received." } );
981 float sep = -1000000000;
983 if(
ipRecv.find(
"current" ) )
985 sep =
ipRecv[
"current"].get<
float>();
988 if(
ipRecv.find(
"target" ) )
990 sep =
ipRecv[
"target"].get<
float>();
993 if( sep == -1000000000 )
995 log<software_error>( { __FILE__, __LINE__,
"No requested separation_1" } );
999 std::unique_lock<std::mutex>
lock( m_indiMutex );
1000 m_separation_1 = sep;
1010 if(
ipRecv.getName() != m_indiP_separation_2.getName() )
1012 log<software_error>( { __FILE__, __LINE__,
"wrong INDI property received." } );
1016 float sep = -1000000000;
1018 if(
ipRecv.find(
"current" ) )
1020 sep =
ipRecv[
"current"].get<
float>();
1023 if(
ipRecv.find(
"target" ) )
1025 sep =
ipRecv[
"target"].get<
float>();
1028 if( sep == -1000000000 )
1030 log<software_error>( { __FILE__, __LINE__,
"No requested separation_2" } );
1034 std::unique_lock<std::mutex>
lock( m_indiMutex );
1035 m_separation_2 = sep;
1044 (
const pcf::IndiProperty &
ipRecv )
1046 if(
ipRecv.getName() != m_indiP_angle.getName() )
1048 log<software_error>( { __FILE__, __LINE__,
"wrong INDI property received." } );
1052 float ang = -1000000000;
1054 if(
ipRecv.find(
"current" ) )
1056 ang =
ipRecv[
"current"].get<
float>();
1059 if(
ipRecv.find(
"target" ) )
1061 ang =
ipRecv[
"target"].get<
float>();
1064 if( ang == -1000000000 )
1066 log<software_error>( { __FILE__, __LINE__,
"No angle received" } );
1070 std::unique_lock<std::mutex>
lock( m_indiMutex );
1079 (
const pcf::IndiProperty &
ipRecv )
1081 if(
ipRecv.getName() != m_indiP_amp.getName() )
1083 log<software_error>( { __FILE__, __LINE__,
"wrong INDI property received." } );
1087 float amp = -1000000000;
1089 if(
ipRecv.find(
"current" ) )
1091 amp =
ipRecv[
"current"].get<
float>();
1094 if(
ipRecv.find(
"target" ) )
1096 amp =
ipRecv[
"target"].get<
float>();
1099 if( amp == -1000000000 )
1101 log<software_error>( { __FILE__, __LINE__,
"Invalid requested amp: " + std::to_string( amp ) } );
1105 std::unique_lock<std::mutex>
lock( m_indiMutex );
1114 (
const pcf::IndiProperty &
ipRecv )
1116 if(
ipRecv.createUniqueKey() != m_indiP_cross.createUniqueKey() )
1118 log<software_error>( { __FILE__, __LINE__,
"invalid indi property received" } );
1122 if( !
ipRecv.find(
"toggle" ) )
1125 std::unique_lock<std::mutex>
lock( m_indiMutex );
1127 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
1133 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1145 (
const pcf::IndiProperty &
ipRecv )
1147 if(
ipRecv.getName() != m_indiP_frequency.getName() )
1149 log<software_error>( { __FILE__, __LINE__,
"wrong INDI property received." } );
1155 if(
ipRecv.find(
"current" ) )
1157 freq =
ipRecv[
"current"].get<
float>();
1160 if(
ipRecv.find(
"target" ) )
1162 freq =
ipRecv[
"target"].get<
float>();
1167 log<software_error>( { __FILE__, __LINE__,
"Invalid requested frequency: " + std::to_string( freq ) } );
1171 std::unique_lock<std::mutex>
lock( m_indiMutex );
1181 (
const pcf::IndiProperty &
ipRecv )
1183 if(
ipRecv.getName() != m_indiP_interval.getName() )
1185 log<software_error>( { __FILE__, __LINE__,
"wrong INDI property received." } );
1189 float interval = -1;
1191 if(
ipRecv.find(
"current" ) )
1193 interval =
ipRecv[
"current"].get<
float>();
1196 if(
ipRecv.find(
"target" ) )
1198 interval =
ipRecv[
"target"].get<
float>();
1203 log<software_error>( { __FILE__, __LINE__,
"Invalid requested interval: " + std::to_string( interval ) } );
1207 std::unique_lock<std::mutex>
lock( m_indiMutex );
1217 (
const pcf::IndiProperty &
ipRecv )
1219 if(
ipRecv.createUniqueKey() != m_indiP_dwell.createUniqueKey() )
1221 log<software_error>( { __FILE__, __LINE__,
"wrong INDI property received." } );
1227 if(
ipRecv.find(
"current" ) )
1229 dwell =
ipRecv[
"current"].get<
unsigned>();
1232 if(
ipRecv.find(
"target" ) )
1234 dwell =
ipRecv[
"target"].get<
unsigned>();
1239 log<software_error>( { __FILE__, __LINE__,
"Invalid requested dwell: " + std::to_string( dwell ) } );
1243 std::unique_lock<std::mutex>
lock( m_indiMutex );
1253 (
const pcf::IndiProperty &
ipRecv )
1259 if(
ipRecv.find(
"current" ) )
1264 if(
ipRecv.find(
"target" ) )
1269 if( single < -1 || single > 3 )
1271 log<software_error>( { __FILE__, __LINE__,
"Invalid requested dwell: " + std::to_string(
single ) } );
1275 std::unique_lock<std::mutex>
lock( m_indiMutex );
1282 (
const pcf::IndiProperty &
ipRecv )
1284 if(
ipRecv.getName() != m_indiP_modulating.getName() )
1286 log<software_error>( { __FILE__, __LINE__,
"invalid indi property received" } );
1290 if( !
ipRecv.find(
"toggle" ) )
1293 std::unique_lock<std::mutex>
lock( m_indiMutex );
1295 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
1297 m_modulating =
false;
1301 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1303 m_modulating =
true;
1311 (
const pcf::IndiProperty &
ipRecv )
1313 if(
ipRecv.getName() != m_indiP_zero.getName() )
1315 log<software_error>( { __FILE__, __LINE__,
"invalid indi property received" } );
1319 if( m_modulating ==
true )
1325 if( !
ipRecv.find(
"request" ) )
1328 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
1330 m_imageStream.md->write = 1;
1332 memset( m_imageStream.array.raw, 0, m_width * m_height * m_typeSize );
1334 clock_gettime( CLOCK_REALTIME, &currtime );
1335 m_imageStream.md->atime = currtime;
1336 m_imageStream.md->writetime = currtime;
1338 m_imageStream.md->cnt0++;
1340 m_imageStream.md->write = 0;
1341 ImageStreamIO_sempost( &m_imageStream, -1 );
1342 log<text_log>(
"zeroed" );
1366 static float lastAngle =
m_angle;
1367 static float lastAmp =
m_amp;
1368 static bool lastCross =
m_cross;
1372 !( lastCross ==
m_cross ) || force )
The base-class for MagAO-X applications.
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
int createStandardIndiRequestSw(pcf::IndiProperty &prop, const std::string &name, const std::string &label="", const std::string &group="")
Create a standard R/W INDI switch with a single request element.
stateCodes::stateCodeT state()
Get the current state code.
int registerIndiPropertyNew(pcf::IndiProperty &prop, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for.
int createStandardIndiToggleSw(pcf::IndiProperty &prop, const std::string &name, const std::string &label="", const std::string &group="")
Create a standard R/W INDI switch with a single toggle element.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
int threadStart(std::thread &thrd, bool &thrdInit, pid_t &tpid, pcf::IndiProperty &thProp, int thrdPrio, const std::string &cpuset, const std::string &thrdName, thisPtr *thrdThis, Function &&thrdStart)
Start a thread, using this class's privileges to set priority, etc.
The MagAO-X DM mode commander.
std::thread m_modThread
A separate thread for the modulation.
pcf::IndiProperty m_indiP_separation_1
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_amp)
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_angle)
realT m_separation_2
The radial separation of the second set of speckles (default 20.0)
pcf::IndiProperty m_indiP_cross
pcf::IndiProperty m_indiP_dwell
~sparkleClock() noexcept
D'tor, declared and defined for noexcept.
std::string m_modThreadCpuset
The cpuset for the modulator thread.
size_t m_typeSize
The size of the type, in bytes.
pcf::IndiProperty m_indiP_interval
virtual void setupConfig()
realT m_amp
The speckle amplitude on the DM.
pcf::IndiProperty m_indiP_dm
virtual void loadConfig()
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_delay)
pid_t m_modThreadID
Modulate thread PID.
pcf::IndiProperty m_indiP_angle
realT m_interval
time for one complete cycle of the sparkle clock (e.g. exposure time of some camera) (default 1....
pcf::IndiProperty m_indiP_zero
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_dwell)
realT m_frequency
The frequency to modulate at if not triggering (default 2000 Hz)
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_cross)
friend class sparkleClock_test
int m_modThreadPrio
Priority of the modulator thread, should normally be > 00.
virtual int appLogic()
Implementation of the FSM for sparkleClock.
std::string m_dmTriggerChannel
The DM channel to monitor as a trigger.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_separation_1)
uint32_t m_width
The width of the image.
pcf::IndiProperty m_indiP_amp
realT m_sparkleClockInterval
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_single)
pcf::IndiProperty m_indiP_modulating
static void modThreadStart(sparkleClock *d)
Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
int recordTelem(const telem_sparkleclock *)
virtual int appShutdown()
Shutdown the app.
int m_single
if >= 0 a single frame is non-zero.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_trigger)
unsigned m_dwell
The dwell time for each speckle, or for how many frames it is held.
bool m_cross
If true, also apply the cross speckles rotated by 90 degrees.
bool m_trigger
Run in trigger mode if true (default)
realT m_angleOffset
The calibration offset of angle so that up on camsci1/2 is 0.
void modThreadExec()
Execute the frame grabber main loop.
uint32_t m_height
The height of the image.
pcf::IndiProperty m_indiP_single
pcf::IndiProperty m_modThreadProp
The property to hold the modulator thread details.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_modulating)
int m_triggerSemaphore
The semaphore to use (default 9)
int recordSparkleClock(bool force=false)
pcf::IndiProperty m_indiP_trigger
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_frequency)
realT m_separation_1
The radial separation of the first set of speckles (default 10.0)
virtual int appStartup()
Startup function.
uint8_t m_dataType
The ImageStreamIO type code.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_separation_2)
realT m_angle
The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_zero)
mx::improc::eigenCube< realT > m_shapes
bool m_modThreadInit
Synchronizer to ensure f.g. thread initializes before doing dangerous things.
sparkleClock()
Default c'tor.
pcf::IndiProperty m_indiP_separation_2
int generateSparkleClock()
std::string m_dmName
The descriptive name of this dm. Default is the channel name.
pcf::IndiProperty m_indiP_frequency
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_interval)
pcf::IndiProperty m_indiP_delay
std::string m_dmChannelName
The name of the DM channel to write to.
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
@ READY
The device is ready for operation, but is not operating.
@ CONNECTED
The application has connected to the device or service.
@ NOTCONNECTED
The application is not connected to the device or service.
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
INDI_VALIDATE_CALLBACK_PROPS(function, ipRecv)
void nanoSleep(unsigned long nsec)
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
std::unique_lock< std::mutex > lock(m_indiMutex)
constexpr static logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
constexpr static logPrioT LOG_NOTICE
A normal but significant condition.
A device base class which saves telemetry.
int appShutdown()
Perform telemeter application shutdown.
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
int appLogic()
Perform telemeter application logic.
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Log entry recording sparkle clock status.
A simple text log, a string-type log.