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 mx::error_t errc = ff.read( m_shapes, m_fileName ) ;
643 if( errc != mx::error_t::noerror )
644 {
645 return log<software_critical, -1>( { __FILE__, __LINE__, "no file with that name" } );
646 }
647
648 if( m_shapes.rows() != m_width || m_shapes.cols() != m_height )
649 {
650 return log<software_critical, -1>( { __FILE__, __LINE__, "shape cube is not the right size" } );
651 }
652
653 for(int p =0; p < m_shapes.planes(); ++p)
654 {
655 m_shapes.image(p) *= (float) m_amp;
656 }
657 }
658 else
659 {
660 mx::improc::eigenImage<realT> onesp, onespC;
661 onesp.resize( m_width, m_height );
662 onespC.resize( m_width, m_height );
663
664 m_shapes.resize( m_width, m_height, 4 );
665
666 realT m = m_separation * cos( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset ) );
667 realT n = m_separation * sin( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset ) );
668
669 mx::sigproc::makeFourierMode( m_shapes.image( 0 ), m, n, 1 );
670
671 if( m_cross )
672 {
673 onesp = m_shapes.image( 0 );
674 mx::sigproc::makeFourierMode( m_shapes.image( 0 ), -n, m, 1 );
675 m_shapes.image( 0 ) += onesp;
676 }
677
678 m_shapes.image( 0 ) *= m_amp;
679 m_shapes.image( 1 ) = -1 * m_shapes.image( 0 );
680
681 mx::sigproc::makeFourierMode( m_shapes.image( 2 ), m, n, -1 );
682
683 if( m_cross )
684 {
685 onesp = m_shapes.image( 2 );
686 mx::sigproc::makeFourierMode( m_shapes.image( 2 ), -n, m, -1 );
687 m_shapes.image( 2 ) += onesp;
688 }
689
690 m_shapes.image( 2 ) *= m_amp;
691 m_shapes.image( 3 ) = -m_shapes.image( 2 );
692
693 mx::fits::fitsFile<realT> ff;
694 ff.write( "/tmp/specks.fits", m_shapes );
695
696 if( m_single >= 0 )
697 {
698 for( int pp = 0; pp < m_shapes.planes(); ++pp )
699 {
700 if( pp != m_single )
701 {
702 m_shapes.image( pp ) *= 0;
703 }
704 }
705 }
708 }
710 updateIfChanged( m_indiP_amp, "current", m_amp );
714
715 return 0;
716}
717
719{
720 d->modThreadExec();
721}
722
724{
726
727 // Wait fpr the thread starter to finish initializing this thread.
728 while( ( m_modThreadInit == true || state() != stateCodes::READY ) && m_shutdown == 0 )
729 {
730 sleep( 1 );
731 }
732
733 while( m_shutdown == 0 )
734 {
735 if( !m_modulating && !m_shutdown ) // If we aren't modulating we sleep for 1/2 a second
736 {
737 mx::sys::milliSleep( 500 );
738 }
739
740 if( m_modulating && !m_shutdown )
741 {
742 m_restartSp = false;
743 if(generateSpeckles() < 0)
744 {
745 m_modulating = false;
746 continue;
747 }
748
749 int64_t freqNsec = ( 1.0 / m_frequency ) * 1e9;
751
752 int idx = 0;
753
754 timespec modstart;
755 timespec currtime;
756
757 bool triggered = false;
758 sem_t *sem = nullptr;
759 if( m_dmTriggerChannel == "" )
760 {
761 m_trigger = false;
763 m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
764 }
765 else if( m_trigger == true )
766 {
768
769 sem = m_triggerStream.semptr[m_triggerSemaphore]; ///< The semaphore to monitor for new image data
770 }
771
772 log<text_log>( "started modulating", logPrio::LOG_NOTICE );
773 // To send a message
775 m_trigger,
777 std::vector<float>( { m_separation } ),
778 std::vector<float>( { m_angle } ),
779 std::vector<float>( { m_amp } ),
780 std::vector<bool>( { m_cross } ) },
782 // The official record:
783 recordDmSpeck( true );
784
785 dnsec = 0;
787
788 unsigned dwelled = 0;
789 if( m_dwell == 0 )
790 m_dwell = 1;
791
792 float triggerDelay = m_triggerDelay / 1e6;
793
794 double t0, t1;
795
796 while( m_modulating && !m_restartSp && !m_shutdown )
797 {
798 if( m_trigger )
799 {
800 timespec ts;
801
802 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
803 {
804 log<software_critical>( { __FILE__, __LINE__, errno, 0, "clock_gettime" } );
805 return;
806 }
807
808 ts.tv_sec += 1;
809
810 if( sem_timedwait( sem, &ts ) == 0 )
811 {
812 t0 = mx::sys::get_curr_time();
813 t1 = t0;
814
815 while( t1 - t0 < triggerDelay )
816 {
817 double dt = ( 1e8 ) * ( triggerDelay -
818 ( t1 - t0 ) ); // This is 0.1 times remaining time, but in nanosecs
819 if( dt <= 0 )
820 break;
821 mx::sys::nanoSleep( dt );
822 t1 = mx::sys::get_curr_time();
823 }
824
825 triggered = true;
826 }
827 else
828 {
829 triggered = false;
830
831 // Check for why we timed out
832 if( errno == EINTR )
833 break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit
834 // normally if flags set.
835
836 // ETIMEDOUT just means we should wait more.
837 // Otherwise, report an error.
838 if( errno != ETIMEDOUT )
839 {
840 log<software_error>( { __FILE__, __LINE__, errno, "sem_timedwait" } );
841 break;
842 }
843 }
844 }
845 else
846 {
847 mx::sys::nanoSleep( 0.5 * dnsec );
849
850 dnsec =
851 ( currtime.tv_sec - modstart.tv_sec ) * 1000000000 + ( currtime.tv_nsec - modstart.tv_nsec );
852 triggered = false;
853 }
854
855 if( dwelled < m_dwell - 1 )
856 {
857 ++dwelled;
858 }
859 else if( dnsec >= freqNsec || triggered )
860 {
861 // Do the write
862 dwelled = 0;
863
864 m_imageStream.md->write = 1;
865
866 memcpy( m_imageStream.array.raw, m_shapes.image( idx ).data(), m_width * m_height * m_typeSize );
867
868 m_imageStream.md->atime = currtime;
869 m_imageStream.md->writetime = currtime;
870
871 if( !m_trigger || triggerDelay > 0 )
872 {
873 m_imageStream.md->cnt0++;
874 }
875
876 m_imageStream.md->write = 0;
878
879 ++idx;
880 if( idx >= m_shapes.planes() )
881 idx = 0;
882
883 if( !m_trigger )
884 {
885 modstart.tv_nsec += freqNsec;
886 if( modstart.tv_nsec >= 1000000000 )
887 {
888 modstart.tv_nsec -= 1000000000;
889 modstart.tv_sec += 1;
890 }
891 dnsec = freqNsec;
892 }
893 }
894 }
895 if( m_restartSp )
896 continue;
897
898 recordDmSpeck( true );
899 log<text_log>( "stopped modulating", logPrio::LOG_NOTICE );
900 // Always zero when done
902 m_imageStream.md->write = 1;
903
904 memset( m_imageStream.array.raw, 0.0, m_width * m_height * m_typeSize );
905
906 m_imageStream.md->atime = currtime;
907 m_imageStream.md->writetime = currtime;
908
909 if( !m_trigger )
910 m_imageStream.md->cnt0++;
911
912 m_imageStream.md->write = 0;
914 log<text_log>( "zeroed" );
915 }
916 }
917}
918
919INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_trigger )
920( const pcf::IndiProperty &ipRecv )
921{
922 if( ipRecv.getName() != m_indiP_trigger.getName() )
923 {
924 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
925 return -1;
926 }
927
928 if( !ipRecv.find( "toggle" ) )
929 return 0;
930
931 std::unique_lock<std::mutex> lock( m_indiMutex );
932
933 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
934 {
935 m_trigger = false;
936 indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
937 }
938
939 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
940 {
941 m_trigger = true;
942 indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
943 }
944
946
947 return 0;
948}
949
950INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_delay )( const pcf::IndiProperty &ipRecv )
951{
952 if( ipRecv.getName() != m_indiP_delay.getName() )
953 {
954 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
955 return -1;
956 }
957
958 float del = -1000000000;
959
960 if( ipRecv.find( "current" ) )
961 {
962 del = ipRecv["current"].get<float>();
963 }
964
965 if( ipRecv.find( "target" ) )
966 {
967 del = ipRecv["target"].get<float>();
968 }
969
970 if( del == -1000000000 )
971 {
972 log<software_error>( { __FILE__, __LINE__, "No requested delay" } );
973 return 0;
974 }
975
976 std::unique_lock<std::mutex> lock( m_indiMutex );
977 m_triggerDelay = del;
978 updateIfChanged( m_indiP_delay, "target", m_triggerDelay );
979
980 m_restartSp = true;
981 return 0;
982}
983
984INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_separation )( const pcf::IndiProperty &ipRecv )
985{
986 if( ipRecv.getName() != m_indiP_separation.getName() )
987 {
988 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
989 return -1;
990 }
991
992 float sep = -1000000000;
993
994 if( ipRecv.find( "current" ) )
995 {
996 sep = ipRecv["current"].get<float>();
997 }
998
999 if( ipRecv.find( "target" ) )
1000 {
1001 sep = ipRecv["target"].get<float>();
1002 }
1003
1004 if( sep == -1000000000 )
1005 {
1006 log<software_error>( { __FILE__, __LINE__, "No requested separation" } );
1007 return 0;
1008 }
1009
1010 std::unique_lock<std::mutex> lock( m_indiMutex );
1011 m_separation = sep;
1012 updateIfChanged( m_indiP_separation, "target", m_separation );
1013
1014 m_restartSp = true;
1015
1016 return 0;
1017}
1018
1019INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_angle )
1020( const pcf::IndiProperty &ipRecv )
1021{
1022 if( ipRecv.getName() != m_indiP_angle.getName() )
1023 {
1024 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1025 return -1;
1026 }
1027
1028 float ang = -1000000000;
1029
1030 if( ipRecv.find( "current" ) )
1031 {
1032 ang = ipRecv["current"].get<float>();
1033 }
1034
1035 if( ipRecv.find( "target" ) )
1036 {
1037 ang = ipRecv["target"].get<float>();
1038 }
1039
1040 if( ang == -1000000000 )
1041 {
1042 log<software_error>( { __FILE__, __LINE__, "No angle received" } );
1043 return 0;
1044 }
1045
1046 std::unique_lock<std::mutex> lock( m_indiMutex );
1047 m_angle = ang;
1048 updateIfChanged( m_indiP_angle, "target", m_angle );
1049
1050 m_restartSp = true;
1051 return 0;
1052}
1053
1054INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_amp )
1055( const pcf::IndiProperty &ipRecv )
1056{
1057 if( ipRecv.getName() != m_indiP_amp.getName() )
1058 {
1059 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1060 return -1;
1061 }
1062
1063 float amp = -1000000000;
1064
1065 if( ipRecv.find( "current" ) )
1066 {
1067 amp = ipRecv["current"].get<float>();
1068 }
1069
1070 if( ipRecv.find( "target" ) )
1071 {
1072 amp = ipRecv["target"].get<float>();
1073 }
1074
1075 if( amp == -1000000000 )
1076 {
1077 log<software_error>( { __FILE__, __LINE__, "Invalid requested amp: " + std::to_string( amp ) } );
1078 return 0;
1079 }
1080
1081 std::unique_lock<std::mutex> lock( m_indiMutex );
1082 m_amp = amp;
1083 updateIfChanged( m_indiP_amp, "target", m_amp );
1084
1085 m_restartSp = true;
1086 return 0;
1087}
1088
1089INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_cross )
1090( const pcf::IndiProperty &ipRecv )
1091{
1092 if( ipRecv.createUniqueKey() != m_indiP_cross.createUniqueKey() )
1093 {
1094 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1095 return -1;
1096 }
1097
1098 if( !ipRecv.find( "toggle" ) )
1099 return 0;
1100
1101 std::unique_lock<std::mutex> lock( m_indiMutex );
1102
1103 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1104 {
1105 m_cross = false;
1106 indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1107 }
1108
1109 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1110 {
1111 m_cross = true;
1112 indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1113 }
1114
1115 m_restartSp = true;
1116
1117 return 0;
1118}
1119
1120INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_frequency )
1121( const pcf::IndiProperty &ipRecv )
1122{
1123 if( ipRecv.getName() != m_indiP_frequency.getName() )
1124 {
1125 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1126 return -1;
1127 }
1128
1129 float freq = -1;
1130
1131 if( ipRecv.find( "current" ) )
1132 {
1133 freq = ipRecv["current"].get<float>();
1134 }
1135
1136 if( ipRecv.find( "target" ) )
1137 {
1138 freq = ipRecv["target"].get<float>();
1139 }
1140
1141 if( freq < 0 )
1142 {
1143 log<software_error>( { __FILE__, __LINE__, "Invalid requested frequency: " + std::to_string( freq ) } );
1144 return 0;
1145 }
1146
1147 std::unique_lock<std::mutex> lock( m_indiMutex );
1149 updateIfChanged( m_indiP_frequency, "target", m_frequency );
1150
1151 m_restartSp = true;
1152
1153 return 0;
1154}
1155
1156INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_dwell )
1157( const pcf::IndiProperty &ipRecv )
1158{
1159 if( ipRecv.createUniqueKey() != m_indiP_dwell.createUniqueKey() )
1160 {
1161 log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1162 return -1;
1163 }
1164
1165 unsigned dwell = 0;
1166
1167 if( ipRecv.find( "current" ) )
1168 {
1169 dwell = ipRecv["current"].get<unsigned>();
1170 }
1171
1172 if( ipRecv.find( "target" ) )
1173 {
1174 dwell = ipRecv["target"].get<unsigned>();
1175 }
1176
1177 if( dwell == 0 )
1178 {
1179 log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( dwell ) } );
1180 return 0;
1181 }
1182
1183 std::unique_lock<std::mutex> lock( m_indiMutex );
1184 m_dwell = dwell;
1185 updateIfChanged( m_indiP_dwell, "target", m_dwell );
1186
1187 m_restartSp = true;
1188
1189 return 0;
1190}
1191
1192INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_single )
1193( const pcf::IndiProperty &ipRecv )
1194{
1195 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_single, ipRecv );
1196
1197 int single = 0;
1198
1199 if( ipRecv.find( "current" ) )
1200 {
1201 single = ipRecv["current"].get<int>();
1202 }
1203
1204 if( ipRecv.find( "target" ) )
1205 {
1206 single = ipRecv["target"].get<int>();
1207 }
1208
1209 if( single < -1 || single > 3 )
1210 {
1211 log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( single ) } );
1212 return 0;
1213 }
1214
1215 std::unique_lock<std::mutex> lock( m_indiMutex );
1217 updateIfChanged( m_indiP_single, "target", m_single );
1218 return 0;
1219}
1220
1221INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_modulating )
1222( const pcf::IndiProperty &ipRecv )
1223{
1224 if( ipRecv.getName() != m_indiP_modulating.getName() )
1225 {
1226 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1227 return -1;
1228 }
1229
1230 if( !ipRecv.find( "toggle" ) )
1231 return 0;
1232
1233 std::unique_lock<std::mutex> lock( m_indiMutex );
1234
1235 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1236 {
1237 m_modulating = false;
1238 indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1239 }
1240
1241 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1242 {
1243 m_modulating = true;
1244 indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1245 }
1246
1247 return 0;
1248}
1249
1250INDI_NEWCALLBACK_DEFN( dmSpeckle, m_indiP_zero )
1251( const pcf::IndiProperty &ipRecv )
1252{
1253 if( ipRecv.getName() != m_indiP_zero.getName() )
1254 {
1255 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1256 return -1;
1257 }
1258
1259 if( m_modulating == true )
1260 {
1261 log<text_log>( "zero requested but currently modulating", logPrio::LOG_NOTICE );
1262 return 0;
1263 }
1264
1265 if( !ipRecv.find( "request" ) )
1266 return 0;
1267
1268 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1269 {
1270 m_imageStream.md->write = 1;
1271
1272 memset( m_imageStream.array.raw, 0, m_width * m_height * m_typeSize );
1273 timespec currtime;
1274 clock_gettime( CLOCK_REALTIME, &currtime );
1275 m_imageStream.md->atime = currtime;
1276 m_imageStream.md->writetime = currtime;
1277
1278 m_imageStream.md->cnt0++;
1279
1280 m_imageStream.md->write = 0;
1281 ImageStreamIO_sempost( &m_imageStream, -1 );
1282 log<text_log>( "zeroed" );
1283 }
1284
1285 return 0;
1286}
1287
1292
1294{
1295 return recordDmSpeck( true );
1296}
1297
1298inline int dmSpeckle::recordDmSpeck( bool force )
1299{
1300 static bool lastModulating = m_modulating;
1301 static bool lastTrigger = m_trigger;
1302 static float lastFrequency = m_frequency;
1303 static float lastSeparation = m_separation;
1304 static float lastAngle = m_angle;
1305 static float lastAmp = m_amp;
1306 static bool lastCross = m_cross;
1307
1308 if( !( lastModulating == m_modulating ) || !( lastTrigger == m_trigger ) || !( lastFrequency == m_frequency ) ||
1309 !( lastSeparation == m_separation ) || !( lastAngle == m_angle ) || !( lastAmp == m_amp ) ||
1310 !( lastCross == m_cross ) || force )
1311 {
1313 m_trigger,
1315 std::vector<float>( { m_separation } ),
1316 std::vector<float>( { m_angle } ),
1317 std::vector<float>( { m_amp } ),
1318 std::vector<bool>( { m_cross } ) } );
1319
1325 lastAmp = m_amp;
1327 }
1328
1329 return 0;
1330}
1331
1332} // namespace app
1333} // namespace MagAOX
1334
1335#endif // dmSpeckle_hpp
The base-class for XWCTk 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.
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:28
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