API
sparkleClock.hpp
Go to the documentation of this file.
1 /** \file sparkleClock.hpp
2  * \brief The MagAO-X DM speckle maker header file
3  *
4  * \ingroup sparkleClock_files
5  */
6 
7 #ifndef sparkleClock_hpp
8 #define sparkleClock_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 sparkleClock
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/sparkleClock.html">Application Documentation</a>
28  *
29  * \ingroup apps
30  *
31  */
32 
33 /** \defgroup sparkleClock_files
34  * \ingroup sparkleClock
35  */
36 
37 namespace MagAOX
38 {
39 namespace app
40 {
41 
42 /// The MagAO-X DM mode commander
43 /**
44  * \ingroup sparkleClock
45  */
46 class sparkleClock : public MagAOXApp<true>, public dev::telemeter<sparkleClock>
47 {
48 
49  typedef float realT;
50 
51  friend class dev::telemeter<sparkleClock>;
52  friend class sparkleClock_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_interval{ 1.0 }; ///< time for one complete cycle of the sparkle clock (e.g. exposure time of some camera) (default 1.0)
72  realT m_separation_1{ 10.0 }; ///< The radial separation of the first set of speckles (default 10.0)
73  realT m_separation_2{ 20.0 }; ///< The radial separation of the second set of speckles (default 20.0)
74 
75  realT m_angle{ 0.0 }; ///< The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)
76 
77  realT m_angleOffset{ 28.0 }; ///< The calibration offset of angle so that up on camsci1/2 is 0
78 
79  realT m_amp{ 0.01 }; ///< The speckle amplitude on the DM
80 
81  bool m_cross{ true }; ///< If true, also apply the cross speckles rotated by 90 degrees
82 
83  realT m_frequency{ 2000 }; ///< The frequency to modulate at if not triggering (default 2000 Hz)
84  realT m_sparkleClockInterval{ 1 }; ///< The time in seconds during which the sparkle clock should sweep out both
85  ///< cycles (e.g. the exposure time of your science cam)
86 
87  unsigned m_dwell{ 1 }; ///< The dwell time for each speckle, or for how many frames it is held.
88 
89  int m_single{ -1 }; ///< if >= 0 a single frame is non-zero.
90  ///@}
91 
92  mx::improc::eigenCube<realT> m_shapes;
93 
95  uint32_t m_width{ 0 }; ///< The width of the image
96  uint32_t m_height{ 0 }; ///< The height of the image.
97 
99 
100  uint8_t m_dataType{ 0 }; ///< The ImageStreamIO type code.
101  size_t m_typeSize{ 0 }; ///< The size of the type, in bytes.
102 
103  bool m_opened{ true };
104  bool m_restart{ false };
105 
106  bool m_modulating{ false };
107 
108  bool m_restartSp{ false };
109 
110  public:
111  /// Default c'tor.
112  sparkleClock();
113 
114  /// D'tor, declared and defined for noexcept.
115  ~sparkleClock() noexcept
116  {
117  }
118 
119  virtual void setupConfig();
120 
121  /// Implementation of loadConfig logic, separated for testing.
122  /** This is called by loadConfig().
123  */
124  int loadConfigImpl(
125  mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
126 
127  virtual void loadConfig();
128 
129  /// Startup function
130  /**
131  *
132  */
133  virtual int appStartup();
134 
135  /// Implementation of the FSM for sparkleClock.
136  /**
137  * \returns 0 on no critical error
138  * \returns -1 on an error requiring shutdown
139  */
140  virtual int appLogic();
141 
142  /// Shutdown the app.
143  /**
144  *
145  */
146  virtual int appShutdown();
147 
148  protected:
149  int generateSparkleClock();
150 
151  /** \name Modulator Thread
152  * This thread sends the signal to the dm at the prescribed frequency
153  *
154  * @{
155  */
156  int m_modThreadPrio{ 60 }; ///< Priority of the modulator thread, should normally be > 00.
157 
158  std::string m_modThreadCpuset; ///< The cpuset for the modulator thread.
159 
160  std::thread m_modThread; ///< A separate thread for the modulation
161 
162  bool m_modThreadInit{ true }; ///< Synchronizer to ensure f.g. thread initializes before doing dangerous things.
163 
164  pid_t m_modThreadID{ 0 }; ///< Modulate thread PID.
165 
166  pcf::IndiProperty m_modThreadProp; ///< The property to hold the modulator thread details.
167 
168  /// Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
169  static void modThreadStart( sparkleClock *d /**< [in] a pointer to a sparkleClock instance (normally this) */ );
170 
171  /// Execute the frame grabber main loop.
172  void modThreadExec();
173 
174  ///@}
175 
176  // INDI:
177  protected:
178  // declare our properties
179  pcf::IndiProperty m_indiP_dm;
180  pcf::IndiProperty m_indiP_trigger;
181  pcf::IndiProperty m_indiP_delay;
182  pcf::IndiProperty m_indiP_separation_1;
183  pcf::IndiProperty m_indiP_separation_2;
184  pcf::IndiProperty m_indiP_angle;
185  pcf::IndiProperty m_indiP_amp;
186  pcf::IndiProperty m_indiP_cross;
187  pcf::IndiProperty m_indiP_frequency;
188  pcf::IndiProperty m_indiP_interval;
189  pcf::IndiProperty m_indiP_dwell;
190  pcf::IndiProperty m_indiP_single;
191  pcf::IndiProperty m_indiP_modulating;
192  pcf::IndiProperty m_indiP_zero;
193 
194  public:
208 
209  /** \name Telemeter Interface
210  *
211  * @{
212  */
213  int checkRecordTimes();
214 
215  int recordTelem( const telem_sparkleclock * );
216 
217  int recordSparkleClock( bool force = false );
218 
219  ///@}
220 };
221 
222 sparkleClock::sparkleClock() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
223 {
224  return;
225 }
226 
228 {
229  config.add( "dm.channelName",
230  "",
231  "dm.channelName",
232  argType::Required,
233  "dm",
234  "channelName",
235  false,
236  "string",
237  "The name of the DM channel to write to." );
238  config.add( "dm.triggerChannel",
239  "",
240  "dm.triggerChannel",
241  argType::Required,
242  "dm",
243  "triggerChannel",
244  false,
245  "string",
246  "The name of the DM channel to trigger on." );
247  config.add( "dm.triggerSemaphore",
248  "",
249  "dm.triggerSemaphore",
250  argType::Required,
251  "dm",
252  "triggerSemaphore",
253  false,
254  "int",
255  "The semaphore to use (default 9)." );
256  config.add( "dm.trigger",
257  "",
258  "dm.trigger",
259  argType::True,
260  "dm",
261  "trigger",
262  false,
263  "bool",
264  "Run in trigger mode if true (default)." );
265  config.add( "dm.triggerDelay",
266  "",
267  "dm.triggerDelay",
268  argType::Required,
269  "dm",
270  "triggerDelay",
271  false,
272  "float",
273  "Delay to apply to the trigger." );
274 
275  config.add( "dm.separation_1",
276  "",
277  "dm.separation_1",
278  argType::Required,
279  "dm",
280  "separation",
281  false,
282  "float",
283  "The radial separation of the first set of speckles (default 10.0)." );
284  config.add( "dm.separation_2",
285  "",
286  "dm.separation_2",
287  argType::Required,
288  "dm",
289  "separation",
290  false,
291  "float",
292  "The radial separation of the first set of speckles (default 20.0)." );
293  config.add( "dm.angle",
294  "",
295  "dm.angle",
296  argType::Required,
297  "dm",
298  "angle",
299  false,
300  "float",
301  "The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)." );
302  config.add( "dm.angleOffset",
303  "",
304  "dm.angleOffset",
305  argType::Required,
306  "dm",
307  "angleOffset",
308  false,
309  "float",
310  "The calibration offset of angle so that up on camsci1/2 is 0." );
311  config.add( "dm.amp",
312  "",
313  "dm.amp",
314  argType::Required,
315  "dm",
316  "amp",
317  false,
318  "float",
319  "The speckle amplitude on the DM (default 0.01)." );
320  config.add( "dm.cross",
321  "",
322  "dm.cross",
323  argType::True,
324  "dm",
325  "cross",
326  false,
327  "bool",
328  "If true, also apply the cross speckles rotated by 90 degrees." );
329 
330  config.add( "dm.frequency",
331  "",
332  "dm.frequency",
333  argType::Required,
334  "dm",
335  "frequency",
336  false,
337  "float",
338  "The frequency to modulate at if not triggering (default 2000 Hz)." );
339 
340  config.add( "dm.dwell",
341  "",
342  "dm.dwell",
343  argType::True,
344  "dm",
345  "dwell",
346  false,
347  "int",
348  "The dwell time for each speckle, or for how many frames it is held. Default=1." );
349 
350  config.add( "modulator.threadPrio",
351  "",
352  "modulator.threadPrio",
353  argType::Required,
354  "modulator",
355  "threadPrio",
356  false,
357  "int",
358  "The real-time priority of the modulator thread." );
359 
360  config.add( "modulator.cpuset",
361  "",
362  "modulator.cpuset",
363  argType::Required,
364  "modulator",
365  "cpuset",
366  false,
367  "string",
368  "The cpuset to assign the modulator thread to." );
369 }
370 
371 int sparkleClock::loadConfigImpl( mx::app::appConfigurator &_config )
372 {
373  _config( m_dmChannelName, "dm.channelName" );
374 
376  _config( m_dmName, "dm.name" );
377 
378  _config( m_dmTriggerChannel, "dm.triggerChannel" );
379 
380  _config( m_triggerSemaphore, "dm.triggerSemaphore" );
381 
382  if( _config.isSet( "dm.trigger" ) )
383  {
384  _config( m_trigger, "dm.trigger" );
385  }
386 
387  _config( m_triggerDelay, "dm.triggerDelay" );
388 
389  _config( m_separation_1, "dm.separation_1" );
390  _config( m_separation_2, "dm.separation_2" );
391  _config( m_angle, "dm.angle" );
392  _config( m_angleOffset, "dm.angleOffset" );
393  _config( m_amp, "dm.amp" );
394 
395  if( _config.isSet( "dm.cross" ) )
396  {
397  _config( m_cross, "dm.cross" );
398  }
399 
400  _config( m_frequency, "dm.frequency" );
401  _config( m_dwell, "dm.dwell" );
402  _config( m_modThreadPrio, "modulator.threadPrio" );
403  _config( m_modThreadCpuset, "modulator.cpuset" );
404 
406  return 0;
407 }
408 
410 {
411  loadConfigImpl( config );
412 }
413 
415 {
416 
417  REG_INDI_NEWPROP_NOCB( m_indiP_dm, "dm", pcf::IndiProperty::Text );
418  m_indiP_dm.add( pcf::IndiElement( "name" ) );
419  m_indiP_dm["name"] = m_dmName;
420  m_indiP_dm.add( pcf::IndiElement( "channel" ) );
421  m_indiP_dm["channel"] = m_dmChannelName;
422 
423  createStandardIndiNumber<float>( m_indiP_delay, "delay", 0, 0, 1, "%f" );
424  m_indiP_delay["current"] = m_triggerDelay;
425  m_indiP_delay["target"] = m_triggerDelay;
427 
428  createStandardIndiNumber<float>( m_indiP_separation_1, "separation_1", 2, 24, 100, "%f" );
432 
433  createStandardIndiNumber<float>( m_indiP_separation_2, "separation_2", 2, 24, 100, "%f" );
437 
438  createStandardIndiNumber<float>( m_indiP_angle, "angle", 0, 0, 100, "%f" );
439  m_indiP_angle["current"] = m_angle;
440  m_indiP_angle["target"] = m_angle;
442 
445  {
446  log<software_error>( { __FILE__, __LINE__ } );
447  return -1;
448  }
449  if( m_cross )
450  {
451  m_indiP_cross["toggle"] = pcf::IndiElement::On;
452  }
453  else
454  {
455  m_indiP_cross["toggle"] = pcf::IndiElement::Off;
456  }
457 
458  createStandardIndiNumber<float>( m_indiP_amp, "amp", -1, 0, 1, "%f" );
459  m_indiP_amp["current"] = m_amp;
460  m_indiP_amp["target"] = m_amp;
462 
463  createStandardIndiNumber<float>( m_indiP_frequency, "frequency", 0, 0, 10000, "%f" );
464  m_indiP_frequency["current"] = m_frequency;
465  m_indiP_frequency["target"] = m_frequency;
467 
468  createStandardIndiNumber<float>( m_indiP_interval, "interval", 0, 0, 10000, "%f" );
472 
475  {
476  log<software_error>( { __FILE__, __LINE__ } );
477  return -1;
478  }
479  if( m_trigger )
480  {
481  m_indiP_trigger["toggle"] = pcf::IndiElement::On;
482  }
483  else
484  {
485  m_indiP_trigger["toggle"] = pcf::IndiElement::Off;
486  }
487 
488  createStandardIndiNumber<int>( m_indiP_dwell, "dwell", 1, 100, 1, "%d" );
489  m_indiP_dwell["current"] = m_dwell;
490  m_indiP_dwell["target"] = m_dwell;
492 
493  createStandardIndiNumber<int>( m_indiP_single, "single", -1, 3, 1, "%d" );
494  m_indiP_single["current"] = m_single;
495  m_indiP_single["target"] = m_single;
497 
500  {
501  log<software_error>( { __FILE__, __LINE__ } );
502  return -1;
503  }
504 
507  {
508  log<software_error>( { __FILE__, __LINE__ } );
509  return -1;
510  }
511 
518  "modulator",
519  this,
520  modThreadStart ) < 0 )
521  {
522  log<software_critical>( { __FILE__, __LINE__ } );
523  return -1;
524  }
525 
527  {
528  return log<software_error, -1>( { __FILE__, __LINE__ } );
529  }
530 
532 
533  return 0;
534 }
535 
537 {
539  {
540  m_opened = false;
541  m_restart = false; // Set this up front, since we're about to restart.
542 
543  if( ImageStreamIO_openIm( &m_imageStream, m_dmChannelName.c_str() ) == 0 )
544  {
545  if( m_imageStream.md[0].sem < 10 ) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
546  {
547  ImageStreamIO_closeIm( &m_imageStream );
548  }
549  else
550  {
551  m_opened = true;
552  }
553  }
554 
555  // Only bother to try if previous worked and we have a spec
556  if( m_opened == true && m_dmTriggerChannel != "" )
557  {
558  if( ImageStreamIO_openIm( &m_triggerStream, m_dmTriggerChannel.c_str() ) == 0 )
559  {
560  if( m_triggerStream.md[0].sem <
561  10 ) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
562  {
563  ImageStreamIO_closeIm( &m_triggerStream );
564  m_opened = false;
565  }
566  }
567  }
568 
569  if( m_opened )
570  {
572  }
573  }
574 
575  if( state() == stateCodes::CONNECTED )
576  {
577  m_dataType = m_imageStream.md[0].datatype;
578  m_typeSize = ImageStreamIO_typesize( m_dataType );
579  m_width = m_imageStream.md[0].size[0];
580  m_height = m_imageStream.md[0].size[1];
581 
582  if( m_dataType != _DATATYPE_FLOAT )
583  {
584  return log<text_log, -1>( "Data type of DM channel is not float.", logPrio::LOG_CRITICAL );
585  }
586 
587  if( m_typeSize != sizeof( realT ) )
588  {
589  return log<text_log, -1>( "Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL );
590  }
591 
593  }
594 
596  {
597  log<software_error>( { __FILE__, __LINE__ } );
598  return 0;
599  }
600 
601  return 0;
602 }
603 
605 {
606  if( m_modThread.joinable() )
607  {
608  try
609  {
610  m_modThread.join(); // this will throw if it was already joined
611  }
612  catch( ... )
613  {
614  }
615  }
616 
618 
619  return 0;
620 }
621 
623 {
624  mx::improc::eigenImage<realT> onesp, onespC;
625  onesp.resize( m_width, m_height );
626  onespC.resize( m_width, m_height );
627 
628  int nFrames = 4 * static_cast<int>( m_sparkleClockInterval / 4.0 * m_frequency );
629  int _nFramesRound = static_cast<int>( m_sparkleClockInterval * m_frequency );
630  if( nFrames != _nFramesRound )
631  {
632  std::cerr << "Got nFrames = " << nFrames << " to be evenly divisible by 4, but integer rounding would give "
633  << _nFramesRound << " frames per " << m_sparkleClockInterval << " sec interval given " << m_frequency
634  << " Hz frequency\n";
635  }
636  m_shapes.resize( m_width, m_height, nFrames );
637 
638  // One complete cycle of the sparkle clock spins through 2pi radians, twice, in one m_sparkleClockInterval.
639  // Each step in position should be 4 frames. +/-, sin/cos.
640  // So we figure out the number of positions as nFrames / 2 (radii) / 4 (phase/parity).
641  float nPositions = nFrames / 4 / 2;
642  float angleStep = mx::math::two_pi<realT>() / nPositions;
643  for( int radii = 0; radii < 2; radii++ )
644  {
645  float separation = (radii == 0) ? m_separation_1 : m_separation_2;
646  for( int pos = 0; pos < nPositions; pos++ )
647  {
648  int baseIdx = ( nPositions * radii ) + pos;
649  int thisIdx = baseIdx;
650 
651  realT m = separation * cos( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset + angleStep * pos) );
652  realT n = separation * sin( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset + angleStep * pos) );
653 
654  mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), m, n, 1 );
655 
656  if( m_cross )
657  {
658  onesp = m_shapes.image( thisIdx );
659  mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), -n, m, 1 );
660  m_shapes.image( thisIdx ) += onesp;
661  }
662 
663  m_shapes.image( thisIdx ) *= m_amp;
664  thisIdx += 1;
665  m_shapes.image( thisIdx ) = -1 * m_shapes.image( thisIdx - 1 );
666 
667  thisIdx += 1;
668  mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), m, n, -1 );
669 
670  if( m_cross )
671  {
672  onesp = m_shapes.image( thisIdx );
673  mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), -n, m, -1 );
674  m_shapes.image( thisIdx ) += onesp;
675  }
676 
677  m_shapes.image( thisIdx ) *= m_amp;
678 
679  thisIdx += 1;
680  m_shapes.image( thisIdx ) = -1 * m_shapes.image( thisIdx - 1 );
681 
682  mx::fits::fitsFile<realT> ff;
683  ff.write( "/tmp/specks.fits", m_shapes );
684 
685  // if( m_single >= 0 )
686  // {
687  // for( int pp = 0; pp < m_shapes.planes(); ++pp )
688  // {
689  // if( pp != m_single )
690  // {
691  // m_shapes.image( pp ) *= 0;
692  // }
693  // }
694  // }
695  }
696  }
697 
701  updateIfChanged( m_indiP_angle, "current", m_angle );
702  updateIfChanged( m_indiP_amp, "current", m_amp );
705  updateIfChanged( m_indiP_dwell, "current", m_dwell );
706  updateIfChanged( m_indiP_single, "current", m_single );
707 
708  return 0;
709 }
710 
712 {
713  d->modThreadExec();
714 }
715 
717 {
718  m_modThreadID = syscall( SYS_gettid );
719 
720  // Wait fpr the thread starter to finish initializing this thread.
721  while( ( m_modThreadInit == true || state() != stateCodes::READY ) && m_shutdown == 0 )
722  {
723  sleep( 1 );
724  }
725 
726  while( m_shutdown == 0 )
727  {
728  if( !m_modulating && !m_shutdown ) // If we aren't modulating we sleep for 1/2 a second
729  {
730  mx::sys::milliSleep( 500 );
731  }
732 
733  if( m_modulating && !m_shutdown )
734  {
735  m_restartSp = false;
737 
738  int64_t freqNsec = ( 1.0 / m_frequency ) * 1e9;
739  int64_t dnsec;
740 
741  int idx = 0;
742 
743  timespec modstart;
744  timespec currtime;
745 
746  bool triggered = false;
747  sem_t *sem = nullptr;
748  if( m_dmTriggerChannel == "" )
749  {
750  m_trigger = false;
752  m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
753  }
754  else if( m_trigger == true )
755  {
756  ImageStreamIO_semflush( &m_triggerStream, m_triggerSemaphore );
757 
758  sem = m_triggerStream.semptr[m_triggerSemaphore]; ///< The semaphore to monitor for new image data
759  }
760 
761  log<text_log>( "started modulating", logPrio::LOG_NOTICE );
762  // To send a message
763  // log<telem_sparkleclock>( { m_modulating,
764  // m_trigger,
765  // m_frequency,
766  // std::vector<float>( { m_separation } ),
767  // std::vector<float>( { m_angle } ),
768  // std::vector<float>( { m_amp } ),
769  // std::vector<bool>( { m_cross } ) },
770  // logPrio::LOG_INFO );
771  // // The official record:
772  // recordSparkleClock( true );
773 
774  dnsec = 0;
775  clock_gettime( CLOCK_REALTIME, &modstart );
776 
777  unsigned dwelled = 0;
778  if( m_dwell == 0 )
779  m_dwell = 1;
780 
781  float triggerDelay = m_triggerDelay / 1e6;
782 
783  double t0, t1;
784 
785  while( m_modulating && !m_restartSp && !m_shutdown )
786  {
787  if( m_trigger )
788  {
789  timespec ts;
790 
791  if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
792  {
793  log<software_critical>( { __FILE__, __LINE__, errno, 0, "clock_gettime" } );
794  return;
795  }
796 
797  ts.tv_sec += 1;
798 
799  if( sem_timedwait( sem, &ts ) == 0 )
800  {
801  t0 = mx::sys::get_curr_time();
802  t1 = t0;
803 
804  while( t1 - t0 < triggerDelay )
805  {
806  double dt = ( 1e8 ) * ( triggerDelay -
807  ( t1 - t0 ) ); // This is 0.1 times remaining time, but in nanosecs
808  if( dt <= 0 )
809  break;
810  mx::sys::nanoSleep( dt );
811  t1 = mx::sys::get_curr_time();
812  }
813 
814  triggered = true;
815  }
816  else
817  {
818  triggered = false;
819 
820  // Check for why we timed out
821  if( errno == EINTR )
822  break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit
823  // normally if flags set.
824 
825  // ETIMEDOUT just means we should wait more.
826  // Otherwise, report an error.
827  if( errno != ETIMEDOUT )
828  {
829  log<software_error>( { __FILE__, __LINE__, errno, "sem_timedwait" } );
830  break;
831  }
832  }
833  }
834  else
835  {
836  mx::sys::nanoSleep( 0.5 * dnsec );
837  clock_gettime( CLOCK_REALTIME, &currtime );
838 
839  dnsec =
840  ( currtime.tv_sec - modstart.tv_sec ) * 1000000000 + ( currtime.tv_nsec - modstart.tv_nsec );
841  triggered = false;
842  }
843 
844  if( dwelled < m_dwell - 1 )
845  {
846  ++dwelled;
847  }
848  else if( dnsec >= freqNsec || triggered )
849  {
850  // Do the write
851  dwelled = 0;
852 
853  m_imageStream.md->write = 1;
854 
855  memcpy( m_imageStream.array.raw, m_shapes.image( idx ).data(), m_width * m_height * m_typeSize );
856 
857  m_imageStream.md->atime = currtime;
858  m_imageStream.md->writetime = currtime;
859 
860  if( !m_trigger || triggerDelay > 0 )
861  {
862  m_imageStream.md->cnt0++;
863  }
864 
865  m_imageStream.md->write = 0;
866  ImageStreamIO_sempost( &m_imageStream, -1 );
867 
868  ++idx;
869  if( idx >= m_shapes.planes() )
870  idx = 0;
871 
872  if( !m_trigger )
873  {
874  modstart.tv_nsec += freqNsec;
875  if( modstart.tv_nsec >= 1000000000 )
876  {
877  modstart.tv_nsec -= 1000000000;
878  modstart.tv_sec += 1;
879  }
880  dnsec = freqNsec;
881  }
882  }
883  }
884  if( m_restartSp )
885  continue;
886 
887  recordSparkleClock( true );
888  log<text_log>( "stopped modulating", logPrio::LOG_NOTICE );
889  // Always zero when done
890  clock_gettime( CLOCK_REALTIME, &currtime );
891  m_imageStream.md->write = 1;
892 
893  memset( m_imageStream.array.raw, 0.0, m_width * m_height * m_typeSize );
894 
895  m_imageStream.md->atime = currtime;
896  m_imageStream.md->writetime = currtime;
897 
898  if( !m_trigger )
899  m_imageStream.md->cnt0++;
900 
901  m_imageStream.md->write = 0;
902  ImageStreamIO_sempost( &m_imageStream, -1 );
903  log<text_log>( "zeroed" );
904  }
905  }
906 }
907 
908 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_trigger )
909 ( const pcf::IndiProperty &ipRecv )
910 {
911  if( ipRecv.getName() != m_indiP_trigger.getName() )
912  {
913  log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
914  return -1;
915  }
916 
917  if( !ipRecv.find( "toggle" ) )
918  return 0;
919 
920  std::unique_lock<std::mutex> lock( m_indiMutex );
921 
922  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
923  {
924  m_trigger = false;
925  indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
926  }
927 
928  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
929  {
930  m_trigger = true;
931  indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
932  }
933 
934  m_restartSp = true;
935 
936  return 0;
937 }
938 
939 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_delay )( const pcf::IndiProperty &ipRecv )
940 {
941  if( ipRecv.getName() != m_indiP_delay.getName() )
942  {
943  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
944  return -1;
945  }
946 
947  float del = -1000000000;
948 
949  if( ipRecv.find( "current" ) )
950  {
951  del = ipRecv["current"].get<float>();
952  }
953 
954  if( ipRecv.find( "target" ) )
955  {
956  del = ipRecv["target"].get<float>();
957  }
958 
959  if( del == -1000000000 )
960  {
961  log<software_error>( { __FILE__, __LINE__, "No requested delay" } );
962  return 0;
963  }
964 
965  std::unique_lock<std::mutex> lock( m_indiMutex );
966  m_triggerDelay = del;
967  updateIfChanged( m_indiP_delay, "target", m_triggerDelay );
968 
969  m_restartSp = true;
970  return 0;
971 }
972 
973 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_separation_1 )( const pcf::IndiProperty &ipRecv )
974 {
975  if( ipRecv.getName() != m_indiP_separation_1.getName() )
976  {
977  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
978  return -1;
979  }
980 
981  float sep = -1000000000;
982 
983  if( ipRecv.find( "current" ) )
984  {
985  sep = ipRecv["current"].get<float>();
986  }
987 
988  if( ipRecv.find( "target" ) )
989  {
990  sep = ipRecv["target"].get<float>();
991  }
992 
993  if( sep == -1000000000 )
994  {
995  log<software_error>( { __FILE__, __LINE__, "No requested separation_1" } );
996  return 0;
997  }
998 
999  std::unique_lock<std::mutex> lock( m_indiMutex );
1000  m_separation_1 = sep;
1001  updateIfChanged( m_indiP_separation_1, "target", m_separation_1 );
1002 
1003  m_restartSp = true;
1004 
1005  return 0;
1006 }
1007 
1008 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_separation_2 )( const pcf::IndiProperty &ipRecv )
1009 {
1010  if( ipRecv.getName() != m_indiP_separation_2.getName() )
1011  {
1012  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1013  return -1;
1014  }
1015 
1016  float sep = -1000000000;
1017 
1018  if( ipRecv.find( "current" ) )
1019  {
1020  sep = ipRecv["current"].get<float>();
1021  }
1022 
1023  if( ipRecv.find( "target" ) )
1024  {
1025  sep = ipRecv["target"].get<float>();
1026  }
1027 
1028  if( sep == -1000000000 )
1029  {
1030  log<software_error>( { __FILE__, __LINE__, "No requested separation_2" } );
1031  return 0;
1032  }
1033 
1034  std::unique_lock<std::mutex> lock( m_indiMutex );
1035  m_separation_2 = sep;
1036  updateIfChanged( m_indiP_separation_2, "target", m_separation_2 );
1037 
1038  m_restartSp = true;
1039 
1040  return 0;
1041 }
1042 
1043 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_angle )
1044 ( const pcf::IndiProperty &ipRecv )
1045 {
1046  if( ipRecv.getName() != m_indiP_angle.getName() )
1047  {
1048  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1049  return -1;
1050  }
1051 
1052  float ang = -1000000000;
1053 
1054  if( ipRecv.find( "current" ) )
1055  {
1056  ang = ipRecv["current"].get<float>();
1057  }
1058 
1059  if( ipRecv.find( "target" ) )
1060  {
1061  ang = ipRecv["target"].get<float>();
1062  }
1063 
1064  if( ang == -1000000000 )
1065  {
1066  log<software_error>( { __FILE__, __LINE__, "No angle received" } );
1067  return 0;
1068  }
1069 
1070  std::unique_lock<std::mutex> lock( m_indiMutex );
1071  m_angle = ang;
1072  updateIfChanged( m_indiP_angle, "target", m_angle );
1073 
1074  m_restartSp = true;
1075  return 0;
1076 }
1077 
1078 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_amp )
1079 ( const pcf::IndiProperty &ipRecv )
1080 {
1081  if( ipRecv.getName() != m_indiP_amp.getName() )
1082  {
1083  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1084  return -1;
1085  }
1086 
1087  float amp = -1000000000;
1088 
1089  if( ipRecv.find( "current" ) )
1090  {
1091  amp = ipRecv["current"].get<float>();
1092  }
1093 
1094  if( ipRecv.find( "target" ) )
1095  {
1096  amp = ipRecv["target"].get<float>();
1097  }
1098 
1099  if( amp == -1000000000 )
1100  {
1101  log<software_error>( { __FILE__, __LINE__, "Invalid requested amp: " + std::to_string( amp ) } );
1102  return 0;
1103  }
1104 
1105  std::unique_lock<std::mutex> lock( m_indiMutex );
1106  m_amp = amp;
1107  updateIfChanged( m_indiP_amp, "target", m_amp );
1108 
1109  m_restartSp = true;
1110  return 0;
1111 }
1112 
1113 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_cross )
1114 ( const pcf::IndiProperty &ipRecv )
1115 {
1116  if( ipRecv.createUniqueKey() != m_indiP_cross.createUniqueKey() )
1117  {
1118  log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1119  return -1;
1120  }
1121 
1122  if( !ipRecv.find( "toggle" ) )
1123  return 0;
1124 
1125  std::unique_lock<std::mutex> lock( m_indiMutex );
1126 
1127  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1128  {
1129  m_cross = false;
1130  indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1131  }
1132 
1133  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1134  {
1135  m_cross = true;
1136  indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1137  }
1138 
1139  m_restartSp = true;
1140 
1141  return 0;
1142 }
1143 
1144 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_frequency )
1145 ( const pcf::IndiProperty &ipRecv )
1146 {
1147  if( ipRecv.getName() != m_indiP_frequency.getName() )
1148  {
1149  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1150  return -1;
1151  }
1152 
1153  float freq = -1;
1154 
1155  if( ipRecv.find( "current" ) )
1156  {
1157  freq = ipRecv["current"].get<float>();
1158  }
1159 
1160  if( ipRecv.find( "target" ) )
1161  {
1162  freq = ipRecv["target"].get<float>();
1163  }
1164 
1165  if( freq < 0 )
1166  {
1167  log<software_error>( { __FILE__, __LINE__, "Invalid requested frequency: " + std::to_string( freq ) } );
1168  return 0;
1169  }
1170 
1171  std::unique_lock<std::mutex> lock( m_indiMutex );
1172  m_frequency = freq;
1173  updateIfChanged( m_indiP_frequency, "target", m_frequency );
1174 
1175  m_restartSp = true;
1176 
1177  return 0;
1178 }
1179 
1180 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_interval )
1181 ( const pcf::IndiProperty &ipRecv )
1182 {
1183  if( ipRecv.getName() != m_indiP_interval.getName() )
1184  {
1185  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1186  return -1;
1187  }
1188 
1189  float interval = -1;
1190 
1191  if( ipRecv.find( "current" ) )
1192  {
1193  interval = ipRecv["current"].get<float>();
1194  }
1195 
1196  if( ipRecv.find( "target" ) )
1197  {
1198  interval = ipRecv["target"].get<float>();
1199  }
1200 
1201  if( interval < 0 )
1202  {
1203  log<software_error>( { __FILE__, __LINE__, "Invalid requested interval: " + std::to_string( interval ) } );
1204  return 0;
1205  }
1206 
1207  std::unique_lock<std::mutex> lock( m_indiMutex );
1209  updateIfChanged( m_indiP_interval, "target", m_sparkleClockInterval );
1210 
1211  m_restartSp = true;
1212 
1213  return 0;
1214 }
1215 
1216 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_dwell )
1217 ( const pcf::IndiProperty &ipRecv )
1218 {
1219  if( ipRecv.createUniqueKey() != m_indiP_dwell.createUniqueKey() )
1220  {
1221  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1222  return -1;
1223  }
1224 
1225  unsigned dwell = 0;
1226 
1227  if( ipRecv.find( "current" ) )
1228  {
1229  dwell = ipRecv["current"].get<unsigned>();
1230  }
1231 
1232  if( ipRecv.find( "target" ) )
1233  {
1234  dwell = ipRecv["target"].get<unsigned>();
1235  }
1236 
1237  if( dwell == 0 )
1238  {
1239  log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( dwell ) } );
1240  return 0;
1241  }
1242 
1243  std::unique_lock<std::mutex> lock( m_indiMutex );
1244  m_dwell = dwell;
1245  updateIfChanged( m_indiP_dwell, "target", m_dwell );
1246 
1247  m_restartSp = true;
1248 
1249  return 0;
1250 }
1251 
1252 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_single )
1253 ( const pcf::IndiProperty &ipRecv )
1254 {
1255  INDI_VALIDATE_CALLBACK_PROPS( m_indiP_single, ipRecv );
1256 
1257  int single = 0;
1258 
1259  if( ipRecv.find( "current" ) )
1260  {
1261  single = ipRecv["current"].get<int>();
1262  }
1263 
1264  if( ipRecv.find( "target" ) )
1265  {
1266  single = ipRecv["target"].get<int>();
1267  }
1268 
1269  if( single < -1 || single > 3 )
1270  {
1271  log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( single ) } );
1272  return 0;
1273  }
1274 
1275  std::unique_lock<std::mutex> lock( m_indiMutex );
1276  m_single = single;
1277  updateIfChanged( m_indiP_single, "target", m_single );
1278  return 0;
1279 }
1280 
1281 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_modulating )
1282 ( const pcf::IndiProperty &ipRecv )
1283 {
1284  if( ipRecv.getName() != m_indiP_modulating.getName() )
1285  {
1286  log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1287  return -1;
1288  }
1289 
1290  if( !ipRecv.find( "toggle" ) )
1291  return 0;
1292 
1293  std::unique_lock<std::mutex> lock( m_indiMutex );
1294 
1295  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1296  {
1297  m_modulating = false;
1298  indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1299  }
1300 
1301  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1302  {
1303  m_modulating = true;
1304  indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1305  }
1306 
1307  return 0;
1308 }
1309 
1310 INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_zero )
1311 ( const pcf::IndiProperty &ipRecv )
1312 {
1313  if( ipRecv.getName() != m_indiP_zero.getName() )
1314  {
1315  log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1316  return -1;
1317  }
1318 
1319  if( m_modulating == true )
1320  {
1321  log<text_log>( "zero requested but currently modulating", logPrio::LOG_NOTICE );
1322  return 0;
1323  }
1324 
1325  if( !ipRecv.find( "request" ) )
1326  return 0;
1327 
1328  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1329  {
1330  m_imageStream.md->write = 1;
1331 
1332  memset( m_imageStream.array.raw, 0, m_width * m_height * m_typeSize );
1333  timespec currtime;
1334  clock_gettime( CLOCK_REALTIME, &currtime );
1335  m_imageStream.md->atime = currtime;
1336  m_imageStream.md->writetime = currtime;
1337 
1338  m_imageStream.md->cnt0++;
1339 
1340  m_imageStream.md->write = 0;
1341  ImageStreamIO_sempost( &m_imageStream, -1 );
1342  log<text_log>( "zeroed" );
1343  }
1344 
1345  return 0;
1346 }
1347 
1349 {
1351 }
1352 
1354 {
1355  return recordSparkleClock( true );
1356 }
1357 
1358 inline int sparkleClock::recordSparkleClock( bool force )
1359 {
1360  static bool lastModulating = m_modulating;
1361  static bool lastTrigger = m_trigger;
1362  static float lastFrequency = m_frequency;
1363  static float lastInterval = m_frequency;
1364  static float lastSeparation1 = m_separation_1;
1365  static float lastSeparation2 = m_separation_2;
1366  static float lastAngle = m_angle;
1367  static float lastAmp = m_amp;
1368  static bool lastCross = m_cross;
1369 
1370  if( !( lastModulating == m_modulating ) || !( lastTrigger == m_trigger ) || !( lastFrequency == m_frequency ) || !( lastInterval == m_interval ) ||
1371  !( lastSeparation1 == m_separation_1 ) ||!( lastSeparation2 == m_separation_2 ) || !( lastAngle == m_angle ) || !( lastAmp == m_amp ) ||
1372  !( lastCross == m_cross ) || force )
1373  {
1374  telem<telem_sparkleclock>({ m_modulating,
1375  m_trigger,
1376  m_frequency,
1377  m_interval,
1378  std::vector<float>( { m_separation_1, m_separation_2 } ),
1379  m_angle,
1380  m_amp });
1381 
1382  lastModulating = m_modulating;
1383  lastTrigger = m_trigger;
1384  lastFrequency = m_frequency;
1385  lastSeparation1 = m_separation_1;
1386  lastSeparation2 = m_separation_2;
1387  lastAngle = m_angle;
1388  lastAmp = m_amp;
1389  }
1390 
1391  return 0;
1392 }
1393 
1394 } // namespace app
1395 } // namespace MagAOX
1396 
1397 #endif // sparkleClock_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.
std::thread m_modThread
A separate thread for the modulation.
pcf::IndiProperty m_indiP_separation_1
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_amp)
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_angle)
realT m_separation_2
The radial separation of the second set of speckles (default 20.0)
pcf::IndiProperty m_indiP_cross
pcf::IndiProperty m_indiP_dwell
~sparkleClock() noexcept
D'tor, declared and defined for noexcept.
std::string m_modThreadCpuset
The cpuset for the modulator thread.
size_t m_typeSize
The size of the type, in bytes.
pcf::IndiProperty m_indiP_interval
realT m_amp
The speckle amplitude on the DM.
pcf::IndiProperty m_indiP_dm
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_delay)
pid_t m_modThreadID
Modulate thread PID.
pcf::IndiProperty m_indiP_angle
realT m_interval
time for one complete cycle of the sparkle clock (e.g. exposure time of some camera) (default 1....
pcf::IndiProperty m_indiP_zero
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_dwell)
realT m_frequency
The frequency to modulate at if not triggering (default 2000 Hz)
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_cross)
friend class sparkleClock_test
int m_modThreadPrio
Priority of the modulator thread, should normally be > 00.
virtual int appLogic()
Implementation of the FSM for sparkleClock.
std::string m_dmTriggerChannel
The DM channel to monitor as a trigger.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_separation_1)
uint32_t m_width
The width of the image.
pcf::IndiProperty m_indiP_amp
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_single)
pcf::IndiProperty m_indiP_modulating
static void modThreadStart(sparkleClock *d)
Thread starter, called by modThreadStart on thread construction. Calls modThreadExec.
int recordTelem(const telem_sparkleclock *)
virtual int appShutdown()
Shutdown the app.
int m_single
if >= 0 a single frame is non-zero.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_trigger)
unsigned m_dwell
The dwell time for each speckle, or for how many frames it is held.
bool m_cross
If true, also apply the cross speckles rotated by 90 degrees.
bool m_trigger
Run in trigger mode if true (default)
realT m_angleOffset
The calibration offset of angle so that up on camsci1/2 is 0.
void modThreadExec()
Execute the frame grabber main loop.
uint32_t m_height
The height of the image.
pcf::IndiProperty m_indiP_single
pcf::IndiProperty m_modThreadProp
The property to hold the modulator thread details.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_modulating)
int m_triggerSemaphore
The semaphore to use (default 9)
int recordSparkleClock(bool force=false)
pcf::IndiProperty m_indiP_trigger
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_frequency)
realT m_separation_1
The radial separation of the first set of speckles (default 10.0)
virtual int appStartup()
Startup function.
uint8_t m_dataType
The ImageStreamIO type code.
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_separation_2)
realT m_angle
The angle of the speckle pattern c.c.w. from up on camsci1/2 (default 0.0)
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_zero)
mx::improc::eigenCube< realT > m_shapes
bool m_modThreadInit
Synchronizer to ensure f.g. thread initializes before doing dangerous things.
sparkleClock()
Default c'tor.
pcf::IndiProperty m_indiP_separation_2
std::string m_dmName
The descriptive name of this dm. Default is the channel name.
pcf::IndiProperty m_indiP_frequency
INDI_NEWCALLBACK_DECL(sparkleClock, m_indiP_interval)
pcf::IndiProperty m_indiP_delay
std::string m_dmChannelName
The name of the DM channel to write to.
#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
std::ostream & cerr()
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_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 sparkle clock status.
A simple text log, a string-type log.
Definition: text_log.hpp:24