Line data Source code
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 :
94 : IMAGE m_imageStream;
95 : uint32_t m_width{ 0 }; ///< The width of the image
96 : uint32_t m_height{ 0 }; ///< The height of the image.
97 :
98 : IMAGE m_triggerStream;
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 0 : ~sparkleClock() noexcept
116 0 : {
117 0 : }
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:
195 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_trigger );
196 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_delay );
197 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_separation_1 );
198 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_separation_2 );
199 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_angle );
200 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_cross );
201 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_amp );
202 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_frequency );
203 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_interval );
204 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_dwell );
205 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_single );
206 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_modulating );
207 0 : INDI_NEWCALLBACK_DECL( sparkleClock, m_indiP_zero );
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 0 : sparkleClock::sparkleClock() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
223 : {
224 0 : return;
225 0 : }
226 :
227 0 : void sparkleClock::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_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 0 : 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 0 : 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 0 : 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 0 : 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 0 : 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 0 : 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 0 : 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 0 : 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 0 : 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 0 : }
370 :
371 0 : int sparkleClock::loadConfigImpl( mx::app::appConfigurator &_config )
372 : {
373 0 : _config( m_dmChannelName, "dm.channelName" );
374 :
375 0 : m_dmName = m_dmChannelName;
376 0 : _config( m_dmName, "dm.name" );
377 :
378 0 : _config( m_dmTriggerChannel, "dm.triggerChannel" );
379 :
380 0 : _config( m_triggerSemaphore, "dm.triggerSemaphore" );
381 :
382 0 : if( _config.isSet( "dm.trigger" ) )
383 : {
384 0 : _config( m_trigger, "dm.trigger" );
385 : }
386 :
387 0 : _config( m_triggerDelay, "dm.triggerDelay" );
388 :
389 0 : _config( m_separation_1, "dm.separation_1" );
390 0 : _config( m_separation_2, "dm.separation_2" );
391 0 : _config( m_angle, "dm.angle" );
392 0 : _config( m_angleOffset, "dm.angleOffset" );
393 0 : _config( m_amp, "dm.amp" );
394 :
395 0 : if( _config.isSet( "dm.cross" ) )
396 : {
397 0 : _config( m_cross, "dm.cross" );
398 : }
399 :
400 0 : _config( m_frequency, "dm.frequency" );
401 0 : _config( m_dwell, "dm.dwell" );
402 0 : _config( m_modThreadPrio, "modulator.threadPrio" );
403 0 : _config( m_modThreadCpuset, "modulator.cpuset" );
404 :
405 0 : dev::telemeter<sparkleClock>::loadConfig( _config );
406 0 : return 0;
407 : }
408 :
409 0 : void sparkleClock::loadConfig()
410 : {
411 0 : loadConfigImpl( config );
412 0 : }
413 :
414 0 : int sparkleClock::appStartup()
415 : {
416 :
417 0 : REG_INDI_NEWPROP_NOCB( m_indiP_dm, "dm", pcf::IndiProperty::Text );
418 0 : m_indiP_dm.add( pcf::IndiElement( "name" ) );
419 0 : m_indiP_dm["name"] = m_dmName;
420 0 : m_indiP_dm.add( pcf::IndiElement( "channel" ) );
421 0 : m_indiP_dm["channel"] = m_dmChannelName;
422 :
423 0 : createStandardIndiNumber<float>( m_indiP_delay, "delay", 0, 0, 1, "%f" );
424 0 : m_indiP_delay["current"] = m_triggerDelay;
425 0 : m_indiP_delay["target"] = m_triggerDelay;
426 0 : registerIndiPropertyNew( m_indiP_delay, INDI_NEWCALLBACK( m_indiP_delay ) );
427 :
428 0 : createStandardIndiNumber<float>( m_indiP_separation_1, "separation_1", 2, 24, 100, "%f" );
429 0 : m_indiP_separation_1["current"] = m_separation_1;
430 0 : m_indiP_separation_1["target"] = m_separation_1;
431 0 : registerIndiPropertyNew( m_indiP_separation_1, INDI_NEWCALLBACK( m_indiP_separation_1 ) );
432 :
433 0 : createStandardIndiNumber<float>( m_indiP_separation_2, "separation_2", 2, 24, 100, "%f" );
434 0 : m_indiP_separation_2["current"] = m_separation_2;
435 0 : m_indiP_separation_2["target"] = m_separation_2;
436 0 : registerIndiPropertyNew( m_indiP_separation_2, INDI_NEWCALLBACK( m_indiP_separation_2 ) );
437 :
438 0 : createStandardIndiNumber<float>( m_indiP_angle, "angle", 0, 0, 100, "%f" );
439 0 : m_indiP_angle["current"] = m_angle;
440 0 : m_indiP_angle["target"] = m_angle;
441 0 : registerIndiPropertyNew( m_indiP_angle, INDI_NEWCALLBACK( m_indiP_angle ) );
442 :
443 0 : createStandardIndiToggleSw( m_indiP_cross, "cross" );
444 0 : if( registerIndiPropertyNew( m_indiP_cross, INDI_NEWCALLBACK( m_indiP_cross ) ) < 0 )
445 : {
446 0 : log<software_error>( { __FILE__, __LINE__ } );
447 0 : return -1;
448 : }
449 0 : if( m_cross )
450 : {
451 0 : m_indiP_cross["toggle"] = pcf::IndiElement::On;
452 : }
453 : else
454 : {
455 0 : m_indiP_cross["toggle"] = pcf::IndiElement::Off;
456 : }
457 :
458 0 : createStandardIndiNumber<float>( m_indiP_amp, "amp", -1, 0, 1, "%f" );
459 0 : m_indiP_amp["current"] = m_amp;
460 0 : m_indiP_amp["target"] = m_amp;
461 0 : registerIndiPropertyNew( m_indiP_amp, INDI_NEWCALLBACK( m_indiP_amp ) );
462 :
463 0 : createStandardIndiNumber<float>( m_indiP_frequency, "frequency", 0, 0, 10000, "%f" );
464 0 : m_indiP_frequency["current"] = m_frequency;
465 0 : m_indiP_frequency["target"] = m_frequency;
466 0 : registerIndiPropertyNew( m_indiP_frequency, INDI_NEWCALLBACK( m_indiP_frequency ) );
467 :
468 0 : createStandardIndiNumber<float>( m_indiP_interval, "interval", 0, 0, 10000, "%f" );
469 0 : m_indiP_frequency["current"] = m_sparkleClockInterval;
470 0 : m_indiP_frequency["target"] = m_sparkleClockInterval;
471 0 : registerIndiPropertyNew( m_indiP_interval, INDI_NEWCALLBACK( m_indiP_interval ) );
472 :
473 0 : createStandardIndiToggleSw( m_indiP_trigger, "trigger" );
474 0 : if( registerIndiPropertyNew( m_indiP_trigger, INDI_NEWCALLBACK( m_indiP_trigger ) ) < 0 )
475 : {
476 0 : log<software_error>( { __FILE__, __LINE__ } );
477 0 : return -1;
478 : }
479 0 : if( m_trigger )
480 : {
481 0 : m_indiP_trigger["toggle"] = pcf::IndiElement::On;
482 : }
483 : else
484 : {
485 0 : m_indiP_trigger["toggle"] = pcf::IndiElement::Off;
486 : }
487 :
488 0 : createStandardIndiNumber<int>( m_indiP_dwell, "dwell", 1, 100, 1, "%d" );
489 0 : m_indiP_dwell["current"] = m_dwell;
490 0 : m_indiP_dwell["target"] = m_dwell;
491 0 : registerIndiPropertyNew( m_indiP_dwell, INDI_NEWCALLBACK( m_indiP_dwell ) );
492 :
493 0 : createStandardIndiNumber<int>( m_indiP_single, "single", -1, 3, 1, "%d" );
494 0 : m_indiP_single["current"] = m_single;
495 0 : m_indiP_single["target"] = m_single;
496 0 : registerIndiPropertyNew( m_indiP_single, INDI_NEWCALLBACK( m_indiP_single ) );
497 :
498 0 : createStandardIndiToggleSw( m_indiP_modulating, "modulating" );
499 0 : if( registerIndiPropertyNew( m_indiP_modulating, INDI_NEWCALLBACK( m_indiP_modulating ) ) < 0 )
500 : {
501 0 : log<software_error>( { __FILE__, __LINE__ } );
502 0 : return -1;
503 : }
504 :
505 0 : createStandardIndiRequestSw( m_indiP_zero, "zero" );
506 0 : if( registerIndiPropertyNew( m_indiP_zero, INDI_NEWCALLBACK( m_indiP_zero ) ) < 0 )
507 : {
508 0 : log<software_error>( { __FILE__, __LINE__ } );
509 0 : return -1;
510 : }
511 :
512 0 : if( threadStart( m_modThread,
513 0 : m_modThreadInit,
514 0 : m_modThreadID,
515 0 : m_modThreadProp,
516 : m_modThreadPrio,
517 0 : m_modThreadCpuset,
518 : "modulator",
519 : this,
520 0 : modThreadStart ) < 0 )
521 : {
522 0 : log<software_critical>( { __FILE__, __LINE__ } );
523 0 : return -1;
524 : }
525 :
526 0 : if( dev::telemeter<sparkleClock>::appStartup() < 0 )
527 : {
528 0 : return log<software_error, -1>( { __FILE__, __LINE__ } );
529 : }
530 :
531 0 : state( stateCodes::NOTCONNECTED );
532 :
533 0 : return 0;
534 : }
535 :
536 0 : int sparkleClock::appLogic()
537 : {
538 0 : if( state() == stateCodes::NOTCONNECTED )
539 : {
540 0 : m_opened = false;
541 0 : m_restart = false; // Set this up front, since we're about to restart.
542 :
543 0 : if( ImageStreamIO_openIm( &m_imageStream, m_dmChannelName.c_str() ) == 0 )
544 : {
545 0 : if( m_imageStream.md[0].sem < 10 ) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
546 : {
547 0 : ImageStreamIO_closeIm( &m_imageStream );
548 : }
549 : else
550 : {
551 0 : m_opened = true;
552 : }
553 : }
554 :
555 : // Only bother to try if previous worked and we have a spec
556 0 : if( m_opened == true && m_dmTriggerChannel != "" )
557 : {
558 0 : if( ImageStreamIO_openIm( &m_triggerStream, m_dmTriggerChannel.c_str() ) == 0 )
559 : {
560 0 : if( m_triggerStream.md[0].sem <
561 : 10 ) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
562 : {
563 0 : ImageStreamIO_closeIm( &m_triggerStream );
564 0 : m_opened = false;
565 : }
566 : }
567 : }
568 :
569 0 : if( m_opened )
570 : {
571 0 : state( stateCodes::CONNECTED );
572 : }
573 : }
574 :
575 0 : if( state() == stateCodes::CONNECTED )
576 : {
577 0 : m_dataType = m_imageStream.md[0].datatype;
578 0 : m_typeSize = ImageStreamIO_typesize( m_dataType );
579 0 : m_width = m_imageStream.md[0].size[0];
580 0 : m_height = m_imageStream.md[0].size[1];
581 :
582 0 : if( m_dataType != _DATATYPE_FLOAT )
583 : {
584 0 : return log<text_log, -1>( "Data type of DM channel is not float.", logPrio::LOG_CRITICAL );
585 : }
586 :
587 0 : if( m_typeSize != sizeof( realT ) )
588 : {
589 0 : return log<text_log, -1>( "Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL );
590 : }
591 :
592 0 : state( stateCodes::READY );
593 : }
594 :
595 0 : if( telemeter<sparkleClock>::appLogic() < 0 )
596 : {
597 0 : log<software_error>( { __FILE__, __LINE__ } );
598 0 : return 0;
599 : }
600 :
601 0 : return 0;
602 : }
603 :
604 0 : int sparkleClock::appShutdown()
605 : {
606 0 : if( m_modThread.joinable() )
607 : {
608 : try
609 : {
610 0 : m_modThread.join(); // this will throw if it was already joined
611 : }
612 0 : catch( ... )
613 : {
614 0 : }
615 : }
616 :
617 0 : dev::telemeter<sparkleClock>::appShutdown();
618 :
619 0 : return 0;
620 : }
621 :
622 0 : int sparkleClock::generateSparkleClock()
623 : {
624 0 : mx::improc::eigenImage<realT> onesp, onespC;
625 0 : onesp.resize( m_width, m_height );
626 0 : onespC.resize( m_width, m_height );
627 :
628 0 : int nFrames = 4 * static_cast<int>( m_sparkleClockInterval / 4.0 * m_frequency );
629 0 : int _nFramesRound = static_cast<int>( m_sparkleClockInterval * m_frequency );
630 0 : if( nFrames != _nFramesRound )
631 : {
632 0 : std::cerr << "Got nFrames = " << nFrames << " to be evenly divisible by 4, but integer rounding would give "
633 0 : << _nFramesRound << " frames per " << m_sparkleClockInterval << " sec interval given " << m_frequency
634 0 : << " Hz frequency\n";
635 : }
636 0 : 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 0 : float nPositions = nFrames / 4 / 2;
642 0 : float angleStep = mx::math::two_pi<realT>() / nPositions;
643 0 : for( int radii = 0; radii < 2; radii++ )
644 : {
645 0 : float separation = (radii == 0) ? m_separation_1 : m_separation_2;
646 0 : for( int pos = 0; pos < nPositions; pos++ )
647 : {
648 0 : int baseIdx = ( nPositions * radii ) + pos;
649 0 : int thisIdx = baseIdx;
650 :
651 0 : realT m = separation * cos( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset + angleStep * pos) );
652 0 : realT n = separation * sin( mx::math::dtor<realT>( -1 * m_angle + m_angleOffset + angleStep * pos) );
653 :
654 0 : mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), m, n, 1 );
655 :
656 0 : if( m_cross )
657 : {
658 0 : onesp = m_shapes.image( thisIdx );
659 0 : mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), -n, m, 1 );
660 0 : m_shapes.image( thisIdx ) += onesp;
661 : }
662 :
663 0 : m_shapes.image( thisIdx ) *= m_amp;
664 0 : thisIdx += 1;
665 0 : m_shapes.image( thisIdx ) = -1 * m_shapes.image( thisIdx - 1 );
666 :
667 0 : thisIdx += 1;
668 0 : mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), m, n, -1 );
669 :
670 0 : if( m_cross )
671 : {
672 0 : onesp = m_shapes.image( thisIdx );
673 0 : mx::sigproc::makeFourierMode( m_shapes.image( thisIdx ), -n, m, -1 );
674 0 : m_shapes.image( thisIdx ) += onesp;
675 : }
676 :
677 0 : m_shapes.image( thisIdx ) *= m_amp;
678 :
679 0 : thisIdx += 1;
680 0 : m_shapes.image( thisIdx ) = -1 * m_shapes.image( thisIdx - 1 );
681 :
682 0 : mx::fits::fitsFile<realT> ff;
683 0 : 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 0 : }
696 : }
697 :
698 0 : updateIfChanged( m_indiP_delay, "current", m_triggerDelay );
699 0 : updateIfChanged( m_indiP_separation_1, "current", m_separation_1 );
700 0 : updateIfChanged( m_indiP_separation_2, "current", m_separation_2 );
701 0 : updateIfChanged( m_indiP_angle, "current", m_angle );
702 0 : updateIfChanged( m_indiP_amp, "current", m_amp );
703 0 : updateIfChanged( m_indiP_frequency, "current", m_frequency );
704 0 : updateIfChanged( m_indiP_interval, "current", m_sparkleClockInterval );
705 0 : updateIfChanged( m_indiP_dwell, "current", m_dwell );
706 0 : updateIfChanged( m_indiP_single, "current", m_single );
707 :
708 0 : return 0;
709 0 : }
710 :
711 0 : inline void sparkleClock::modThreadStart( sparkleClock *d )
712 : {
713 0 : d->modThreadExec();
714 0 : }
715 :
716 0 : inline void sparkleClock::modThreadExec()
717 : {
718 0 : m_modThreadID = syscall( SYS_gettid );
719 :
720 : // Wait fpr the thread starter to finish initializing this thread.
721 0 : while( ( m_modThreadInit == true || state() != stateCodes::READY ) && m_shutdown == 0 )
722 : {
723 0 : sleep( 1 );
724 : }
725 :
726 0 : while( m_shutdown == 0 )
727 : {
728 0 : if( !m_modulating && !m_shutdown ) // If we aren't modulating we sleep for 1/2 a second
729 : {
730 0 : mx::sys::milliSleep( 500 );
731 : }
732 :
733 0 : if( m_modulating && !m_shutdown )
734 : {
735 0 : m_restartSp = false;
736 0 : generateSparkleClock();
737 :
738 0 : int64_t freqNsec = ( 1.0 / m_frequency ) * 1e9;
739 : int64_t dnsec;
740 :
741 0 : int idx = 0;
742 :
743 : timespec modstart;
744 : timespec currtime;
745 :
746 0 : bool triggered = false;
747 0 : sem_t *sem = nullptr;
748 0 : if( m_dmTriggerChannel == "" )
749 : {
750 0 : m_trigger = false;
751 0 : indi::updateSwitchIfChanged(
752 0 : m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
753 : }
754 0 : else if( m_trigger == true )
755 : {
756 0 : ImageStreamIO_semflush( &m_triggerStream, m_triggerSemaphore );
757 :
758 0 : sem = m_triggerStream.semptr[m_triggerSemaphore]; ///< The semaphore to monitor for new image data
759 : }
760 :
761 0 : 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 0 : dnsec = 0;
775 0 : clock_gettime( CLOCK_REALTIME, &modstart );
776 :
777 0 : unsigned dwelled = 0;
778 0 : if( m_dwell == 0 )
779 0 : m_dwell = 1;
780 :
781 0 : float triggerDelay = m_triggerDelay / 1e6;
782 :
783 : double t0, t1;
784 :
785 0 : while( m_modulating && !m_restartSp && !m_shutdown )
786 : {
787 0 : if( m_trigger )
788 : {
789 : timespec ts;
790 :
791 0 : if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
792 : {
793 0 : log<software_critical>( { __FILE__, __LINE__, errno, 0, "clock_gettime" } );
794 0 : return;
795 : }
796 :
797 0 : ts.tv_sec += 1;
798 :
799 0 : if( sem_timedwait( sem, &ts ) == 0 )
800 : {
801 0 : t0 = mx::sys::get_curr_time();
802 0 : t1 = t0;
803 :
804 0 : while( t1 - t0 < triggerDelay )
805 : {
806 0 : double dt = ( 1e8 ) * ( triggerDelay -
807 : ( t1 - t0 ) ); // This is 0.1 times remaining time, but in nanosecs
808 0 : if( dt <= 0 )
809 0 : break;
810 0 : mx::sys::nanoSleep( dt );
811 0 : t1 = mx::sys::get_curr_time();
812 : }
813 :
814 0 : triggered = true;
815 : }
816 : else
817 : {
818 0 : triggered = false;
819 :
820 : // Check for why we timed out
821 0 : if( errno == EINTR )
822 0 : 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 0 : if( errno != ETIMEDOUT )
828 : {
829 0 : log<software_error>( { __FILE__, __LINE__, errno, "sem_timedwait" } );
830 0 : break;
831 : }
832 : }
833 : }
834 : else
835 : {
836 0 : mx::sys::nanoSleep( 0.5 * dnsec );
837 0 : clock_gettime( CLOCK_REALTIME, &currtime );
838 :
839 0 : dnsec =
840 0 : ( currtime.tv_sec - modstart.tv_sec ) * 1000000000 + ( currtime.tv_nsec - modstart.tv_nsec );
841 0 : triggered = false;
842 : }
843 :
844 0 : if( dwelled < m_dwell - 1 )
845 : {
846 0 : ++dwelled;
847 : }
848 0 : else if( dnsec >= freqNsec || triggered )
849 : {
850 : // Do the write
851 0 : dwelled = 0;
852 :
853 0 : m_imageStream.md->write = 1;
854 :
855 0 : memcpy( m_imageStream.array.raw, m_shapes.image( idx ).data(), m_width * m_height * m_typeSize );
856 :
857 0 : m_imageStream.md->atime = currtime;
858 0 : m_imageStream.md->writetime = currtime;
859 :
860 0 : if( !m_trigger || triggerDelay > 0 )
861 : {
862 0 : m_imageStream.md->cnt0++;
863 : }
864 :
865 0 : m_imageStream.md->write = 0;
866 0 : ImageStreamIO_sempost( &m_imageStream, -1 );
867 :
868 0 : ++idx;
869 0 : if( idx >= m_shapes.planes() )
870 0 : idx = 0;
871 :
872 0 : if( !m_trigger )
873 : {
874 0 : modstart.tv_nsec += freqNsec;
875 0 : if( modstart.tv_nsec >= 1000000000 )
876 : {
877 0 : modstart.tv_nsec -= 1000000000;
878 0 : modstart.tv_sec += 1;
879 : }
880 0 : dnsec = freqNsec;
881 : }
882 : }
883 : }
884 0 : if( m_restartSp )
885 0 : continue;
886 :
887 0 : recordSparkleClock( true );
888 0 : log<text_log>( "stopped modulating", logPrio::LOG_NOTICE );
889 : // Always zero when done
890 0 : clock_gettime( CLOCK_REALTIME, &currtime );
891 0 : m_imageStream.md->write = 1;
892 :
893 0 : memset( m_imageStream.array.raw, 0.0, m_width * m_height * m_typeSize );
894 :
895 0 : m_imageStream.md->atime = currtime;
896 0 : m_imageStream.md->writetime = currtime;
897 :
898 0 : if( !m_trigger )
899 0 : m_imageStream.md->cnt0++;
900 :
901 0 : m_imageStream.md->write = 0;
902 0 : ImageStreamIO_sempost( &m_imageStream, -1 );
903 0 : log<text_log>( "zeroed" );
904 : }
905 : }
906 : }
907 :
908 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_trigger )
909 : ( const pcf::IndiProperty &ipRecv )
910 : {
911 0 : if( ipRecv.getName() != m_indiP_trigger.getName() )
912 : {
913 0 : log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
914 0 : return -1;
915 : }
916 :
917 0 : if( !ipRecv.find( "toggle" ) )
918 0 : return 0;
919 :
920 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
921 :
922 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
923 : {
924 0 : m_trigger = false;
925 0 : indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
926 : }
927 :
928 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
929 : {
930 0 : m_trigger = true;
931 0 : indi::updateSwitchIfChanged( m_indiP_trigger, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
932 : }
933 :
934 0 : m_restartSp = true;
935 :
936 0 : return 0;
937 0 : }
938 :
939 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_delay )( const pcf::IndiProperty &ipRecv )
940 : {
941 0 : if( ipRecv.getName() != m_indiP_delay.getName() )
942 : {
943 0 : log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
944 0 : return -1;
945 : }
946 :
947 0 : float del = -1000000000;
948 :
949 0 : if( ipRecv.find( "current" ) )
950 : {
951 0 : del = ipRecv["current"].get<float>();
952 : }
953 :
954 0 : if( ipRecv.find( "target" ) )
955 : {
956 0 : del = ipRecv["target"].get<float>();
957 : }
958 :
959 0 : if( del == -1000000000 )
960 : {
961 0 : log<software_error>( { __FILE__, __LINE__, "No requested delay" } );
962 0 : return 0;
963 : }
964 :
965 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
966 0 : m_triggerDelay = del;
967 0 : updateIfChanged( m_indiP_delay, "target", m_triggerDelay );
968 :
969 0 : m_restartSp = true;
970 0 : return 0;
971 0 : }
972 :
973 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_separation_1 )( const pcf::IndiProperty &ipRecv )
974 : {
975 0 : if( ipRecv.getName() != m_indiP_separation_1.getName() )
976 : {
977 0 : log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
978 0 : return -1;
979 : }
980 :
981 0 : float sep = -1000000000;
982 :
983 0 : if( ipRecv.find( "current" ) )
984 : {
985 0 : sep = ipRecv["current"].get<float>();
986 : }
987 :
988 0 : if( ipRecv.find( "target" ) )
989 : {
990 0 : sep = ipRecv["target"].get<float>();
991 : }
992 :
993 0 : if( sep == -1000000000 )
994 : {
995 0 : log<software_error>( { __FILE__, __LINE__, "No requested separation_1" } );
996 0 : return 0;
997 : }
998 :
999 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1000 0 : m_separation_1 = sep;
1001 0 : updateIfChanged( m_indiP_separation_1, "target", m_separation_1 );
1002 :
1003 0 : m_restartSp = true;
1004 :
1005 0 : return 0;
1006 0 : }
1007 :
1008 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_separation_2 )( const pcf::IndiProperty &ipRecv )
1009 : {
1010 0 : if( ipRecv.getName() != m_indiP_separation_2.getName() )
1011 : {
1012 0 : log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1013 0 : return -1;
1014 : }
1015 :
1016 0 : float sep = -1000000000;
1017 :
1018 0 : if( ipRecv.find( "current" ) )
1019 : {
1020 0 : sep = ipRecv["current"].get<float>();
1021 : }
1022 :
1023 0 : if( ipRecv.find( "target" ) )
1024 : {
1025 0 : sep = ipRecv["target"].get<float>();
1026 : }
1027 :
1028 0 : if( sep == -1000000000 )
1029 : {
1030 0 : log<software_error>( { __FILE__, __LINE__, "No requested separation_2" } );
1031 0 : return 0;
1032 : }
1033 :
1034 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1035 0 : m_separation_2 = sep;
1036 0 : updateIfChanged( m_indiP_separation_2, "target", m_separation_2 );
1037 :
1038 0 : m_restartSp = true;
1039 :
1040 0 : return 0;
1041 0 : }
1042 :
1043 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_angle )
1044 : ( const pcf::IndiProperty &ipRecv )
1045 : {
1046 0 : if( ipRecv.getName() != m_indiP_angle.getName() )
1047 : {
1048 0 : log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1049 0 : return -1;
1050 : }
1051 :
1052 0 : float ang = -1000000000;
1053 :
1054 0 : if( ipRecv.find( "current" ) )
1055 : {
1056 0 : ang = ipRecv["current"].get<float>();
1057 : }
1058 :
1059 0 : if( ipRecv.find( "target" ) )
1060 : {
1061 0 : ang = ipRecv["target"].get<float>();
1062 : }
1063 :
1064 0 : if( ang == -1000000000 )
1065 : {
1066 0 : log<software_error>( { __FILE__, __LINE__, "No angle received" } );
1067 0 : return 0;
1068 : }
1069 :
1070 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1071 0 : m_angle = ang;
1072 0 : updateIfChanged( m_indiP_angle, "target", m_angle );
1073 :
1074 0 : m_restartSp = true;
1075 0 : return 0;
1076 0 : }
1077 :
1078 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_amp )
1079 : ( const pcf::IndiProperty &ipRecv )
1080 : {
1081 0 : if( ipRecv.getName() != m_indiP_amp.getName() )
1082 : {
1083 0 : log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1084 0 : return -1;
1085 : }
1086 :
1087 0 : float amp = -1000000000;
1088 :
1089 0 : if( ipRecv.find( "current" ) )
1090 : {
1091 0 : amp = ipRecv["current"].get<float>();
1092 : }
1093 :
1094 0 : if( ipRecv.find( "target" ) )
1095 : {
1096 0 : amp = ipRecv["target"].get<float>();
1097 : }
1098 :
1099 0 : if( amp == -1000000000 )
1100 : {
1101 0 : log<software_error>( { __FILE__, __LINE__, "Invalid requested amp: " + std::to_string( amp ) } );
1102 0 : return 0;
1103 : }
1104 :
1105 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1106 0 : m_amp = amp;
1107 0 : updateIfChanged( m_indiP_amp, "target", m_amp );
1108 :
1109 0 : m_restartSp = true;
1110 0 : return 0;
1111 0 : }
1112 :
1113 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_cross )
1114 : ( const pcf::IndiProperty &ipRecv )
1115 : {
1116 0 : if( ipRecv.createUniqueKey() != m_indiP_cross.createUniqueKey() )
1117 : {
1118 0 : log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1119 0 : return -1;
1120 : }
1121 :
1122 0 : if( !ipRecv.find( "toggle" ) )
1123 0 : return 0;
1124 :
1125 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1126 :
1127 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1128 : {
1129 0 : m_cross = false;
1130 0 : indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1131 : }
1132 :
1133 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1134 : {
1135 0 : m_cross = true;
1136 0 : indi::updateSwitchIfChanged( m_indiP_cross, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1137 : }
1138 :
1139 0 : m_restartSp = true;
1140 :
1141 0 : return 0;
1142 0 : }
1143 :
1144 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_frequency )
1145 : ( const pcf::IndiProperty &ipRecv )
1146 : {
1147 0 : if( ipRecv.getName() != m_indiP_frequency.getName() )
1148 : {
1149 0 : log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1150 0 : return -1;
1151 : }
1152 :
1153 0 : float freq = -1;
1154 :
1155 0 : if( ipRecv.find( "current" ) )
1156 : {
1157 0 : freq = ipRecv["current"].get<float>();
1158 : }
1159 :
1160 0 : if( ipRecv.find( "target" ) )
1161 : {
1162 0 : freq = ipRecv["target"].get<float>();
1163 : }
1164 :
1165 0 : if( freq < 0 )
1166 : {
1167 0 : log<software_error>( { __FILE__, __LINE__, "Invalid requested frequency: " + std::to_string( freq ) } );
1168 0 : return 0;
1169 : }
1170 :
1171 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1172 0 : m_frequency = freq;
1173 0 : updateIfChanged( m_indiP_frequency, "target", m_frequency );
1174 :
1175 0 : m_restartSp = true;
1176 :
1177 0 : return 0;
1178 0 : }
1179 :
1180 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_interval )
1181 : ( const pcf::IndiProperty &ipRecv )
1182 : {
1183 0 : if( ipRecv.getName() != m_indiP_interval.getName() )
1184 : {
1185 0 : log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1186 0 : return -1;
1187 : }
1188 :
1189 0 : float interval = -1;
1190 :
1191 0 : if( ipRecv.find( "current" ) )
1192 : {
1193 0 : interval = ipRecv["current"].get<float>();
1194 : }
1195 :
1196 0 : if( ipRecv.find( "target" ) )
1197 : {
1198 0 : interval = ipRecv["target"].get<float>();
1199 : }
1200 :
1201 0 : if( interval < 0 )
1202 : {
1203 0 : log<software_error>( { __FILE__, __LINE__, "Invalid requested interval: " + std::to_string( interval ) } );
1204 0 : return 0;
1205 : }
1206 :
1207 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1208 0 : m_sparkleClockInterval = interval;
1209 0 : updateIfChanged( m_indiP_interval, "target", m_sparkleClockInterval );
1210 :
1211 0 : m_restartSp = true;
1212 :
1213 0 : return 0;
1214 0 : }
1215 :
1216 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_dwell )
1217 : ( const pcf::IndiProperty &ipRecv )
1218 : {
1219 0 : if( ipRecv.createUniqueKey() != m_indiP_dwell.createUniqueKey() )
1220 : {
1221 0 : log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
1222 0 : return -1;
1223 : }
1224 :
1225 0 : unsigned dwell = 0;
1226 :
1227 0 : if( ipRecv.find( "current" ) )
1228 : {
1229 0 : dwell = ipRecv["current"].get<unsigned>();
1230 : }
1231 :
1232 0 : if( ipRecv.find( "target" ) )
1233 : {
1234 0 : dwell = ipRecv["target"].get<unsigned>();
1235 : }
1236 :
1237 0 : if( dwell == 0 )
1238 : {
1239 0 : log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( dwell ) } );
1240 0 : return 0;
1241 : }
1242 :
1243 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1244 0 : m_dwell = dwell;
1245 0 : updateIfChanged( m_indiP_dwell, "target", m_dwell );
1246 :
1247 0 : m_restartSp = true;
1248 :
1249 0 : return 0;
1250 0 : }
1251 :
1252 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_single )
1253 : ( const pcf::IndiProperty &ipRecv )
1254 : {
1255 0 : INDI_VALIDATE_CALLBACK_PROPS( m_indiP_single, ipRecv );
1256 :
1257 0 : int single = 0;
1258 :
1259 0 : if( ipRecv.find( "current" ) )
1260 : {
1261 0 : single = ipRecv["current"].get<int>();
1262 : }
1263 :
1264 0 : if( ipRecv.find( "target" ) )
1265 : {
1266 0 : single = ipRecv["target"].get<int>();
1267 : }
1268 :
1269 0 : if( single < -1 || single > 3 )
1270 : {
1271 0 : log<software_error>( { __FILE__, __LINE__, "Invalid requested dwell: " + std::to_string( single ) } );
1272 0 : return 0;
1273 : }
1274 :
1275 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1276 0 : m_single = single;
1277 0 : updateIfChanged( m_indiP_single, "target", m_single );
1278 0 : return 0;
1279 0 : }
1280 :
1281 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_modulating )
1282 : ( const pcf::IndiProperty &ipRecv )
1283 : {
1284 0 : if( ipRecv.getName() != m_indiP_modulating.getName() )
1285 : {
1286 0 : log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1287 0 : return -1;
1288 : }
1289 :
1290 0 : if( !ipRecv.find( "toggle" ) )
1291 0 : return 0;
1292 :
1293 0 : std::unique_lock<std::mutex> lock( m_indiMutex );
1294 :
1295 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1296 : {
1297 0 : m_modulating = false;
1298 0 : indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::Off, m_indiDriver, INDI_IDLE );
1299 : }
1300 :
1301 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1302 : {
1303 0 : m_modulating = true;
1304 0 : indi::updateSwitchIfChanged( m_indiP_modulating, "toggle", pcf::IndiElement::On, m_indiDriver, INDI_OK );
1305 : }
1306 :
1307 0 : return 0;
1308 0 : }
1309 :
1310 0 : INDI_NEWCALLBACK_DEFN( sparkleClock, m_indiP_zero )
1311 : ( const pcf::IndiProperty &ipRecv )
1312 : {
1313 0 : if( ipRecv.getName() != m_indiP_zero.getName() )
1314 : {
1315 0 : log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1316 0 : return -1;
1317 : }
1318 :
1319 0 : if( m_modulating == true )
1320 : {
1321 0 : log<text_log>( "zero requested but currently modulating", logPrio::LOG_NOTICE );
1322 0 : return 0;
1323 : }
1324 :
1325 0 : if( !ipRecv.find( "request" ) )
1326 0 : return 0;
1327 :
1328 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1329 : {
1330 0 : m_imageStream.md->write = 1;
1331 :
1332 0 : memset( m_imageStream.array.raw, 0, m_width * m_height * m_typeSize );
1333 : timespec currtime;
1334 0 : clock_gettime( CLOCK_REALTIME, &currtime );
1335 0 : m_imageStream.md->atime = currtime;
1336 0 : m_imageStream.md->writetime = currtime;
1337 :
1338 0 : m_imageStream.md->cnt0++;
1339 :
1340 0 : m_imageStream.md->write = 0;
1341 0 : ImageStreamIO_sempost( &m_imageStream, -1 );
1342 0 : log<text_log>( "zeroed" );
1343 : }
1344 :
1345 0 : return 0;
1346 : }
1347 :
1348 0 : inline int sparkleClock::checkRecordTimes()
1349 : {
1350 0 : return telemeter<sparkleClock>::checkRecordTimes( telem_sparkleclock() );
1351 : }
1352 :
1353 0 : inline int sparkleClock::recordTelem( const telem_sparkleclock * )
1354 : {
1355 0 : return recordSparkleClock( true );
1356 : }
1357 :
1358 0 : inline int sparkleClock::recordSparkleClock( bool force )
1359 : {
1360 0 : static bool lastModulating = m_modulating;
1361 0 : static bool lastTrigger = m_trigger;
1362 0 : static float lastFrequency = m_frequency;
1363 0 : static float lastInterval = m_frequency;
1364 0 : static float lastSeparation1 = m_separation_1;
1365 0 : static float lastSeparation2 = m_separation_2;
1366 0 : static float lastAngle = m_angle;
1367 0 : static float lastAmp = m_amp;
1368 0 : static bool lastCross = m_cross;
1369 :
1370 0 : if( !( lastModulating == m_modulating ) || !( lastTrigger == m_trigger ) || !( lastFrequency == m_frequency ) || !( lastInterval == m_interval ) ||
1371 0 : !( lastSeparation1 == m_separation_1 ) ||!( lastSeparation2 == m_separation_2 ) || !( lastAngle == m_angle ) || !( lastAmp == m_amp ) ||
1372 0 : !( lastCross == m_cross ) || force )
1373 : {
1374 0 : telem<telem_sparkleclock>({ m_modulating,
1375 0 : m_trigger,
1376 0 : m_frequency,
1377 0 : m_interval,
1378 0 : std::vector<float>( { m_separation_1, m_separation_2 } ),
1379 0 : m_angle,
1380 0 : m_amp });
1381 :
1382 0 : lastModulating = m_modulating;
1383 0 : lastTrigger = m_trigger;
1384 0 : lastFrequency = m_frequency;
1385 0 : lastSeparation1 = m_separation_1;
1386 0 : lastSeparation2 = m_separation_2;
1387 0 : lastAngle = m_angle;
1388 0 : lastAmp = m_amp;
1389 : }
1390 :
1391 0 : return 0;
1392 : }
1393 :
1394 : } // namespace app
1395 : } // namespace MagAOX
1396 :
1397 : #endif // sparkleClock_hpp
|