API
 
Loading...
Searching...
No Matches
sparkleClock.hpp
Go to the documentation of this file.
1/** \file sparkleClock.hpp
2 * \brief The MagAO-X DM speckle maker header file
3 *
4 * \ingroup sparkleClock_files
5 */
6
7#ifndef sparkleClock_hpp
8#define sparkleClock_hpp
9
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>
16
17#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
18#include "../../magaox_git_version.h"
19
20/** \defgroup sparkleClock
21 * \brief The DM speckle maker app
22 *
23 * Creates a set of fourier modes to generate speckles, then applies them to a DM channel
24 * at a specified rate.
25 *
26 *
27 * <a href="../handbook/operating/software/apps/sparkleClock.html">Application Documentation</a>
28 *
29 * \ingroup apps
30 *
31 */
32
33/** \defgroup sparkleClock_files
34 * \ingroup sparkleClock
35 */
36
37namespace MagAOX
38{
39namespace app
40{
41
42/// The MagAO-X DM mode commander
43/**
44 * \ingroup sparkleClock
45 */
46class sparkleClock : public MagAOXApp<true>, public dev::telemeter<sparkleClock>
47{
48
49 typedef float realT;
50
51 friend class dev::telemeter<sparkleClock>;
52 friend class sparkleClock_test;
53
54 protected:
55 /** \name Configurable Parameters
56 *@{
57 */
58
59 std::string m_dmName; ///< The descriptive name of this dm. Default is the channel name.
60
61 std::string m_dmChannelName; ///< The name of the DM channel to write to.
62
63 std::string m_dmTriggerChannel; ///< The DM channel to monitor as a trigger
64
65 float m_triggerDelay{ 0 }; // 0.000375- 0.5/2000.;
66
67 int m_triggerSemaphore{ 9 }; ///< The semaphore to use (default 9)
68
69 bool m_trigger{ true }; ///< Run in trigger mode if true (default)
70
71 realT m_interval{ 1.0 }; ///< time for one complete cycle of the sparkle clock (e.g. exposure time of some camera) (default 1.0)
72 realT m_separation_1{ 10.0 }; ///< The radial separation of the first set of speckles (default 10.0)
73 realT m_separation_2{ 20.0 }; ///< The radial separation of the second set of speckles (default 20.0)
74
75 realT m_angle{ 0.0 }; ///< The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)
76
77 realT m_angleOffset{ 28.0 }; ///< The calibration offset of angle so that up on camsci1/2 is 0
78
79 realT m_amp{ 0.01 }; ///< The speckle amplitude on the DM
80
81 bool m_cross{ true }; ///< If true, also apply the cross speckles rotated by 90 degrees
82
83 realT m_frequency{ 2000 }; ///< The frequency to modulate at if not triggering (default 2000 Hz)
84 realT m_sparkleClockInterval{ 1 }; ///< The time in seconds during which the sparkle clock should sweep out both
85 ///< cycles (e.g. the exposure time of your science cam)
86
87 unsigned m_dwell{ 1 }; ///< The dwell time for each speckle, or for how many frames it is held.
88
89 int m_single{ -1 }; ///< if >= 0 a single frame is non-zero.
90 ///@}
91
92 mx::improc::eigenCube<realT> m_shapes;
93
95 uint32_t m_width{ 0 }; ///< The width of the image
96 uint32_t m_height{ 0 }; ///< The height of the image.
97
99
100 uint8_t m_dataType{ 0 }; ///< The ImageStreamIO type code.
101 size_t m_typeSize{ 0 }; ///< The size of the type, in bytes.
102
103 bool m_opened{ true };
104 bool m_restart{ false };
105
106 bool m_modulating{ false };
107
108 bool m_restartSp{ false };
109
110 public:
111 /// Default c'tor.
112 sparkleClock();
113
114 /// D'tor, declared and defined for noexcept.
116 {
117 }
118
119 virtual void setupConfig();
120
121 /// Implementation of loadConfig logic, separated for testing.
122 /** This is called by loadConfig().
123 */
124 int loadConfigImpl(
125 mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
126
127 virtual void loadConfig();
128
129 /// Startup function
130 /**
131 *
132 */
133 virtual int appStartup();
134
135 /// Implementation of the FSM for sparkleClock.
136 /**
137 * \returns 0 on no critical error
138 * \returns -1 on an error requiring shutdown
139 */
140 virtual int appLogic();
141
142 /// Shutdown the app.
143 /**
144 *
145 */
146 virtual int appShutdown();
147
148 protected:
150
151 /** \name Modulator Thread
152 * This thread sends the signal to the dm at the prescribed frequency
153 *
154 * @{
155 */
156 int m_modThreadPrio{ 60 }; ///< Priority of the modulator thread, should normally be > 00.
157
158 std::string m_modThreadCpuset; ///< The cpuset for the modulator thread.
159
160 std::thread m_modThread; ///< A separate thread for the modulation
161
162 bool m_modThreadInit{ true }; ///< Synchronizer to ensure f.g. thread initializes before doing dangerous things.
163
164 pid_t m_modThreadID{ 0 }; ///< Modulate thread PID.
165
166 pcf::IndiProperty m_modThreadProp; ///< The property to hold the modulator thread details.
167
168 /// Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
169 static void modThreadStart( sparkleClock *d /**< [in] a pointer to a sparkleClock instance (normally this) */ );
170
171 /// Execute the frame grabber main loop.
172 void modThreadExec();
173
174 ///@}
175
176 // INDI:
177 protected:
178 // declare our properties
179 pcf::IndiProperty m_indiP_dm;
180 pcf::IndiProperty m_indiP_trigger;
181 pcf::IndiProperty m_indiP_delay;
182 pcf::IndiProperty m_indiP_separation_1;
183 pcf::IndiProperty m_indiP_separation_2;
184 pcf::IndiProperty m_indiP_angle;
185 pcf::IndiProperty m_indiP_amp;
186 pcf::IndiProperty m_indiP_cross;
187 pcf::IndiProperty m_indiP_frequency;
188 pcf::IndiProperty m_indiP_interval;
189 pcf::IndiProperty m_indiP_dwell;
190 pcf::IndiProperty m_indiP_single;
191 pcf::IndiProperty m_indiP_modulating;
192 pcf::IndiProperty m_indiP_zero;
193
194 public:
208
209 /** \name Telemeter Interface
210 *
211 * @{
212 */
213 int checkRecordTimes();
214
215 int recordTelem( const telem_sparkleclock * );
216
217 int recordSparkleClock( bool force = false );
218
219 ///@}
220};
221
222sparkleClock::sparkleClock() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
223{
224 return;
225}
226
228{
229 config.add( "dm.channelName",
230 "",
231 "dm.channelName",
232 argType::Required,
233 "dm",
234 "channelName",
235 false,
236 "string",
237 "The name of the DM channel to write to." );
238 config.add( "dm.triggerChannel",
239 "",
240 "dm.triggerChannel",
241 argType::Required,
242 "dm",
243 "triggerChannel",
244 false,
245 "string",
246 "The name of the DM channel to trigger on." );
247 config.add( "dm.triggerSemaphore",
248 "",
249 "dm.triggerSemaphore",
250 argType::Required,
251 "dm",
252 "triggerSemaphore",
253 false,
254 "int",
255 "The semaphore to use (default 9)." );
256 config.add( "dm.trigger",
257 "",
258 "dm.trigger",
259 argType::True,
260 "dm",
261 "trigger",
262 false,
263 "bool",
264 "Run in trigger mode if true (default)." );
265 config.add( "dm.triggerDelay",
266 "",
267 "dm.triggerDelay",
268 argType::Required,
269 "dm",
270 "triggerDelay",
271 false,
272 "float",
273 "Delay to apply to the trigger." );
274
275 config.add( "dm.separation_1",
276 "",
277 "dm.separation_1",
278 argType::Required,
279 "dm",
280 "separation",
281 false,
282 "float",
283 "The radial separation of the first set of speckles (default 10.0)." );
284 config.add( "dm.separation_2",
285 "",
286 "dm.separation_2",
287 argType::Required,
288 "dm",
289 "separation",
290 false,
291 "float",
292 "The radial separation of the first set of speckles (default 20.0)." );
293 config.add( "dm.angle",
294 "",
295 "dm.angle",
296 argType::Required,
297 "dm",
298 "angle",
299 false,
300 "float",
301 "The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)." );
302 config.add( "dm.angleOffset",
303 "",
304 "dm.angleOffset",
305 argType::Required,
306 "dm",
307 "angleOffset",
308 false,
309 "float",
310 "The calibration offset of angle so that up on camsci1/2 is 0." );
311 config.add( "dm.amp",
312 "",
313 "dm.amp",
314 argType::Required,
315 "dm",
316 "amp",
317 false,
318 "float",
319 "The speckle amplitude on the DM (default 0.01)." );
320 config.add( "dm.cross",
321 "",
322 "dm.cross",
323 argType::True,
324 "dm",
325 "cross",
326 false,
327 "bool",
328 "If true, also apply the cross speckles rotated by 90 degrees." );
329
330 config.add( "dm.frequency",
331 "",
332 "dm.frequency",
333 argType::Required,
334 "dm",
335 "frequency",
336 false,
337 "float",
338 "The frequency to modulate at if not triggering (default 2000 Hz)." );
339
340 config.add( "dm.dwell",
341 "",
342 "dm.dwell",
343 argType::True,
344 "dm",
345 "dwell",
346 false,
347 "int",
348 "The dwell time for each speckle, or for how many frames it is held. Default=1." );
349
350 config.add( "modulator.threadPrio",
351 "",
352 "modulator.threadPrio",
353 argType::Required,
354 "modulator",
355 "threadPrio",
356 false,
357 "int",
358 "The real-time priority of the modulator thread." );
359
360 config.add( "modulator.cpuset",
361 "",
362 "modulator.cpuset",
363 argType::Required,
364 "modulator",
365 "cpuset",
366 false,
367 "string",
368 "The cpuset to assign the modulator thread to." );
369}
370
371int sparkleClock::loadConfigImpl( mx::app::appConfigurator &_config )
372{
373 _config( m_dmChannelName, "dm.channelName" );
374
376 _config( m_dmName, "dm.name" );
377
378 _config( m_dmTriggerChannel, "dm.triggerChannel" );
379
380 _config( m_triggerSemaphore, "dm.triggerSemaphore" );
381
382 if( _config.isSet( "dm.trigger" ) )
383 {
384 _config( m_trigger, "dm.trigger" );
385 }
386
387 _config( m_triggerDelay, "dm.triggerDelay" );
388
389 _config( m_separation_1, "dm.separation_1" );
390 _config( m_separation_2, "dm.separation_2" );
391 _config( m_angle, "dm.angle" );
392 _config( m_angleOffset, "dm.angleOffset" );
393 _config( m_amp, "dm.amp" );
394
395 if( _config.isSet( "dm.cross" ) )
396 {
397 _config( m_cross, "dm.cross" );
398 }
399
400 _config( m_frequency, "dm.frequency" );
401 _config( m_dwell, "dm.dwell" );
402 _config( m_modThreadPrio, "modulator.threadPrio" );
403 _config( m_modThreadCpuset, "modulator.cpuset" );
404
406 return 0;
407}
408
410{
411 loadConfigImpl( config );
412}
413
415{
416
417 REG_INDI_NEWPROP_NOCB( m_indiP_dm, "dm", pcf::IndiProperty::Text );
418 m_indiP_dm.add( pcf::IndiElement( "name" ) );
419 m_indiP_dm["name"] = m_dmName;
420 m_indiP_dm.add( pcf::IndiElement( "channel" ) );
421 m_indiP_dm["channel"] = m_dmChannelName;
422
423 createStandardIndiNumber<float>( m_indiP_delay, "delay", 0, 0, 1, "%f" );
424 m_indiP_delay["current"] = m_triggerDelay;
425 m_indiP_delay["target"] = m_triggerDelay;
427
428 createStandardIndiNumber<float>( m_indiP_separation_1, "separation_1", 2, 24, 100, "%f" );
432
433 createStandardIndiNumber<float>( m_indiP_separation_2, "separation_2", 2, 24, 100, "%f" );
437
438 createStandardIndiNumber<float>( m_indiP_angle, "angle", 0, 0, 100, "%f" );
439 m_indiP_angle["current"] = m_angle;
440 m_indiP_angle["target"] = m_angle;
442
445 {
447 return -1;
448 }
449 if( m_cross )
450 {
451 m_indiP_cross["toggle"] = pcf::IndiElement::On;
452 }
453 else
454 {
455 m_indiP_cross["toggle"] = pcf::IndiElement::Off;
456 }
457
458 createStandardIndiNumber<float>( m_indiP_amp, "amp", -1, 0, 1, "%f" );
459 m_indiP_amp["current"] = m_amp;
460 m_indiP_amp["target"] = m_amp;
462
463 createStandardIndiNumber<float>( m_indiP_frequency, "frequency", 0, 0, 10000, "%f" );
464 m_indiP_frequency["current"] = m_frequency;
465 m_indiP_frequency["target"] = m_frequency;
467
468 createStandardIndiNumber<float>( m_indiP_interval, "interval", 0, 0, 10000, "%f" );
472
475 {
477 return -1;
478 }
479 if( m_trigger )
480 {
481 m_indiP_trigger["toggle"] = pcf::IndiElement::On;
482 }
483 else
484 {
485 m_indiP_trigger["toggle"] = pcf::IndiElement::Off;
486 }
487
488 createStandardIndiNumber<int>( m_indiP_dwell, "dwell", 1, 100, 1, "%d" );
489 m_indiP_dwell["current"] = m_dwell;
490 m_indiP_dwell["target"] = m_dwell;
492
493 createStandardIndiNumber<int>( m_indiP_single, "single", -1, 3, 1, "%d" );
494 m_indiP_single["current"] = m_single;
495 m_indiP_single["target"] = m_single;
497
500 {
502 return -1;
503 }
504
507 {
509 return -1;
510 }
511
518 "modulator",
519 this,
520 modThreadStart ) < 0 )
521 {
523 return -1;
524 }
525
527 {
528 return log<software_error, -1>( { __FILE__, __LINE__ } );
529 }
530
532
533 return 0;
534}
535
537{
539 {
540 m_opened = false;
541 m_restart = false; // Set this up front, since we're about to restart.
542
544 {
545 if( m_imageStream.md[0].sem < 10 ) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
546 {
548 }
549 else
550 {
551 m_opened = true;
552 }
553 }
554
555 // Only bother to try if previous worked and we have a spec
556 if( m_opened == true && m_dmTriggerChannel != "" )
557 {
559 {
560 if( m_triggerStream.md[0].sem <
561 10 ) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
562 {
564 m_opened = false;
565 }
566 }
567 }
568
569 if( m_opened )
570 {
572 }
573 }
574
576 {
577 m_dataType = m_imageStream.md[0].datatype;
579 m_width = m_imageStream.md[0].size[0];
580 m_height = m_imageStream.md[0].size[1];
581
583 {
584 return log<text_log, -1>( "Data type of DM channel is not float.", logPrio::LOG_CRITICAL );
585 }
586
587 if( m_typeSize != sizeof( realT ) )
588 {
589 return log<text_log, -1>( "Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL );
590 }
591
593 }
594
596 {
598 return 0;
599 }
600
601 return 0;
602}
603
605{
606 if( m_modThread.joinable() )
607 {
608 try
609 {
610 m_modThread.join(); // this will throw if it was already joined
611 }
612 catch( ... )
613 {
614 }
615 }
616
618
619 return 0;
620}
621
623{
624 mx::improc::eigenImage<realT> onesp, onespC;
625 onesp.resize( m_width, m_height );
626 onespC.resize( m_width, m_height );
627
628 int nFrames = 4 * static_cast<int>( m_sparkleClockInterval / 4.0 * m_frequency );
629 int _nFramesRound = static_cast<int>( m_sparkleClockInterval * m_frequency );
630 if( nFrames != _nFramesRound )
631 {
632 std::cerr << "Got nFrames = " << nFrames << " to be evenly divisible by 4, but integer rounding would give "
633 << _nFramesRound << " frames per " << m_sparkleClockInterval << " sec interval given " << m_frequency
634 << " Hz frequency\n";
635 }
636 m_shapes.resize( m_width, m_height, nFrames );
637
638 // One complete cycle of the sparkle clock spins through 2pi radians, twice, in one m_sparkleClockInterval.
639 // Each step in position should be 4 frames. +/-, sin/cos.
640 // So we figure out the number of positions as nFrames / 2 (radii) / 4 (phase/parity).
641 float nPositions = nFrames / 4 / 2;
642 float angleStep = mx::math::two_pi<realT>() / nPositions;
643 for( int radii = 0; radii < 2; radii++ )
644 {
646 for( int pos = 0; pos < nPositions; pos++ )
647 {
648 int baseIdx = ( nPositions * radii ) + pos;
649 int thisIdx = baseIdx;
650
651 realT m = separation * cos( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset + angleStep * pos) );
652 realT n = separation * sin( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset + angleStep * pos) );
653
654 mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), m, n, 1 );
655
656 if( m_cross )
657 {
658 onesp = m_shapes.image( thisIdx );
659 mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), -n, m, 1 );
660 m_shapes.image( thisIdx ) += onesp;
661 }
662
663 m_shapes.image( thisIdx ) *= m_amp;
664 thisIdx += 1;
665 m_shapes.image( thisIdx ) = -1 * m_shapes.image( thisIdx - 1 );
666
667 thisIdx += 1;
668 mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), m, n, -1 );
669
670 if( m_cross )
671 {
672 onesp = m_shapes.image( thisIdx );
673 mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), -n, m, -1 );
674 m_shapes.image( thisIdx ) += onesp;
675 }
676
677 m_shapes.image( thisIdx ) *= m_amp;
678
679 thisIdx += 1;
680 m_shapes.image( thisIdx ) = -1 * m_shapes.image( thisIdx - 1 );
681
682 mx::fits::fitsFile<realT> ff;
683 ff.write( "/tmp/specks.fits", m_shapes );
684
685 // if( m_single >= 0 )
686 // {
687 // for( int pp = 0; pp < m_shapes.planes(); ++pp )
688 // {
689 // if( pp != m_single )
690 // {
691 // m_shapes.image( pp ) *= 0;
692 // }
693 // }
694 // }
695 }
696 }
697
702 updateIfChanged( m_indiP_amp, "current", m_amp );
707
708 return 0;
709}
710
712{
713 d->modThreadExec();
714}
715
717{
719
720 // Wait fpr the thread starter to finish initializing this thread.
721 while( ( m_modThreadInit == true || state() != stateCodes::READY ) && m_shutdown == 0 )
722 {
723 sleep( 1 );
724 }
725
726 while( m_shutdown == 0 )
727 {
728 if( !m_modulating && !m_shutdown ) // If we aren't modulating we sleep for 1/2 a second
729 {
730 mx::sys::milliSleep( 500 );
731 }
732
733 if( m_modulating && !m_shutdown )
734 {
735 m_restartSp = false;
737
738 int64_t freqNsec = ( 1.0 / m_frequency ) * 1e9;
740
741 int idx = 0;
742
743 timespec modstart;
744 timespec currtime;
745
746 bool triggered = false;
747 sem_t *sem = nullptr;
748 if( m_dmTriggerChannel == "" )
749 {
750 m_trigger = false;
752 m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
753 }
754 else if( m_trigger == true )
755 {
757
758 sem = m_triggerStream.semptr[m_triggerSemaphore]; ///< The semaphore to monitor for new image data
759 }
760
761 log<text_log>( "started modulating", logPrio::LOG_NOTICE );
762 // To send a message
763 // log<telem_sparkleclock>( { m_modulating,
764 // m_trigger,
765 // m_frequency,
766 // std::vector<float>( { m_separation } ),
767 // std::vector<float>( { m_angle } ),
768 // std::vector<float>( { m_amp } ),
769 // std::vector<bool>( { m_cross } ) },
770 // logPrio::LOG_INFO );
771 // // The official record:
772 // recordSparkleClock( true );
773
774 dnsec = 0;
776
777 unsigned dwelled = 0;
778 if( m_dwell == 0 )
779 m_dwell = 1;
780
781 float triggerDelay = m_triggerDelay / 1e6;
782
783 double t0, t1;
784
785 while( m_modulating && !m_restartSp && !m_shutdown )
786 {
787 if( m_trigger )
788 {
789 timespec ts;
790
791 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
792 {
793 log<software_critical>( { __FILE__, __LINE__, errno, 0, "clock_gettime" } );
794 return;
795 }
796
797 ts.tv_sec += 1;
798
799 if( sem_timedwait( sem, &ts ) == 0 )
800 {
801 t0 = mx::sys::get_curr_time();
802 t1 = t0;
803
804 while( t1 - t0 < triggerDelay )
805 {
806 double dt = ( 1e8 ) * ( triggerDelay -
807 ( t1 - t0 ) ); // This is 0.1 times remaining time, but in nanosecs
808 if( dt <= 0 )
809 break;
810 mx::sys::nanoSleep( dt );
811 t1 = mx::sys::get_curr_time();
812 }
813
814 triggered = true;
815 }
816 else
817 {
818 triggered = false;
819
820 // Check for why we timed out
821 if( errno == EINTR )
822 break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit
823 // normally if flags set.
824
825 // ETIMEDOUT just means we should wait more.
826 // Otherwise, report an error.
827 if( errno != ETIMEDOUT )
828 {
829 log<software_error>( { __FILE__, __LINE__, errno, "sem_timedwait" } );
830 break;
831 }
832 }
833 }
834 else
835 {
836 mx::sys::nanoSleep( 0.5 * dnsec );
838
839 dnsec =
840 ( currtime.tv_sec - modstart.tv_sec ) * 1000000000 + ( currtime.tv_nsec - modstart.tv_nsec );
841 triggered = false;
842 }
843
844 if( dwelled < m_dwell - 1 )
845 {
846 ++dwelled;
847 }
848 else if( dnsec >= freqNsec || triggered )
849 {
850 // Do the write
851 dwelled = 0;
852
853 m_imageStream.md->write = 1;
854
855 memcpy( m_imageStream.array.raw, m_shapes.image( idx ).data(), m_width * m_height * m_typeSize );
856
857 m_imageStream.md->atime = currtime;
858 m_imageStream.md->writetime = currtime;
859
860 if( !m_trigger || triggerDelay > 0 )
861 {
862 m_imageStream.md->cnt0++;
863 }
864
865 m_imageStream.md->write = 0;
867
868 ++idx;
869 if( idx >= m_shapes.planes() )
870 idx = 0;
871
872 if( !m_trigger )
873 {
874 modstart.tv_nsec += freqNsec;
875 if( modstart.tv_nsec >= 1000000000 )
876 {
877 modstart.tv_nsec -= 1000000000;
878 modstart.tv_sec += 1;
879 }
880 dnsec = freqNsec;
881 }
882 }
883 }
884 if( m_restartSp )
885 continue;
886
887 recordSparkleClock( true );
888 log<text_log>( "stopped modulating", logPrio::LOG_NOTICE );
889 // Always zero when done
891 m_imageStream.md->write = 1;
892
893 memset( m_imageStream.array.raw, 0.0, m_width * m_height * m_typeSize );
894
895 m_imageStream.md->atime = currtime;
896 m_imageStream.md->writetime = currtime;
897
898 if( !m_trigger )
899 m_imageStream.md->cnt0++;
900
901 m_imageStream.md->write = 0;
903 log<text_log>( "zeroed" );
904 }
905 }
906}
907
908INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_trigger )
909( const pcf::IndiProperty &ipRecv )
910{
911 if( ipRecv.getName() != m_indiP_trigger.getName() )
912 {
913 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
914 return -1;
915 }
916
917 if( !ipRecv.find( "toggle" ) )
918 return 0;
919
920 std::unique_lock<std::mutex> lock( m_indiMutex );
921
922 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
923 {
924 m_trigger = false;
925 indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
926 }
927
928 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
929 {
930 m_trigger = true;
931 indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
932 }
933
934 m_restartSp = true;
935
936 return 0;
937}
938
939INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_delay )( const pcf::IndiProperty &ipRecv )
940{
941 if( ipRecv.getName() != m_indiP_delay.getName() )
942 {
943 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
944 return -1;
945 }
946
947 float del = -1000000000;
948
949 if( ipRecv.find( "current" ) )
950 {
951 del = ipRecv["current"].get<float>();
952 }
953
954 if( ipRecv.find( "target" ) )
955 {
956 del = ipRecv["target"].get<float>();
957 }
958
959 if( del == -1000000000 )
960 {
961 log<software_error>( { __FILE__, __LINE__, "No requested delay" } );
962 return 0;
963 }
964
965 std::unique_lock<std::mutex> lock( m_indiMutex );
966 m_triggerDelay = del;
967 updateIfChanged( m_indiP_delay, "target", m_triggerDelay );
968
969 m_restartSp = true;
970 return 0;
971}
972
973INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_separation_1 )( const pcf::IndiProperty &ipRecv )
974{
975 if( ipRecv.getName() != m_indiP_separation_1.getName() )
976 {
977 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
978 return -1;
979 }
980
981 float sep = -1000000000;
982
983 if( ipRecv.find( "current" ) )
984 {
985 sep = ipRecv["current"].get<float>();
986 }
987
988 if( ipRecv.find( "target" ) )
989 {
990 sep = ipRecv["target"].get<float>();
991 }
992
993 if( sep == -1000000000 )
994 {
995 log<software_error>( { __FILE__, __LINE__, "No requested separation_1" } );
996 return 0;
997 }
998
999 std::unique_lock<std::mutex> lock( m_indiMutex );
1000 m_separation_1 = sep;
1001 updateIfChanged( m_indiP_separation_1, "target", m_separation_1 );
1002
1003 m_restartSp = true;
1004
1005 return 0;
1006}
1007
1008INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_separation_2 )( const pcf::IndiProperty &ipRecv )
1009{
1010 if( ipRecv.getName() != m_indiP_separation_2.getName() )
1011 {
1012 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1013 return -1;
1014 }
1015
1016 float sep = -1000000000;
1017
1018 if( ipRecv.find( "current" ) )
1019 {
1020 sep = ipRecv["current"].get<float>();
1021 }
1022
1023 if( ipRecv.find( "target" ) )
1024 {
1025 sep = ipRecv["target"].get<float>();
1026 }
1027
1028 if( sep == -1000000000 )
1029 {
1030 log<software_error>( { __FILE__, __LINE__, "No requested separation_2" } );
1031 return 0;
1032 }
1033
1034 std::unique_lock<std::mutex> lock( m_indiMutex );
1035 m_separation_2 = sep;
1036 updateIfChanged( m_indiP_separation_2, "target", m_separation_2 );
1037
1038 m_restartSp = true;
1039
1040 return 0;
1041}
1042
1043INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_angle )
1044( const pcf::IndiProperty &ipRecv )
1045{
1046 if( ipRecv.getName() != m_indiP_angle.getName() )
1047 {
1048 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1049 return -1;
1050 }
1051
1052 float ang = -1000000000;
1053
1054 if( ipRecv.find( "current" ) )
1055 {
1056 ang = ipRecv["current"].get<float>();
1057 }
1058
1059 if( ipRecv.find( "target" ) )
1060 {
1061 ang = ipRecv["target"].get<float>();
1062 }
1063
1064 if( ang == -1000000000 )
1065 {
1066 log<software_error>( { __FILE__, __LINE__, "No angle received" } );
1067 return 0;
1068 }
1069
1070 std::unique_lock<std::mutex> lock( m_indiMutex );
1071 m_angle = ang;
1072 updateIfChanged( m_indiP_angle, "target", m_angle );
1073
1074 m_restartSp = true;
1075 return 0;
1076}
1077
1078INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_amp )
1079( const pcf::IndiProperty &ipRecv )
1080{
1081 if( ipRecv.getName() != m_indiP_amp.getName() )
1082 {
1083 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1084 return -1;
1085 }
1086
1087 float amp = -1000000000;
1088
1089 if( ipRecv.find( "current" ) )
1090 {
1091 amp = ipRecv["current"].get<float>();
1092 }
1093
1094 if( ipRecv.find( "target" ) )
1095 {
1096 amp = ipRecv["target"].get<float>();
1097 }
1098
1099 if( amp == -1000000000 )
1100 {
1101 log<software_error>( { __FILE__, __LINE__, "Invalid requested amp: " + std::to_string( amp ) } );
1102 return 0;
1103 }
1104
1105 std::unique_lock<std::mutex> lock( m_indiMutex );
1106 m_amp = amp;
1107 updateIfChanged( m_indiP_amp, "target", m_amp );
1108
1109 m_restartSp = true;
1110 return 0;
1111}
1112
1113INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_cross )
1114( const pcf::IndiProperty &ipRecv )
1115{
1116 if( ipRecv.createUniqueKey() != m_indiP_cross.createUniqueKey() )
1117 {
1118 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1119 return -1;
1120 }
1121
1122 if( !ipRecv.find( "toggle" ) )
1123 return 0;
1124
1125 std::unique_lock<std::mutex> lock( m_indiMutex );
1126
1127 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1128 {
1129 m_cross = false;
1130 indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1131 }
1132
1133 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1134 {
1135 m_cross = true;
1136 indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1137 }
1138
1139 m_restartSp = true;
1140
1141 return 0;
1142}
1143
1144INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_frequency )
1145( const pcf::IndiProperty &ipRecv )
1146{
1147 if( ipRecv.getName() != m_indiP_frequency.getName() )
1148 {
1149 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1150 return -1;
1151 }
1152
1153 float freq = -1;
1154
1155 if( ipRecv.find( "current" ) )
1156 {
1157 freq = ipRecv["current"].get<float>();
1158 }
1159
1160 if( ipRecv.find( "target" ) )
1161 {
1162 freq = ipRecv["target"].get<float>();
1163 }
1164
1165 if( freq < 0 )
1166 {
1167 log<software_error>( { __FILE__, __LINE__, "Invalid requested frequency: " + std::to_string( freq ) } );
1168 return 0;
1169 }
1170
1171 std::unique_lock<std::mutex> lock( m_indiMutex );
1172 m_frequency = freq;
1173 updateIfChanged( m_indiP_frequency, "target", m_frequency );
1174
1175 m_restartSp = true;
1176
1177 return 0;
1178}
1179
1180INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_interval )
1181( const pcf::IndiProperty &ipRecv )
1182{
1183 if( ipRecv.getName() != m_indiP_interval.getName() )
1184 {
1185 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1186 return -1;
1187 }
1188
1189 float interval = -1;
1190
1191 if( ipRecv.find( "current" ) )
1192 {
1193 interval = ipRecv["current"].get<float>();
1194 }
1195
1196 if( ipRecv.find( "target" ) )
1197 {
1198 interval = ipRecv["target"].get<float>();
1199 }
1200
1201 if( interval < 0 )
1202 {
1203 log<software_error>( { __FILE__, __LINE__, "Invalid requested interval: " + std::to_string( interval ) } );
1204 return 0;
1205 }
1206
1207 std::unique_lock<std::mutex> lock( m_indiMutex );
1209 updateIfChanged( m_indiP_interval, "target", m_sparkleClockInterval );
1210
1211 m_restartSp = true;
1212
1213 return 0;
1214}
1215
1216INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_dwell )
1217( const pcf::IndiProperty &ipRecv )
1218{
1219 if( ipRecv.createUniqueKey() != m_indiP_dwell.createUniqueKey() )
1220 {
1221 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1222 return -1;
1223 }
1224
1225 unsigned dwell = 0;
1226
1227 if( ipRecv.find( "current" ) )
1228 {
1229 dwell = ipRecv["current"].get<unsigned>();
1230 }
1231
1232 if( ipRecv.find( "target" ) )
1233 {
1234 dwell = ipRecv["target"].get<unsigned>();
1235 }
1236
1237 if( dwell == 0 )
1238 {
1239 log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( dwell ) } );
1240 return 0;
1241 }
1242
1243 std::unique_lock<std::mutex> lock( m_indiMutex );
1244 m_dwell = dwell;
1245 updateIfChanged( m_indiP_dwell, "target", m_dwell );
1246
1247 m_restartSp = true;
1248
1249 return 0;
1250}
1251
1252INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_single )
1253( const pcf::IndiProperty &ipRecv )
1254{
1255 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_single, ipRecv );
1256
1257 int single = 0;
1258
1259 if( ipRecv.find( "current" ) )
1260 {
1261 single = ipRecv["current"].get<int>();
1262 }
1263
1264 if( ipRecv.find( "target" ) )
1265 {
1266 single = ipRecv["target"].get<int>();
1267 }
1268
1269 if( single < -1 || single > 3 )
1270 {
1271 log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( single ) } );
1272 return 0;
1273 }
1274
1275 std::unique_lock<std::mutex> lock( m_indiMutex );
1276 m_single = single;
1277 updateIfChanged( m_indiP_single, "target", m_single );
1278 return 0;
1279}
1280
1281INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_modulating )
1282( const pcf::IndiProperty &ipRecv )
1283{
1284 if( ipRecv.getName() != m_indiP_modulating.getName() )
1285 {
1286 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1287 return -1;
1288 }
1289
1290 if( !ipRecv.find( "toggle" ) )
1291 return 0;
1292
1293 std::unique_lock<std::mutex> lock( m_indiMutex );
1294
1295 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1296 {
1297 m_modulating = false;
1298 indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1299 }
1300
1301 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1302 {
1303 m_modulating = true;
1304 indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1305 }
1306
1307 return 0;
1308}
1309
1310INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_zero )
1311( const pcf::IndiProperty &ipRecv )
1312{
1313 if( ipRecv.getName() != m_indiP_zero.getName() )
1314 {
1315 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1316 return -1;
1317 }
1318
1319 if( m_modulating == true )
1320 {
1321 log<text_log>( "zero requested but currently modulating", logPrio::LOG_NOTICE );
1322 return 0;
1323 }
1324
1325 if( !ipRecv.find( "request" ) )
1326 return 0;
1327
1328 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1329 {
1330 m_imageStream.md->write = 1;
1331
1332 memset( m_imageStream.array.raw, 0, m_width * m_height * m_typeSize );
1333 timespec currtime;
1334 clock_gettime( CLOCK_REALTIME, &currtime );
1335 m_imageStream.md->atime = currtime;
1336 m_imageStream.md->writetime = currtime;
1337
1338 m_imageStream.md->cnt0++;
1339
1340 m_imageStream.md->write = 0;
1341 ImageStreamIO_sempost( &m_imageStream, -1 );
1342 log<text_log>( "zeroed" );
1343 }
1344
1345 return 0;
1346}
1347
1352
1354{
1355 return recordSparkleClock( true );
1356}
1357
1358inline int sparkleClock::recordSparkleClock( bool force )
1359{
1360 static bool lastModulating = m_modulating;
1361 static bool lastTrigger = m_trigger;
1362 static float lastFrequency = m_frequency;
1363 static float lastInterval = m_frequency;
1364 static float lastSeparation1 = m_separation_1;
1365 static float lastSeparation2 = m_separation_2;
1366 static float lastAngle = m_angle;
1367 static float lastAmp = m_amp;
1368 static bool lastCross = m_cross;
1369
1372 !( lastCross == m_cross ) || force )
1373 {
1375 m_trigger,
1377 m_interval,
1378 std::vector<float>( { m_separation_1, m_separation_2 } ),
1379 m_angle,
1380 m_amp });
1381
1388 lastAmp = m_amp;
1389 }
1390
1391 return 0;
1392}
1393
1394} // namespace app
1395} // namespace MagAOX
1396
1397#endif // sparkleClock_hpp
The base-class for MagAO-X applications.
Definition MagAOXApp.hpp:73
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
realT m_amp
The speckle amplitude on the DM.
pcf::IndiProperty m_indiP_dm
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)
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
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.
pcf::IndiProperty m_indiP_separation_2
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 INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#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.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
#define INDI_IDLE
Definition indiUtils.hpp:28
#define INDI_OK
Definition indiUtils.hpp:29
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.
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:24
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
static constexpr logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
A device base class which saves telemetry.
Definition telemeter.hpp:69
int appShutdown()
Perform telemeter application shutdown.
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Software ERR log entry.
Log entry recording sparkle clock status.
A simple text log, a string-type log.
Definition text_log.hpp:24