API
 
Loading...
Searching...
No Matches
dm.hpp
Go to the documentation of this file.
1/** \file dm.hpp
2 * \brief The MagAO-X generic deformable mirror controller.
3 *
4 * \author Jared R. Males (jaredmales@gmail.com)
5 *
6 * \ingroup app_files
7 */
8
9#ifndef dm_hpp
10#define dm_hpp
11
12#include <mx/improc/eigenImage.hpp>
13#include <mx/improc/milkImage.hpp>
14#include <mx/ioutils/fits/fitsFile.hpp>
15
16#include "shmimMonitor.hpp"
17
18namespace MagAOX
19{
20namespace app
21{
22namespace dev
23{
24
25template <typename typeT>
26constexpr uint8_t ImageStreamTypeCode()
27{
28 return 0;
29}
30
31template <>
32constexpr uint8_t ImageStreamTypeCode<float>()
33{
34 return _DATATYPE_FLOAT;
35}
36
37template <>
38constexpr uint8_t ImageStreamTypeCode<double>()
39{
40 return _DATATYPE_DOUBLE;
41}
42
43/** MagAO-X generic deformable mirror controller
44 *
45 *
46 * The derived class `derivedT` must meet the following requirements:
47 * - Must be a MagAOXApp<true>
48 *
49 * - Must be a dev::shmimMonitor<derivedT>
50 *
51 * - Must NOT call dev::shmimMonitor<derivedT>::setupConfog and dev::shmimMonitor<derivedT>::loadConfig. These are
52 * handled by this class.
53 *
54 * - Must expose the following interface
55 * \code
56 * int initDM();
57 * int commandDM(void * cmd);
58 * int zeroDM();
59 * int releaseDM();
60 * \endcode
61 * Each of the above functions should return 0 on success, and -1 on an error.
62 *
63 * - This class must be declared a friend in the derived class, like so:
64 * \code
65 * friend class dev::dm<derivedT,realT>;
66 * \endcode
67 *
68 * - Must contain the following typedef:
69 * \code
70 * typedef dev::dm<derivedT, realT> dmT;
71 * \endcode
72 *
73 * - Calls to this class's `setupConfig`, `loadConfig`, `appStartup`, `appLogic`, `appShutdown`, and `udpdateINDI`
74 * functions must be placed in the derived class's functions of the same name. For convenience the
75 * following macros are defined to provide error checking:
76 * \code
77 * DM_SETUP_CONFIG( cfig )
78 * DM_LOAD_CONFIG( cfig )
79 * DM_APP_STARTUP
80 * DM_APP_LOGIC
81 * DM_UPDATE_INDI
82 * DM_APP_SHUTDOWN
83 * \endcode
84 *
85 *
86 * \ingroup appdev
87 */
88template <class derivedT, typename realT>
89class dm
90{
91
92 typedef mx::verbose::vvv verboseT;
93
94 protected:
95 /** \name Configurable Parameters
96 * @{
97 */
98
99 std::string m_calibPath; ///< The path to this DM's calibration files.
100 std::string m_flatPath; ///< The path to this DM's flat files (usually the same as calibPath)
101 std::string m_testPath; ///< The path to this DM's test files (default is calibPath/tests;
102
103 std::string m_actMaskPath; ///< The file name of the actuator mask for this DM
104
105 std::string m_flatDefault; ///< The file name of the this DM's default flat command. Path and extension will be
106 ///< ignored and can be omitted.
107 std::string m_testDefault; ///< The file name of the this DM's default test command. Path and extension will be
108 ///< ignored and can be omitted.
109
110 std::string m_shmimFlat; ///< The name of the shmim stream to write the flat to.
111 std::string m_shmimTest; ///< The name of the shmim stream to write the test to.
112 std::string m_shmimSat; ///< The name of the shmim stream to write the saturation map to.
113 std::string m_shmimSatPerc; ///< The name of the shmim stream to write the saturation percentage map to.
114
115 int m_satAvgInt{ 100 }; ///< The time in milliseconds to accumulate saturation over.
116
117 int m_satThreadPrio{ 0 }; ///< Priority of the saturation thread. Usually ok to be 0.
118
119 std::string m_shmimShape; ///< The name of the shmim stream to write the desaturated true shape to.
120 std::string m_shmimDelta; ///< The name of the shmim stream to write the desaturated delta command to.
121 std::string m_shmimDiff; ///< The name of the shmim stream to write the difference to.
122
123 uint32_t m_dmWidth{ 0 }; ///< The width of the images in the stream
124 uint32_t m_dmHeight{ 0 }; ///< The height of the images in the stream
125
126 static constexpr uint8_t m_dmDataType = ImageStreamTypeCode<realT>(); ///< The ImageStreamIO type code.
127
128 float m_percThreshold{ 0.98 }; ///< Threshold on percentage of frames an actuator is saturated over an interval.
129
130 float m_intervalSatThreshold{ 0.50 }; /**< Threshold on percentage of actuators which exceed
131 percThreshold in an interval.*/
132
133 int m_intervalSatCountThreshold{ 10 }; /**< Threshold on number of consecutive intervals
134 the intervalSatThreshold is exceeded. */
135
136 std::vector<std::string> m_satTriggerDevice; ///< Device(s) with a toggle switch to toggle on saturation trigger.
137
138 std::vector<std::string> m_satTriggerProperty; /**< Property with a toggle switch to toggle on saturation trigger,
139 one per entry in satTriggerDevice.*/
140
141 ///@}
142
143 std::string m_calibRelDir; ///< The directory relative to the calibPath. Set this before calling
144 ///< dm<derivedT,realT>::loadConfig().
145
146 int m_numChannels{ 0 }; ///< The number of dmcomb channels found as part of allocation.
147
148 std::vector<mx::improc::milkImage<realT> *> m_channels;
149
150 std::map<std::string, std::string> m_flatCommands; ///< Map of flat file name to full path
151 std::string m_flatCurrent; ///< The name of the current flat command
152
153 mx::improc::eigenImage<realT> m_flatCommand; ///< Data storage for the flat command
154 bool m_flatLoaded{ false }; ///< Flag indicating whether a flat is loaded in memory
155
156 IMAGE m_flatImageStream; ///< The ImageStreamIO shared memory buffer for the flat.
157 bool m_flatSet{ false }; ///< Flag indicating whether the flat command has been set.
158
159 mx::improc::milkImage<realT> m_actMask;
160
161 std::map<std::string, std::string> m_testCommands; ///< Map of test file name to full path
162 std::string m_testCurrent;
163
164 mx::improc::eigenImage<realT> m_testCommand; ///< Data storage for the test command
165 bool m_testLoaded{ false }; ///< Flag indicating whether a test command is loaded in memory
166
167 IMAGE m_testImageStream; ///< The ImageStreamIO shared memory buffer for the test.
168 bool m_testSet{ false }; ///< Flag indicating whether the test command has been set.
169
170 mx::improc::eigenImage<uint8_t> m_instSatMap; /**< The instantaneous saturation map, 0/1, set by the commandDM()
171 function of the derived class.*/
172
173 mx::improc::eigenImage<uint16_t> m_accumSatMap; /**< The accumulated saturation map, which acccumulates for
174 m_satAvgInt then is publised as a 0/1 image. */
175
176 mx::improc::eigenImage<float> m_satPercMap; /**< Map of the percentage of time each actuator was
177 saturated during the avg. interval.*/
178
179 IMAGE m_satImageStream; ///< The ImageStreamIO shared memory buffer for the sat map.
180 IMAGE m_satPercImageStream; ///< The ImageStreamIO shared memory buffer for the sat percentage map.
181
182 int m_overSatAct{ 0 }; // counter
183 int m_intervalSatExceeds{ 0 }; // counter
184 bool m_intervalSatTrip{ 0 }; // flag to trip the loop opening
185
186 mx::improc::milkImage<realT> m_outputShape; ///< The true output shape after saturation.
187
188 std::vector<std::string> m_deltaChannels; ///< The names of channels which are treated as delta commands
189
190 std::vector<size_t> m_deltas; ///< Indices of the channels which are delta commands
191 std::vector<size_t> m_notDeltas; ///< Indices of the channels which are not delta commands
192
193 mx::improc::eigenImage<realT> m_totalFlat; ///< the total of all non-delta channels
194 mx::improc::eigenImage<realT> m_totalDelta; ///< the total of all delta channels
195
196 mx::improc::milkImage<realT> m_outputDelta; ///< The true output delta command after saturation.
197 mx::improc::milkImage<realT>
198 m_outputDiff; ///< The difference between command and true delta command after saturation.
199
200 /** \name Saturation Thread Data
201 * This thread processes the saturation maps
202 * @{
203 */
204
205 sem_t m_satSemaphore; ///< Semaphore used to tell the saturation thread to run.
206
207 bool m_satThreadInit{ true }; ///< Synchronizer for thread startup, to allow priority setting to finish.
208
209 pid_t m_satThreadID{ 0 }; ///< The ID of the saturation thread.
210
211 pcf::IndiProperty m_satThreadProp; ///< The property to hold the saturation thread details.
212
213 std::thread m_satThread; ///< A separate thread for the actual saturation processing
214
215 ///@}
216
217 public:
218 /// Destructor
219 /** deallocates the m_channels vector
220 *
221 */
223
224 /// Get the
225 /**
226 * \returns the current value of
227 */
228 const std::string &calibPath() const;
229
230 /// Get the
231 /**
232 * \returns the current value of
233 */
234 const std::string &flatPath() const;
235
236 /// Get the
237 /**
238 * \returns the current value of
239 */
240 const std::string &testPath() const;
241
242 /// Get the
243 /**
244 * \returns the current value of
245 */
246 const std::string &flatDefault() const;
247
248 /// Get the
249 /**
250 * \returns the current value of
251 */
252 const std::string &testDefault() const;
253
254 /// Get the
255 /**
256 * \returns the current value of
257 */
258 const std::string &shmimFlat() const;
259
260 /// Get the
261 /**
262 * \returns the current value of
263 */
264 const std::string &shmimTest() const;
265
266 /// Get the
267 /**
268 * \returns the current value of
269 */
270 const std::string &shmimSat() const;
271
272 /// Get the stream name for saturation percentage
273 /**
274 * \returns the current value of m_shmimSatPerc
275 */
276 const std::string &shmimSatPerc() const;
277
278 /// Get the saturation accumulation interval
279 /**
280 * \returns the current value of m_satAvgInt
281 */
282 int satAvgInt() const;
283
284 /// Get the saturation thread priority
285 /**
286 * \returns the current value of m_satThreadPrio
287 */
288 int satThreadPrio() const;
289
290 /// Get the
291 /**
292 * \returns the current value of
293 */
294 const std::string &shmimShape() const;
295
296 /// Get the
297 /**
298 * \returns the current value of
299 */
300 const std::string &shmimDelta() const;
301
302 /// Get the DM Width
303 /**
304 * \returns the current value of m_dmWidth
305 */
306 uint32_t dmWidth() const;
307
308 /// Get the DM Height
309 /**
310 * \returns the current value of m_dmHeight
311 */
312 uint32_t dmHeight() const;
313
314 /// Get the DM data type
315 /**
316 * \returns the current value of m_dmDataType
317 */
318 uint8_t dmDataType() const;
319
320 /// Get the saturation percentage threshold
321 /**
322 * \returns the current value of m_percThreshold
323 */
324 float percThreshold() const;
325
326 /// Get the interval saturation threshold
327 /**
328 * \returns the current value of m_intervalSatThreshold
329 */
330 float intervalSatThreshold() const;
331
332 /// Get the interval saturation count threshold
333 /**
334 * \returns the current value of m_intervalSatCountThreshold
335 */
337
338 /// Get the saturation trigger device(s)
339 /**
340 * \returns the current value of m_satTriggerDevice
341 */
342 const std::vector<std::string> &satTriggerDevice() const;
343
344 /// Get the saturation trigger property(ies)
345 /**
346 * \returns the current value of m_satTriggerProperty
347 */
348 const std::vector<std::string> &satTriggerProperty() const;
349
350 const std::string &calibRelDir() const;
351
352 int numChannels() const;
353
354 const mx::improc::eigenImage<uint8_t> &instSatMap() const;
355
356 const mx::improc::eigenImage<uint16_t> &accumSatMap() const;
357
358 const mx::improc::eigenImage<float> &satPercMap() const;
359
360 const std::vector<std::string> &deltaChannels() const;
361
362 const std::vector<size_t> &notDeltas() const;
363
364 const mx::improc::eigenImage<float> &totalFlat() const;
365
366 /// Setup the configuration system
367 /**
368 * This should be called in `derivedT::setupConfig` as
369 * \code
370 dm<derivedT,realT>::setupConfig(config);
371 \endcode
372 * with appropriate error checking.
373 */
374 int setupConfig( mx::app::appConfigurator &config /**< [out] the derived classes configurator*/ );
375
376 /// load the configuration system results
377 /**
378 * This should be called in `derivedT::loadConfig` as
379 * \code
380 dm<derivedT,realT>::loadConfig(config);
381 \endcode
382 * with appropriate error checking.
383 */
384 int loadConfig( mx::app::appConfigurator &config /**< [in] the derived classes configurator*/ );
385
386 /// Startup function
387 /**
388 * This should be called in `derivedT::appStartup` as
389 * \code
390 dm<derivedT,realT>::appStartup();
391 \endcode
392 * with appropriate error checking.
393 *
394 * \returns 0 on success
395 * \returns -1 on error, which is logged.
396 */
398
399 /// DM application logic
400 /** This should be called in `derivedT::appLogic` as
401 * \code
402 dm<derivedT,realT>::appLogic();
403 \endcode
404 * with appropriate error checking.
405 *
406 * \returns 0 on success
407 * \returns -1 on error, which is logged.
408 */
409 int appLogic();
410
411 /// DM shutdown
412 /** This should be called in `derivedT::appShutdown` as
413 * \code
414 dm<derivedT,realT>::appShutdown();
415 \endcode
416 * with appropriate error checking.
417 *
418 * \returns 0 on success
419 * \returns -1 on error, which is logged.
420 */
422
423 /// DM Poweroff
424 /** This should be called in `derivedT::onPowerOff` as
425 * \code
426 dm<derivedT,realT>::onPowerOff();
427 \endcode
428 * with appropriate error checking.
429 *
430 * \returns 0 on success
431 * \returns -1 on error, which is logged.
432 */
434
435 /// DM Poweroff Updates
436 /** This should be called in `derivedT::whilePowerOff` as
437 * \code
438 dm<derivedT,realT>::whilePowerOff();
439 \endcode
440 * with appropriate error checking.
441 *
442 * \returns 0 on success
443 * \returns -1 on error, which is logged.
444 */
446
447 /// Find the DM comb channels
448 /** Introspectively finds all dmXXdispYY channels, zeroes them, and raises the semapahore
449 * on the last to cause dmcomb to update.
450 */
452
453 /// Called after shmimMonitor connects to the dmXXdisp stream. Checks for proper size.
454 /**
455 * \returns 0 on success
456 * \returns -1 if incorrect size or data type in stream.
457 */
458 int allocate( const dev::shmimT &sp );
459
460 /// Called by shmimMonitor when a new DM command is available. This is just a pass-through to
461 /// derivedT::commandDM(char*).
462 int processImage( void *curr_src, const dev::shmimT &sp );
463
464 /// Calls derived()->initDM()
465 /**
466 * \returns 0 on success
467 * \returns -1 on error from derived()->initDM()
468 */
470
471 /// Calls derived()->releaseDM() and then 0s all channels and the sat map.
472 /** This is called by the relevant INDI callback
473 *
474 * \returns 0 on success
475 * \returns -1 on error
476 */
478
479 /// Check the flats directory and update the list of flats if anything changes
480 /** This is called once per appLogic and whilePowerOff loops.
481 *
482 * \returns 0 on success
483 * \returns -1 on error
484 */
486
487 /// Load a flat file
488 /** Uses the target argument for lookup in m_flatCommands to find the path
489 * and loads the command in the local memory. Calls setFlat if the flat
490 * is currently set.
491 *
492 * \returns 0 on success
493 * \returns -1 on error
494 */
495 int loadFlat( const std::string &target /**< [in] the name of the flat to load */ );
496
497 /// Send the current flat command to the DM
498 /** Writes the command to the designated shmim.
499 *
500 * \returns 0 on success
501 * \returns -1 on error
502 */
503 int setFlat( bool update = false /**< [in] If true, this is an update rather than a new set*/ );
504
505 /// Zero the flat command on the DM
506 /** Writes a 0 array the designated shmim.
507 *
508 * \returns 0 on success
509 * \returns -1 on error
510 */
511 int zeroFlat();
512
513 /// Check the tests directory and update the list of tests if anything changes
514 /** This is called once per appLogic and whilePowerOff loops.
515 *
516 * \returns 0 on success
517 * \returns -1 on error
518 */
520
521 /// Load a test file
522 /** Uses the target argument for lookup in m_testCommands to find the path
523 * and loads the command in the local memory. Calls setTest if the test
524 * is currently set.
525 */
526 int loadTest( const std::string &target );
527
528 /// Send the current test command to the DM
529 /** Writes the command to the designated shmim.
530 *
531 * \returns 0 on success
532 * \returns -1 on error
533 */
534 int setTest();
535
536 /// Zero the test command on the DM
537 /** Writes a 0 array the designated shmim.
538 *
539 * \returns 0 on success
540 * \returns -1 on error
541 */
542 int zeroTest();
543
544 /// Zero all channels
545 /**
546 * \returns 0 on sucess
547 * \returns <0 on an error
548 */
549 int zeroAll( bool nosem = false /**< [in] [optional] if true then the semaphore
550 is not raised after zeroing all channels*/
551 );
552
553 /// Calculate the delta command from the output shape.
555
556 /// Clear the saturation maps and zero the shared memory.
557 /**
558 * \returns 0 on success
559 * \returns -1 on error
560 */
561 int clearSat();
562
563 protected:
564 /** \name Saturation Thread Functions
565 * This thread processes the saturation maps
566 * @{
567 */
568
569 /// Thread starter, called by MagAOXApp::threadStart on thread construction. Calls satThreadExec.
570 static void satThreadStart( dm *d /**< [in] a pointer to a dm instance (normally this) */ );
571
572 /// Execute saturation processing
574
575 /// Trigger loop openings because of excessive saturation
577
578 ///@}
579
580 protected:
581 /** \name INDI
582 *
583 *@{
584 */
585 protected:
586 // declare our properties
587
588 pcf::IndiProperty m_indiP_flat; ///< Property used to set and report the current flat
589
590 pcf::IndiProperty m_indiP_init;
591 pcf::IndiProperty m_indiP_zero;
592 pcf::IndiProperty m_indiP_release;
593
594 pcf::IndiProperty m_indiP_flats; ///< INDI Selection switch containing the flat files.
595 pcf::IndiProperty m_indiP_flatShmim; ///< Publish the shmim being used for the flat
596 pcf::IndiProperty m_indiP_setFlat; ///< INDI toggle switch to set the current flat.
597
598 pcf::IndiProperty m_indiP_tests; ///< INDI Selection switch containing the test pattern files.
599 pcf::IndiProperty m_indiP_testShmim; ///< Publish the shmim being used for the test command
600 pcf::IndiProperty m_indiP_setTest; ///< INDI toggle switch to set the current test pattern.
601
602 pcf::IndiProperty m_indiP_zeroAll;
603
604 public:
605 /// The static callback function to be registered for initializing the DM.
606 /**
607 * \returns 0 on success.
608 * \returns -1 on error.
609 */
610 static int st_newCallBack_init( void *app, /**< [in] a pointer to this, will be
611 static_cast-ed to derivedT.*/
612 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the
613 the new property request.*/
614 );
615
616 /// The callback called by the static version, to actually process the new request.
617 /**
618 * \returns 0 on success.
619 * \returns -1 on error.
620 */
621 int newCallBack_init( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with
622 the the new property request.*/
623 );
624
625 /// The static callback function to be registered for initializing the DM.
626 /**
627 * \returns 0 on success.
628 * \returns -1 on error.
629 */
630 static int st_newCallBack_zero( void *app, /**< [in] a pointer to this, will be
631 static_cast-ed to derivedT.*/
632 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with
633 the the new property request.*/
634 );
635
636 /// The callback called by the static version, to actually process the new request.
637 /**
638 * \returns 0 on success.
639 * \returns -1 on error.
640 */
642 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the
643 the new property request.*/ );
644
645 /// The static callback function to be registered for initializing the DM.
646 /**
647 * \returns 0 on success.
648 * \returns -1 on error.
649 */
650 static int st_newCallBack_release( void *app, /**< [in] a pointer to this, will be
651 static_cast-ed to derivedT.*/
652 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with
653 the the new property request.*/
654 );
655
656 /// The callback called by the static version, to actually process the new request.
657 /**
658 * \returns 0 on success.
659 * \returns -1 on error.
660 */
661 int newCallBack_release( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with
662 the new property request.*/
663 );
664
665 /// The static callback function to be registered for selecting the flat file
666 /**
667 * \returns 0 on success.
668 * \returns -1 on error.
669 */
670 static int st_newCallBack_flats( void *app, /**< [in] a pointer to this, will be
671 static_cast-ed to derivedT.*/
672 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with
673 the new property request.*/
674 );
675
676 /// The callback called by the static version, to actually process the new request.
677 /**
678 * \returns 0 on success.
679 * \returns -1 on error.
680 */
682 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/ );
683
684 /// The static callback function to be registered for setting the flat
685 /**
686 * \returns 0 on success.
687 * \returns -1 on error.
688 */
690 void *app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
691 const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
692 );
693
694 /// The callback called by the static version, to actually process the new request.
695 /**
696 * \returns 0 on success.
697 * \returns -1 on error.
698 */
700 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/ );
701
702 /// The static callback function to be registered for selecting the test file
703 /**
704 * \returns 0 on success.
705 * \returns -1 on error.
706 */
708 void *app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
709 const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
710 );
711
712 /// The callback called by the static version, to actually process the new request.
713 /**
714 * \returns 0 on success.
715 * \returns -1 on error.
716 */
718 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/ );
719
720 /// The static callback function to be registered for setting the test shape
721 /**
722 * \returns 0 on success.
723 * \returns -1 on error.
724 */
725 static int st_newCallBack_setTest( void *app, /**< [in] a pointer to this, will be
726 static_cast-ed to derivedT.*/
727 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with
728 the the new property request.*/
729 );
730
731 /// The callback called by the static version, to actually process the new request.
732 /**
733 * \returns 0 on success.
734 * \returns -1 on error.
735 */
736 int newCallBack_setTest( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the
737 the new property request.*/
738 );
739
740 /// The static callback function to be registered for zeroing all channels
741 /**
742 * \returns 0 on success.
743 * \returns -1 on error.
744 */
746 void *app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
747 const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
748 );
749
750 /// The callback for the zeroAll toggle switch, called by the static version
751 /**
752 * \returns 0 on success.
753 * \returns -1 on error.
754 */
756 const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/ );
757
758 /// Update the INDI properties for this device controller
759 /** You should call this once per main loop.
760 * It is not called automatically.
761 *
762 * \returns 0 on success.
763 * \returns -1 on error.
764 */
766
767 ///@}
768
769 public:
770 // clang-format off
771 #ifdef XWC_DMTIMINGS //clang-format on
772
773 typedef int32_t cbIndexT;
774
775 double m_t0{ 0 }, m_tf{ 0 }, m_tsat0{ 0 }, m_tsatf{ 0 };
776 double m_tact0{ 0 }, m_tact1{ 0 }, m_tact2{ 0 }, m_tact3{ 0 }, m_tact4{ 0 };
777 double m_tdelta0 {0}, m_tdeltaf {0};
778
779 mx::sigproc::circularBufferIndex<double, cbIndexT> m_piTimes;
780
781 mx::sigproc::circularBufferIndex<double, cbIndexT> m_satSem;
782
783 mx::sigproc::circularBufferIndex<double, cbIndexT> m_actProc;
784
785 mx::sigproc::circularBufferIndex<double, cbIndexT> m_actCom;
786
787 mx::sigproc::circularBufferIndex<double, cbIndexT> m_satUp;
788
789 mx::sigproc::circularBufferIndex<double, cbIndexT> m_deltaUp;
790
791
792 std::vector<double> m_piTimesD;
793 std::vector<double> m_satSemD;
794 std::vector<double> m_actProcD;
795 std::vector<double> m_actComD;
796 std::vector<double> m_satUpD;
797 std::vector<double> m_deltaUpD;
798
799 // clang-format off
800 #endif // clang-format on
801
802 private:
803 derivedT &derived()
804 {
805 return *static_cast<derivedT *>( this );
806 }
807};
808
809template <class derivedT, typename realT>
811{
812 for( auto &mi : m_channels )
813 {
814 if( mi != nullptr )
815 {
816 delete mi;
817 }
818 }
819}
820
821template <class derivedT, typename realT>
822const std::string &dm<derivedT, realT>::calibPath() const
823{
824 return m_calibPath;
825}
826
827template <class derivedT, typename realT>
828const std::string &dm<derivedT, realT>::flatPath() const
829{
830 return m_flatPath;
831}
832
833template <class derivedT, typename realT>
834const std::string &dm<derivedT, realT>::testPath() const
835{
836 return m_testPath;
837}
838
839template <class derivedT, typename realT>
840const std::string &dm<derivedT, realT>::flatDefault() const
841{
842 return m_flatDefault;
843}
844
845template <class derivedT, typename realT>
846const std::string &dm<derivedT, realT>::testDefault() const
847{
848 return m_testDefault;
849}
850
851template <class derivedT, typename realT>
852const std::string &dm<derivedT, realT>::shmimFlat() const
853{
854 return m_shmimFlat;
855}
856
857template <class derivedT, typename realT>
858const std::string &dm<derivedT, realT>::shmimTest() const
859{
860 return m_shmimTest;
861}
862
863template <class derivedT, typename realT>
864const std::string &dm<derivedT, realT>::shmimSat() const
865{
866 return m_shmimSat;
867}
868
869template <class derivedT, typename realT>
870const std::string &dm<derivedT, realT>::shmimSatPerc() const
871{
872 return m_shmimSatPerc;
873}
874
875template <class derivedT, typename realT>
876const std::string &dm<derivedT, realT>::shmimShape() const
877{
878 return m_shmimShape;
879}
880
881template <class derivedT, typename realT>
882const std::string &dm<derivedT, realT>::shmimDelta() const
883{
884 return m_shmimDelta;
885}
886
887template <class derivedT, typename realT>
889{
890 return m_dmWidth;
891}
892
893template <class derivedT, typename realT>
895{
896 return m_dmHeight;
897}
898
899template <class derivedT, typename realT>
901{
902 return m_dmDataType;
903}
904
905template <class derivedT, typename realT>
907{
908 return m_percThreshold;
909}
910
911template <class derivedT, typename realT>
913{
914 return m_intervalSatThreshold;
915}
916
917template <class derivedT, typename realT>
919{
920 return m_intervalSatCountThreshold;
921}
922
923template <class derivedT, typename realT>
924const std::vector<std::string> &dm<derivedT, realT>::satTriggerDevice() const
925{
926 return m_satTriggerDevice;
927}
928
929template <class derivedT, typename realT>
930const std::vector<std::string> &dm<derivedT, realT>::satTriggerProperty() const
931{
932 return m_satTriggerProperty;
933}
934
935template <class derivedT, typename realT>
936const std::string &dm<derivedT, realT>::calibRelDir() const
937{
938 return m_calibRelDir;
939}
940
941template <class derivedT, typename realT>
943{
944 return m_numChannels;
945}
946
947template <class derivedT, typename realT>
948const mx::improc::eigenImage<uint8_t> &dm<derivedT, realT>::instSatMap() const
949{
950 return m_instSatMap;
951}
952
953template <class derivedT, typename realT>
954const mx::improc::eigenImage<uint16_t> &dm<derivedT, realT>::accumSatMap() const
955{
956 return m_accumSatMap;
957}
958
959template <class derivedT, typename realT>
960const mx::improc::eigenImage<float> &dm<derivedT, realT>::satPercMap() const
961{
962 return m_satPercMap;
963}
964
965template <class derivedT, typename realT>
966const std::vector<std::string> &dm<derivedT, realT>::deltaChannels() const
967{
968 return m_deltaChannels;
969}
970
971template <class derivedT, typename realT>
972const std::vector<size_t> &dm<derivedT, realT>::notDeltas() const
973{
974 return m_notDeltas;
975}
976
977template <class derivedT, typename realT>
978const mx::improc::eigenImage<float> &dm<derivedT, realT>::totalFlat() const
979{
980 return m_totalFlat;
981}
982
983template <class derivedT, typename realT>
984int dm<derivedT, realT>::setupConfig( mx::app::appConfigurator &config )
985{
986 config.add( "dm.calibPath",
987 "",
988 "dm.calibPath",
989 argType::Required,
990 "dm",
991 "calibPath",
992 false,
993 "string",
994 "The path to calibration files, relative to the MagAO-X calibration path." );
995
996 config.add( "dm.flatPath",
997 "",
998 "dm.flatPath",
999 argType::Required,
1000 "dm",
1001 "flatPath",
1002 false,
1003 "string",
1004 "The path to flat files. Default is the calibration path." );
1005
1006 config.add( "dm.flatDefault",
1007 "",
1008 "dm.flatDefault",
1009 argType::Required,
1010 "dm",
1011 "flatDefault",
1012 false,
1013 "string",
1014 "The default flat file (path and extension are not required)." );
1015
1016 config.add( "dm.testPath",
1017 "",
1018 "dm.testPath",
1019 argType::Required,
1020 "dm",
1021 "testPath",
1022 false,
1023 "string",
1024 "The path to test files. Default is the calibration path plus /tests." );
1025
1026 config.add( "dm.testDefault",
1027 "",
1028 "dm.testDefault",
1029 argType::Required,
1030 "dm",
1031 "testDefault",
1032 false,
1033 "string",
1034 "The default test file (path and extension are not required)." );
1035
1036 config.add( "dm.actMaskPath",
1037 "",
1038 "dm.actMaskPath",
1039 argType::Required,
1040 "dm",
1041 "actMaskPath",
1042 false,
1043 "string",
1044 "The path to the actuator mask for this DM, relative to the calib path." );
1045
1046 // Overriding the shmimMonitor setup so that these all go in the dm section
1047 // Otherwise, would call shmimMonitor<dm<derivedT,realT>>::setupConfig();
1048 ///\todo shmimMonitor now has configSection so this isn't necessary.
1049 config.add( "dm.threadPrio",
1050 "",
1051 "dm.threadPrio",
1052 argType::Required,
1053 "dm",
1054 "threadPrio",
1055 false,
1056 "int",
1057 "The real-time priority of the dm control thread." );
1058
1059 config.add( "dm.cpuset",
1060 "",
1061 "dm.cpuset",
1062 argType::Required,
1063 "dm",
1064 "cpuset",
1065 false,
1066 "int",
1067 "The cpuset for the dm control thread." );
1068
1069 config.add( "dm.shmimName",
1070 "",
1071 "dm.shmimName",
1072 argType::Required,
1073 "dm",
1074 "shmimName",
1075 false,
1076 "string",
1077 "The name of the ImageStreamIO shared memory image to monitor for DM comands. Will be used as "
1078 "/tmp/<shmimName>.im.shm." );
1079
1080 // end of shmimmonitor overrides
1081
1082 config.add( "dm.shmimFlat",
1083 "",
1084 "dm.shmimFlat",
1085 argType::Required,
1086 "dm",
1087 "shmimFlat",
1088 false,
1089 "string",
1090 "The name of the ImageStreamIO shared memory image to write the flat command to. Default is shmimName "
1091 "with 00 apended (i.e. dm00disp -> dm00disp00). " );
1092
1093 config.add( "dm.shmimTest",
1094 "",
1095 "dm.shmimTest",
1096 argType::Required,
1097 "dm",
1098 "shmimTest",
1099 false,
1100 "string",
1101 "The name of the ImageStreamIO shared memory image to write the test command to. Default is shmimName "
1102 "with 01 apended (i.e. dm00disp -> dm00disp01). " );
1103
1104 config.add( "dm.shmimSat",
1105 "",
1106 "dm.shmimSat",
1107 argType::Required,
1108 "dm",
1109 "shmimSat",
1110 false,
1111 "string",
1112 "The name of the ImageStreamIO shared memory image to write the saturation map to. Default is "
1113 "shmimName with SA apended (i.e. dm00disp -> dm00dispSA). This is created." );
1114
1115 config.add( "dm.shmimSatPerc",
1116 "",
1117 "dm.shmimSatPerc",
1118 argType::Required,
1119 "dm",
1120 "shmimSatPerc",
1121 false,
1122 "string",
1123 "The name of the ImageStreamIO shared memory image to write the saturation percentage map to. Default "
1124 "is shmimName with SP apended (i.e. dm00disp -> dm00dispSP). This is created." );
1125
1126 config.add( "dm.satAvgInt",
1127 "",
1128 "dm.satAvgInt",
1129 argType::Required,
1130 "dm",
1131 "satAvgInt",
1132 false,
1133 "int",
1134 "The interval in milliseconds over which saturation "
1135 "is accumulated before updating. Default is 100 ms." );
1136
1137 config.add( "dm.satThreadPrio",
1138 "",
1139 "dm.satThreadPrio",
1140 argType::Required,
1141 "dm",
1142 "satThreadPrio",
1143 false,
1144 "int",
1145 "The priority for the saturation thread. "
1146 "Usually ok to be 0." );
1147
1148 config.add( "dm.shmimShape",
1149 "",
1150 "dm.shmimShape",
1151 argType::Required,
1152 "dm",
1153 "shmimShape",
1154 false,
1155 "string",
1156 "The name of the ImageStreamIO shared memory image to write the desaturated shape to. Default is "
1157 "shmimName with _shape apended (i.e. dm00disp -> dm00disp_shape). This is created." );
1158
1159 config.add( "dm.shmimDelta",
1160 "",
1161 "dm.shmimDelta",
1162 argType::Required,
1163 "dm",
1164 "shmimDelta",
1165 false,
1166 "string",
1167 "The name of the ImageStreamIO shared memory image to write the "
1168 "desaturated delta-shape to. Default is "
1169 "shmimName with _delta apended (i.e. dm00disp -> dm00disp_delta). This is created." );
1170
1171 config.add( "dm.deltaChannels",
1172 "",
1173 "dm.deltaChannels",
1174 argType::Required,
1175 "dm",
1176 "deltaChannels",
1177 false,
1178 "vector<string>",
1179 "The names of the DM channels which are delta commands to be excluded from the total flat." );
1180
1181 config.add( "dm.width",
1182 "",
1183 "dm.width",
1184 argType::Required,
1185 "dm",
1186 "width",
1187 false,
1188 "string",
1189 "The width of the DM in actuators." );
1190
1191 config.add( "dm.height",
1192 "",
1193 "dm.height",
1194 argType::Required,
1195 "dm",
1196 "height",
1197 false,
1198 "string",
1199 "The height of the DM in actuators." );
1200
1201 config.add( "dm.percThreshold",
1202 "",
1203 "dm.percThreshold",
1204 argType::Required,
1205 "dm",
1206 "percThreshold",
1207 false,
1208 "float",
1209 "Threshold on percentage of frames an actuator is saturated over an interval. Default is 0.98." );
1210
1211 config.add( "dm.intervalSatThreshold",
1212 "",
1213 "dm.intervalSatThreshold",
1214 argType::Required,
1215 "dm",
1216 "intervalSatThreshold",
1217 false,
1218 "float",
1219 "Threshold on percentage of actuators which exceed percThreshold in an interval. Default is 0.5." );
1220
1221 config.add( "dm.intervalSatCountThreshold",
1222 "",
1223 "dm.intervalSatCountThreshold",
1224 argType::Required,
1225 "dm",
1226 "intervalSatCountThreshold",
1227 false,
1228 "float",
1229 "Threshold on number of consecutive intervals the intervalSatThreshold is exceeded. Default is 10." );
1230
1231 config.add( "dm.satTriggerDevice",
1232 "",
1233 "dm.satTriggerDevice",
1234 argType::Required,
1235 "dm",
1236 "satTriggerDevice",
1237 false,
1238 "vector<string>",
1239 "Device(s) with a toggle switch to toggle on saturation trigger." );
1240
1241 config.add( "dm.satTriggerProperty",
1242 "",
1243 "dm.satTriggerProperty",
1244 argType::Required,
1245 "dm",
1246 "satTriggerProperty",
1247 false,
1248 "vector<string>",
1249 "Property with a toggle switch to toggle on saturation trigger, one per entry in satTriggerDevice." );
1250
1251 return 0;
1252}
1253
1254template <class derivedT, typename realT>
1255int dm<derivedT, realT>::loadConfig( mx::app::appConfigurator &config )
1256{
1257
1258 m_calibPath = derived().m_calibDir + "/" + m_calibRelDir;
1259 config( m_calibPath, "dm.calibPath" );
1260
1261 // setup flats
1262 m_flatPath = m_calibPath + "/flats";
1263 config( m_flatPath, "dm.flatPath" );
1264
1265 config( m_flatDefault, "dm.flatDefault" );
1266 if( m_flatDefault != "" )
1267 {
1268 m_flatDefault = mx::ioutils::pathStem( m_flatDefault ); // strip off path and extension if provided.
1269 m_flatCurrent = "default";
1270 }
1271
1272 // setup tests
1273 m_testPath = m_calibPath + "/tests";
1274 config( m_testPath, "dm.testPath" );
1275
1276 config( m_testDefault, "dm.testDefault" );
1277 if( m_testDefault != "" )
1278 {
1279 m_testDefault = mx::ioutils::pathStem( m_testDefault ); // strip off path and extension if provided.
1280 m_testCurrent = "default";
1281 }
1282
1283 config( m_actMaskPath, "dm.actMaskPath" );
1284
1285 // Overriding the shmimMonitor setup so that these all go in the dm section
1286 // Otherwise, would call shmimMonitor<dm<derivedT,realT>>::loadConfig(config);
1287 config( derived().m_smThreadPrio, "dm.threadPrio" );
1288 config( derived().m_smCpuset, "dm.cpuset" );
1289
1290 config( derived().m_shmimName, "dm.shmimName" );
1291
1292 derived().m_getExistingFirst = true;
1293 // end of shmimmonitor overrides
1294
1295 if( derived().m_shmimName != "" )
1296 {
1297 m_shmimFlat = derived().m_shmimName + "00";
1298 config( m_shmimFlat, "dm.shmimFlat" );
1299
1300 m_shmimTest = derived().m_shmimName + "02";
1301 config( m_shmimTest, "dm.shmimTest" );
1302
1303 m_shmimSat = derived().m_shmimName + "ST";
1304 config( m_shmimSat, "dm.shmimSat" );
1305
1306 m_shmimSatPerc = derived().m_shmimName + "SP";
1307 config( m_shmimSatPerc, "dm.shmimSatPerc" );
1308
1309 config( m_satAvgInt, "dm.satAvgInt" );
1310
1311 config( m_satThreadPrio, "dm.satSatThreadPrio" );
1312
1313 m_shmimShape = derived().m_shmimName + "_shape";
1314 config( m_shmimShape, "dm.shmimShape" );
1315
1316 m_shmimDelta = derived().m_shmimName + "_delta";
1317 config( m_shmimDelta, "dm.shmimDelta" );
1318
1319 m_shmimDiff = derived().m_shmimName + "_diff";
1320 config( m_shmimDiff, "dm.shmimDiff" );
1321
1322 config( m_deltaChannels, "dm.deltaChannels" );
1323 }
1324 else
1325 {
1326 // Avoid unused error
1327 config.isSet( "dm.shmimFlat" );
1328 config.isSet( "dm.shmimTest" );
1329 config.isSet( "dm.shmimSat" );
1330 config.isSet( "dm.shmimSatPerc" );
1331 config.isSet( "dm.satAvgInt" );
1332 config.isSet( "dm.shmimShape" );
1333 config.isSet( "dm.shmimDelta" );
1334 config.isSet( "dm.deltaChannels" );
1335 }
1336
1337 config( m_dmWidth, "dm.width" );
1338 config( m_dmHeight, "dm.height" );
1339
1340 config( m_percThreshold, "dm.percThreshold" );
1341 config( m_intervalSatThreshold, "dm.intervalSatThreshold" );
1342 config( m_intervalSatCountThreshold, "dm.intervalSatCountThreshold" );
1343 config( m_satTriggerDevice, "dm.satTriggerDevice" );
1344 config( m_satTriggerProperty, "dm.satTriggerProperty" );
1345
1346 if( m_dmWidth > 0 && m_dmHeight > 0 )
1347 {
1348 try
1349 {
1350 m_actMask.create( derived().m_shmimName + "_actmask", m_dmWidth, m_dmHeight );
1351 }
1352 catch( const std::exception &e )
1353 {
1354 derivedT::template log<text_log>( std::format( "exception caught creating actuator mask: "
1355 "{}: {}",
1356 derived().m_shmimName + "_actmask",
1357 e.what(),
1358 logPrio::LOG_ERROR ) );
1359 return -1;
1360 }
1361
1362 if( m_actMaskPath != "" )
1363 {
1364 mx::improc::eigenImage<realT> actMask;
1365
1366 mx::fits::fitsFile<realT> ff;
1367
1368 mx::error_t errc = ff.read( actMask, m_calibPath + '/' + m_actMaskPath );
1369
1370 if( errc != mx::error_t::noerror )
1371 {
1372 derivedT::template log<text_log>( std::format( "error reading actuator mask file {}: "
1373 "{} ({})",
1374 m_calibPath + '/' + m_actMaskPath,
1375 mx::errorMessage( errc ),
1376 mx::errorName( errc ) ),
1377 logPrio::LOG_ERROR );
1378 return -1;
1379 }
1380
1381 if( actMask.rows() != m_dmWidth || actMask.cols() != m_dmHeight )
1382 {
1383 derivedT::template log<text_log>( std::format( "actuaor mask {}x{} is not same size as flag {}x{}",
1384 actMask.rows(),
1385 actMask.cols(),
1386 m_dmWidth,
1387 m_dmHeight ),
1388 logPrio::LOG_ERROR );
1389
1390 return -1;
1391 }
1392
1393 m_actMask = actMask;
1394 }
1395 else
1396 {
1397 m_actMask().setConstant( 1.0 );
1398 }
1399 }
1400
1401 return 0;
1402}
1403
1404template <class derivedT, typename realT>
1406{
1407 if( m_dmDataType == 0 )
1408 {
1409 derivedT::template log<software_error>( { "unsupported DM data type" } );
1410 return -1;
1411 }
1412
1413 //-----------------
1414 // Get the flats
1415 checkFlats();
1416
1417 // Register the test shmim INDI property
1418 m_indiP_flatShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
1419 m_indiP_flatShmim.setDevice( derived().configName() );
1420 m_indiP_flatShmim.setName( "flat_shmim" );
1421 m_indiP_flatShmim.setPerm( pcf::IndiProperty::ReadOnly );
1422 m_indiP_flatShmim.setState( pcf::IndiProperty::Idle );
1423 m_indiP_flatShmim.add( pcf::IndiElement( "channel" ) );
1424 m_indiP_flatShmim["channel"] = m_shmimFlat;
1425
1426 if( derived().registerIndiPropertyReadOnly( m_indiP_flatShmim ) < 0 )
1427 {
1428#ifndef DM_TEST_NOLOG
1429 derivedT::template log<software_error>( { "" } );
1430#endif
1431 return -1;
1432 }
1433
1434 // Register the setFlat INDI property
1435 derived().createStandardIndiToggleSw( m_indiP_setFlat, "flat_set" );
1436 if( derived().registerIndiPropertyNew( m_indiP_setFlat, st_newCallBack_setFlat ) < 0 )
1437 {
1438#ifndef DM_TEST_NOLOG
1439 derivedT::template log<software_error>( { "" } );
1440#endif
1441 return -1;
1442 }
1443
1444 //-----------------
1445 // Get the tests
1446 checkTests();
1447
1448 // Register the test shmim INDI property
1449 m_indiP_testShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
1450 m_indiP_testShmim.setDevice( derived().configName() );
1451 m_indiP_testShmim.setName( "test_shmim" );
1452 m_indiP_testShmim.setPerm( pcf::IndiProperty::ReadOnly );
1453 m_indiP_testShmim.setState( pcf::IndiProperty::Idle );
1454 m_indiP_testShmim.add( pcf::IndiElement( "channel" ) );
1455 m_indiP_testShmim["channel"] = m_shmimTest;
1456 derived().createStandardIndiToggleSw( m_indiP_setTest, "test_shmim" );
1457 if( derived().registerIndiPropertyReadOnly( m_indiP_testShmim ) < 0 )
1458 {
1459#ifndef DM_TEST_NOLOG
1460 derivedT::template log<software_error>( { "" } );
1461#endif
1462 return -1;
1463 }
1464
1465 // Register the setTest INDI property
1466 derived().createStandardIndiToggleSw( m_indiP_setTest, "test_set" );
1467 if( derived().registerIndiPropertyNew( m_indiP_setTest, st_newCallBack_setTest ) < 0 )
1468 {
1469#ifndef DM_TEST_NOLOG
1470 derivedT::template log<software_error>( { "" } );
1471#endif
1472 return -1;
1473 }
1474
1475 // Register the init INDI property
1476 derived().createStandardIndiRequestSw( m_indiP_init, "initDM" );
1477 if( derived().registerIndiPropertyNew( m_indiP_init, st_newCallBack_init ) < 0 )
1478 {
1479 // clang-format off
1480 #ifndef DM_TEST_NOLOG
1481 derivedT::template log<software_error>( {""} );
1482 #endif
1483 // clang-format on
1484
1485 return -1;
1486 }
1487
1488 // Register the zero INDI property
1489 derived().createStandardIndiRequestSw( m_indiP_zero, "zeroDM" );
1490
1491 if( derived().registerIndiPropertyNew( m_indiP_zero, st_newCallBack_zero ) < 0 )
1492 {
1493 // clang-format off
1494 #ifndef DM_TEST_NOLOG
1495 derivedT::template log<software_error>( {""} );
1496 #endif
1497 // clang-format on
1498
1499 return -1;
1500 }
1501
1502 // Register the release INDI property
1503 derived().createStandardIndiRequestSw( m_indiP_release, "releaseDM" );
1504 if( derived().registerIndiPropertyNew( m_indiP_release, st_newCallBack_release ) < 0 )
1505 {
1506 return derivedT::template log<software_error, -1>( { "" } );
1507 }
1508
1509 derived().createStandardIndiRequestSw( m_indiP_zeroAll, "zeroAll" );
1510 if( derived().registerIndiPropertyNew( m_indiP_zeroAll, st_newCallBack_zeroAll ) < 0 )
1511 {
1512#ifndef DM_TEST_NOLOG
1513 derivedT::template log<software_error>( { "" } );
1514#endif
1515 return -1;
1516 }
1517
1518 if( m_flatDefault != "" )
1519 {
1520 loadFlat( "default" );
1521 }
1522
1523 if( m_testDefault != "" )
1524 {
1525 loadTest( "default" );
1526 }
1527
1528 if( sem_init( &m_satSemaphore, 0, 0 ) < 0 )
1529 {
1530 return derivedT::template log<software_critical, -1>( { errno, 0, "Initializing sat semaphore" } );
1531 }
1532
1533 if( derived().threadStart( m_satThread,
1534 m_satThreadInit,
1535 m_satThreadID,
1536 m_satThreadProp,
1537 m_satThreadPrio,
1538 "",
1539 "saturation",
1540 this,
1541 satThreadStart ) < 0 )
1542 {
1543 derivedT::template log<software_error, -1>( { "" } );
1544 return -1;
1545 }
1546
1547 return 0;
1548}
1549
1550template <class derivedT, typename realT>
1552{
1553 // do a join check to see if other threads have exited.
1554 if( pthread_tryjoin_np( m_satThread.native_handle(), 0 ) == 0 )
1555 {
1556 derivedT::template log<software_error>( { "saturation thread has exited" } );
1557
1558 return -1;
1559 }
1560
1561 checkFlats();
1562
1563 checkTests();
1564
1565 if( m_intervalSatTrip )
1566 {
1567 intervalSatTrip();
1568 m_intervalSatTrip = false;
1569 }
1570
1571#ifdef XWC_DMTIMINGS
1572 static uint64_t lastMono = 0;
1573
1574 if( m_piTimes.size() >= m_piTimes.maxEntries() && m_piTimes.maxEntries() > 0 && m_piTimes.mono() != lastMono )
1575 {
1576 cbIndexT refEntry = m_piTimes.earliest();
1577
1578 m_piTimesD.resize( m_piTimes.maxEntries() );
1579 m_satSemD.resize( m_satSem.maxEntries() );
1580 m_actProcD.resize( m_actProc.maxEntries() );
1581 m_actComD.resize( m_actCom.maxEntries() );
1582 m_satUpD.resize( m_satUp.maxEntries() );
1583 m_deltaUpD.resize( m_deltaUp.maxEntries() );
1584
1585 for( size_t n = 0; n < m_piTimesD.size(); ++n )
1586 {
1587 m_piTimesD[n] = m_piTimes.at( refEntry, n );
1588 m_satSemD[n] = m_satSem.at( refEntry, n );
1589 m_actProcD[n] = m_actProc.at( refEntry, n );
1590 m_actComD[n] = m_actCom.at( refEntry, n );
1591 m_satUpD[n] = m_satUp.at( refEntry, n );
1592 m_deltaUpD[n] = m_deltaUp.at( refEntry, n );
1593 }
1594
1595 std::cerr << "Act. Process: " << mx::math::vectorMean( m_actProcD ) << " +/- "
1596 << sqrt( mx::math::vectorVariance( m_actProcD ) ) << "\n";
1597 std::cerr << "Act. Command: " << mx::math::vectorMean( m_actComD ) << " +/- "
1598 << sqrt( mx::math::vectorVariance( m_actComD ) ) << "\n";
1599 std::cerr << "Sat. Update: " << mx::math::vectorMean( m_satUpD ) << " +/- "
1600 << sqrt( mx::math::vectorVariance( m_satUpD ) ) << "\n";
1601 std::cerr << "Delta Update: " << mx::math::vectorMean( m_deltaUpD ) << " +/- "
1602 << sqrt( mx::math::vectorVariance( m_deltaUpD ) ) << "\n";
1603 std::cerr << "Tot. CommandDM: " << mx::math::vectorMean( m_piTimesD ) << " +/- "
1604 << sqrt( mx::math::vectorVariance( m_piTimesD ) ) << "\n";
1605 std::cerr << "Sat. Semaphore: " << mx::math::vectorMean( m_satSemD ) << " +/- "
1606 << sqrt( mx::math::vectorVariance( m_satSemD ) ) << "\n";
1607 std::cerr << "\n";
1608
1609 lastMono = m_piTimes.mono();
1610 }
1611#endif // XWC_DMTIMINGS
1612
1613 return 0;
1614}
1615
1616template <class derivedT, typename realT>
1618{
1619 if( m_satThread.joinable() )
1620 {
1621 pthread_kill( m_satThread.native_handle(), SIGUSR1 );
1622 try
1623 {
1624 m_satThread.join(); // this will throw if it was already joined
1625 }
1626 catch( ... )
1627 {
1628 }
1629 }
1630
1631 return 0;
1632}
1633
1634template <class derivedT, typename realT>
1636{
1637 baseReleaseDM();
1638
1639 return 0;
1640}
1641
1642template <class derivedT, typename realT>
1644{
1645 checkFlats();
1646 checkTests();
1647
1648 return 0;
1649}
1650
1651template <class derivedT, typename realT>
1653{
1654 std::string milkShmimDir = mx::sys::getEnv( "MILK_SHM_DIR" );
1655 if( milkShmimDir == "" )
1656 {
1657 milkShmimDir = "/milk/shm";
1658 }
1659
1660 std::vector<std::string> dmlist;
1661 mx::error_t errc = mx::ioutils::getFileNames( dmlist, milkShmimDir, derived().m_shmimName, ".im", ".shm" );
1662
1663 mx_error_check_rv( errc, -1 );
1664
1665 if( dmlist.size() == 0 )
1666 {
1667 derivedT::template log<software_error>( { "no dm channels found for " + derived().m_shmimName } );
1668
1669 return -1;
1670 }
1671
1672 m_numChannels = -1;
1673 for( size_t n = 0; n < dmlist.size(); ++n )
1674 {
1675 char nstr[16];
1676 snprintf( nstr, sizeof( nstr ), "%02d.im.shm", (int)n );
1677 std::string tgt = derived().m_shmimName;
1678 tgt += nstr;
1679
1680 for( size_t m = 0; m < dmlist.size(); ++m )
1681 {
1682 if( dmlist[m].find( tgt ) != std::string::npos )
1683 {
1684 if( (int)n > m_numChannels )
1685 {
1686 m_numChannels = n;
1687 }
1688 }
1689 }
1690 }
1691
1692 ++m_numChannels;
1693
1694 derivedT::template log<text_log>(
1695 { std::format( "Found {} chanels for {} ", m_numChannels, derived().m_shmimName ) } );
1696
1697 m_channels.resize( m_numChannels, nullptr );
1698
1699 m_notDeltas.clear();
1700 m_deltas.clear();
1701
1702 for( size_t n = 0; n < m_channels.size(); ++n )
1703 {
1704 std::string sname = std::format( "{}{:02}", derived().m_shmimName, n );
1705
1706 try
1707 {
1708 m_channels[n] = new mx::improc::milkImage<realT>( sname ); // this opens the channel stream
1709 }
1710 catch( const std::exception &e )
1711 {
1712 derivedT::template log<software_error>( { "exception opening " + sname + ": " + e.what() } );
1713 }
1714
1715 std::cerr << "looking for " << sname << '\n';
1716 auto res = std::find( m_deltaChannels.begin(), m_deltaChannels.end(), sname );
1717 if( res == m_deltaChannels.end() )
1718 {
1719 std::cerr << " not a delta\n";
1720 m_notDeltas.push_back( n );
1721 }
1722 else
1723 {
1724 std::cerr << " is a delta\n";
1725 m_deltas.push_back( n );
1726 }
1727 }
1728
1729 std::cerr << "not deltas: ";
1730 for( size_t n = 0; n < m_notDeltas.size(); ++n )
1731 {
1732 std::cerr << m_notDeltas[n] << ' ';
1733 }
1734 std::cerr << '\n';
1735
1736 std::cerr << "deltas: ";
1737 for( size_t n = 0; n < m_deltas.size(); ++n )
1738 {
1739 std::cerr << m_deltas[n] << ' ';
1740 }
1741 std::cerr << '\n';
1742
1743 return 0;
1744}
1745
1746template <class derivedT, typename realT>
1748{
1749 static_cast<void>( sp ); // be unused
1750
1751 int err = 0;
1752
1753 if( derived().m_width != m_dmWidth )
1754 {
1755 derivedT::template log<software_critical>( { "shmim width does not match configured DM width" } );
1756 ++err;
1757 }
1758
1759 if( derived().m_height != m_dmHeight )
1760 {
1761 derivedT::template log<software_critical>( { "shmim height does not match configured DM height" } );
1762 ++err;
1763 }
1764
1765 if( derived().m_dataType != m_dmDataType )
1766 {
1767 derivedT::template log<software_critical>( { "shmim data type does not match configured DM data type" } );
1768 ++err;
1769 }
1770
1771 if( err )
1772 {
1773 return -1;
1774 }
1775
1776 m_instSatMap.resize( m_dmWidth, m_dmHeight );
1777 m_instSatMap.setZero();
1778
1779 m_accumSatMap.resize( m_dmWidth, m_dmHeight );
1780 m_accumSatMap.setZero();
1781
1782 m_satPercMap.resize( m_dmWidth, m_dmHeight );
1783 m_satPercMap.setZero();
1784
1785 if( findDMChannels() < 0 )
1786 {
1787 derivedT::template log<software_critical>( { "error finding DM channels" } );
1788
1789 return -1;
1790 }
1791
1792 try
1793 {
1794 m_outputShape.create( m_shmimShape, m_dmWidth, m_dmHeight );
1795 m_outputShape().setZero();
1796 }
1797 catch( const std::exception &e )
1798 {
1799 return derivedT::template log<software_error, -1>(
1800 { std::string( "creating output shape shmim: " ) + e.what() } );
1801 }
1802
1803 try
1804 {
1805 m_outputDelta.create( m_shmimDelta, m_dmWidth, m_dmHeight );
1806 m_outputDelta().setZero();
1807 }
1808 catch( const std::exception &e )
1809 {
1810 return derivedT::template log<software_error, -1>(
1811 { std::string( "creating output delta shmim: " ) + e.what() } );
1812 }
1813
1814 try
1815 {
1816 m_outputDiff.create( m_shmimDiff, m_dmWidth, m_dmHeight );
1817 m_outputDiff().setZero();
1818 }
1819 catch( const std::exception &e )
1820 {
1821 return derivedT::template log<software_error, -1>(
1822 { std::string( "creating output diff shmim: " ) + e.what() } );
1823 }
1824
1825 m_totalFlat.resize( m_dmWidth, m_dmHeight );
1826 m_totalFlat.setZero();
1827
1828 m_totalDelta.resize( m_dmWidth, m_dmHeight );
1829 m_totalDelta.setZero();
1830
1831 // clang-format off
1832 #ifdef XWC_DMTIMINGS
1833 m_piTimes.maxEntries( 2000 );
1834 m_satSem.maxEntries( 2000 );
1835 m_actProc.maxEntries( 2000 );
1836 m_actCom.maxEntries( 2000 );
1837 m_satUp.maxEntries( 2000 );
1838 m_deltaUp.maxEntries( 2000 );
1839 #endif // clang-format on
1840
1841 return 0;
1842}
1843
1844template <class derivedT, typename realT>
1845int dm<derivedT, realT>::processImage( void *curr_src, const dev::shmimT &sp )
1846{
1847 static_cast<void>( sp ); // be unused
1848
1849 // clang-format off
1850 #ifdef XWC_DMTIMINGS
1851 m_t0 = mx::sys::get_curr_time();
1852 #endif // clang-format on
1853
1854 int rv = derived().commandDM( curr_src );
1855
1856 if( rv < 0 )
1857 {
1858 derivedT::template log<software_critical>( { errno, rv, "Error from commandDM" } );
1859 return rv;
1860 }
1861
1862 // clang-format off
1863 #ifdef XWC_DMTIMINGS
1864 m_tdelta0 = mx::sys::get_curr_time();
1865 #endif // clang-format on
1866
1867 if( m_deltaChannels.size() > 0 )
1868 {
1869 rv = makeDelta();
1870
1871 if( rv < 0 )
1872 {
1873 derivedT::template log<software_critical>( { errno, rv, "Error from makeDelta" } );
1874 return rv;
1875 }
1876 }
1877
1878 // clang-format off
1879 #ifdef XWC_DMTIMINGS
1880 m_tdeltaf = mx::sys::get_curr_time();
1881
1882 m_tf = m_tdeltaf;
1883 #endif // clang-format on
1884
1885 // clang-format off
1886 #ifdef XWC_DMTIMINGS
1887 m_tsat0 = mx::sys::get_curr_time();
1888 #endif // clang-format on
1889
1890 // Tell the sat thread to get going
1891 if( sem_post( &m_satSemaphore ) < 0 )
1892 {
1893 derivedT::template log<software_critical>( { errno, 0, "Error posting to semaphore" } );
1894 return -1;
1895 }
1896
1897 // clang-format off
1898 #ifdef XWC_DMTIMINGS // clang-format on
1899
1900 m_tsatf = mx::sys::get_curr_time();
1901
1902 // Update the latency circ. buffs
1903 if( m_piTimes.maxEntries() > 0 )
1904 {
1905 m_piTimes.nextEntry( m_tf - m_t0 );
1906 m_satSem.nextEntry( m_tsatf - m_tsat0 );
1907 m_actProc.nextEntry( m_tact1 - m_tact0 );
1908 m_actCom.nextEntry( m_tact2 - m_tact1 );
1909 m_satUp.nextEntry( m_tact4 - m_tact3 );
1910 m_deltaUp.nextEntry( m_tdeltaf - m_tdelta0 );
1911 }
1912
1913 // clang-format off
1914 #endif // clang-format on
1915
1916 return rv;
1917}
1918
1919template <class derivedT, typename realT>
1921{
1922 if( derived().state() != stateCodes::NOTHOMED )
1923 {
1924 derivedT::template log<software_error>( { errno, "DM is not ready to be initialized" } );
1925 derived().state( stateCodes::ERROR );
1926 return -1;
1927 }
1928
1929 derived().state( stateCodes::HOMING );
1930
1931 int rv;
1932 if( ( rv = derived().initDM() ) < 0 )
1933 {
1934 derivedT::template log<software_critical>( { errno, rv, "Error from initDM" } );
1935 derived().state( stateCodes::ERROR );
1936 return rv;
1937 }
1938
1939 return 0;
1940}
1941
1942template <class derivedT, typename realT>
1944{
1945 if( derived().state() != stateCodes::POWEROFF )
1946 {
1947 derived().state( stateCodes::NOTHOMED );
1948 }
1949
1950 int rv;
1951 if( ( rv = derived().releaseDM() ) < 0 )
1952 {
1953 derivedT::template log<software_critical>( { errno, rv, "Error from releaseDM" } );
1954 derived().state( stateCodes::ERROR );
1955 return rv;
1956 }
1957
1958 if( ( rv = zeroAll( true ) ) < 0 )
1959 {
1960 derivedT::template log<software_error>( { errno, rv, "Error from zeroAll" } );
1961 derived().state( stateCodes::ERROR );
1962 return rv;
1963 }
1964
1965 return 0;
1966}
1967
1968template <class derivedT, typename realT>
1970{
1971 std::vector<std::string> tfs;
1972
1973 bool gfn_logged = false;
1974
1975 mx::error_t errc = mx::ioutils::getFileNames( tfs, m_flatPath, "", "", ".fits" );
1976
1977 if( errc != mx::error_t::noerror )
1978 {
1979 if( !gfn_logged )
1980 {
1981 derivedT::template log<software_error>(
1982 { std::format( "error getting flat files: {}", mx::errorMessage( errc ) ) } );
1983 }
1984 gfn_logged = true;
1985 return -1;
1986 }
1987
1988 gfn_logged = false;
1989
1990 // First remove default, b/c we always add it and don't want to include it in timestamp selected ones
1991 for( size_t n = 0; n < tfs.size(); ++n )
1992 {
1993 if( mx::ioutils::pathStem( tfs[n] ) == "default" )
1994 {
1995 tfs.erase( tfs.begin() + n );
1996 --n;
1997 }
1998 }
1999
2000 unsigned m_nFlatFiles = 5;
2001
2002 // Here we keep only the m_nFlatFiles most recent files
2003 if( tfs.size() >= m_nFlatFiles )
2004 {
2005 std::vector<std::filesystem::file_time_type> wtimes( tfs.size() );
2006
2007 for( size_t n = 0; n < wtimes.size(); ++n )
2008 {
2009 wtimes[n] = std::filesystem::last_write_time( tfs[n] );
2010 }
2011
2012 std::sort( wtimes.begin(), wtimes.end() );
2013
2014 std::filesystem::file_time_type tn = wtimes[wtimes.size() - m_nFlatFiles];
2015
2016 for( size_t n = 0; n < tfs.size(); ++n )
2017 {
2018 std::filesystem::file_time_type lmt = std::filesystem::last_write_time( tfs[n] );
2019 if( lmt < tn )
2020 {
2021 tfs.erase( tfs.begin() + n );
2022 --n;
2023 }
2024 }
2025 }
2026
2027 for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2028 {
2029 it->second = "";
2030 }
2031
2032 bool changed = false;
2033 for( size_t n = 0; n < tfs.size(); ++n )
2034 {
2035 auto ir =
2036 m_flatCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2037 if( ir.second == true )
2038 changed = true;
2039 else
2040 ir.first->second = tfs[n];
2041 }
2042
2043 for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2044 {
2045 if( it->second == "" )
2046 {
2047 changed = true;
2048 // Erase the current iterator safely, even if the first one.
2049 auto itdel = it;
2050 ++it;
2051 m_flatCommands.erase( itdel );
2052 --it;
2053 };
2054 }
2055
2056 if( changed )
2057 {
2058 if( derived().m_indiDriver )
2059 {
2060 derived().m_indiDriver->sendDelProperty( m_indiP_flats );
2061 derived().m_indiNewCallBacks.erase( m_indiP_flats.createUniqueKey() );
2062 }
2063
2064 m_indiP_flats = pcf::IndiProperty( pcf::IndiProperty::Switch );
2065 m_indiP_flats.setDevice( derived().configName() );
2066 m_indiP_flats.setName( "flat" );
2067 m_indiP_flats.setPerm( pcf::IndiProperty::ReadWrite );
2068 m_indiP_flats.setState( pcf::IndiProperty::Idle );
2069 m_indiP_flats.setRule( pcf::IndiProperty::OneOfMany );
2070
2071 // Add the toggle element initialized to Off
2072 for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2073 {
2074 if( it->first == m_flatCurrent || m_flatCurrent == "" )
2075 {
2076 m_indiP_flats.add( pcf::IndiElement( it->first, pcf::IndiElement::On ) );
2077 m_flatCurrent = it->first; // handles the case m_flatCurrent == "" b/c it was not set in config
2078 }
2079 else
2080 {
2081 m_indiP_flats.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
2082 }
2083 }
2084
2085 if( m_flatDefault != "" )
2086 {
2087 if( m_flatCurrent == "default" )
2088 {
2089 m_indiP_flats.add( pcf::IndiElement( "default", pcf::IndiElement::On ) );
2090 }
2091 else
2092 {
2093 m_indiP_flats.add( pcf::IndiElement( "default", pcf::IndiElement::Off ) );
2094 }
2095 }
2096
2097 if( derived().registerIndiPropertyNew( m_indiP_flats, st_newCallBack_flats ) < 0 )
2098 {
2099 // clang-format off
2100 #ifndef DM_TEST_NOLOG
2101 derivedT::template log<software_error>( {""} );
2102 #endif
2103 // clang-format on
2104
2105 return -1;
2106 }
2107
2108 if( derived().m_indiDriver )
2109 {
2110 derived().m_indiDriver->sendDefProperty( m_indiP_flats );
2111 }
2112 }
2113
2114 return 0;
2115}
2116
2117template <class derivedT, typename realT>
2118int dm<derivedT, realT>::loadFlat( const std::string &intarget )
2119{
2120 std::string target = intarget;
2121
2122 std::string targetPath;
2123
2124 if( target == "default" )
2125 {
2126 target = m_flatDefault;
2127 targetPath = m_flatPath + "/" + m_flatDefault + ".fits";
2128 }
2129 else
2130 {
2131 try
2132 {
2133 targetPath = m_flatCommands.at( target );
2134 }
2135 catch( ... )
2136 {
2137 derivedT::template log<text_log>( "flat file " + target + " not found", logPrio::LOG_ERROR );
2138 return -1;
2139 }
2140 }
2141
2142 m_flatLoaded = false;
2143
2144 // load into memory.
2145 mx::fits::fitsFile<realT> ff;
2146
2147 mx::error_t errc = ff.read( m_flatCommand, targetPath );
2148
2149 if( errc != mx::error_t::noerror )
2150 {
2151 derivedT::template log<text_log>( std::format( "error reading flat file {}: "
2152 "{} ({})",
2153 targetPath,
2154 mx::errorMessage( errc ),
2155 mx::errorName( errc ) ),
2156 logPrio::LOG_ERROR );
2157 return -1;
2158 }
2159
2160 if( m_actMask.rows() != m_flatCommand.rows() || m_actMask.cols() != m_flatCommand.cols() )
2161 {
2162 derivedT::template log<text_log>( std::format( "actuaor mask {}x{} is not same size as flag {}x{}",
2163 m_actMask.rows(),
2164 m_actMask.cols(),
2165 m_flatCommand.rows(),
2166 m_flatCommand.cols() ),
2167 logPrio::LOG_ERROR );
2168
2169 return -1;
2170 }
2171
2172 m_flatCommand *= m_actMask();
2173
2174 derivedT::template log<text_log>( "loaded flat file " + targetPath );
2175 m_flatLoaded = true;
2176
2177 m_flatCurrent = intarget;
2178
2179 if( m_indiP_flats.find( "default" ) )
2180 {
2181 if( m_flatCurrent == "default" )
2182 {
2183 m_indiP_flats["default"] = pcf::IndiElement::On;
2184 }
2185 else
2186 {
2187 m_indiP_flats["default"] = pcf::IndiElement::Off;
2188 }
2189 }
2190
2191 for( auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
2192 {
2193 if( !m_indiP_flats.find( i->first ) )
2194 {
2195 continue;
2196 }
2197
2198 if( i->first == m_flatCurrent )
2199 {
2200 m_indiP_flats[i->first] = pcf::IndiElement::On;
2201 }
2202 else
2203 {
2204 m_indiP_flats[i->first] = pcf::IndiElement::Off;
2205 }
2206 }
2207
2208 if( derived().m_indiDriver )
2209 {
2210 derived().m_indiDriver->sendSetProperty( m_indiP_flats );
2211 }
2212
2213 if( m_flatSet )
2214 {
2215 setFlat();
2216 }
2217
2218 return 0;
2219}
2220
2221template <class derivedT, typename realT>
2223{
2224 if( m_shmimFlat == "" )
2225 {
2226 return 0;
2227 }
2228
2229 if( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) )
2230 {
2231 derivedT::template log<text_log>( "can not set flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2232 return -1;
2233 }
2234
2235 if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2236 {
2237 derivedT::template log<text_log>( "could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2238 return -1;
2239 }
2240
2241 if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2242 {
2243 ImageStreamIO_closeIm( &m_flatImageStream );
2244 derivedT::template log<text_log>( "width mismatch between " + m_shmimFlat + " and configured DM",
2245 logPrio::LOG_ERROR );
2246 return -1;
2247 }
2248
2249 if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2250 {
2251 ImageStreamIO_closeIm( &m_flatImageStream );
2252 derivedT::template log<text_log>( "height mismatch between " + m_shmimFlat + " and configured DM",
2253 logPrio::LOG_ERROR );
2254 return -1;
2255 }
2256
2257 if( !m_flatLoaded )
2258 {
2259 bool flatSet = m_flatSet;
2260 m_flatSet = false; // make sure we don't loop
2261
2262 if( loadFlat( m_flatCurrent ) < 0 )
2263 {
2264 derivedT::template log<text_log>( "error loading flat " + m_flatCurrent, logPrio::LOG_ERROR );
2265 }
2266 m_flatSet = flatSet;
2267 }
2268
2269 if( !m_flatLoaded )
2270 {
2271 ImageStreamIO_closeIm( &m_flatImageStream );
2272 derivedT::template log<text_log>( "no flat loaded", logPrio::LOG_ERROR );
2273 return -1;
2274 }
2275
2276 if( m_flatCommand.rows() != m_dmWidth )
2277 {
2278 ImageStreamIO_closeIm( &m_flatImageStream );
2279 derivedT::template log<text_log>( "width mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2280 return -1;
2281 }
2282
2283 if( m_flatCommand.cols() != m_dmHeight )
2284 {
2285 ImageStreamIO_closeIm( &m_flatImageStream );
2286 derivedT::template log<text_log>( "height mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2287 return -1;
2288 }
2289
2290 m_flatImageStream.md->write = 1;
2291
2292 ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here
2293 /// anyway. With bounds checks b/c not everyone handles cnt1 properly.
2294 // Copy
2295 memcpy( m_flatImageStream.array.raw, m_flatCommand.data(), m_dmWidth * m_dmHeight * sizeof( realT ) );
2296
2297 // Set the time of last write
2298 clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2299
2300 // Set the image acquisition timestamp
2301 m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2302
2303 m_flatImageStream.md->cnt0++;
2304 m_flatImageStream.md->write = 0;
2305
2306 // Post the semaphores
2307 ImageStreamIO_sempost( &m_flatImageStream, -1 );
2308
2309 m_flatSet = true;
2310
2311 ImageStreamIO_closeIm( &m_flatImageStream );
2312
2313 derived().state( stateCodes::OPERATING );
2314
2315 if( !update )
2316 {
2317 derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2318
2319 derivedT::template log<text_log>( "flat set" );
2320 }
2321
2322 return 0;
2323}
2324
2325template <class derivedT, typename realT>
2327{
2328 if( m_shmimFlat == "" )
2329 {
2330 return 0;
2331 }
2332
2333 if( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) )
2334 {
2335 derivedT::template log<text_log>( "can not zero flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2336 return -1;
2337 }
2338
2339 if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2340 {
2341 derivedT::template log<text_log>( "could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2342 return -1;
2343 }
2344
2345 if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2346 {
2347 ImageStreamIO_closeIm( &m_flatImageStream );
2348 derivedT::template log<text_log>( "width mismatch between " + m_shmimFlat + " and configured DM",
2349 logPrio::LOG_ERROR );
2350 return -1;
2351 }
2352
2353 if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2354 {
2355 ImageStreamIO_closeIm( &m_flatImageStream );
2356 derivedT::template log<text_log>( "height mismatch between " + m_shmimFlat + " and configured DM",
2357 logPrio::LOG_ERROR );
2358 return -1;
2359 }
2360
2361 m_flatImageStream.md->write = 1;
2362
2363 ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here
2364 /// anyway. With bounds checks b/c not everyone handles cnt1 properly.
2365 // Zero
2366 memset( m_flatImageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2367
2368 // Set the time of last write
2369 clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2370
2371 // Set the image acquisition timestamp
2372 m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2373
2374 m_flatImageStream.md->cnt0++;
2375 m_flatImageStream.md->write = 0;
2376 ImageStreamIO_sempost( &m_flatImageStream, -1 );
2377
2378 m_flatSet = false;
2379
2380 // Post the semaphore
2381 ImageStreamIO_closeIm( &m_flatImageStream );
2382
2383 derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2384
2385 derivedT::template log<text_log>( "flat zeroed" );
2386
2387 if( derived().zeroDM() < 0 )
2388 {
2389 derivedT::template log<software_error>( { "error from zeroDM" } );
2390 }
2391
2392 if( clearSat() < 0 )
2393 {
2394 derivedT::template log<software_error>( { "error from clearSat" } );
2395 }
2396 derived().state( stateCodes::READY );
2397
2398 return 0;
2399}
2400
2401template <class derivedT, typename realT>
2403{
2404 std::vector<std::string> tfs;
2405
2406 static bool gfn_logged = false; // tracks if not finding the test path is logged
2407
2408 mx::error_t errc = mx::ioutils::getFileNames( tfs, m_testPath, "", "", ".fits" );
2409
2410 if( errc != mx::error_t::noerror )
2411 {
2412 if( !gfn_logged )
2413 {
2414 derivedT::template log<software_error>(
2415 { std::format( "error getting test files: {}", mx::errorMessage( errc ) ) } );
2416 }
2417 gfn_logged = true;
2418 return -1;
2419 }
2420
2421 gfn_logged = false;
2422
2423 mx_error_check_rv( errc, -1 );
2424
2425 for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2426 {
2427 it->second = "";
2428 }
2429
2430 bool changed = false;
2431 for( size_t n = 0; n < tfs.size(); ++n )
2432 {
2433 auto ir =
2434 m_testCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2435 if( ir.second == true )
2436 changed = true;
2437 else
2438 ir.first->second = tfs[n];
2439 }
2440
2441 for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2442 {
2443 if( it->second == "" )
2444 {
2445 changed = true;
2446 // Erase the current iterator safely, even if the first one.
2447 auto itdel = it;
2448 ++it;
2449 m_testCommands.erase( itdel );
2450 --it;
2451 };
2452 }
2453
2454 if( changed )
2455 {
2456 if( derived().m_indiDriver )
2457 {
2458 derived().m_indiDriver->sendDelProperty( m_indiP_tests );
2459 derived().m_indiNewCallBacks.erase( m_indiP_tests.createUniqueKey() );
2460 }
2461
2462 m_indiP_tests = pcf::IndiProperty( pcf::IndiProperty::Switch );
2463 m_indiP_tests.setDevice( derived().configName() );
2464 m_indiP_tests.setName( "test" );
2465 m_indiP_tests.setPerm( pcf::IndiProperty::ReadWrite );
2466 m_indiP_tests.setState( pcf::IndiProperty::Idle );
2467 m_indiP_tests.setRule( pcf::IndiProperty::OneOfMany );
2468
2469 // Add the toggle element initialized to Off
2470 for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2471 {
2472 if( it->first == m_testCurrent || m_testCurrent == "" )
2473 {
2474 m_indiP_tests.add( pcf::IndiElement( it->first, pcf::IndiElement::On ) );
2475 m_testCurrent = it->first; // Handles the case when m_testCurrent=="" b/c it was not set in config
2476 }
2477 else
2478 {
2479 m_indiP_tests.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
2480 }
2481 }
2482
2483 if( m_testDefault != "" )
2484 {
2485 if( m_testCurrent == "default" )
2486 {
2487 m_indiP_tests.add( pcf::IndiElement( "default", pcf::IndiElement::On ) );
2488 }
2489 else
2490 {
2491 m_indiP_tests.add( pcf::IndiElement( "default", pcf::IndiElement::Off ) );
2492 }
2493 }
2494
2495 if( derived().registerIndiPropertyNew( m_indiP_tests, st_newCallBack_tests ) < 0 )
2496 {
2497#ifndef DM_TEST_NOLOG
2498 derivedT::template log<software_error>( { "" } );
2499#endif
2500 return -1;
2501 }
2502
2503 if( derived().m_indiDriver )
2504 {
2505 derived().m_indiDriver->sendDefProperty( m_indiP_tests );
2506 }
2507 }
2508
2509 return 0;
2510}
2511
2512template <class derivedT, typename realT>
2513int dm<derivedT, realT>::loadTest( const std::string &intarget )
2514{
2515 std::string target = intarget; // store this for later to resolve default next:
2516
2517 if( target == "default" )
2518 {
2519 target = m_testDefault;
2520 }
2521
2522 std::string targetPath;
2523
2524 try
2525 {
2526 targetPath = m_testCommands.at( target );
2527 }
2528 catch( ... )
2529 {
2530 derivedT::template log<text_log>( "test file " + target + " not found", logPrio::LOG_ERROR );
2531 return -1;
2532 }
2533
2534 m_testLoaded = false;
2535 // load into memory.
2536 mx::fits::fitsFile<realT> ff;
2537 mx::error_t errc = ff.read( m_testCommand, targetPath );
2538 if( errc != mx::error_t::noerror )
2539 {
2540 derivedT::template log<text_log>( std::format( "error reading test file {}: "
2541 "{} ({})",
2542 targetPath,
2543 mx::errorMessage( errc ),
2544 mx::errorName( errc ) ),
2545 logPrio::LOG_ERROR );
2546 return -1;
2547 }
2548
2549 derivedT::template log<text_log>( "loaded test file " + targetPath );
2550 m_testLoaded = true;
2551
2552 m_testCurrent = intarget;
2553
2554 if( m_indiP_tests.find( "default" ) )
2555 {
2556 if( m_testCurrent == "default" )
2557 {
2558 m_indiP_tests["default"] = pcf::IndiElement::On;
2559 }
2560 else
2561 {
2562 m_indiP_tests["default"] = pcf::IndiElement::Off;
2563 }
2564 }
2565
2566 for( auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
2567 {
2568 if( !m_indiP_tests.find( i->first ) )
2569 {
2570 continue;
2571 }
2572
2573 if( i->first == m_testCurrent )
2574 {
2575 m_indiP_tests[i->first] = pcf::IndiElement::On;
2576 }
2577 else
2578 {
2579 m_indiP_tests[i->first] = pcf::IndiElement::Off;
2580 }
2581 }
2582
2583 if( derived().m_indiDriver )
2584 derived().m_indiDriver->sendSetProperty( m_indiP_tests );
2585
2586 if( m_testSet )
2587 setTest();
2588
2589 return 0;
2590}
2591
2592template <class derivedT, typename realT>
2594{
2595
2596 if( m_shmimTest == "" )
2597 return 0;
2598
2599 if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2600 {
2601 derivedT::template log<text_log>( "could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2602 return -1;
2603 }
2604
2605 if( m_testImageStream.md->size[0] != m_dmWidth )
2606 {
2607 ImageStreamIO_closeIm( &m_testImageStream );
2608 derivedT::template log<text_log>( "width mismatch between " + m_shmimTest + " and configured DM",
2609 logPrio::LOG_ERROR );
2610 return -1;
2611 }
2612
2613 if( m_testImageStream.md->size[1] != m_dmHeight )
2614 {
2615 ImageStreamIO_closeIm( &m_testImageStream );
2616 derivedT::template log<text_log>( "height mismatch between " + m_shmimTest + " and configured DM",
2617 logPrio::LOG_ERROR );
2618 return -1;
2619 }
2620
2621 if( !m_testLoaded )
2622 {
2623 bool testSet = m_testSet;
2624 m_testSet = false; // make sure we don't loop
2625
2626 if( loadTest( m_testCurrent ) < 0 )
2627 {
2628 derivedT::template log<text_log>( "error loading test " + m_testCurrent, logPrio::LOG_ERROR );
2629 }
2630 m_testSet = testSet;
2631 }
2632
2633 if( !m_testLoaded )
2634 {
2635 ImageStreamIO_closeIm( &m_testImageStream );
2636 derivedT::template log<text_log>( "no test loaded", logPrio::LOG_ERROR );
2637 return -1;
2638 }
2639
2640 if( m_testCommand.rows() != m_dmWidth )
2641 {
2642 ImageStreamIO_closeIm( &m_testImageStream );
2643 derivedT::template log<text_log>( "width mismatch between test file and configured DM", logPrio::LOG_ERROR );
2644 return -1;
2645 }
2646
2647 if( m_testCommand.cols() != m_dmHeight )
2648 {
2649 ImageStreamIO_closeIm( &m_testImageStream );
2650 derivedT::template log<text_log>( "height mismatch between test file and configured DM", logPrio::LOG_ERROR );
2651 return -1;
2652 }
2653
2654 m_testImageStream.md->write = 1;
2655
2656 ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here
2657 /// anyway. With bounds checks b/c not everyone handles cnt1 properly.
2658 // Copy
2659 memcpy( m_testImageStream.array.raw, m_testCommand.data(), m_dmWidth * m_dmHeight * sizeof( realT ) );
2660
2661 // Set the time of last write
2662 clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2663
2664 // Set the image acquisition timestamp
2665 m_testImageStream.md->atime = m_testImageStream.md->writetime;
2666
2667 m_testImageStream.md->cnt0++;
2668 m_testImageStream.md->write = 0;
2669 ImageStreamIO_sempost( &m_testImageStream, -1 );
2670
2671 m_testSet = true;
2672
2673 // Post the semaphore
2674 ImageStreamIO_closeIm( &m_testImageStream );
2675
2676 derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2677
2678 derivedT::template log<text_log>( "test set" );
2679
2680 return 0;
2681}
2682
2683template <class derivedT, typename realT>
2685{
2686 if( m_shmimTest == "" )
2687 return 0;
2688
2689 if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2690 {
2691 derivedT::template log<text_log>( "could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2692 return -1;
2693 }
2694
2695 if( m_testImageStream.md[0].size[0] != m_dmWidth )
2696 {
2697 ImageStreamIO_closeIm( &m_testImageStream );
2698 derivedT::template log<text_log>( "width mismatch between " + m_shmimTest + " and configured DM",
2699 logPrio::LOG_ERROR );
2700 return -1;
2701 }
2702
2703 if( m_testImageStream.md[0].size[1] != m_dmHeight )
2704 {
2705 ImageStreamIO_closeIm( &m_testImageStream );
2706 derivedT::template log<text_log>( "height mismatch between " + m_shmimTest + " and configured DM",
2707 logPrio::LOG_ERROR );
2708 return -1;
2709 }
2710
2711 m_testImageStream.md->write = 1;
2712
2713 ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here
2714 /// anyway. With bounds checks b/c not everyone handles cnt1 properly.
2715 // Zero
2716 memset( m_testImageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2717
2718 // Set the time of last write
2719 clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2720
2721 // Set the image acquisition timestamp
2722 m_testImageStream.md->atime = m_testImageStream.md->writetime;
2723
2724 m_testImageStream.md->cnt0++;
2725 m_testImageStream.md->write = 0;
2726
2727 // Post the semaphore
2728 ImageStreamIO_sempost( &m_testImageStream, -1 );
2729
2730 m_testSet = false;
2731
2732 ImageStreamIO_closeIm( &m_testImageStream );
2733
2734 derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2735
2736 derivedT::template log<text_log>( "test zeroed" );
2737
2738 return 0;
2739}
2740
2741template <class derivedT, typename realT>
2743{
2744 if( derived().m_shmimName == "" )
2745 {
2746 return 0;
2747 }
2748
2749 IMAGE imageStream;
2750
2751 for( int n = 0; n < m_numChannels; ++n )
2752 {
2753 char nstr[16];
2754 snprintf( nstr, sizeof( nstr ), "%02d", n );
2755 std::string shmimN = derived().m_shmimName + nstr;
2756
2757 if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2758 {
2759 derivedT::template log<text_log>( "could not connect to channel " + shmimN, logPrio::LOG_WARNING );
2760 continue;
2761 }
2762
2763 if( imageStream.md->size[0] != m_dmWidth )
2764 {
2765 ImageStreamIO_closeIm( &imageStream );
2766 derivedT::template log<text_log>( "width mismatch between " + shmimN + " and configured DM",
2767 logPrio::LOG_ERROR );
2768 derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2769 return -1;
2770 }
2771
2772 if( imageStream.md->size[1] != m_dmHeight )
2773 {
2774 ImageStreamIO_closeIm( &imageStream );
2775 derivedT::template log<text_log>( "height mismatch between " + shmimN + " and configured DM",
2776 logPrio::LOG_ERROR );
2777 derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2778 return -1;
2779 }
2780
2781 imageStream.md->write = 1;
2782 memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2783
2784 clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2785
2786 // Set the image acquisition timestamp
2787 imageStream.md->atime = imageStream.md->writetime;
2788
2789 imageStream.md->cnt0++;
2790 imageStream.md->write = 0;
2791
2792 // Raise the semaphore on last one.
2793 if( n == m_numChannels - 1 && !nosem )
2794 {
2795 ImageStreamIO_sempost( &imageStream, -1 );
2796 }
2797
2798 ImageStreamIO_closeIm( &imageStream );
2799 }
2800
2801 derivedT::template log<text_log>( "all channels zeroed", logPrio::LOG_NOTICE );
2802
2803 derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2804
2805 // Also cleanup flat and test
2806 m_flatSet = false;
2807 derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2808 if( derived().state() == stateCodes::OPERATING )
2809 {
2810 derived().state( stateCodes::READY );
2811 }
2812
2813 // Also cleanup flat and test
2814 m_testSet = false;
2815 derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2816
2817 int rv;
2818 if( ( rv = clearSat() ) < 0 )
2819 {
2820 derivedT::template log<software_error>( { errno, rv, "Error from clearSat" } );
2821 return rv;
2822 }
2823
2824 return 0;
2825}
2826
2827template <class derivedT, typename realT>
2829{
2830 if( m_notDeltas.size() == 0 )
2831 {
2832 return 0;
2833 }
2834
2835 m_totalFlat = ( *m_channels[m_notDeltas[0]] )();
2836
2837 for( size_t n = 1; n < m_notDeltas.size(); ++n )
2838 {
2839 m_totalFlat += ( *m_channels[m_notDeltas[n]] )();
2840 }
2841
2842 m_outputDelta = m_outputShape() - m_totalFlat; // this posts and everything
2843
2844 m_totalDelta = ( *m_channels[m_deltas[0]] )();
2845
2846 for( size_t n = 1; n < m_deltas.size(); ++n )
2847 {
2848 m_totalDelta += ( *m_channels[m_deltas[n]] )();
2849 }
2850
2851 m_outputDiff = m_totalDelta - m_outputDelta();
2852
2853 return 0;
2854}
2855
2856template <class derivedT, typename realT>
2858{
2859 if( m_shmimSat == "" || m_dmWidth == 0 || m_dmHeight == 0 )
2860 {
2861 return 0;
2862 }
2863
2864 IMAGE imageStream;
2865
2866 std::vector<std::string> sats = { m_shmimSat, m_shmimSatPerc };
2867
2868 for( size_t n = 0; n < sats.size(); ++n )
2869 {
2870 std::string shmimN = sats[n];
2871
2872 if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2873 {
2874 derivedT::template log<text_log>( "could not connect to sat map " + shmimN, logPrio::LOG_WARNING );
2875 return 0;
2876 }
2877
2878 if( imageStream.md->size[0] != m_dmWidth )
2879 {
2880 ImageStreamIO_closeIm( &imageStream );
2881 derivedT::template log<text_log>( "width mismatch between " + shmimN + " and configured DM",
2882 logPrio::LOG_ERROR );
2883 derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2884 return -1;
2885 }
2886
2887 if( imageStream.md->size[1] != m_dmHeight )
2888 {
2889 ImageStreamIO_closeIm( &imageStream );
2890 derivedT::template log<text_log>( "height mismatch between " + shmimN + " and configured DM",
2891 logPrio::LOG_ERROR );
2892 derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2893 return -1;
2894 }
2895
2896 imageStream.md->write = 1;
2897 memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * ImageStreamIO_typesize( imageStream.md->datatype ) );
2898
2899 clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2900
2901 // Set the image acquisition timestamp
2902 imageStream.md->atime = imageStream.md->writetime;
2903
2904 imageStream.md->cnt0++;
2905 imageStream.md->write = 0;
2906 ImageStreamIO_sempost( &imageStream, -1 );
2907
2908 ImageStreamIO_closeIm( &imageStream );
2909 }
2910
2911 m_accumSatMap.setZero();
2912 m_instSatMap.setZero();
2913
2914 return 0;
2915}
2916
2917template <class derivedT, typename realT>
2919{
2920 d->satThreadExec();
2921}
2922
2923template <class derivedT, typename realT>
2925{
2926 // Get the thread PID immediately so the caller can return.
2927 m_satThreadID = syscall( SYS_gettid );
2928
2929 // Wait for the thread starter to finish initializing this thread.
2930 while( m_satThreadInit == true && derived().shutdown() == 0 )
2931 {
2932 sleep( 1 );
2933 }
2934
2935 if( derived().shutdown() )
2936 {
2937 return;
2938 }
2939
2940 uint32_t imsize[3] = { 0, 0, 0 };
2941
2942 // Check for allocation to have happened.
2943 while( ( m_shmimSat == "" || m_accumSatMap.rows() == 0 || m_accumSatMap.cols() == 0 ) && !derived().shutdown() )
2944 {
2945 sleep( 1 );
2946 }
2947
2948 if( derived().shutdown() )
2949 {
2950 return;
2951 }
2952
2953 imsize[0] = m_dmWidth;
2954 imsize[1] = m_dmHeight;
2955 imsize[2] = 1;
2956
2957 ImageStreamIO_createIm_gpu( &m_satImageStream,
2958 m_shmimSat.c_str(),
2959 3,
2960 imsize,
2962 -1,
2963 1,
2964 IMAGE_NB_SEMAPHORE,
2965 0,
2966 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2967 0 );
2968 ImageStreamIO_createIm_gpu( &m_satPercImageStream,
2969 m_shmimSatPerc.c_str(),
2970 3,
2971 imsize,
2973 -1,
2974 1,
2975 IMAGE_NB_SEMAPHORE,
2976 0,
2977 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2978 0 );
2979
2980 bool opened = true;
2981
2982 m_satImageStream.md->cnt1 = 0;
2983 m_satPercImageStream.md->cnt1 = 0;
2984
2985 // This is the working memory for making the 1/0 mask out of m_accumSatMap
2986 mx::improc::eigenImage<uint8_t> satmap( m_dmWidth, m_dmHeight );
2987
2988 int naccum = 0;
2989 double t_accumst = mx::sys::get_curr_time();
2990
2991 // This is the main image grabbing loop.
2992 while( !derived().shutdown() )
2993 {
2994 // Get timespec for sem_timedwait
2995 timespec ts;
2996 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
2997 {
2998 derivedT::template log<software_critical>( { errno, 0, "clock_gettime" } );
2999 return;
3000 }
3001 ts.tv_sec += 1;
3002
3003 // Wait on semaphore
3004 if( sem_timedwait( &m_satSemaphore, &ts ) == 0 )
3005 {
3006 // not a timeout -->accumulate
3007 for( int rr = 0; rr < m_instSatMap.rows(); ++rr )
3008 {
3009 for( int cc = 0; cc < m_instSatMap.cols(); ++cc )
3010 {
3011 m_accumSatMap( rr, cc ) += m_instSatMap( rr, cc );
3012 }
3013 }
3014 ++naccum;
3015
3016 // If less than avg int --> go back and wait again
3017 if( mx::sys::get_curr_time( ts ) - t_accumst < m_satAvgInt / 1000.0 )
3018 {
3019 continue;
3020 }
3021
3022 // If greater than avg int --> calc stats, write to streams.
3023 m_overSatAct = 0;
3024 for( int rr = 0; rr < m_instSatMap.rows(); ++rr )
3025 {
3026 for( int cc = 0; cc < m_instSatMap.cols(); ++cc )
3027 {
3028 m_satPercMap( rr, cc ) = m_accumSatMap( rr, cc ) / naccum;
3029 if( m_satPercMap( rr, cc ) >= m_percThreshold )
3030 {
3031 ++m_overSatAct;
3032 }
3033 satmap( rr, cc ) = ( m_accumSatMap( rr, cc ) > 0 ); // it's 1/0 map
3034 }
3035 }
3036
3037 // Check of the number of actuators saturated above the percent threshold is greater than the number
3038 // threshold if it is, increment the counter
3039 if( m_overSatAct / ( m_satPercMap.rows() * m_satPercMap.cols() * 0.75 ) > m_intervalSatThreshold )
3040 {
3041 ++m_intervalSatExceeds;
3042 }
3043 else
3044 {
3045 m_intervalSatExceeds = 0;
3046 }
3047
3048 // If enough consecutive intervals exceed the count threshold, we trigger
3049 if( m_intervalSatExceeds >= m_intervalSatCountThreshold )
3050 {
3051 m_intervalSatTrip = true;
3052 }
3053
3054 m_satImageStream.md->write = 1;
3055 m_satPercImageStream.md->write = 1;
3056
3057 memcpy( m_satImageStream.array.raw, satmap.data(), m_dmWidth * m_dmHeight * sizeof( uint8_t ) );
3058 memcpy( m_satPercImageStream.array.raw, m_satPercMap.data(), m_dmWidth * m_dmHeight * sizeof( float ) );
3059
3060 // Set the time of last write
3061 clock_gettime( CLOCK_REALTIME, &m_satImageStream.md->writetime );
3062 m_satPercImageStream.md->writetime = m_satImageStream.md->writetime;
3063
3064 // Set the image acquisition timestamp
3065 m_satImageStream.md->atime = m_satImageStream.md->writetime;
3066 m_satPercImageStream.md->atime = m_satPercImageStream.md->writetime;
3067
3068 // Update cnt1
3069 m_satImageStream.md->cnt1 = 0;
3070 m_satPercImageStream.md->cnt1 = 0;
3071
3072 // Update cnt0
3073 m_satImageStream.md->cnt0++;
3074 m_satPercImageStream.md->cnt0++;
3075
3076 m_satImageStream.writetimearray[0] = m_satImageStream.md->writetime;
3077 m_satImageStream.atimearray[0] = m_satImageStream.md->atime;
3078 m_satImageStream.cntarray[0] = m_satImageStream.md->cnt0;
3079
3080 m_satPercImageStream.writetimearray[0] = m_satPercImageStream.md->writetime;
3081 m_satPercImageStream.atimearray[0] = m_satPercImageStream.md->atime;
3082 m_satPercImageStream.cntarray[0] = m_satPercImageStream.md->cnt0;
3083
3084 // And post
3085 m_satImageStream.md->write = 0;
3086 ImageStreamIO_sempost( &m_satImageStream, -1 );
3087
3088 m_satPercImageStream.md->write = 0;
3089 ImageStreamIO_sempost( &m_satPercImageStream, -1 );
3090
3091 m_accumSatMap.setZero();
3092 naccum = 0;
3093 t_accumst = mx::sys::get_curr_time( ts );
3094 }
3095 else
3096 {
3097 // Check for why we timed out
3098 if( errno == EINTR )
3099 {
3100 break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit normally if
3101 // flags set.
3102 }
3103
3104 // ETIMEDOUT just means we should wait more.
3105 // Otherwise, report an error.
3106 if( errno != ETIMEDOUT )
3107 {
3108 derivedT::template log<software_error>( { errno, "sem_timedwait" } );
3109 break;
3110 }
3111 }
3112 }
3113
3114 if( opened )
3115 {
3116 ImageStreamIO_destroyIm( &m_satImageStream );
3117
3118 ImageStreamIO_destroyIm( &m_satPercImageStream );
3119 }
3120}
3121
3122template <class derivedT, typename realT>
3124{
3125 if( m_satTriggerDevice.size() > 0 && m_satTriggerProperty.size() == m_satTriggerDevice.size() )
3126 {
3127 for( size_t n = 0; n < m_satTriggerDevice.size(); ++n )
3128 {
3129 // We just silently fail
3130 try
3131 {
3132 pcf::IndiProperty ipFreq( pcf::IndiProperty::Switch );
3133
3134 ipFreq.setDevice( m_satTriggerDevice[n] );
3135 ipFreq.setName( m_satTriggerProperty[n] );
3136 ipFreq.add( pcf::IndiElement( "toggle" ) );
3137 ipFreq["toggle"] = pcf::IndiElement::Off;
3138 derived().sendNewProperty( ipFreq );
3139
3140 derivedT::template log<text_log>( "DM saturation threshold exceeded. Loop opened.",
3141 logPrio::LOG_WARNING );
3142 }
3143 catch( ... )
3144 {
3145 }
3146 }
3147 }
3148}
3149
3150template <class derivedT, typename realT>
3152{
3153 if( !derived().m_indiDriver )
3154 {
3155 return 0;
3156 }
3157
3158 return 0;
3159}
3160
3161template <class derivedT, typename realT>
3162int dm<derivedT, realT>::st_newCallBack_init( void *app, const pcf::IndiProperty &ipRecv )
3163{
3164 return static_cast<derivedT *>( app )->newCallBack_init( ipRecv );
3165}
3166
3167template <class derivedT, typename realT>
3168int dm<derivedT, realT>::newCallBack_init( const pcf::IndiProperty &ipRecv )
3169{
3170 if( ipRecv.createUniqueKey() != m_indiP_init.createUniqueKey() )
3171 {
3172 return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3173 }
3174
3175 if( !ipRecv.find( "request" ) )
3176 {
3177 return 0;
3178 }
3179
3180 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3181 {
3182 int rv = baseInitDM();
3183 if( rv < 0 )
3184 {
3185 return derivedT::template log<software_error, -1>( { "error from initDM in INDI callback" } );
3186 }
3187 }
3188
3189 return 0;
3190}
3191
3192template <class derivedT, typename realT>
3193int dm<derivedT, realT>::st_newCallBack_zero( void *app, const pcf::IndiProperty &ipRecv )
3194{
3195 return static_cast<derivedT *>( app )->newCallBack_zero( ipRecv );
3196}
3197
3198template <class derivedT, typename realT>
3199int dm<derivedT, realT>::newCallBack_zero( const pcf::IndiProperty &ipRecv )
3200{
3201 if( ipRecv.createUniqueKey() != m_indiP_zero.createUniqueKey() )
3202 {
3203 return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3204 }
3205
3206 if( !ipRecv.find( "request" ) )
3207 return 0;
3208
3209 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3210 {
3211 return derived().zeroDM();
3212 }
3213 return 0;
3214}
3215
3216template <class derivedT, typename realT>
3217int dm<derivedT, realT>::st_newCallBack_release( void *app, const pcf::IndiProperty &ipRecv )
3218{
3219 return static_cast<derivedT *>( app )->newCallBack_release( ipRecv );
3220}
3221
3222template <class derivedT, typename realT>
3224{
3225 if( ipRecv.createUniqueKey() != m_indiP_release.createUniqueKey() )
3226 {
3227 return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3228 }
3229
3230 if( !ipRecv.find( "request" ) )
3231 return 0;
3232
3233 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3234 {
3235 return baseReleaseDM();
3236 }
3237 return 0;
3238}
3239
3240template <class derivedT, typename realT>
3241int dm<derivedT, realT>::st_newCallBack_flats( void *app, const pcf::IndiProperty &ipRecv )
3242{
3243 return static_cast<derivedT *>( app )->newCallBack_flats( ipRecv );
3244}
3245
3246template <class derivedT, typename realT>
3247int dm<derivedT, realT>::newCallBack_flats( const pcf::IndiProperty &ipRecv )
3248{
3249 if( ipRecv.createUniqueKey() != m_indiP_flats.createUniqueKey() )
3250 {
3251 derivedT::template log<software_error>( { "invalid indi property received" } );
3252 return -1;
3253 }
3254
3255 std::string newFlat;
3256
3257 if( ipRecv.find( "default" ) )
3258 {
3259 if( ipRecv["default"].getSwitchState() == pcf::IndiElement::On )
3260 {
3261 newFlat = "default";
3262 }
3263 }
3264
3265 // always do this to check for error:
3266 for( auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
3267 {
3268 if( !ipRecv.find( i->first ) )
3269 continue;
3270
3271 if( ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3272 {
3273 if( newFlat != "" )
3274 {
3275 derivedT::template log<text_log>( "More than one flat selected", logPrio::LOG_ERROR );
3276 return -1;
3277 }
3278
3279 newFlat = i->first;
3280 }
3281 }
3282
3283 if( newFlat == "" )
3284 {
3285 return 0;
3286 }
3287
3288 return loadFlat( newFlat );
3289}
3290
3291template <class derivedT, typename realT>
3292int dm<derivedT, realT>::st_newCallBack_setFlat( void *app, const pcf::IndiProperty &ipRecv )
3293{
3294 return static_cast<derivedT *>( app )->newCallBack_setFlat( ipRecv );
3295}
3296
3297template <class derivedT, typename realT>
3299{
3300 if( ipRecv.createUniqueKey() != m_indiP_setFlat.createUniqueKey() )
3301 {
3302 return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3303 }
3304
3305 if( !ipRecv.find( "toggle" ) )
3306 return 0;
3307
3308 if( ipRecv["toggle"] == pcf::IndiElement::On )
3309 {
3310 return setFlat();
3311 }
3312 else
3313 {
3314 return zeroFlat();
3315 }
3316}
3317
3318template <class derivedT, typename realT>
3319int dm<derivedT, realT>::st_newCallBack_tests( void *app, const pcf::IndiProperty &ipRecv )
3320{
3321 return static_cast<derivedT *>( app )->newCallBack_tests( ipRecv );
3322}
3323
3324template <class derivedT, typename realT>
3325int dm<derivedT, realT>::newCallBack_tests( const pcf::IndiProperty &ipRecv )
3326{
3327 if( ipRecv.createUniqueKey() != m_indiP_tests.createUniqueKey() )
3328 {
3329 derivedT::template log<software_error>( { "invalid indi property received" } );
3330 return -1;
3331 }
3332
3333 std::string newTest;
3334
3335 if( ipRecv.find( "default" ) )
3336 {
3337 if( ipRecv["default"].getSwitchState() == pcf::IndiElement::On )
3338 {
3339 newTest = "default";
3340 }
3341 }
3342
3343 // always do this to check for error:
3344 for( auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
3345 {
3346 if( !ipRecv.find( i->first ) )
3347 continue;
3348
3349 if( ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3350 {
3351 if( newTest != "" )
3352 {
3353 derivedT::template log<text_log>( "More than one test selected", logPrio::LOG_ERROR );
3354 return -1;
3355 }
3356
3357 newTest = i->first;
3358 }
3359 }
3360
3361 if( newTest == "" )
3362 {
3363 return 0;
3364 }
3365
3366 return loadTest( newTest );
3367}
3368
3369template <class derivedT, typename realT>
3370int dm<derivedT, realT>::st_newCallBack_setTest( void *app, const pcf::IndiProperty &ipRecv )
3371{
3372 return static_cast<derivedT *>( app )->newCallBack_setTest( ipRecv );
3373}
3374
3375template <class derivedT, typename realT>
3377{
3378 if( ipRecv.createUniqueKey() != m_indiP_setTest.createUniqueKey() )
3379 {
3380 return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3381 }
3382
3383 if( !ipRecv.find( "toggle" ) )
3384 return 0;
3385
3386 if( ipRecv["toggle"] == pcf::IndiElement::On )
3387 {
3388 return setTest();
3389 }
3390 else
3391 {
3392 return zeroTest();
3393 }
3394}
3395
3396template <class derivedT, typename realT>
3397int dm<derivedT, realT>::st_newCallBack_zeroAll( void *app, const pcf::IndiProperty &ipRecv )
3398{
3399 return static_cast<derivedT *>( app )->newCallBack_zeroAll( ipRecv );
3400}
3401
3402template <class derivedT, typename realT>
3404{
3405 if( ipRecv.createUniqueKey() != m_indiP_zeroAll.createUniqueKey() )
3406 {
3407 return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3408 }
3409
3410 if( !ipRecv.find( "request" ) )
3411 return 0;
3412
3413 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3414 {
3416 m_indiP_zeroAll, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY );
3417
3418 std::lock_guard<std::mutex> guard( derived().m_indiMutex );
3419 return zeroAll();
3420 }
3421 return 0;
3422}
3423
3424/// Call dmT::setupConfig with error checking for dm
3425/**
3426 * \param cfig the application configurator
3427 */
3428#define DM_SETUP_CONFIG( cfig ) \
3429 if( dmT::setupConfig( cfig ) < 0 ) \
3430 { \
3431 log<software_error>( { "Error from dmT::setupConfig" } ); \
3432 m_shutdown = true; \
3433 return; \
3434 }
3435
3436/// Call dmT::loadConfig with error checking for dm
3437/** This must be inside a function that returns int, e.g. the standard loadConfigImpl.
3438 * \param cfig the application configurator
3439 */
3440#define DM_LOAD_CONFIG( cfig ) \
3441 if( dmT::loadConfig( cfig ) < 0 ) \
3442 { \
3443 return log<software_error, -1>( { "Error from dmT::loadConfig" } ); \
3444 }
3445
3446/// Call shmimMonitorT::appStartup with error checking for dm
3447#define DM_APP_STARTUP \
3448 if( dmT::appStartup() < 0 ) \
3449 { \
3450 return log<software_error, -1>( { "Error from dmT::appStartup" } ); \
3451 }
3452
3453/// Call dmT::appLogic with error checking for dm
3454#define DM_APP_LOGIC \
3455 if( dmT::appLogic() < 0 ) \
3456 { \
3457 return log<software_error, -1>( { "Error from dmT::appLogic" } ); \
3458 }
3459
3460/// Call dmT::updateINDI with error checking for dm
3461#define DM_UPDATE_INDI \
3462 if( dmT::updateINDI() < 0 ) \
3463 { \
3464 return log<software_error, -1>( { "Error from dmT::updateINDI" } ); \
3465 }
3466
3467/// Call dmT::appShutdown with error checking for dm
3468#define DM_APP_SHUTDOWN \
3469 if( dmT::appShutdown() < 0 ) \
3470 { \
3471 return log<software_error, -1>( { "Error from dmT::appShutdown" } ); \
3472 }
3473
3474} // namespace dev
3475} // namespace app
3476} // namespace MagAOX
3477#endif
#define IMAGESTRUCT_FLOAT
#define IMAGESTRUCT_UINT8
std::string m_calibPath
The path to this DM's calibration files.
Definition dm.hpp:99
std::map< std::string, std::string > m_flatCommands
Map of flat file name to full path.
Definition dm.hpp:150
int newCallBack_tests(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition dm.hpp:3325
bool m_testSet
Flag indicating whether the test command has been set.
Definition dm.hpp:168
std::string m_flatPath
The path to this DM's flat files (usually the same as calibPath)
Definition dm.hpp:100
float percThreshold() const
Get the saturation percentage threshold.
Definition dm.hpp:906
const std::string & testDefault() const
Get the.
Definition dm.hpp:846
const std::string & calibRelDir() const
Definition dm.hpp:936
uint32_t dmWidth() const
Get the DM Width.
Definition dm.hpp:888
std::string m_shmimDiff
The name of the shmim stream to write the difference to.
Definition dm.hpp:121
const mx::improc::eigenImage< uint16_t > & accumSatMap() const
Definition dm.hpp:954
pcf::IndiProperty m_indiP_setFlat
INDI toggle switch to set the current flat.
Definition dm.hpp:596
static void satThreadStart(dm *d)
Thread starter, called by MagAOXApp::threadStart on thread construction. Calls satThreadExec.
Definition dm.hpp:2918
pcf::IndiProperty m_indiP_init
Definition dm.hpp:590
uint32_t m_dmWidth
The width of the images in the stream.
Definition dm.hpp:123
int satAvgInt() const
Get the saturation accumulation interval.
pcf::IndiProperty m_indiP_flats
INDI Selection switch containing the flat files.
Definition dm.hpp:594
int zeroFlat()
Zero the flat command on the DM.
Definition dm.hpp:2326
static int st_newCallBack_init(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
Definition dm.hpp:3162
int checkFlats()
Check the flats directory and update the list of flats if anything changes.
Definition dm.hpp:1969
pcf::IndiProperty m_indiP_testShmim
Publish the shmim being used for the test command.
Definition dm.hpp:599
int checkTests()
Check the tests directory and update the list of tests if anything changes.
Definition dm.hpp:2402
static int st_newCallBack_tests(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for selecting the test file.
Definition dm.hpp:3319
const mx::improc::eigenImage< float > & satPercMap() const
Definition dm.hpp:960
std::string m_flatDefault
Definition dm.hpp:105
int setTest()
Send the current test command to the DM.
Definition dm.hpp:2593
int clearSat()
Clear the saturation maps and zero the shared memory.
Definition dm.hpp:2857
const std::vector< std::string > & satTriggerProperty() const
Get the saturation trigger property(ies)
Definition dm.hpp:930
static int st_newCallBack_zero(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
Definition dm.hpp:3193
int loadTest(const std::string &target)
Load a test file.
Definition dm.hpp:2513
int loadFlat(const std::string &target)
Load a flat file.
Definition dm.hpp:2118
int m_satThreadPrio
Priority of the saturation thread. Usually ok to be 0.
Definition dm.hpp:117
IMAGE m_satPercImageStream
The ImageStreamIO shared memory buffer for the sat percentage map.
Definition dm.hpp:180
uint8_t dmDataType() const
Get the DM data type.
Definition dm.hpp:900
std::string m_calibRelDir
Definition dm.hpp:143
pcf::IndiProperty m_indiP_setTest
INDI toggle switch to set the current test pattern.
Definition dm.hpp:600
const std::string & shmimShape() const
Get the.
Definition dm.hpp:876
int findDMChannels()
Find the DM comb channels.
Definition dm.hpp:1652
static constexpr uint8_t m_dmDataType
The ImageStreamIO type code.
Definition dm.hpp:126
std::vector< size_t > m_notDeltas
Indices of the channels which are not delta commands.
Definition dm.hpp:191
pcf::IndiProperty m_indiP_tests
INDI Selection switch containing the test pattern files.
Definition dm.hpp:598
mx::improc::eigenImage< realT > m_totalDelta
the total of all delta channels
Definition dm.hpp:194
mx::improc::eigenImage< realT > m_totalFlat
the total of all non-delta channels
Definition dm.hpp:193
std::string m_shmimDelta
The name of the shmim stream to write the desaturated delta command to.
Definition dm.hpp:120
std::string m_shmimFlat
The name of the shmim stream to write the flat to.
Definition dm.hpp:110
int processImage(void *curr_src, const dev::shmimT &sp)
Definition dm.hpp:1845
const std::string & shmimSatPerc() const
Get the stream name for saturation percentage.
Definition dm.hpp:870
const std::string & shmimDelta() const
Get the.
Definition dm.hpp:882
std::vector< mx::improc::milkImage< realT > * > m_channels
Definition dm.hpp:148
uint32_t m_dmHeight
The height of the images in the stream.
Definition dm.hpp:124
int appShutdown()
DM shutdown.
Definition dm.hpp:1617
std::string m_testPath
The path to this DM's test files (default is calibPath/tests;.
Definition dm.hpp:101
int newCallBack_zeroAll(const pcf::IndiProperty &ipRecv)
The callback for the zeroAll toggle switch, called by the static version.
Definition dm.hpp:3403
bool m_flatSet
Flag indicating whether the flat command has been set.
Definition dm.hpp:157
pcf::IndiProperty m_satThreadProp
The property to hold the saturation thread details.
Definition dm.hpp:211
int baseReleaseDM()
Calls derived()->releaseDM() and then 0s all channels and the sat map.
Definition dm.hpp:1943
std::string m_shmimSatPerc
The name of the shmim stream to write the saturation percentage map to.
Definition dm.hpp:113
int newCallBack_setTest(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition dm.hpp:3376
const std::string & calibPath() const
Get the.
Definition dm.hpp:822
static int st_newCallBack_flats(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for selecting the flat file.
Definition dm.hpp:3241
std::string m_actMaskPath
The file name of the actuator mask for this DM.
Definition dm.hpp:103
mx::verbose::vvv verboseT
Definition dm.hpp:92
std::string m_testCurrent
Definition dm.hpp:162
int m_intervalSatExceeds
Definition dm.hpp:183
bool m_flatLoaded
Flag indicating whether a flat is loaded in memory.
Definition dm.hpp:154
bool m_testLoaded
Flag indicating whether a test command is loaded in memory.
Definition dm.hpp:165
mx::improc::eigenImage< float > m_satPercMap
Definition dm.hpp:176
int setFlat(bool update=false)
Send the current flat command to the DM.
Definition dm.hpp:2222
static int st_newCallBack_zeroAll(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for zeroing all channels.
Definition dm.hpp:3397
const std::vector< std::string > & deltaChannels() const
Definition dm.hpp:966
int newCallBack_init(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition dm.hpp:3168
const std::string & shmimTest() const
Get the.
Definition dm.hpp:858
const std::vector< size_t > & notDeltas() const
Definition dm.hpp:972
const std::string & shmimFlat() const
Get the.
Definition dm.hpp:852
mx::improc::milkImage< realT > m_outputDelta
The true output delta command after saturation.
Definition dm.hpp:196
int zeroAll(bool nosem=false)
Zero all channels.
Definition dm.hpp:2742
mx::improc::milkImage< realT > m_actMask
Definition dm.hpp:159
pid_t m_satThreadID
The ID of the saturation thread.
Definition dm.hpp:209
int intervalSatCountThreshold() const
Get the interval saturation count threshold.
Definition dm.hpp:918
int baseInitDM()
Calls derived()->initDM()
Definition dm.hpp:1920
int zeroTest()
Zero the test command on the DM.
Definition dm.hpp:2684
int m_satAvgInt
The time in milliseconds to accumulate saturation over.
Definition dm.hpp:115
int newCallBack_release(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition dm.hpp:3223
std::vector< std::string > m_satTriggerProperty
Definition dm.hpp:138
const std::string & flatDefault() const
Get the.
Definition dm.hpp:840
int updateINDI()
Update the INDI properties for this device controller.
Definition dm.hpp:3151
std::vector< size_t > m_deltas
Indices of the channels which are delta commands.
Definition dm.hpp:190
mx::improc::milkImage< realT > m_outputDiff
The difference between command and true delta command after saturation.
Definition dm.hpp:198
int newCallBack_flats(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition dm.hpp:3247
sem_t m_satSemaphore
Semaphore used to tell the saturation thread to run.
Definition dm.hpp:205
pcf::IndiProperty m_indiP_flat
Property used to set and report the current flat.
Definition dm.hpp:588
IMAGE m_satImageStream
The ImageStreamIO shared memory buffer for the sat map.
Definition dm.hpp:179
int newCallBack_zero(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition dm.hpp:3199
bool m_satThreadInit
Synchronizer for thread startup, to allow priority setting to finish.
Definition dm.hpp:207
derivedT & derived()
Definition dm.hpp:803
std::vector< std::string > m_satTriggerDevice
Device(s) with a toggle switch to toggle on saturation trigger.
Definition dm.hpp:136
mx::improc::eigenImage< realT > m_flatCommand
Data storage for the flat command.
Definition dm.hpp:153
int m_numChannels
The number of dmcomb channels found as part of allocation.
Definition dm.hpp:146
const mx::improc::eigenImage< uint8_t > & instSatMap() const
Definition dm.hpp:948
mx::improc::eigenImage< uint8_t > m_instSatMap
Definition dm.hpp:170
void satThreadExec()
Execute saturation processing.
Definition dm.hpp:2924
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
Definition dm.hpp:1255
static int st_newCallBack_release(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
Definition dm.hpp:3217
pcf::IndiProperty m_indiP_flatShmim
Publish the shmim being used for the flat.
Definition dm.hpp:595
float m_intervalSatThreshold
Definition dm.hpp:130
int whilePowerOff()
DM Poweroff Updates.
Definition dm.hpp:1643
static int st_newCallBack_setFlat(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for setting the flat.
Definition dm.hpp:3292
static int st_newCallBack_setTest(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for setting the test shape.
Definition dm.hpp:3370
pcf::IndiProperty m_indiP_zeroAll
Definition dm.hpp:602
std::map< std::string, std::string > m_testCommands
Map of test file name to full path.
Definition dm.hpp:161
int makeDelta()
Calculate the delta command from the output shape.
Definition dm.hpp:2828
mx::improc::milkImage< realT > m_outputShape
The true output shape after saturation.
Definition dm.hpp:186
std::vector< std::string > m_deltaChannels
The names of channels which are treated as delta commands.
Definition dm.hpp:188
const std::string & testPath() const
Get the.
Definition dm.hpp:834
IMAGE m_testImageStream
The ImageStreamIO shared memory buffer for the test.
Definition dm.hpp:167
std::string m_testDefault
Definition dm.hpp:107
std::string m_shmimTest
The name of the shmim stream to write the test to.
Definition dm.hpp:111
bool m_intervalSatTrip
Definition dm.hpp:184
std::string m_shmimShape
The name of the shmim stream to write the desaturated true shape to.
Definition dm.hpp:119
int m_intervalSatCountThreshold
Definition dm.hpp:133
~dm()
Destructor.
Definition dm.hpp:810
int appStartup()
Startup function.
Definition dm.hpp:1405
int newCallBack_setFlat(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition dm.hpp:3298
mx::improc::eigenImage< uint16_t > m_accumSatMap
Definition dm.hpp:173
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
Definition dm.hpp:984
int onPowerOff()
DM Poweroff.
Definition dm.hpp:1635
float m_percThreshold
Threshold on percentage of frames an actuator is saturated over an interval.
Definition dm.hpp:128
int satThreadPrio() const
Get the saturation thread priority.
std::string m_shmimSat
The name of the shmim stream to write the saturation map to.
Definition dm.hpp:112
float intervalSatThreshold() const
Get the interval saturation threshold.
Definition dm.hpp:912
const mx::improc::eigenImage< float > & totalFlat() const
Definition dm.hpp:978
mx::improc::eigenImage< realT > m_testCommand
Data storage for the test command.
Definition dm.hpp:164
IMAGE m_flatImageStream
The ImageStreamIO shared memory buffer for the flat.
Definition dm.hpp:156
std::thread m_satThread
A separate thread for the actual saturation processing.
Definition dm.hpp:213
pcf::IndiProperty m_indiP_release
Definition dm.hpp:592
pcf::IndiProperty m_indiP_zero
Definition dm.hpp:591
int numChannels() const
Definition dm.hpp:942
uint32_t dmHeight() const
Get the DM Height.
Definition dm.hpp:894
int allocate(const dev::shmimT &sp)
Called after shmimMonitor connects to the dmXXdisp stream. Checks for proper size.
Definition dm.hpp:1747
std::string m_flatCurrent
The name of the current flat command.
Definition dm.hpp:151
const std::vector< std::string > & satTriggerDevice() const
Get the saturation trigger device(s)
Definition dm.hpp:924
const std::string & shmimSat() const
Get the.
Definition dm.hpp:864
void intervalSatTrip()
Trigger loop openings because of excessive saturation.
Definition dm.hpp:3123
const std::string & flatPath() const
Get the.
Definition dm.hpp:828
int appLogic()
DM application logic.
Definition dm.hpp:1551
#define INDI_IDLE
Definition indiUtils.hpp:27
#define INDI_BUSY
Definition indiUtils.hpp:29
constexpr uint8_t ImageStreamTypeCode< float >()
Definition dm.hpp:32
constexpr uint8_t ImageStreamTypeCode()
Definition dm.hpp:26
constexpr uint8_t ImageStreamTypeCode< double >()
Definition dm.hpp:38
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.
const pcf::IndiProperty & ipRecv
Definition dm.hpp:19
The MagAO-X generic shared memory monitor.
@ OPERATING
The device is operating, other than homing.
@ POWEROFF
The device power is off.
@ NOTHOMED
The device has not been homed.
@ HOMING
The device is homing.
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
@ READY
The device is ready for operation, but is not operating.