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