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/// The MagAO-X DM mode commander
43/**
44 * \ingroup dmSpeckle
45 */
46class dmSpeckle : public MagAOXApp<true>, public dev::telemeter<dmSpeckle>
47{
48
49 typedef float realT;
50
51 friend class dev::telemeter<dmSpeckle>;
52 friend class dmSpeckle_test;
53
54protected:
55 /** \name Configurable Parameters
56 *@{
57 */
58
59 std::string m_dmName; ///< The descriptive name of this dm. Default is the channel name.
60
61 std::string m_dmChannelName; ///< The name of the DM channel to write to.
62
63 std::string m_dmTriggerChannel; ///< The DM channel to monitor as a trigger
64
65 float m_triggerDelay {0};//0.000375- 0.5/2000.;
66
67 int m_triggerSemaphore{9}; ///< The semaphore to use (default 9)
68
69 bool m_trigger{true}; ///< Run in trigger mode if true (default)
70
71 realT m_separation{15.0}; ///< The radial separation of the speckles (default 15.0)
72
73 realT m_angle{0.0}; ///< The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)
74
75 realT m_angleOffset{28.0}; ///< The calibration offset of angle so that up on camsci1/2 is 0
76
77 realT m_amp{0.01}; ///< The speckle amplitude on the DM
78
79 bool m_cross{true}; ///< If true, also apply the cross speckles rotated by 90 degrees
80
81 realT m_frequency{2000}; ///< The frequency to modulate at if not triggering (default 2000 Hz)
82
83 unsigned m_dwell{1}; ///< The dwell time for each speckle, or for how many frames it is held.
84
85 int m_single {-1}; ///< if >= 0 a single frame is non-zero.
86 ///@}
87
88 mx::improc::eigenCube<realT> m_shapes;
89
91 uint32_t m_width{0}; ///< The width of the image
92 uint32_t m_height{0}; ///< The height of the image.
93
95
96
97 uint8_t m_dataType{0}; ///< The ImageStreamIO type code.
98 size_t m_typeSize{0}; ///< The size of the type, in bytes.
99
100 bool m_opened{true};
101 bool m_restart{false};
102
103 bool m_modulating{false};
104
105 bool m_restartSp {false};
106public:
107 /// Default c'tor.
108 dmSpeckle();
109
110 /// D'tor, declared and defined for noexcept.
112 {
113 }
114
115 virtual void setupConfig();
116
117 /// Implementation of loadConfig logic, separated for testing.
118 /** This is called by loadConfig().
119 */
120 int loadConfigImpl(mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/);
121
122 virtual void loadConfig();
123
124 /// Startup function
125 /**
126 *
127 */
128 virtual int appStartup();
129
130 /// Implementation of the FSM for dmSpeckle.
131 /**
132 * \returns 0 on no critical error
133 * \returns -1 on an error requiring shutdown
134 */
135 virtual int appLogic();
136
137 /// Shutdown the app.
138 /**
139 *
140 */
141 virtual int appShutdown();
142
143protected:
144 int generateSpeckles();
145
146 /** \name Modulator Thread
147 * This thread sends the signal to the dm at the prescribed frequency
148 *
149 * @{
150 */
151 int m_modThreadPrio{60}; ///< Priority of the modulator thread, should normally be > 00.
152
153 std::string m_modThreadCpuset; ///< The cpuset for the modulator thread.
154
155 std::thread m_modThread; ///< A separate thread for the modulation
156
157 bool m_modThreadInit{true}; ///< Synchronizer to ensure f.g. thread initializes before doing dangerous things.
158
159 pid_t m_modThreadID{0}; ///< Modulate thread PID.
160
161 pcf::IndiProperty m_modThreadProp; ///< The property to hold the modulator thread details.
162
163 /// Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
164 static void modThreadStart(dmSpeckle *d /**< [in] a pointer to a dmSpeckle instance (normally this) */);
165
166 /// Execute the frame grabber main loop.
167 void modThreadExec();
168
169 ///@}
170
171 // INDI:
172protected:
173 // declare our properties
174 pcf::IndiProperty m_indiP_dm;
175 pcf::IndiProperty m_indiP_trigger;
176 pcf::IndiProperty m_indiP_delay;
177 pcf::IndiProperty m_indiP_separation;
178 pcf::IndiProperty m_indiP_angle;
179 pcf::IndiProperty m_indiP_amp;
180 pcf::IndiProperty m_indiP_cross;
181 pcf::IndiProperty m_indiP_frequency;
182 pcf::IndiProperty m_indiP_dwell;
183 pcf::IndiProperty m_indiP_single;
184 pcf::IndiProperty m_indiP_modulating;
185 pcf::IndiProperty m_indiP_zero;
186
187public:
199
200 /** \name Telemeter Interface
201 *
202 * @{
203 */
204 int checkRecordTimes();
205
206 int recordTelem(const telem_dmspeck *);
207
208 int recordDmSpeck(bool force = false);
209
210 ///@}
211};
212
213dmSpeckle::dmSpeckle() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
214{
215 return;
216}
217
219{
220 config.add("dm.channelName", "", "dm.channelName", argType::Required, "dm", "channelName", false, "string", "The name of the DM channel to write to.");
221 config.add("dm.triggerChannel", "", "dm.triggerChannel", argType::Required, "dm", "triggerChannel", false, "string", "The name of the DM channel to trigger on.");
222 config.add("dm.triggerSemaphore", "", "dm.triggerSemaphore", argType::Required, "dm", "triggerSemaphore", false, "int", "The semaphore to use (default 9).");
223 config.add("dm.trigger", "", "dm.trigger", argType::True, "dm", "trigger", false, "bool", "Run in trigger mode if true (default).");
224 config.add("dm.triggerDelay", "", "dm.triggerDelay", argType::Required, "dm", "triggerDelay", false, "float", "Delay to apply to the trigger.");
225
226 config.add("dm.separation", "", "dm.separation", argType::Required, "dm", "separation", false, "float", "The radial separation of the speckles (default 15.0).");
227 config.add("dm.angle", "", "dm.angle", argType::Required, "dm", "angle", false, "float", "The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0).");
228 config.add("dm.angleOffset", "", "dm.angleOffset", argType::Required, "dm", "angleOffset", false, "float", "The calibration offset of angle so that up on camsci1/2 is 0.");
229 config.add("dm.amp", "", "dm.amp", argType::Required, "dm", "amp", false, "float", "The speckle amplitude on the DM (default 0.01).");
230 config.add("dm.cross", "", "dm.cross", argType::True, "dm", "cross", false, "bool", "If true, also apply the cross speckles rotated by 90 degrees.");
231
232 config.add("dm.frequency", "", "dm.frequency", argType::Required, "dm", "frequency", false, "float", "The frequency to modulate at if not triggering (default 2000 Hz).");
233
234 config.add("dm.dwell", "", "dm.dwell", argType::True, "dm", "dwell", false, "int", "The dwell time for each speckle, or for how many frames it is held. Default=1.");
235
236 config.add("modulator.threadPrio", "", "modulator.threadPrio", argType::Required, "modulator", "threadPrio", false, "int", "The real-time priority of the modulator thread.");
237
238 config.add("modulator.cpuset", "", "modulator.cpuset", argType::Required, "modulator", "cpuset", false, "string", "The cpuset to assign the modulator thread to.");
239}
240
241int dmSpeckle::loadConfigImpl(mx::app::appConfigurator &_config)
242{
243 _config(m_dmChannelName, "dm.channelName");
244
246 _config(m_dmName, "dm.name");
247
248 _config(m_dmTriggerChannel, "dm.triggerChannel");
249 _config(m_triggerSemaphore, "dm.triggerSemaphore");
250
251 if (_config.isSet("dm.trigger"))
252 {
253 _config(m_trigger, "dm.trigger");
254 }
255
256 _config(m_triggerDelay, "dm.triggerDelay");
257
258 _config(m_separation, "dm.separation");
259 _config(m_angle, "dm.angle");
260 _config(m_angleOffset, "dm.angleOffset");
261 _config(m_amp, "dm.amp");
262
263 if (_config.isSet("dm.cross"))
264 {
265 _config(m_cross, "dm.cross");
266 }
267
268 _config(m_frequency, "dm.frequency");
269 _config(m_dwell, "dm.dwell");
270 _config(m_modThreadPrio, "modulator.threadPrio");
271 _config(m_modThreadCpuset, "modulator.cpuset");
272
274 return 0;
275}
276
278{
279 loadConfigImpl(config);
280}
281
283{
284
285 REG_INDI_NEWPROP_NOCB(m_indiP_dm, "dm", pcf::IndiProperty::Text);
286 m_indiP_dm.add(pcf::IndiElement("name"));
287 m_indiP_dm["name"] = m_dmName;
288 m_indiP_dm.add(pcf::IndiElement("channel"));
289 m_indiP_dm["channel"] = m_dmChannelName;
290
291 createStandardIndiNumber<float>(m_indiP_delay, "delay", 0, 0, 1, "%f");
292 m_indiP_delay["current"] = m_triggerDelay;
293 m_indiP_delay["target"] = m_triggerDelay;
295
296 createStandardIndiNumber<float>(m_indiP_separation, "separation", 0, 0, 100, "%f");
297 m_indiP_separation["current"] = m_separation;
300
301 createStandardIndiNumber<float>(m_indiP_angle, "angle", 0, 0, 100, "%f");
302 m_indiP_angle["current"] = m_angle;
303 m_indiP_angle["target"] = m_angle;
305
308 {
310 return -1;
311 }
312 if (m_cross)
313 {
314 m_indiP_cross["toggle"] = pcf::IndiElement::On;
315 }
316 else
317 {
318 m_indiP_cross["toggle"] = pcf::IndiElement::Off;
319 }
320
321 createStandardIndiNumber<float>(m_indiP_amp, "amp", -1, 0, 1, "%f");
322 m_indiP_amp["current"] = m_amp;
323 m_indiP_amp["target"] = m_amp;
325
326 createStandardIndiNumber<float>(m_indiP_frequency, "frequency", 0, 0, 10000, "%f");
327 m_indiP_frequency["current"] = m_frequency;
328 m_indiP_frequency["target"] = m_frequency;
330
333 {
335 return -1;
336 }
337 if (m_trigger)
338 {
339 m_indiP_trigger["toggle"] = pcf::IndiElement::On;
340 }
341 else
342 {
343 m_indiP_trigger["toggle"] = pcf::IndiElement::Off;
344 }
345
346 createStandardIndiNumber<int>(m_indiP_dwell, "dwell", 1, 100, 1, "%d");
347 m_indiP_dwell["current"] = m_dwell;
348 m_indiP_dwell["target"] = m_dwell;
350
351 createStandardIndiNumber<int>(m_indiP_single, "single", -1, 3, 1, "%d");
352 m_indiP_single["current"] = m_single;
353 m_indiP_single["target"] = m_single;
355
358 {
360 return -1;
361 }
362
365 {
367 return -1;
368 }
369
371 {
373 return -1;
374 }
375
377 {
378 return log<software_error, -1>({__FILE__, __LINE__});
379 }
380
382
383 return 0;
384}
385
387{
389 {
390 m_opened = false;
391 m_restart = false; // Set this up front, since we're about to restart.
392
394 {
395 if (m_imageStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
396 {
398 }
399 else
400 {
401 m_opened = true;
402 }
403 }
404
405 // Only bother to try if previous worked and we have a spec
406 if (m_opened == true && m_dmTriggerChannel != "")
407 {
409 {
410 if (m_triggerStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
411 {
413 m_opened = false;
414 }
415 }
416 }
417
418 if (m_opened)
419 {
421 }
422 }
423
425 {
426 m_dataType = m_imageStream.md[0].datatype;
428 m_width = m_imageStream.md[0].size[0];
429 m_height = m_imageStream.md[0].size[1];
430
432 {
433 return log<text_log, -1>("Data type of DM channel is not float.", logPrio::LOG_CRITICAL);
434 }
435
436 if (m_typeSize != sizeof(realT))
437 {
438 return log<text_log, -1>("Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL);
439 }
440
442 }
443
445 {
447 return 0;
448 }
449
450 return 0;
451}
452
454{
455 if (m_modThread.joinable())
456 {
457 try
458 {
459 m_modThread.join(); // this will throw if it was already joined
460 }
461 catch (...)
462 {
463 }
464 }
465
467
468 return 0;
469}
470
472{
473 mx::improc::eigenImage<realT> onesp, onespC;
474 onesp.resize(m_width, m_height);
475 onespC.resize(m_width, m_height);
476
477 m_shapes.resize(m_width, m_height, 4);
478
479 realT m = m_separation * cos(mx::math::dtor<realT>(-1 * m_angle + m_angleOffset));
480 realT n = m_separation * sin(mx::math::dtor<realT>(-1 * m_angle + m_angleOffset));
481
482 mx::sigproc::makeFourierMode(m_shapes.image(0), m, n, 1);
483
484 if (m_cross)
485 {
486 onesp = m_shapes.image(0);
487 mx::sigproc::makeFourierMode(m_shapes.image(0), -n, m, 1);
488 m_shapes.image(0) += onesp;
489 }
490
491 m_shapes.image(0) *= m_amp;
492 m_shapes.image(1) = -1 * m_shapes.image(0);
493
494 /* m_shapes.image(0) += -0.75;
495 m_shapes.image(1) += -0.75;
496*/
497 mx::sigproc::makeFourierMode(m_shapes.image(2), m, n, -1);
498
499 if (m_cross)
500 {
501 onesp = m_shapes.image(2);
502 mx::sigproc::makeFourierMode(m_shapes.image(2), -n, m, -1);
503 m_shapes.image(2) += onesp;
504 }
505
506 m_shapes.image(2) *= m_amp;
507 m_shapes.image(3) = -m_shapes.image(2);
508
509 mx::fits::fitsFile<realT> ff;
510 ff.write("/tmp/specks.fits", m_shapes);
511
512 if(m_single >= 0)
513 {
514 for(int pp = 0; pp < m_shapes.planes(); ++pp)
515 {
516 if(pp != m_single)
517 {
518 m_shapes.image(pp) *= 0;
519 }
520 }
521 }
522
523
527 updateIfChanged(m_indiP_amp, "current", m_amp);
531
532 return 0;
533}
534
536{
537 d->modThreadExec();
538}
539
541{
543
544 // Wait fpr the thread starter to finish initializing this thread.
545 while ((m_modThreadInit == true || state() != stateCodes::READY) && m_shutdown == 0)
546 {
547 sleep(1);
548 }
549
550 while (m_shutdown == 0)
551 {
552 if (!m_modulating && !m_shutdown) // If we aren't modulating we sleep for 1/2 a second
553 {
554 mx::sys::milliSleep(500);
555 }
556
557 if (m_modulating && !m_shutdown)
558 {
559 m_restartSp = false;
561
562 int64_t freqNsec = (1.0 / m_frequency) * 1e9;
564
565 int idx = 0;
566
567 timespec modstart;
568 timespec currtime;
569
570 bool triggered = false;
571 sem_t *sem = nullptr;
572 if (m_dmTriggerChannel == "")
573 {
574 m_trigger = false;
575 indi::updateSwitchIfChanged(m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE);
576 }
577 else if (m_trigger == true)
578 {
580
581 sem = m_triggerStream.semptr[m_triggerSemaphore]; ///< The semaphore to monitor for new image data
582 }
583
584 log<text_log>("started modulating", logPrio::LOG_NOTICE);
585 // To send a message
587 std::vector<float>({m_angle}), std::vector<float>({m_amp}), std::vector<bool>({m_cross})},
589 // The official record:
590 recordDmSpeck(true);
591
592 dnsec = 0;
594
595 unsigned dwelled = 0;
596 if (m_dwell == 0)
597 m_dwell = 1;
598
599
601
602 double t0, t1;
603
604 while (m_modulating && !m_restartSp && !m_shutdown)
605 {
606 if (m_trigger)
607 {
608 timespec ts;
609
611 {
612 log<software_critical>({__FILE__, __LINE__, errno, 0, "clock_gettime"});
613 return;
614 }
615
616 ts.tv_sec += 1;
617
618
619 if (sem_timedwait(sem, &ts) == 0)
620 {
621 t0 = mx::sys::get_curr_time();
622 t1 = t0;
623
624 while(t1 - t0 < triggerDelay)
625 {
626 double dt = (1e8)*( triggerDelay - (t1-t0)); //This is 0.1 times remaining time, but in nanosecs
627 if(dt <= 0) break;
628 mx::sys::nanoSleep(dt);
629 t1 = mx::sys::get_curr_time();
630 }
631
632 triggered = true;
633 }
634 else
635 {
636 triggered = false;
637
638 // Check for why we timed out
639 if (errno == EINTR)
640 break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit normally if flags set.
641
642 // ETIMEDOUT just means we should wait more.
643 // Otherwise, report an error.
644 if (errno != ETIMEDOUT)
645 {
646 log<software_error>({__FILE__, __LINE__, errno, "sem_timedwait"});
647 break;
648 }
649 }
650 }
651 else
652 {
653 mx::sys::nanoSleep(0.5 * dnsec);
655
656 dnsec = (currtime.tv_sec - modstart.tv_sec) * 1000000000 + (currtime.tv_nsec - modstart.tv_nsec);
657 triggered = false;
658 }
659
660 if (dwelled < m_dwell - 1)
661 {
662 ++dwelled;
663 }
664 else if (dnsec >= freqNsec || triggered)
665 {
666 // Do the write
667 dwelled = 0;
668
669 m_imageStream.md->write = 1;
670
671 memcpy(m_imageStream.array.raw, m_shapes.image(idx).data(), m_width * m_height * m_typeSize);
672
673 m_imageStream.md->atime = currtime;
674 m_imageStream.md->writetime = currtime;
675
676 if(!m_trigger || triggerDelay > 0)
677 {
678 m_imageStream.md->cnt0++;
679 }
680
681 m_imageStream.md->write = 0;
683
684 ++idx;
685 if (idx >= m_shapes.planes())
686 idx = 0;
687
688 if (!m_trigger)
689 {
690 modstart.tv_nsec += freqNsec;
691 if (modstart.tv_nsec >= 1000000000)
692 {
693 modstart.tv_nsec -= 1000000000;
694 modstart.tv_sec += 1;
695 }
696 dnsec = freqNsec;
697 }
698 }
699 }
700 if(m_restartSp) continue;
701
702 recordDmSpeck(true);
703 log<text_log>("stopped modulating", logPrio::LOG_NOTICE);
704 // Always zero when done
706 m_imageStream.md->write = 1;
707
708 memset(m_imageStream.array.raw, 0.0, m_width * m_height * m_typeSize);
709
710 m_imageStream.md->atime = currtime;
711 m_imageStream.md->writetime = currtime;
712
713 if (!m_trigger)
714 m_imageStream.md->cnt0++;
715
716 m_imageStream.md->write = 0;
718 log<text_log>("zeroed");
719 }
720 }
721}
722
723INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_trigger)
724(const pcf::IndiProperty &ipRecv)
725{
726 if (ipRecv.getName() != m_indiP_trigger.getName())
727 {
728 log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
729 return -1;
730 }
731
732 if (!ipRecv.find("toggle"))
733 return 0;
734
735 std::unique_lock<std::mutex> lock(m_indiMutex);
736
737 if (ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
738 {
739 m_trigger = false;
740 indi::updateSwitchIfChanged(m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE);
741 }
742
743 if (ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
744 {
745 m_trigger = true;
746 indi::updateSwitchIfChanged(m_indiP_trigger, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK);
747 }
748
750
751 return 0;
752}
753
754INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_delay)(const pcf::IndiProperty &ipRecv)
755{
756 if (ipRecv.getName() != m_indiP_delay.getName())
757 {
758 log<software_error>({__FILE__, __LINE__, "wrong INDI property received."});
759 return -1;
760 }
761
762 float del = -1000000000;
763
764 if (ipRecv.find("current"))
765 {
766 del = ipRecv["current"].get<float>();
767 }
768
769 if (ipRecv.find("target"))
770 {
771 del = ipRecv["target"].get<float>();
772 }
773
774 if (del == -1000000000)
775 {
776 log<software_error>({__FILE__, __LINE__, "No requested delay"});
777 return 0;
778 }
779
780 std::unique_lock<std::mutex> lock(m_indiMutex);
781 m_triggerDelay = del;
782 updateIfChanged(m_indiP_delay, "target", m_triggerDelay);
783
784 m_restartSp = true;
785 return 0;
786}
787
788INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_separation)(const pcf::IndiProperty &ipRecv)
789{
790 if (ipRecv.getName() != m_indiP_separation.getName())
791 {
792 log<software_error>({__FILE__, __LINE__, "wrong INDI property received."});
793 return -1;
794 }
795
796 float sep = -1000000000;
797
798 if (ipRecv.find("current"))
799 {
800 sep = ipRecv["current"].get<float>();
801 }
802
803 if (ipRecv.find("target"))
804 {
805 sep = ipRecv["target"].get<float>();
806 }
807
808 if (sep == -1000000000)
809 {
810 log<software_error>({__FILE__, __LINE__, "No requested separation"});
811 return 0;
812 }
813
814 std::unique_lock<std::mutex> lock(m_indiMutex);
815 m_separation = sep;
816 updateIfChanged(m_indiP_separation, "target", m_separation);
817
818 m_restartSp = true;
819
820 return 0;
821}
822
823INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_angle)
824(const pcf::IndiProperty &ipRecv)
825{
826 if (ipRecv.getName() != m_indiP_angle.getName())
827 {
828 log<software_error>({__FILE__, __LINE__, "wrong INDI property received."});
829 return -1;
830 }
831
832 float ang = -1000000000;
833
834 if (ipRecv.find("current"))
835 {
836 ang = ipRecv["current"].get<float>();
837 }
838
839 if (ipRecv.find("target"))
840 {
841 ang = ipRecv["target"].get<float>();
842 }
843
844 if (ang == -1000000000)
845 {
846 log<software_error>({__FILE__, __LINE__, "No angle received"});
847 return 0;
848 }
849
850 std::unique_lock<std::mutex> lock(m_indiMutex);
851 m_angle = ang;
852 updateIfChanged(m_indiP_angle, "target", m_angle);
853
854 m_restartSp = true;
855 return 0;
856}
857
858INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_amp)
859(const pcf::IndiProperty &ipRecv)
860{
861 if (ipRecv.getName() != m_indiP_amp.getName())
862 {
863 log<software_error>({__FILE__, __LINE__, "wrong INDI property received."});
864 return -1;
865 }
866
867 float amp = -1000000000;
868
869 if (ipRecv.find("current"))
870 {
871 amp = ipRecv["current"].get<float>();
872 }
873
874 if (ipRecv.find("target"))
875 {
876 amp = ipRecv["target"].get<float>();
877 }
878
879 if (amp == -1000000000)
880 {
881 log<software_error>({__FILE__, __LINE__, "Invalid requested amp: " + std::to_string(amp)});
882 return 0;
883 }
884
885 std::unique_lock<std::mutex> lock(m_indiMutex);
886 m_amp = amp;
887 updateIfChanged(m_indiP_amp, "target", m_amp);
888
889 m_restartSp = true;
890 return 0;
891}
892
893INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_cross)
894(const pcf::IndiProperty &ipRecv)
895{
896 if (ipRecv.createUniqueKey() != m_indiP_cross.createUniqueKey())
897 {
898 log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
899 return -1;
900 }
901
902 if (!ipRecv.find("toggle"))
903 return 0;
904
905 std::unique_lock<std::mutex> lock(m_indiMutex);
906
907 if (ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
908 {
909 m_cross = false;
910 indi::updateSwitchIfChanged(m_indiP_cross, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE);
911 }
912
913 if (ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
914 {
915 m_cross = true;
916 indi::updateSwitchIfChanged(m_indiP_cross, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK);
917 }
918
919 m_restartSp = true;
920
921 return 0;
922}
923
924INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_frequency)
925(const pcf::IndiProperty &ipRecv)
926{
927 if (ipRecv.getName() != m_indiP_frequency.getName())
928 {
929 log<software_error>({__FILE__, __LINE__, "wrong INDI property received."});
930 return -1;
931 }
932
933 float freq = -1;
934
935 if (ipRecv.find("current"))
936 {
937 freq = ipRecv["current"].get<float>();
938 }
939
940 if (ipRecv.find("target"))
941 {
942 freq = ipRecv["target"].get<float>();
943 }
944
945 if (freq < 0)
946 {
947 log<software_error>({__FILE__, __LINE__, "Invalid requested frequency: " + std::to_string(freq)});
948 return 0;
949 }
950
951 std::unique_lock<std::mutex> lock(m_indiMutex);
953 updateIfChanged(m_indiP_frequency, "target", m_frequency);
954
955 m_restartSp = true;
956
957 return 0;
958}
959
960INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_dwell)
961(const pcf::IndiProperty &ipRecv)
962{
963 if (ipRecv.createUniqueKey() != m_indiP_dwell.createUniqueKey())
964 {
965 log<software_error>({__FILE__, __LINE__, "wrong INDI property received."});
966 return -1;
967 }
968
969 unsigned dwell = 0;
970
971 if (ipRecv.find("current"))
972 {
973 dwell = ipRecv["current"].get<unsigned>();
974 }
975
976 if (ipRecv.find("target"))
977 {
978 dwell = ipRecv["target"].get<unsigned>();
979 }
980
981 if (dwell == 0)
982 {
983 log<software_error>({__FILE__, __LINE__, "Invalid requested dwell: " + std::to_string(dwell)});
984 return 0;
985 }
986
987 std::unique_lock<std::mutex> lock(m_indiMutex);
988 m_dwell = dwell;
989 updateIfChanged(m_indiP_dwell, "target", m_dwell);
990
991 m_restartSp = true;
992
993 return 0;
994}
995
996INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_single)
997(const pcf::IndiProperty &ipRecv)
998{
999 INDI_VALIDATE_CALLBACK_PROPS(m_indiP_single, ipRecv);
1000
1001 int single = 0;
1002
1003 if (ipRecv.find("current"))
1004 {
1005 single = ipRecv["current"].get<int>();
1006 }
1007
1008 if (ipRecv.find("target"))
1009 {
1010 single = ipRecv["target"].get<int>();
1011 }
1012
1013 if (single < -1 || single > 3 )
1014 {
1015 log<software_error>({__FILE__, __LINE__, "Invalid requested dwell: " + std::to_string(single)});
1016 return 0;
1017 }
1018
1019 std::unique_lock<std::mutex> lock(m_indiMutex);
1021 updateIfChanged(m_indiP_single, "target", m_single);
1022 return 0;
1023}
1024
1025INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_modulating)
1026(const pcf::IndiProperty &ipRecv)
1027{
1028 if (ipRecv.getName() != m_indiP_modulating.getName())
1029 {
1030 log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1031 return -1;
1032 }
1033
1034 if (!ipRecv.find("toggle"))
1035 return 0;
1036
1037 std::unique_lock<std::mutex> lock(m_indiMutex);
1038
1039 if (ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
1040 {
1041 m_modulating = false;
1042 indi::updateSwitchIfChanged(m_indiP_modulating, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE);
1043 }
1044
1045 if (ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
1046 {
1047 m_modulating = true;
1048 indi::updateSwitchIfChanged(m_indiP_modulating, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK);
1049 }
1050
1051
1052 return 0;
1053}
1054
1055INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_zero)
1056(const pcf::IndiProperty &ipRecv)
1057{
1058 if (ipRecv.getName() != m_indiP_zero.getName())
1059 {
1060 log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1061 return -1;
1062 }
1063
1064 if (m_modulating == true)
1065 {
1066 log<text_log>("zero requested but currently modulating", logPrio::LOG_NOTICE);
1067 return 0;
1068 }
1069
1070 if (!ipRecv.find("request"))
1071 return 0;
1072
1073 if (ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
1074 {
1075 m_imageStream.md->write = 1;
1076
1077 memset(m_imageStream.array.raw, 0, m_width * m_height * m_typeSize);
1078 timespec currtime;
1079 clock_gettime(CLOCK_REALTIME, &currtime);
1080 m_imageStream.md->atime = currtime;
1081 m_imageStream.md->writetime = currtime;
1082
1083 m_imageStream.md->cnt0++;
1084
1085 m_imageStream.md->write = 0;
1086 ImageStreamIO_sempost(&m_imageStream, -1);
1087 log<text_log>("zeroed");
1088 }
1089
1090 return 0;
1091}
1092
1097
1099{
1100 return recordDmSpeck(true);
1101}
1102
1103inline int dmSpeckle::recordDmSpeck(bool force)
1104{
1105 static bool lastModulating = m_modulating;
1106 static bool lastTrigger = m_trigger;
1107 static float lastFrequency = m_frequency;
1108 static float lastSeparation = m_separation;
1109 static float lastAngle = m_angle;
1110 static float lastAmp = m_amp;
1111 static bool lastCross = m_cross;
1112
1113 if (!(lastModulating == m_modulating) ||
1114 !(lastTrigger == m_trigger) ||
1115 !(lastFrequency == m_frequency) ||
1117 !(lastAngle == m_angle) ||
1118 !(lastAmp == m_amp) ||
1119 !(lastCross == m_cross) ||
1120 force)
1121 {
1123 std::vector<float>({m_angle}), std::vector<float>({m_amp}), std::vector<bool>({m_cross})});
1124
1130 lastAmp = m_amp;
1132 }
1133
1134 return 0;
1135}
1136
1137} // namespace app
1138} // namespace MagAOX
1139
1140#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:47
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:52
unsigned m_dwell
The dwell time for each speckle, or for how many frames it is held.
Definition dmSpeckle.hpp:83
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:73
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:91
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_amp)
size_t m_typeSize
The size of the type, in bytes.
Definition dmSpeckle.hpp:98
int m_triggerSemaphore
The semaphore to use (default 9)
Definition dmSpeckle.hpp:67
pcf::IndiProperty m_indiP_trigger
mx::improc::eigenCube< realT > m_shapes
Definition dmSpeckle.hpp:88
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:69
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:63
uint32_t m_height
The height of the image.
Definition dmSpeckle.hpp:92
realT m_angleOffset
The calibration offset of angle so that up on camsci1/2 is 0.
Definition dmSpeckle.hpp:75
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:77
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:79
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:71
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.
Definition dmSpeckle.hpp:97
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:59
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:81
int m_single
if >= 0 a single frame is non-zero.
Definition dmSpeckle.hpp:85
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_modulating)
std::string m_dmChannelName
The name of the DM channel to write to.
Definition dmSpeckle.hpp:61
pcf::IndiProperty m_indiP_dwell
int recordDmSpeck(bool force=false)
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
@ READY
The device is ready for operation, but is not operating.
@ CONNECTED
The application has connected to the device or service.
@ NOTCONNECTED
The application is not connected to the device or service.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
#define INDI_IDLE
Definition indiUtils.hpp:28
#define INDI_OK
Definition indiUtils.hpp:29
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:24
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
static constexpr logPrioT LOG_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 ERR log entry.
Log entry recording stdMotionStage status.
A simple text log, a string-type log.
Definition text_log.hpp:24