API
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 
37 
38 namespace MagAOX
39 {
40 namespace app
41 {
42 
43 /// The MagAO-X DM mode commander
44 /**
45  * \ingroup dmSpeckle
46  */
47 class dmSpeckle : public MagAOXApp<true>, public dev::telemeter<dmSpeckle>
48 {
49 
50  typedef float realT;
51 
52  friend class dev::telemeter<dmSpeckle>;
53  friend class dmSpeckle_test;
54 
55 protected:
56 
57  /** \name Configurable Parameters
58  *@{
59  */
60 
61  std::string m_dmName; ///< The descriptive name of this dm. Default is the channel name.
62 
63  std::string m_dmChannelName; ///< The name of the DM channel to write to.
64 
65  std::string m_dmTriggerChannel; ///< The DM channel to monitor as a trigger
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  ///@}
86 
87  mx::improc::eigenCube<realT> m_shapes;
88 
89  IMAGE m_imageStream;
90  uint32_t m_width {0}; ///< The width of the image
91  uint32_t m_height {0}; ///< The height of the image.
92 
94 
95  uint8_t m_dataType{0}; ///< The ImageStreamIO type code.
96  size_t m_typeSize {0}; ///< The size of the type, in bytes.
97 
98  bool m_opened {true};
99  bool m_restart {false};
100 
101  bool m_modulating {false};
102 
103 public:
104  /// Default c'tor.
105  dmSpeckle();
106 
107  /// D'tor, declared and defined for noexcept.
108  ~dmSpeckle() noexcept
109  {}
110 
111  virtual void setupConfig();
112 
113  /// Implementation of loadConfig logic, separated for testing.
114  /** This is called by loadConfig().
115  */
116  int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
117 
118  virtual void loadConfig();
119 
120  /// Startup function
121  /**
122  *
123  */
124  virtual int appStartup();
125 
126  /// Implementation of the FSM for dmSpeckle.
127  /**
128  * \returns 0 on no critical error
129  * \returns -1 on an error requiring shutdown
130  */
131  virtual int appLogic();
132 
133  /// Shutdown the app.
134  /**
135  *
136  */
137  virtual int appShutdown();
138 
139 protected:
140 
141  int generateSpeckles();
142 
143  /** \name Modulator Thread
144  * This thread sends the signal to the dm at the prescribed frequency
145  *
146  * @{
147  */
148  int m_modThreadPrio {60}; ///< Priority of the modulator thread, should normally be > 00.
149 
150  std::string m_modThreadCpuset; ///< The cpuset for the modulator thread.
151 
152  std::thread m_modThread; ///< A separate thread for the modulation
153 
154  bool m_modThreadInit {true}; ///< Synchronizer to ensure f.g. thread initializes before doing dangerous things.
155 
156  pid_t m_modThreadID {0}; ///< Modulate thread PID.
157 
158  pcf::IndiProperty m_modThreadProp; ///< The property to hold the modulator thread details.
159 
160  ///Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
161  static void modThreadStart( dmSpeckle * d /**< [in] a pointer to a dmSpeckle instance (normally this) */);
162 
163  /// Execute the frame grabber main loop.
164  void modThreadExec();
165 
166  ///@}
167 
168  //INDI:
169 protected:
170  //declare our properties
171  pcf::IndiProperty m_indiP_dm;
172  pcf::IndiProperty m_indiP_trigger;
173  pcf::IndiProperty m_indiP_separation;
174  pcf::IndiProperty m_indiP_angle;
175  pcf::IndiProperty m_indiP_amp;
176  pcf::IndiProperty m_indiP_cross;
177  pcf::IndiProperty m_indiP_frequency;
178  pcf::IndiProperty m_indiP_dwell;
179  pcf::IndiProperty m_indiP_modulating;
180  pcf::IndiProperty m_indiP_zero;
181 
182 public:
192 
193 
194  /** \name Telemeter Interface
195  *
196  * @{
197  */
198  int checkRecordTimes();
199 
200  int recordTelem( const telem_dmspeck * );
201 
202  int recordDmSpeck(bool force = false);
203 
204  ///@}
205 };
206 
207 dmSpeckle::dmSpeckle() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
208 {
209  return;
210 }
211 
213 {
214  config.add("dm.name", "", "dm.name", argType::Required, "dm", "name", false, "string", "The descriptive name of this dm. Default is the channel name.");
215  config.add("dm.channelName", "", "dm.channelName", argType::Required, "dm", "channelName", false, "string", "The name of the DM channel to write to.");
216  config.add("dm.triggerChannel", "", "dm.triggerChannel", argType::Required, "dm", "triggerChannel", false, "string", "The name of the DM channel to trigger on.");
217  config.add("dm.triggerSemaphore", "", "dm.triggerSemaphore", argType::Required, "dm", "triggerSemaphore", false, "int", "The semaphore to use (default 9).");
218  config.add("dm.trigger", "", "dm.trigger", argType::True, "dm", "trigger", false, "bool", "Run in trigger mode if true (default).");
219  config.add("dm.separation", "", "dm.separation", argType::Required, "dm", "separation", false, "float", "The radial separation of the speckles (default 15.0).");
220  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).");
221  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.");
222  config.add("dm.amp", "", "dm.amp", argType::Required, "dm", "amp", false, "float", "The speckle amplitude on the DM (default 0.01).");
223  config.add("dm.cross", "", "dm.cross", argType::True, "dm", "cross", false, "bool", "If true, also apply the cross speckles rotated by 90 degrees.");
224 
225  config.add("dm.frequency", "", "dm.frequency", argType::Required, "dm", "frequency", false, "float", "The frequency to modulate at if not triggering (default 2000 Hz).");
226 
227  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.");
228 
229  config.add("modulator.threadPrio", "", "modulator.threadPrio", argType::Required, "modulator", "threadPrio", false, "int", "The real-time priority of the modulator thread.");
230 
231  config.add("modulator.cpuset", "", "modulator.cpuset", argType::Required, "modulator", "cpuset", false, "string", "The cpuset to assign the modulator thread to.");
232 
233 }
234 
235 int dmSpeckle::loadConfigImpl( mx::app::appConfigurator & _config )
236 {
237  _config(m_dmChannelName, "dm.channelName");
238 
240  _config(m_dmName, "dm.name");
241 
242  _config(m_dmTriggerChannel, "dm.triggerChannel");
243  _config(m_triggerSemaphore, "dm.triggerSemaphore");
244  if(_config.isSet("dm.trigger")) _config(m_trigger, "dm.trigger");
245  _config(m_separation, "dm.separation");
246  _config(m_angle, "dm.angle");
247  _config(m_angleOffset, "dm.angleOffset");
248  _config(m_amp, "dm.amp");
249  if(_config.isSet("dm.cross")) _config(m_cross, "dm.cross");
250  _config(m_frequency, "dm.frequency");
251  _config(m_dwell, "dm.dwell");
252  _config(m_modThreadPrio, "modulator.threadPrio");
253  _config(m_modThreadCpuset, "modulator.cpuset");
254 
256  return 0;
257 }
258 
260 {
261  loadConfigImpl(config);
262 }
263 
265 {
266 
267  REG_INDI_NEWPROP_NOCB(m_indiP_dm, "dm", pcf::IndiProperty::Text);
268  m_indiP_dm.add(pcf::IndiElement("name"));
269  m_indiP_dm["name"] = m_dmName;
270  m_indiP_dm.add(pcf::IndiElement("channel"));
271  m_indiP_dm["channel"] = m_dmChannelName;
272 
273  createStandardIndiNumber<float>(m_indiP_separation, "separation", 0, 0,100, "%f");
274  m_indiP_separation["current"] = m_separation;
275  m_indiP_separation["target"] = m_separation;
277 
278  createStandardIndiNumber<float>(m_indiP_angle, "angle", 0, 0,100, "%f");
279  m_indiP_angle["current"] = m_angle;
280  m_indiP_angle["target"] = m_angle;
282 
285  {
286  log<software_error>({__FILE__,__LINE__});
287  return -1;
288  }
289  if(m_cross)
290  {
291  m_indiP_cross["toggle"] = pcf::IndiElement::On;
292  }
293  else
294  {
295  m_indiP_cross["toggle"] = pcf::IndiElement::Off;
296  }
297 
298  createStandardIndiNumber<float>(m_indiP_amp, "amp", -1, 0,1, "%f");
299  m_indiP_amp["current"] = m_amp;
300  m_indiP_amp["target"] = m_amp;
302 
303  createStandardIndiNumber<float>(m_indiP_frequency, "frequency", 0, 0,10000, "%f");
304  m_indiP_frequency["current"] = m_frequency;
305  m_indiP_frequency["target"] = m_frequency;
307 
310  {
311  log<software_error>({__FILE__,__LINE__});
312  return -1;
313  }
314  if(m_trigger)
315  {
316  m_indiP_trigger["toggle"] = pcf::IndiElement::On;
317  }
318  else
319  {
320  m_indiP_trigger["toggle"] = pcf::IndiElement::Off;
321  }
322 
323  createStandardIndiNumber<float>(m_indiP_dwell, "dwell", 1, 100,1, "%d");
324  m_indiP_dwell["current"] = m_dwell;
325  m_indiP_dwell["target"] = m_dwell;
327 
330  {
331  log<software_error>({__FILE__,__LINE__});
332  return -1;
333  }
334 
337  {
338  log<software_error>({__FILE__,__LINE__});
339  return -1;
340  }
341 
343  {
344  log<software_critical>({__FILE__, __LINE__});
345  return -1;
346  }
347 
349  {
350  return log<software_error,-1>({__FILE__,__LINE__});
351  }
352 
354 
355 
356  return 0;
357 }
358 
360 {
362  {
363  m_opened = false;
364  m_restart = false; //Set this up front, since we're about to restart.
365 
366  if( ImageStreamIO_openIm(&m_imageStream, m_dmChannelName.c_str()) == 0)
367  {
368  if(m_imageStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
369  {
370  ImageStreamIO_closeIm(&m_imageStream);
371  }
372  else
373  {
374  m_opened = true;
375  }
376  }
377 
378  //Only bother to try if previous worked and we have a spec
379  if(m_opened == true && m_dmTriggerChannel != "")
380  {
381  if( ImageStreamIO_openIm(&m_triggerStream, m_dmTriggerChannel.c_str()) == 0)
382  {
383  if(m_triggerStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
384  {
385  ImageStreamIO_closeIm(&m_triggerStream);
386  m_opened = false;
387  }
388  }
389  }
390 
391  if(m_opened)
392  {
394  }
395  }
396 
398  {
399  m_dataType = m_imageStream.md[0].datatype;
400  m_typeSize = ImageStreamIO_typesize(m_dataType);
401  m_width = m_imageStream.md[0].size[0];
402  m_height = m_imageStream.md[0].size[1];
403 
404 
405  if(m_dataType != _DATATYPE_FLOAT )
406  {
407  return log<text_log,-1>("Data type of DM channel is not float.", logPrio::LOG_CRITICAL);
408  }
409 
410  if(m_typeSize != sizeof(realT))
411  {
412  return log<text_log,-1>("Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL);
413  }
414 
416  }
417 
419  {
420  log<software_error>({__FILE__, __LINE__});
421  return 0;
422  }
423 
424  return 0;
425 }
426 
428 {
429  if(m_modThread.joinable())
430  {
431  try
432  {
433  m_modThread.join(); //this will throw if it was already joined
434  }
435  catch(...)
436  {
437  }
438  }
439 
441 
442  return 0;
443 }
444 
446 {
447  mx::improc::eigenImage<realT> onesp, onespC;
448  onesp.resize(m_width, m_height);
449  onespC.resize(m_width, m_height);
450 
451  m_shapes.resize(m_width, m_height, 4);
452 
453  realT m = m_separation * cos( mx::math::dtor<realT>(-1*m_angle + m_angleOffset));
454  realT n = m_separation * sin( mx::math::dtor<realT>(-1*m_angle + m_angleOffset));
455 
456  mx::sigproc::makeFourierMode(m_shapes.image(0), m, n, 1);
457 
458  if(m_cross)
459  {
460  onesp = m_shapes.image(0);
461  mx::sigproc::makeFourierMode(m_shapes.image(0), -n, m, 1);
462  m_shapes.image(0) += onesp;
463  }
464 
465  m_shapes.image(0) *= m_amp;
466  m_shapes.image(1) = -1*m_shapes.image(0);
467 
468  mx::sigproc::makeFourierMode(m_shapes.image(2), m, n, -1);
469 
470  if(m_cross)
471  {
472  onesp = m_shapes.image(2);
473  mx::sigproc::makeFourierMode(m_shapes.image(2), -n, m, -1);
474  m_shapes.image(2) += onesp;
475  }
476 
477  m_shapes.image(2) *= m_amp;
478  m_shapes.image(3) = -m_shapes.image(2);
479 
480  mx::fits::fitsFile<realT> ff;
481  ff.write("/tmp/specks.fits", m_shapes);
482 
485  updateIfChanged(m_indiP_amp, "current", m_amp);
488 
489  return 0;
490 }
491 
492 inline
494 {
495  d->modThreadExec();
496 }
497 
498 inline
500 {
501  m_modThreadID = syscall(SYS_gettid);
502 
503  //Wait fpr the thread starter to finish initializing this thread.
504  while( (m_modThreadInit == true || state() != stateCodes::READY) && m_shutdown == 0)
505  {
506  sleep(1);
507  }
508 
509  while(m_shutdown == 0)
510  {
511  if(!m_modulating && !m_shutdown) //If we aren't modulating we sleep for 1/2 a second
512  {
513  mx::sys::milliSleep(500);
514  }
515 
516  if(m_modulating && !m_shutdown)
517  {
519 
520  int64_t freqNsec = (1.0/m_frequency)*1e9;
521  int64_t dnsec;
522 
523  int idx = 0;
524 
525  timespec modstart;
526  timespec currtime;
527 
528  bool triggered = false;
529  sem_t * sem = nullptr;
530  if(m_dmTriggerChannel == "")
531  {
532  m_trigger = false;
533  indi::updateSwitchIfChanged(m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE);
534  }
535  else if(m_trigger == true)
536  {
537  ImageStreamIO_semflush(&m_triggerStream, m_triggerSemaphore);
538 
539  sem = m_triggerStream.semptr[m_triggerSemaphore]; ///< The semaphore to monitor for new image data
540  }
541 
542 
543  log<text_log>("started modulating",logPrio::LOG_NOTICE);
544  //To send a message
545  log<telem_dmspeck>({m_modulating, m_trigger, m_frequency, std::vector<float>({m_separation}),
546  std::vector<float>({m_angle}), std::vector<float>({m_amp}), std::vector<bool>({m_cross})}, logPrio::LOG_INFO);
547  //The official record:
548  recordDmSpeck(true);
549 
550  dnsec = 0;
551  clock_gettime(CLOCK_REALTIME, &modstart);
552 
553  unsigned dwelled = 0;
554  if(m_dwell == 0) m_dwell = 1;
555 
556  while(m_modulating && !m_shutdown)
557  {
558  if(m_trigger)
559  {
560  timespec ts;
561 
562  if(clock_gettime(CLOCK_REALTIME, &ts) < 0)
563  {
564  log<software_critical>({__FILE__,__LINE__,errno,0,"clock_gettime"});
565  return;
566  }
567 
568  ts.tv_sec += 1;
569 
570  if(sem_timedwait(sem, &ts) == 0)
571  {
572  triggered = true;
573  }
574  else
575  {
576  triggered = false;
577 
578  //Check for why we timed out
579  if(errno == EINTR) break; //This indicates signal interrupted us, time to restart or shutdown, loop will exit normally if flags set.
580 
581  //ETIMEDOUT just means we should wait more.
582  //Otherwise, report an error.
583  if(errno != ETIMEDOUT)
584  {
585  log<software_error>({__FILE__, __LINE__,errno, "sem_timedwait"});
586  break;
587  }
588  }
589  }
590  else
591  {
592  mx::sys::nanoSleep(0.5*dnsec);
593  clock_gettime(CLOCK_REALTIME, &currtime);
594 
595  dnsec = (currtime.tv_sec - modstart.tv_sec)*1000000000 + (currtime.tv_nsec - modstart.tv_nsec);
596  triggered = false;
597  }
598 
599  if(dwelled < m_dwell - 1)
600  {
601  ++dwelled;
602  }
603  else if(dnsec >= freqNsec || triggered)
604  {
605  //Do the write
606  dwelled = 0;
607 
608  m_imageStream.md->write = 1;
609 
610  memcpy(m_imageStream.array.raw, m_shapes.image(idx).data(), m_width*m_height*m_typeSize);
611 
612  m_imageStream.md->atime = currtime;
613  m_imageStream.md->writetime = currtime;
614 
615  if(!m_trigger) m_imageStream.md->cnt0++;
616 
617  m_imageStream.md->write=0;
618  ImageStreamIO_sempost(&m_imageStream,-1);
619 
620  ++idx;
621  if(idx >= m_shapes.planes()) idx=0;
622 
623  if(!m_trigger)
624  {
625  modstart.tv_nsec += freqNsec;
626  if(modstart.tv_nsec >= 1000000000)
627  {
628  modstart.tv_nsec -= 1000000000;
629  modstart.tv_sec += 1;
630  }
631  dnsec = freqNsec;
632  }
633  }
634  }
635  recordDmSpeck(true);
636  log<text_log>("stopped modulating", logPrio::LOG_NOTICE);
637  //Always zero when done
638  clock_gettime(CLOCK_REALTIME, &currtime);
639  m_imageStream.md->write = 1;
640 
641  memset(m_imageStream.array.raw, 0.0, m_width*m_height*m_typeSize);
642 
643  m_imageStream.md->atime = currtime;
644  m_imageStream.md->writetime = currtime;
645 
646  if(!m_trigger) m_imageStream.md->cnt0++;
647 
648  m_imageStream.md->write=0;
649  ImageStreamIO_sempost(&m_imageStream,-1);
650  log<text_log>("zeroed");
651 
652  }
653  }
654 
655 }
656 
657 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_trigger)(const pcf::IndiProperty &ipRecv)
658 {
659  if(ipRecv.getName() != m_indiP_trigger.getName())
660  {
661  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
662  return -1;
663  }
664 
665  if(!ipRecv.find("toggle")) return 0;
666 
667  std::unique_lock<std::mutex> lock(m_indiMutex);
668 
669  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
670  {
671  m_trigger = false;
672  indi::updateSwitchIfChanged(m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE);
673  }
674 
675  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
676  {
677  m_trigger = true;
678  indi::updateSwitchIfChanged(m_indiP_trigger, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK);
679  }
680 
681  return 0;
682 }
683 
684 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_separation)(const pcf::IndiProperty &ipRecv)
685 {
686  if(ipRecv.getName() != m_indiP_separation.getName())
687  {
688  log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
689  return -1;
690  }
691 
692  float sep = -1000000000;
693 
694  if( ipRecv.find("current") )
695  {
696  sep = ipRecv["current"].get<float>();
697  }
698 
699  if( ipRecv.find("target") )
700  {
701  sep = ipRecv["target"].get<float>();
702  }
703 
704  if(sep == -1000000000)
705  {
706  log<software_error>({__FILE__,__LINE__, "No requested separation"});
707  return 0;
708  }
709 
710  std::unique_lock<std::mutex> lock(m_indiMutex);
711  m_separation = sep;
712  updateIfChanged(m_indiP_separation, "target", m_separation);
713  return 0;
714 }
715 
716 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_angle)(const pcf::IndiProperty &ipRecv)
717 {
718  if(ipRecv.getName() != m_indiP_angle.getName())
719  {
720  log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
721  return -1;
722  }
723 
724  float ang = -1000000000;
725 
726  if( ipRecv.find("current") )
727  {
728  ang = ipRecv["current"].get<float>();
729  }
730 
731  if( ipRecv.find("target") )
732  {
733  ang = ipRecv["target"].get<float>();
734  }
735 
736  if(ang == -1000000000)
737  {
738  log<software_error>({__FILE__,__LINE__, "No angle received"});
739  return 0;
740  }
741 
742  std::unique_lock<std::mutex> lock(m_indiMutex);
743  m_angle = ang;
744  updateIfChanged(m_indiP_angle, "target", m_angle);
745  return 0;
746 }
747 
748 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_amp)(const pcf::IndiProperty &ipRecv)
749 {
750  if(ipRecv.getName() != m_indiP_amp.getName())
751  {
752  log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
753  return -1;
754  }
755 
756  float amp = -1000000000;
757 
758  if( ipRecv.find("current") )
759  {
760  amp = ipRecv["current"].get<float>();
761  }
762 
763  if( ipRecv.find("target") )
764  {
765  amp = ipRecv["target"].get<float>();
766  }
767 
768  if(amp == -1000000000)
769  {
770  log<software_error>({__FILE__,__LINE__, "Invalid requested amp: " + std::to_string(amp)});
771  return 0;
772  }
773 
774  std::unique_lock<std::mutex> lock(m_indiMutex);
775  m_amp = amp;
776  updateIfChanged(m_indiP_amp, "target", m_amp);
777  return 0;
778 }
779 
780 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_cross)(const pcf::IndiProperty &ipRecv)
781 {
782  if(ipRecv.createUniqueKey() != m_indiP_cross.createUniqueKey())
783  {
784  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
785  return -1;
786  }
787 
788  if(!ipRecv.find("toggle")) return 0;
789 
790  std::unique_lock<std::mutex> lock(m_indiMutex);
791 
792  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
793  {
794  m_cross = false;
795  indi::updateSwitchIfChanged(m_indiP_cross, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE);
796  }
797 
798  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
799  {
800  m_cross = true;
801  indi::updateSwitchIfChanged(m_indiP_cross, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK);
802  }
803 
804  return 0;
805 }
806 
807 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_frequency)(const pcf::IndiProperty &ipRecv)
808 {
809  if(ipRecv.getName() != m_indiP_frequency.getName())
810  {
811  log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
812  return -1;
813  }
814 
815  float freq = -1;
816 
817  if( ipRecv.find("current") )
818  {
819  freq = ipRecv["current"].get<float>();
820  }
821 
822  if( ipRecv.find("target") )
823  {
824  freq = ipRecv["target"].get<float>();
825  }
826 
827  if(freq < 0)
828  {
829  log<software_error>({__FILE__,__LINE__, "Invalid requested frequency: " + std::to_string(freq)});
830  return 0;
831  }
832 
833  std::unique_lock<std::mutex> lock(m_indiMutex);
834  m_frequency = freq;
835  updateIfChanged(m_indiP_frequency, "target", m_frequency);
836  return 0;
837 }
838 
839 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_dwell)(const pcf::IndiProperty &ipRecv)
840 {
841  if(ipRecv.createUniqueKey() != m_indiP_dwell.createUniqueKey())
842  {
843  log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
844  return -1;
845  }
846 
847  unsigned dwell = 0;
848 
849  if( ipRecv.find("current") )
850  {
851  dwell = ipRecv["current"].get<unsigned>();
852  }
853 
854  if( ipRecv.find("target") )
855  {
856  dwell = ipRecv["target"].get<unsigned>();
857  }
858 
859  if(dwell == 0)
860  {
861  log<software_error>({__FILE__,__LINE__, "Invalid requested dwell: " + std::to_string(dwell)});
862  return 0;
863  }
864 
865  std::unique_lock<std::mutex> lock(m_indiMutex);
866  m_dwell = dwell;
867  updateIfChanged(m_indiP_dwell, "target", m_dwell);
868  return 0;
869 }
870 
871 
872 
873 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_modulating)(const pcf::IndiProperty &ipRecv)
874 {
875  if(ipRecv.getName() != m_indiP_modulating.getName())
876  {
877  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
878  return -1;
879  }
880 
881  if(!ipRecv.find("toggle")) return 0;
882 
883  std::unique_lock<std::mutex> lock(m_indiMutex);
884 
885  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
886  {
887  m_modulating = false;
888  indi::updateSwitchIfChanged(m_indiP_modulating, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE);
889  }
890 
891  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
892  {
893  m_modulating = true;
894  indi::updateSwitchIfChanged(m_indiP_modulating, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK);
895  }
896 
897 
898  return 0;
899 }
900 
901 INDI_NEWCALLBACK_DEFN(dmSpeckle, m_indiP_zero)(const pcf::IndiProperty &ipRecv)
902 {
903  if(ipRecv.getName() != m_indiP_zero.getName())
904  {
905  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
906  return -1;
907  }
908 
909  if(m_modulating == true)
910  {
911  log<text_log>("zero requested but currently modulating", logPrio::LOG_NOTICE);
912  return 0;
913  }
914 
915  if(!ipRecv.find("request")) return 0;
916 
917  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
918  {
919  m_imageStream.md->write = 1;
920 
921  memset(m_imageStream.array.raw, 0, m_width*m_height*m_typeSize);
922  timespec currtime;
923  clock_gettime(CLOCK_REALTIME, &currtime);
924  m_imageStream.md->atime = currtime;
925  m_imageStream.md->writetime = currtime;
926 
927  m_imageStream.md->cnt0++;
928 
929  m_imageStream.md->write=0;
930  ImageStreamIO_sempost(&m_imageStream,-1);
931  log<text_log>("zeroed");
932  }
933 
934 
935  return 0;
936 }
937 
938 inline
940 {
942 }
943 
944 inline
946 {
947  return recordDmSpeck(true);
948 }
949 
950 inline
951 int dmSpeckle::recordDmSpeck( bool force )
952 {
953  static bool lastModulating = m_modulating;
954  static bool lastTrigger = m_trigger;
955  static float lastFrequency = m_frequency;
956  static float lastSeparation = m_separation;
957  static float lastAngle = m_angle;
958  static float lastAmp = m_amp;
959  static bool lastCross = m_cross;
960 
961  if( !(lastModulating == m_modulating) ||
962  !(lastTrigger == m_trigger) ||
963  !(lastFrequency == m_frequency) ||
964  !(lastSeparation == m_separation) ||
965  !(lastAngle == m_angle) ||
966  !(lastAmp == m_amp) ||
967  !(lastCross == m_cross) ||
968  force )
969  {
970  telem<telem_dmspeck>({m_modulating, m_trigger, m_frequency, std::vector<float>({m_separation}),
971  std::vector<float>({m_angle}), std::vector<float>({m_amp}), std::vector<bool>({m_cross})});
972 
973  lastModulating = m_modulating;
974  lastTrigger = m_trigger;
975  lastFrequency = m_frequency;
976  lastSeparation = m_separation;
977  lastAngle = m_angle;
978  lastAmp = m_amp;
979  lastCross = m_cross;
980  }
981 
982  return 0;
983 }
984 
985 } //namespace app
986 } //namespace MagAOX
987 
988 #endif //dmSpeckle_hpp
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:75
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.
Definition: MagAOXApp.hpp:2877
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.
Definition: MagAOXApp.hpp:2352
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2082
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.
Definition: MagAOXApp.hpp:2321
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:102
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
Definition: MagAOXApp.hpp:537
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1590
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.
Definition: MagAOXApp.hpp:1950
The MagAO-X DM mode commander.
Definition: dmSpeckle.hpp:48
pid_t m_modThreadID
Modulate thread PID.
Definition: dmSpeckle.hpp:156
virtual void loadConfig()
Definition: dmSpeckle.hpp:259
static void modThreadStart(dmSpeckle *d)
Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
Definition: dmSpeckle.hpp:493
friend class dmSpeckle_test
Definition: dmSpeckle.hpp:53
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
Definition: dmSpeckle.hpp:179
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.
Definition: dmSpeckle.hpp:154
~dmSpeckle() noexcept
D'tor, declared and defined for noexcept.
Definition: dmSpeckle.hpp:108
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_angle)
uint32_t m_width
The width of the image.
Definition: dmSpeckle.hpp:90
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_amp)
size_t m_typeSize
The size of the type, in bytes.
Definition: dmSpeckle.hpp:96
int m_triggerSemaphore
The semaphore to use (default 9)
Definition: dmSpeckle.hpp:67
pcf::IndiProperty m_indiP_trigger
Definition: dmSpeckle.hpp:172
mx::improc::eigenCube< realT > m_shapes
Definition: dmSpeckle.hpp:87
void modThreadExec()
Execute the frame grabber main loop.
Definition: dmSpeckle.hpp:499
virtual int appStartup()
Startup function.
Definition: dmSpeckle.hpp:264
bool m_trigger
Run in trigger mode if true (default)
Definition: dmSpeckle.hpp:69
virtual int appShutdown()
Shutdown the app.
Definition: dmSpeckle.hpp:427
pcf::IndiProperty m_indiP_cross
Definition: dmSpeckle.hpp:176
std::string m_dmTriggerChannel
The DM channel to monitor as a trigger.
Definition: dmSpeckle.hpp:65
uint32_t m_height
The height of the image.
Definition: dmSpeckle.hpp:91
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.
Definition: dmSpeckle.hpp:359
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_separation)
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
Definition: dmSpeckle.hpp:235
realT m_amp
The speckle amplitude on the DM.
Definition: dmSpeckle.hpp:77
pcf::IndiProperty m_indiP_frequency
Definition: dmSpeckle.hpp:177
int m_modThreadPrio
Priority of the modulator thread, should normally be > 00.
Definition: dmSpeckle.hpp:148
pcf::IndiProperty m_indiP_angle
Definition: dmSpeckle.hpp:174
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.
Definition: dmSpeckle.hpp:152
pcf::IndiProperty m_indiP_amp
Definition: dmSpeckle.hpp:175
pcf::IndiProperty m_indiP_zero
Definition: dmSpeckle.hpp:180
pcf::IndiProperty m_indiP_separation
Definition: dmSpeckle.hpp:173
realT m_separation
The radial separation of the speckles (default 15.0)
Definition: dmSpeckle.hpp:71
pcf::IndiProperty m_indiP_dm
Definition: dmSpeckle.hpp:171
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_zero)
dmSpeckle()
Default c'tor.
Definition: dmSpeckle.hpp:207
virtual void setupConfig()
Definition: dmSpeckle.hpp:212
std::string m_modThreadCpuset
The cpuset for the modulator thread.
Definition: dmSpeckle.hpp:150
int recordTelem(const telem_dmspeck *)
Definition: dmSpeckle.hpp:945
uint8_t m_dataType
The ImageStreamIO type code.
Definition: dmSpeckle.hpp:95
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_frequency)
pcf::IndiProperty m_modThreadProp
The property to hold the modulator thread details.
Definition: dmSpeckle.hpp:158
std::string m_dmName
The descriptive name of this dm. Default is the channel name.
Definition: dmSpeckle.hpp:61
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
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_modulating)
std::string m_dmChannelName
The name of the DM channel to write to.
Definition: dmSpeckle.hpp:63
pcf::IndiProperty m_indiP_dwell
Definition: dmSpeckle.hpp:178
int recordDmSpeck(bool force=false)
Definition: dmSpeckle.hpp:951
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
Definition: indiMacros.hpp:247
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
Definition: indiMacros.hpp:207
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:51
@ CONNECTED
The application has connected to the device or service.
Definition: stateCodes.hpp:45
@ NOTCONNECTED
The application is not connected to the device or service.
Definition: stateCodes.hpp:44
#define INDI_IDLE
Definition: indiUtils.hpp:28
#define INDI_OK
Definition: indiUtils.hpp:29
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
Definition: indiUtils.hpp:95
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.
Definition: indiUtils.hpp:206
const pcf::IndiProperty & ipRecv
void nanoSleep(unsigned long nsec)
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
Definition: dm.hpp:24
constexpr static logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
Definition: logPriority.hpp:37
constexpr static logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
Definition: logPriority.hpp:49
constexpr static logPrioT LOG_NOTICE
A normal but significant condition.
Definition: logPriority.hpp:46
A device which saves telemetry.
Definition: telemeter.hpp:52
int appShutdown()
Perform telemeter application shutdown.
Definition: telemeter.hpp:259
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Definition: telemeter.hpp:208
int appLogic()
Perform telemeter application logic.
Definition: telemeter.hpp:253
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Definition: telemeter.hpp:266
Software ERR log entry.
Log entry recording stdMotionStage status.
A simple text log, a string-type log.
Definition: text_log.hpp:24