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 namespace MagAOX
38 {
39 namespace app
40 {
41 
42 /// The MagAO-X DM mode commander
43 /**
44  * \ingroup dmSpeckle
45  */
46 class 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 
54 protected:
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};
106 public:
107  /// Default c'tor.
108  dmSpeckle();
109 
110  /// D'tor, declared and defined for noexcept.
111  ~dmSpeckle() 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 
143 protected:
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:
172 protected:
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 
187 public:
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 
213 dmSpeckle::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 
241 int 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;
298  m_indiP_separation["target"] = 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  {
309  log<software_error>({__FILE__, __LINE__});
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  {
334  log<software_error>({__FILE__, __LINE__});
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  {
359  log<software_error>({__FILE__, __LINE__});
360  return -1;
361  }
362 
365  {
366  log<software_error>({__FILE__, __LINE__});
367  return -1;
368  }
369 
371  {
372  log<software_critical>({__FILE__, __LINE__});
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 
393  if (ImageStreamIO_openIm(&m_imageStream, m_dmChannelName.c_str()) == 0)
394  {
395  if (m_imageStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
396  {
397  ImageStreamIO_closeIm(&m_imageStream);
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  {
408  if (ImageStreamIO_openIm(&m_triggerStream, m_dmTriggerChannel.c_str()) == 0)
409  {
410  if (m_triggerStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
411  {
412  ImageStreamIO_closeIm(&m_triggerStream);
413  m_opened = false;
414  }
415  }
416  }
417 
418  if (m_opened)
419  {
421  }
422  }
423 
424  if (state() == stateCodes::CONNECTED)
425  {
426  m_dataType = m_imageStream.md[0].datatype;
427  m_typeSize = ImageStreamIO_typesize(m_dataType);
428  m_width = m_imageStream.md[0].size[0];
429  m_height = m_imageStream.md[0].size[1];
430 
431  if (m_dataType != _DATATYPE_FLOAT)
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  {
446  log<software_error>({__FILE__, __LINE__});
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 {
542  m_modThreadID = syscall(SYS_gettid);
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;
563  int64_t dnsec;
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  {
579  ImageStreamIO_semflush(&m_triggerStream, m_triggerSemaphore);
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
586  log<telem_dmspeck>({m_modulating, m_trigger, m_frequency, std::vector<float>({m_separation}),
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;
593  clock_gettime(CLOCK_REALTIME, &modstart);
594 
595  unsigned dwelled = 0;
596  if (m_dwell == 0)
597  m_dwell = 1;
598 
599 
600  float triggerDelay = m_triggerDelay/1e6;
601 
602  double t0, t1;
603 
604  while (m_modulating && !m_restartSp && !m_shutdown)
605  {
606  if (m_trigger)
607  {
608  timespec ts;
609 
610  if (clock_gettime(CLOCK_REALTIME, &ts) < 0)
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);
654  clock_gettime(CLOCK_REALTIME, &currtime);
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;
682  ImageStreamIO_sempost(&m_imageStream, -1);
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
705  clock_gettime(CLOCK_REALTIME, &currtime);
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;
717  ImageStreamIO_sempost(&m_imageStream, -1);
718  log<text_log>("zeroed");
719  }
720  }
721 }
722 
723 INDI_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 
749  m_restartSp = true;
750 
751  return 0;
752 }
753 
754 INDI_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 
788 INDI_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 
823 INDI_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 
858 INDI_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 
893 INDI_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 
924 INDI_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);
952  m_frequency = freq;
953  updateIfChanged(m_indiP_frequency, "target", m_frequency);
954 
955  m_restartSp = true;
956 
957  return 0;
958 }
959 
960 INDI_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 
996 INDI_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 
1025 INDI_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 
1055 INDI_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 
1094 {
1096 }
1097 
1099 {
1100  return recordDmSpeck(true);
1101 }
1102 
1103 inline 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) ||
1116  !(lastSeparation == m_separation) ||
1117  !(lastAngle == m_angle) ||
1118  !(lastAmp == m_amp) ||
1119  !(lastCross == m_cross) ||
1120  force)
1121  {
1122  telem<telem_dmspeck>({m_modulating, m_trigger, m_frequency, std::vector<float>({m_separation}),
1123  std::vector<float>({m_angle}), std::vector<float>({m_amp}), std::vector<bool>({m_cross})});
1124 
1125  lastModulating = m_modulating;
1126  lastTrigger = m_trigger;
1127  lastFrequency = m_frequency;
1128  lastSeparation = m_separation;
1129  lastAngle = m_angle;
1130  lastAmp = m_amp;
1131  lastCross = m_cross;
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.
Definition: MagAOXApp.hpp:3120
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:2573
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2297
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:2543
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:100
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
Definition: MagAOXApp.hpp:542
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1804
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:2157
The MagAO-X DM mode commander.
Definition: dmSpeckle.hpp:47
pid_t m_modThreadID
Modulate thread PID.
Definition: dmSpeckle.hpp:159
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_delay)
virtual void loadConfig()
Definition: dmSpeckle.hpp:277
static void modThreadStart(dmSpeckle *d)
Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
Definition: dmSpeckle.hpp:535
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
Definition: dmSpeckle.hpp:184
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:157
~dmSpeckle() noexcept
D'tor, declared and defined for noexcept.
Definition: dmSpeckle.hpp:111
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
Definition: dmSpeckle.hpp:175
mx::improc::eigenCube< realT > m_shapes
Definition: dmSpeckle.hpp:88
void modThreadExec()
Execute the frame grabber main loop.
Definition: dmSpeckle.hpp:540
virtual int appStartup()
Startup function.
Definition: dmSpeckle.hpp:282
bool m_trigger
Run in trigger mode if true (default)
Definition: dmSpeckle.hpp:69
virtual int appShutdown()
Shutdown the app.
Definition: dmSpeckle.hpp:453
pcf::IndiProperty m_indiP_cross
Definition: dmSpeckle.hpp:180
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.
Definition: dmSpeckle.hpp:386
pcf::IndiProperty m_indiP_single
Definition: dmSpeckle.hpp:183
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_separation)
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
Definition: dmSpeckle.hpp:241
realT m_amp
The speckle amplitude on the DM.
Definition: dmSpeckle.hpp:77
pcf::IndiProperty m_indiP_frequency
Definition: dmSpeckle.hpp:181
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_single)
int m_modThreadPrio
Priority of the modulator thread, should normally be > 00.
Definition: dmSpeckle.hpp:151
pcf::IndiProperty m_indiP_angle
Definition: dmSpeckle.hpp:178
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:155
pcf::IndiProperty m_indiP_amp
Definition: dmSpeckle.hpp:179
pcf::IndiProperty m_indiP_zero
Definition: dmSpeckle.hpp:185
pcf::IndiProperty m_indiP_separation
Definition: dmSpeckle.hpp:177
realT m_separation
The radial separation of the speckles (default 15.0)
Definition: dmSpeckle.hpp:71
pcf::IndiProperty m_indiP_dm
Definition: dmSpeckle.hpp:174
pcf::IndiProperty m_indiP_delay
Definition: dmSpeckle.hpp:176
INDI_NEWCALLBACK_DECL(dmSpeckle, m_indiP_zero)
dmSpeckle()
Default c'tor.
Definition: dmSpeckle.hpp:213
virtual void setupConfig()
Definition: dmSpeckle.hpp:218
std::string m_modThreadCpuset
The cpuset for the modulator thread.
Definition: dmSpeckle.hpp:153
int recordTelem(const telem_dmspeck *)
Definition: dmSpeckle.hpp:1098
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.
Definition: dmSpeckle.hpp:161
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
Definition: dmSpeckle.hpp:182
int recordDmSpeck(bool force=false)
Definition: dmSpeckle.hpp:1103
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
Definition: indiMacros.hpp:248
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
Definition: indiMacros.hpp:208
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:56
@ CONNECTED
The application has connected to the device or service.
Definition: stateCodes.hpp:50
@ NOTCONNECTED
The application is not connected to the device or service.
Definition: stateCodes.hpp:49
#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.
Definition: indiUtils.hpp:212
INDI_VALIDATE_CALLBACK_PROPS(function, ipRecv)
void nanoSleep(unsigned long nsec)
const pcf::IndiProperty & ipRecv
Definition: MagAOXApp.hpp:3434
updateIfChanged(m_indiP_angle, "target", m_angle)
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
std::unique_lock< std::mutex > lock(m_indiMutex)
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 base class which saves telemetry.
Definition: telemeter.hpp:69
int appShutdown()
Perform telemeter application shutdown.
Definition: telemeter.hpp:274
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Definition: telemeter.hpp:223
int appLogic()
Perform telemeter application logic.
Definition: telemeter.hpp:268
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:281
Software ERR log entry.
Log entry recording stdMotionStage status.
A simple text log, a string-type log.
Definition: text_log.hpp:24