API
 
Loading...
Searching...
No Matches
dmSpeckle.hpp
Go to the documentation of this file.
1/** \file dmSpeckle.hpp
2 * \brief The MagAO-X DM speckle maker header file
3 *
4 * \ingroup dmSpeckle_files
5 */
6
7#ifndef dmSpeckle_hpp
8#define dmSpeckle_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 dmSpeckle
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/dmSpeckle.html">Application Documentation</a>
28 *
29 * \ingroup apps
30 *
31 */
32
33/** \defgroup dmSpeckle_files
34 * \ingroup dmSpeckle
35 */
36
37namespace MagAOX
38{
39namespace app
40{
41
42#define SPARKLE 0
43#define ARBCUBE 1
44
45/// The MagAO-X DM mode commander
46/**
47 * \ingroup dmSpeckle
48 */
49class dmSpeckle : public MagAOXApp<true>, public dev::telemeter<dmSpeckle>
50{
51
52 typedef float realT;
53
54 friend class dev::telemeter<dmSpeckle>;
55 friend class dmSpeckle_test;
56
57 protected:
58 /** \name Configurable Parameters
59 *@{
60 */
61
62 std::string m_dmName; ///< The descriptive name of this dm. Default is the channel name.
63
64 std::string m_dmChannelName; ///< The name of the DM channel to write to.
65
66 std::string m_dmTriggerChannel; ///< The DM channel to monitor as a trigger
67
68 float m_triggerDelay{ 0 }; // 0.000375- 0.5/2000.;
69
70 int m_triggerSemaphore{ 9 }; ///< The semaphore to use (default 9)
71
72 bool m_trigger{ true }; ///< Run in trigger mode if true (default)
73
74 realT m_separation{ 15.0 }; ///< The radial separation of the speckles (default 15.0)
75
76 realT m_angle{ 0.0 }; ///< The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)
77
78 realT m_angleOffset{ 28.0 }; ///< The calibration offset of angle so that up on camsci1/2 is 0
79
80 realT m_amp{ 0.01 }; ///< The speckle amplitude on the DM
81
82 bool m_cross{ true }; ///< If true, also apply the cross speckles rotated by 90 degrees
83
84 realT m_frequency{ 2000 }; ///< The frequency to modulate at if not triggering (default 2000 Hz)
85
86 unsigned m_dwell{ 1 }; ///< The dwell time for each speckle, or for how many frames it is held.
87
88 int m_single{ -1 }; ///< if >= 0 a single frame is non-zero.
89
91
92 std::string m_fileName;
93
94 ///@}
95
96 mx::improc::eigenCube<realT> m_shapes;
97
99 uint32_t m_width{ 0 }; ///< The width of the image
100 uint32_t m_height{ 0 }; ///< The height of the image.
101
103
104 uint8_t m_dataType{ 0 }; ///< The ImageStreamIO type code.
105 size_t m_typeSize{ 0 }; ///< The size of the type, in bytes.
106
107 bool m_opened{ true };
108 bool m_restart{ false };
109
110 bool m_modulating{ false };
111
112 bool m_restartSp{ false };
113
114 public:
115 /// Default c'tor.
116 dmSpeckle();
117
118 /// D'tor, declared and defined for noexcept.
120 {
121 }
122
123 virtual void setupConfig();
124
125 /// Implementation of loadConfig logic, separated for testing.
126 /** This is called by loadConfig().
127 */
128 int loadConfigImpl(
129 mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
130
131 virtual void loadConfig();
132
133 /// Startup function
134 /**
135 *
136 */
137 virtual int appStartup();
138
139 /// Implementation of the FSM for dmSpeckle.
140 /**
141 * \returns 0 on no critical error
142 * \returns -1 on an error requiring shutdown
143 */
144 virtual int appLogic();
145
146 /// Shutdown the app.
147 /**
148 *
149 */
150 virtual int appShutdown();
151
152 protected:
153 int generateSpeckles();
154
155 /** \name Modulator Thread
156 * This thread sends the signal to the dm at the prescribed frequency
157 *
158 * @{
159 */
160 int m_modThreadPrio{ 60 }; ///< Priority of the modulator thread, should normally be > 00.
161
162 std::string m_modThreadCpuset; ///< The cpuset for the modulator thread.
163
164 std::thread m_modThread; ///< A separate thread for the modulation
165
166 bool m_modThreadInit{ true }; ///< Synchronizer to ensure f.g. thread initializes before doing dangerous things.
167
168 pid_t m_modThreadID{ 0 }; ///< Modulate thread PID.
169
170 pcf::IndiProperty m_modThreadProp; ///< The property to hold the modulator thread details.
171
172 /// Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
173 static void modThreadStart( dmSpeckle *d /**< [in] a pointer to a dmSpeckle instance (normally this) */ );
174
175 /// Execute the frame grabber main loop.
176 void modThreadExec();
177
178 ///@}
179
180 // INDI:
181 protected:
182 // declare our properties
183 pcf::IndiProperty m_indiP_dm;
184 pcf::IndiProperty m_indiP_trigger;
185 pcf::IndiProperty m_indiP_delay;
186 pcf::IndiProperty m_indiP_separation;
187 pcf::IndiProperty m_indiP_angle;
188 pcf::IndiProperty m_indiP_amp;
189 pcf::IndiProperty m_indiP_cross;
190 pcf::IndiProperty m_indiP_frequency;
191 pcf::IndiProperty m_indiP_dwell;
192 pcf::IndiProperty m_indiP_single;
193 pcf::IndiProperty m_indiP_modulating;
194 pcf::IndiProperty m_indiP_zero;
195
196 public:
208
209 /** \name Telemeter Interface
210 *
211 * @{
212 */
213 int checkRecordTimes();
214
215 int recordTelem( const telem_dmspeck * );
216
217 int recordDmSpeck( bool force = false );
218
219 ///@}
220};
221
222dmSpeckle::dmSpeckle() : 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",
276 "",
277 "dm.separation",
278 argType::Required,
279 "dm",
280 "separation",
281 false,
282 "float",
283 "The radial separation of the speckles (default 15.0)." );
284 config.add( "dm.angle",
285 "",
286 "dm.angle",
287 argType::Required,
288 "dm",
289 "angle",
290 false,
291 "float",
292 "The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)." );
293 config.add( "dm.angleOffset",
294 "",
295 "dm.angleOffset",
296 argType::Required,
297 "dm",
298 "angleOffset",
299 false,
300 "float",
301 "The calibration offset of angle so that up on camsci1/2 is 0." );
302 config.add( "dm.amp",
303 "",
304 "dm.amp",
305 argType::Required,
306 "dm",
307 "amp",
308 false,
309 "float",
310 "The speckle amplitude on the DM (default 0.01)." );
311 config.add( "dm.cross",
312 "",
313 "dm.cross",
314 argType::True,
315 "dm",
316 "cross",
317 false,
318 "bool",
319 "If true, also apply the cross speckles rotated by 90 degrees." );
320
321 config.add( "dm.frequency",
322 "",
323 "dm.frequency",
324 argType::Required,
325 "dm",
326 "frequency",
327 false,
328 "float",
329 "The frequency to modulate at if not triggering (default 2000 Hz)." );
330
331 config.add( "dm.dwell",
332 "",
333 "dm.dwell",
334 argType::True,
335 "dm",
336 "dwell",
337 false,
338 "int",
339 "The dwell time for each speckle, or for how many frames it is held. Default=1." );
340
341 config.add( "modulator.threadPrio",
342 "",
343 "modulator.threadPrio",
344 argType::Required,
345 "modulator",
346 "threadPrio",
347 false,
348 "int",
349 "The real-time priority of the modulator thread." );
350
351 config.add( "modulator.cpuset",
352 "",
353 "modulator.cpuset",
354 argType::Required,
355 "modulator",
356 "cpuset",
357 false,
358 "string",
359 "The cpuset to assign the modulator thread to." );
360 config.add( "modulator.opMode",
361 "",
362 "modulator.opMode",
363 argType::Required,
364 "modulator",
365 "opMode",
366 false,
367 "string",
368 "operation mode, either sparkle (default) or arbcube. If arbcub modulator.fileName must be set." );
369 config.add( "modulator.fileName",
370 "",
371 "modulator.fileName",
372 argType::Required,
373 "modulator",
374 "fileName",
375 false,
376 "string",
377 "File name containing the FITS cube to modulate the DM with. Only valid if opMode==arbcube." );
378}
379
380int dmSpeckle::loadConfigImpl( mx::app::appConfigurator &_config )
381{
382 _config( m_dmChannelName, "dm.channelName" );
383
385 _config( m_dmName, "dm.name" );
386
387 _config( m_dmTriggerChannel, "dm.triggerChannel" );
388 _config( m_triggerSemaphore, "dm.triggerSemaphore" );
389
390 if( _config.isSet( "dm.trigger" ) )
391 {
392 _config( m_trigger, "dm.trigger" );
393 }
394
395 _config( m_triggerDelay, "dm.triggerDelay" );
396
397 std::string opMode = "sparkle";
398 _config( opMode, "modulator.opMode" );
399
400 if( opMode == "arbcube" )
401 {
402 m_opMode = ARBCUBE;
403 }
404 else
405 {
407 }
408 _config(m_fileName, "modulator.fileName");
409 _config( m_separation, "dm.separation" );
410 _config( m_angle, "dm.angle" );
411 _config( m_angleOffset, "dm.angleOffset" );
412
413
414 _config( m_amp, "dm.amp" );
415
416 if( _config.isSet( "dm.cross" ) )
417 {
418 _config( m_cross, "dm.cross" );
419 }
420
421 _config( m_frequency, "dm.frequency" );
422 _config( m_dwell, "dm.dwell" );
423
424 _config( m_modThreadPrio, "modulator.threadPrio" );
425 _config( m_modThreadCpuset, "modulator.cpuset" );
426
428 return 0;
429}
430
432{
433 loadConfigImpl( config );
434}
435
437{
438
439 REG_INDI_NEWPROP_NOCB( m_indiP_dm, "dm", pcf::IndiProperty::Text );
440 m_indiP_dm.add( pcf::IndiElement( "name" ) );
441 m_indiP_dm["name"] = m_dmName;
442 m_indiP_dm.add( pcf::IndiElement( "channel" ) );
443 m_indiP_dm["channel"] = m_dmChannelName;
444
445 createStandardIndiNumber<float>( m_indiP_delay, "delay", 0, 0, 1, "%f" );
446 m_indiP_delay["current"] = m_triggerDelay;
447 m_indiP_delay["target"] = m_triggerDelay;
449
450 if( m_opMode == SPARKLE )
451 {
452 createStandardIndiNumber<float>( m_indiP_separation, "separation", 0, 0, 100, "%f" );
453 m_indiP_separation["current"] = m_separation;
456
457 createStandardIndiNumber<float>( m_indiP_angle, "angle", 0, 0, 100, "%f" );
458 m_indiP_angle["current"] = m_angle;
459 m_indiP_angle["target"] = m_angle;
461
464 {
466 return -1;
467 }
468 if( m_cross )
469 {
470 m_indiP_cross["toggle"] = pcf::IndiElement::On;
471 }
472 else
473 {
474 m_indiP_cross["toggle"] = pcf::IndiElement::Off;
475 }
476 }
477
478 createStandardIndiNumber<float>( m_indiP_amp, "amp", -1, 0, 1, "%f" );
479 m_indiP_amp["current"] = m_amp;
480 m_indiP_amp["target"] = m_amp;
482
483 createStandardIndiNumber<float>( m_indiP_frequency, "frequency", 0, 0, 10000, "%f" );
484 m_indiP_frequency["current"] = m_frequency;
485 m_indiP_frequency["target"] = m_frequency;
487
490 {
492 return -1;
493 }
494 if( m_trigger )
495 {
496 m_indiP_trigger["toggle"] = pcf::IndiElement::On;
497 }
498 else
499 {
500 m_indiP_trigger["toggle"] = pcf::IndiElement::Off;
501 }
502
503 createStandardIndiNumber<int>( m_indiP_dwell, "dwell", 1, 100, 1, "%d" );
504 m_indiP_dwell["current"] = m_dwell;
505 m_indiP_dwell["target"] = m_dwell;
507
508 createStandardIndiNumber<int>( m_indiP_single, "single", -1, 3, 1, "%d" );
509 m_indiP_single["current"] = m_single;
510 m_indiP_single["target"] = m_single;
512
515 {
517 return -1;
518 }
519
522 {
524 return -1;
525 }
526
533 "modulator",
534 this,
535 modThreadStart ) < 0 )
536 {
538 return -1;
539 }
540
542 {
543 return log<software_error, -1>( { __FILE__, __LINE__ } );
544 }
545
547
548 return 0;
549}
550
552{
554 {
555 m_opened = false;
556 m_restart = false; // Set this up front, since we're about to restart.
557
559 {
560 if( m_imageStream.md[0].sem < 10 ) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
561 {
563 }
564 else
565 {
566 m_opened = true;
567 }
568 }
569
570 // Only bother to try if previous worked and we have a spec
571 if( m_opened == true && m_dmTriggerChannel != "" )
572 {
574 {
575 if( m_triggerStream.md[0].sem <
576 10 ) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
577 {
579 m_opened = false;
580 }
581 }
582 }
583
584 if( m_opened )
585 {
587 }
588 }
589
591 {
592 m_dataType = m_imageStream.md[0].datatype;
594 m_width = m_imageStream.md[0].size[0];
595 m_height = m_imageStream.md[0].size[1];
596
598 {
599 return log<text_log, -1>( "Data type of DM channel is not float.", logPrio::LOG_CRITICAL );
600 }
601
602 if( m_typeSize != sizeof( realT ) )
603 {
604 return log<text_log, -1>( "Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL );
605 }
606
608 }
609
611 {
613 return 0;
614 }
615
616 return 0;
617}
618
620{
621 if( m_modThread.joinable() )
622 {
623 try
624 {
625 m_modThread.join(); // this will throw if it was already joined
626 }
627 catch( ... )
628 {
629 }
630 }
631
633
634 return 0;
635}
636
638{
639 if( m_opMode == ARBCUBE )
640 {
641 mx::fits::fitsFile<float> ff;
642 if( ff.read( m_shapes, m_fileName ) < 0 )
643 {
644 return log<software_critical, -1>( { __FILE__, __LINE__, "no file with that name" } );
645 }
646
647 if( m_shapes.rows() != m_width || m_shapes.cols() != m_height )
648 {
649 return log<software_critical, -1>( { __FILE__, __LINE__, "shape cube is not the right size" } );
650 }
651
652 for(int p =0; p < m_shapes.planes(); ++p)
653 {
654 m_shapes.image(p) *= (float) m_amp;
655 }
656 }
657 else
658 {
659 mx::improc::eigenImage<realT> onesp, onespC;
660 onesp.resize( m_width, m_height );
661 onespC.resize( m_width, m_height );
662
663 m_shapes.resize( m_width, m_height, 4 );
664
665 realT m = m_separation * cos( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset ) );
666 realT n = m_separation * sin( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset ) );
667
668 mx::sigproc::makeFourierMode( m_shapes.image( 0 ), m, n, 1 );
669
670 if( m_cross )
671 {
672 onesp = m_shapes.image( 0 );
673 mx::sigproc::makeFourierMode( m_shapes.image( 0 ), -n, m, 1 );
674 m_shapes.image( 0 ) += onesp;
675 }
676
677 m_shapes.image( 0 ) *= m_amp;
678 m_shapes.image( 1 ) = -1 * m_shapes.image( 0 );
679
680 mx::sigproc::makeFourierMode( m_shapes.image( 2 ), m, n, -1 );
681
682 if( m_cross )
683 {
684 onesp = m_shapes.image( 2 );
685 mx::sigproc::makeFourierMode( m_shapes.image( 2 ), -n, m, -1 );
686 m_shapes.image( 2 ) += onesp;
687 }
688
689 m_shapes.image( 2 ) *= m_amp;
690 m_shapes.image( 3 ) = -m_shapes.image( 2 );
691
692 mx::fits::fitsFile<realT> ff;
693 ff.write( "/tmp/specks.fits", m_shapes );
694
695 if( m_single >= 0 )
696 {
697 for( int pp = 0; pp < m_shapes.planes(); ++pp )
698 {
699 if( pp != m_single )
700 {
701 m_shapes.image( pp ) *= 0;
702 }
703 }
704 }
707 }
709 updateIfChanged( m_indiP_amp, "current", m_amp );
713
714 return 0;
715}
716
718{
719 d->modThreadExec();
720}
721
723{
725
726 // Wait fpr the thread starter to finish initializing this thread.
727 while( ( m_modThreadInit == true || state() != stateCodes::READY ) && m_shutdown == 0 )
728 {
729 sleep( 1 );
730 }
731
732 while( m_shutdown == 0 )
733 {
734 if( !m_modulating && !m_shutdown ) // If we aren't modulating we sleep for 1/2 a second
735 {
736 mx::sys::milliSleep( 500 );
737 }
738
739 if( m_modulating && !m_shutdown )
740 {
741 m_restartSp = false;
743
744 int64_t freqNsec = ( 1.0 / m_frequency ) * 1e9;
746
747 int idx = 0;
748
749 timespec modstart;
750 timespec currtime;
751
752 bool triggered = false;
753 sem_t *sem = nullptr;
754 if( m_dmTriggerChannel == "" )
755 {
756 m_trigger = false;
758 m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
759 }
760 else if( m_trigger == true )
761 {
763
764 sem = m_triggerStream.semptr[m_triggerSemaphore]; ///< The semaphore to monitor for new image data
765 }
766
767 log<text_log>( "started modulating", logPrio::LOG_NOTICE );
768 // To send a message
770 m_trigger,
772 std::vector<float>( { m_separation } ),
773 std::vector<float>( { m_angle } ),
774 std::vector<float>( { m_amp } ),
775 std::vector<bool>( { m_cross } ) },
777 // The official record:
778 recordDmSpeck( true );
779
780 dnsec = 0;
782
783 unsigned dwelled = 0;
784 if( m_dwell == 0 )
785 m_dwell = 1;
786
787 float triggerDelay = m_triggerDelay / 1e6;
788
789 double t0, t1;
790
791 while( m_modulating && !m_restartSp && !m_shutdown )
792 {
793 if( m_trigger )
794 {
795 timespec ts;
796
797 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
798 {
799 log<software_critical>( { __FILE__, __LINE__, errno, 0, "clock_gettime" } );
800 return;
801 }
802
803 ts.tv_sec += 1;
804
805 if( sem_timedwait( sem, &ts ) == 0 )
806 {
807 t0 = mx::sys::get_curr_time();
808 t1 = t0;
809
810 while( t1 - t0 < triggerDelay )
811 {
812 double dt = ( 1e8 ) * ( triggerDelay -
813 ( t1 - t0 ) ); // This is 0.1 times remaining time, but in nanosecs
814 if( dt <= 0 )
815 break;
816 mx::sys::nanoSleep( dt );
817 t1 = mx::sys::get_curr_time();
818 }
819
820 triggered = true;
821 }
822 else
823 {
824 triggered = false;
825
826 // Check for why we timed out
827 if( errno == EINTR )
828 break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit
829 // normally if flags set.
830
831 // ETIMEDOUT just means we should wait more.
832 // Otherwise, report an error.
833 if( errno != ETIMEDOUT )
834 {
835 log<software_error>( { __FILE__, __LINE__, errno, "sem_timedwait" } );
836 break;
837 }
838 }
839 }
840 else
841 {
842 mx::sys::nanoSleep( 0.5 * dnsec );
844
845 dnsec =
846 ( currtime.tv_sec - modstart.tv_sec ) * 1000000000 + ( currtime.tv_nsec - modstart.tv_nsec );
847 triggered = false;
848 }
849
850 if( dwelled < m_dwell - 1 )
851 {
852 ++dwelled;
853 }
854 else if( dnsec >= freqNsec || triggered )
855 {
856 // Do the write
857 dwelled = 0;
858
859 m_imageStream.md->write = 1;
860
861 memcpy( m_imageStream.array.raw, m_shapes.image( idx ).data(), m_width * m_height * m_typeSize );
862
863 m_imageStream.md->atime = currtime;
864 m_imageStream.md->writetime = currtime;
865
866 if( !m_trigger || triggerDelay > 0 )
867 {
868 m_imageStream.md->cnt0++;
869 }
870
871 m_imageStream.md->write = 0;
873
874 ++idx;
875 if( idx >= m_shapes.planes() )
876 idx = 0;
877
878 if( !m_trigger )
879 {
880 modstart.tv_nsec += freqNsec;
881 if( modstart.tv_nsec >= 1000000000 )
882 {
883 modstart.tv_nsec -= 1000000000;
884 modstart.tv_sec += 1;
885 }
886 dnsec = freqNsec;
887 }
888 }
889 }
890 if( m_restartSp )
891 continue;
892
893 recordDmSpeck( true );
894 log<text_log>( "stopped modulating", logPrio::LOG_NOTICE );
895 // Always zero when done
897 m_imageStream.md->write = 1;
898
899 memset( m_imageStream.array.raw, 0.0, m_width * m_height * m_typeSize );
900
901 m_imageStream.md->atime = currtime;
902 m_imageStream.md->writetime = currtime;
903
904 if( !m_trigger )
905 m_imageStream.md->cnt0++;
906
907 m_imageStream.md->write = 0;
909 log<text_log>( "zeroed" );
910 }
911 }
912}
913
914INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_trigger )
915( const pcf::IndiProperty &ipRecv )
916{
917 if( ipRecv.getName() != m_indiP_trigger.getName() )
918 {
919 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
920 return -1;
921 }
922
923 if( !ipRecv.find( "toggle" ) )
924 return 0;
925
926 std::unique_lock<std::mutex> lock( m_indiMutex );
927
928 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
929 {
930 m_trigger = false;
931 indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
932 }
933
934 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
935 {
936 m_trigger = true;
937 indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
938 }
939
941
942 return 0;
943}
944
945INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_delay )( const pcf::IndiProperty &ipRecv )
946{
947 if( ipRecv.getName() != m_indiP_delay.getName() )
948 {
949 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
950 return -1;
951 }
952
953 float del = -1000000000;
954
955 if( ipRecv.find( "current" ) )
956 {
957 del = ipRecv["current"].get<float>();
958 }
959
960 if( ipRecv.find( "target" ) )
961 {
962 del = ipRecv["target"].get<float>();
963 }
964
965 if( del == -1000000000 )
966 {
967 log<software_error>( { __FILE__, __LINE__, "No requested delay" } );
968 return 0;
969 }
970
971 std::unique_lock<std::mutex> lock( m_indiMutex );
972 m_triggerDelay = del;
973 updateIfChanged( m_indiP_delay, "target", m_triggerDelay );
974
975 m_restartSp = true;
976 return 0;
977}
978
979INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_separation )( const pcf::IndiProperty &ipRecv )
980{
981 if( ipRecv.getName() != m_indiP_separation.getName() )
982 {
983 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
984 return -1;
985 }
986
987 float sep = -1000000000;
988
989 if( ipRecv.find( "current" ) )
990 {
991 sep = ipRecv["current"].get<float>();
992 }
993
994 if( ipRecv.find( "target" ) )
995 {
996 sep = ipRecv["target"].get<float>();
997 }
998
999 if( sep == -1000000000 )
1000 {
1001 log<software_error>( { __FILE__, __LINE__, "No requested separation" } );
1002 return 0;
1003 }
1004
1005 std::unique_lock<std::mutex> lock( m_indiMutex );
1006 m_separation = sep;
1007 updateIfChanged( m_indiP_separation, "target", m_separation );
1008
1009 m_restartSp = true;
1010
1011 return 0;
1012}
1013
1014INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_angle )
1015( const pcf::IndiProperty &ipRecv )
1016{
1017 if( ipRecv.getName() != m_indiP_angle.getName() )
1018 {
1019 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1020 return -1;
1021 }
1022
1023 float ang = -1000000000;
1024
1025 if( ipRecv.find( "current" ) )
1026 {
1027 ang = ipRecv["current"].get<float>();
1028 }
1029
1030 if( ipRecv.find( "target" ) )
1031 {
1032 ang = ipRecv["target"].get<float>();
1033 }
1034
1035 if( ang == -1000000000 )
1036 {
1037 log<software_error>( { __FILE__, __LINE__, "No angle received" } );
1038 return 0;
1039 }
1040
1041 std::unique_lock<std::mutex> lock( m_indiMutex );
1042 m_angle = ang;
1043 updateIfChanged( m_indiP_angle, "target", m_angle );
1044
1045 m_restartSp = true;
1046 return 0;
1047}
1048
1049INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_amp )
1050( const pcf::IndiProperty &ipRecv )
1051{
1052 if( ipRecv.getName() != m_indiP_amp.getName() )
1053 {
1054 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1055 return -1;
1056 }
1057
1058 float amp = -1000000000;
1059
1060 if( ipRecv.find( "current" ) )
1061 {
1062 amp = ipRecv["current"].get<float>();
1063 }
1064
1065 if( ipRecv.find( "target" ) )
1066 {
1067 amp = ipRecv["target"].get<float>();
1068 }
1069
1070 if( amp == -1000000000 )
1071 {
1072 log<software_error>( { __FILE__, __LINE__, "Invalid requested amp: " + std::to_string( amp ) } );
1073 return 0;
1074 }
1075
1076 std::unique_lock<std::mutex> lock( m_indiMutex );
1077 m_amp = amp;
1078 updateIfChanged( m_indiP_amp, "target", m_amp );
1079
1080 m_restartSp = true;
1081 return 0;
1082}
1083
1084INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_cross )
1085( const pcf::IndiProperty &ipRecv )
1086{
1087 if( ipRecv.createUniqueKey() != m_indiP_cross.createUniqueKey() )
1088 {
1089 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1090 return -1;
1091 }
1092
1093 if( !ipRecv.find( "toggle" ) )
1094 return 0;
1095
1096 std::unique_lock<std::mutex> lock( m_indiMutex );
1097
1098 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1099 {
1100 m_cross = false;
1101 indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1102 }
1103
1104 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1105 {
1106 m_cross = true;
1107 indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1108 }
1109
1110 m_restartSp = true;
1111
1112 return 0;
1113}
1114
1115INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_frequency )
1116( const pcf::IndiProperty &ipRecv )
1117{
1118 if( ipRecv.getName() != m_indiP_frequency.getName() )
1119 {
1120 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1121 return -1;
1122 }
1123
1124 float freq = -1;
1125
1126 if( ipRecv.find( "current" ) )
1127 {
1128 freq = ipRecv["current"].get<float>();
1129 }
1130
1131 if( ipRecv.find( "target" ) )
1132 {
1133 freq = ipRecv["target"].get<float>();
1134 }
1135
1136 if( freq < 0 )
1137 {
1138 log<software_error>( { __FILE__, __LINE__, "Invalid requested frequency: " + std::to_string( freq ) } );
1139 return 0;
1140 }
1141
1142 std::unique_lock<std::mutex> lock( m_indiMutex );
1144 updateIfChanged( m_indiP_frequency, "target", m_frequency );
1145
1146 m_restartSp = true;
1147
1148 return 0;
1149}
1150
1151INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_dwell )
1152( const pcf::IndiProperty &ipRecv )
1153{
1154 if( ipRecv.createUniqueKey() != m_indiP_dwell.createUniqueKey() )
1155 {
1156 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1157 return -1;
1158 }
1159
1160 unsigned dwell = 0;
1161
1162 if( ipRecv.find( "current" ) )
1163 {
1164 dwell = ipRecv["current"].get<unsigned>();
1165 }
1166
1167 if( ipRecv.find( "target" ) )
1168 {
1169 dwell = ipRecv["target"].get<unsigned>();
1170 }
1171
1172 if( dwell == 0 )
1173 {
1174 log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( dwell ) } );
1175 return 0;
1176 }
1177
1178 std::unique_lock<std::mutex> lock( m_indiMutex );
1179 m_dwell = dwell;
1180 updateIfChanged( m_indiP_dwell, "target", m_dwell );
1181
1182 m_restartSp = true;
1183
1184 return 0;
1185}
1186
1187INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_single )
1188( const pcf::IndiProperty &ipRecv )
1189{
1190 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_single, ipRecv );
1191
1192 int single = 0;
1193
1194 if( ipRecv.find( "current" ) )
1195 {
1196 single = ipRecv["current"].get<int>();
1197 }
1198
1199 if( ipRecv.find( "target" ) )
1200 {
1201 single = ipRecv["target"].get<int>();
1202 }
1203
1204 if( single < -1 || single > 3 )
1205 {
1206 log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( single ) } );
1207 return 0;
1208 }
1209
1210 std::unique_lock<std::mutex> lock( m_indiMutex );
1212 updateIfChanged( m_indiP_single, "target", m_single );
1213 return 0;
1214}
1215
1216INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_modulating )
1217( const pcf::IndiProperty &ipRecv )
1218{
1219 if( ipRecv.getName() != m_indiP_modulating.getName() )
1220 {
1221 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1222 return -1;
1223 }
1224
1225 if( !ipRecv.find( "toggle" ) )
1226 return 0;
1227
1228 std::unique_lock<std::mutex> lock( m_indiMutex );
1229
1230 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1231 {
1232 m_modulating = false;
1233 indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1234 }
1235
1236 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1237 {
1238 m_modulating = true;
1239 indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1240 }
1241
1242 return 0;
1243}
1244
1245INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_zero )
1246( const pcf::IndiProperty &ipRecv )
1247{
1248 if( ipRecv.getName() != m_indiP_zero.getName() )
1249 {
1250 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1251 return -1;
1252 }
1253
1254 if( m_modulating == true )
1255 {
1256 log<text_log>( "zero requested but currently modulating", logPrio::LOG_NOTICE );
1257 return 0;
1258 }
1259
1260 if( !ipRecv.find( "request" ) )
1261 return 0;
1262
1263 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1264 {
1265 m_imageStream.md->write = 1;
1266
1267 memset( m_imageStream.array.raw, 0, m_width * m_height * m_typeSize );
1268 timespec currtime;
1269 clock_gettime( CLOCK_REALTIME, &currtime );
1270 m_imageStream.md->atime = currtime;
1271 m_imageStream.md->writetime = currtime;
1272
1273 m_imageStream.md->cnt0++;
1274
1275 m_imageStream.md->write = 0;
1276 ImageStreamIO_sempost( &m_imageStream, -1 );
1277 log<text_log>( "zeroed" );
1278 }
1279
1280 return 0;
1281}
1282
1287
1289{
1290 return recordDmSpeck( true );
1291}
1292
1293inline int dmSpeckle::recordDmSpeck( bool force )
1294{
1295 static bool lastModulating = m_modulating;
1296 static bool lastTrigger = m_trigger;
1297 static float lastFrequency = m_frequency;
1298 static float lastSeparation = m_separation;
1299 static float lastAngle = m_angle;
1300 static float lastAmp = m_amp;
1301 static bool lastCross = m_cross;
1302
1303 if( !( lastModulating == m_modulating ) || !( lastTrigger == m_trigger ) || !( lastFrequency == m_frequency ) ||
1304 !( lastSeparation == m_separation ) || !( lastAngle == m_angle ) || !( lastAmp == m_amp ) ||
1305 !( lastCross == m_cross ) || force )
1306 {
1308 m_trigger,
1310 std::vector<float>( { m_separation } ),
1311 std::vector<float>( { m_angle } ),
1312 std::vector<float>( { m_amp } ),
1313 std::vector<bool>( { m_cross } ) } );
1314
1320 lastAmp = m_amp;
1322 }
1323
1324 return 0;
1325}
1326
1327} // namespace app
1328} // namespace MagAOX
1329
1330#endif // dmSpeckle_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.
Definition dmSpeckle.hpp:50
pid_t m_modThreadID
Modulate thread PID.
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_delay)
virtual void loadConfig()
static void modThreadStart(dmSpeckle *d)
Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
friend class dmSpeckle_test
Definition dmSpeckle.hpp:55
unsigned m_dwell
The dwell time for each speckle, or for how many frames it is held.
Definition dmSpeckle.hpp:86
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_trigger)
pcf::IndiProperty m_indiP_modulating
realT m_angle
The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)
Definition dmSpeckle.hpp:76
bool m_modThreadInit
Synchronizer to ensure f.g. thread initializes before doing dangerous things.
~dmSpeckle() noexcept
D'tor, declared and defined for noexcept.
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_angle)
uint32_t m_width
The width of the image.
Definition dmSpeckle.hpp:99
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_amp)
size_t m_typeSize
The size of the type, in bytes.
int m_triggerSemaphore
The semaphore to use (default 9)
Definition dmSpeckle.hpp:70
pcf::IndiProperty m_indiP_trigger
mx::improc::eigenCube< realT > m_shapes
Definition dmSpeckle.hpp:96
void modThreadExec()
Execute the frame grabber main loop.
virtual int appStartup()
Startup function.
bool m_trigger
Run in trigger mode if true (default)
Definition dmSpeckle.hpp:72
virtual int appShutdown()
Shutdown the app.
pcf::IndiProperty m_indiP_cross
std::string m_dmTriggerChannel
The DM channel to monitor as a trigger.
Definition dmSpeckle.hpp:66
uint32_t m_height
The height of the image.
realT m_angleOffset
The calibration offset of angle so that up on camsci1/2 is 0.
Definition dmSpeckle.hpp:78
virtual int appLogic()
Implementation of the FSM for dmSpeckle.
pcf::IndiProperty m_indiP_single
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_separation)
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
realT m_amp
The speckle amplitude on the DM.
Definition dmSpeckle.hpp:80
pcf::IndiProperty m_indiP_frequency
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_single)
int m_modThreadPrio
Priority of the modulator thread, should normally be > 00.
pcf::IndiProperty m_indiP_angle
bool m_cross
If true, also apply the cross speckles rotated by 90 degrees.
Definition dmSpeckle.hpp:82
std::thread m_modThread
A separate thread for the modulation.
pcf::IndiProperty m_indiP_amp
pcf::IndiProperty m_indiP_zero
pcf::IndiProperty m_indiP_separation
realT m_separation
The radial separation of the speckles (default 15.0)
Definition dmSpeckle.hpp:74
pcf::IndiProperty m_indiP_dm
pcf::IndiProperty m_indiP_delay
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_zero)
dmSpeckle()
Default c'tor.
virtual void setupConfig()
std::string m_modThreadCpuset
The cpuset for the modulator thread.
int recordTelem(const telem_dmspeck *)
uint8_t m_dataType
The ImageStreamIO type code.
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_frequency)
pcf::IndiProperty m_modThreadProp
The property to hold the modulator thread details.
std::string m_dmName
The descriptive name of this dm. Default is the channel name.
Definition dmSpeckle.hpp:62
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_cross)
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_dwell)
realT m_frequency
The frequency to modulate at if not triggering (default 2000 Hz)
Definition dmSpeckle.hpp:84
int m_single
if >= 0 a single frame is non-zero.
Definition dmSpeckle.hpp:88
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_modulating)
std::string m_dmChannelName
The name of the DM channel to write to.
Definition dmSpeckle.hpp:64
pcf::IndiProperty m_indiP_dwell
int recordDmSpeck(bool force=false)
#define SPARKLE
Definition dmSpeckle.hpp:42
#define ARBCUBE
Definition dmSpeckle.hpp:43
#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:27
#define INDI_OK
Definition indiUtils.hpp:28
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_INFO
Informational. The info log level is the lowest level recorded during normal operations.
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 CRITICAL log entry.
Software ERR log entry.
Log entry recording stdMotionStage status.
A simple text log, a string-type log.
Definition text_log.hpp:24