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