Line data Source code
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 :
18 : namespace MagAOX
19 : {
20 : namespace app
21 : {
22 : namespace dev
23 : {
24 :
25 : template <typename typeT>
26 : constexpr uint8_t ImageStreamTypeCode()
27 : {
28 : return 0;
29 : }
30 :
31 : template <>
32 : constexpr uint8_t ImageStreamTypeCode<float>()
33 : {
34 : return _DATATYPE_FLOAT;
35 : }
36 :
37 : template <>
38 : constexpr 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 : */
88 : template <class derivedT, typename realT>
89 : class 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 : */
222 : ~dm();
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 : */
336 : int intervalSatCountThreshold() const;
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> ¬Deltas() 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 : */
397 : int appStartup();
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 : */
421 : int appShutdown();
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 : */
433 : int onPowerOff();
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 : */
445 : int whilePowerOff();
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 : */
451 : int findDMChannels();
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 : */
469 : int baseInitDM();
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 : */
477 : int baseReleaseDM();
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 : */
485 : int checkFlats();
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 : */
519 : int checkTests();
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.
554 : int makeDelta();
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
573 : void satThreadExec();
574 :
575 : /// Trigger loop openings because of excessive saturation
576 : void intervalSatTrip();
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 : */
641 : int newCallBack_zero(
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 : */
681 : int newCallBack_flats(
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 : */
689 : static int st_newCallBack_setFlat(
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 : */
699 : int newCallBack_setFlat(
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 : */
707 : static int st_newCallBack_tests(
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 : */
717 : int newCallBack_tests(
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 : */
745 : static int st_newCallBack_zeroAll(
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 : */
755 : int newCallBack_zeroAll(
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 : */
765 : int updateINDI();
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 46 : derivedT &derived()
804 : {
805 46 : return *static_cast<derivedT *>( this );
806 : }
807 : };
808 :
809 : template <class derivedT, typename realT>
810 4 : dm<derivedT, realT>::~dm()
811 : {
812 9 : for( auto &mi : m_channels )
813 : {
814 5 : if( mi != nullptr )
815 : {
816 5 : delete mi;
817 : }
818 : }
819 4 : }
820 :
821 : template <class derivedT, typename realT>
822 2 : const std::string &dm<derivedT, realT>::calibPath() const
823 : {
824 2 : return m_calibPath;
825 : }
826 :
827 : template <class derivedT, typename realT>
828 : const std::string &dm<derivedT, realT>::flatPath() const
829 : {
830 : return m_flatPath;
831 : }
832 :
833 : template <class derivedT, typename realT>
834 : const std::string &dm<derivedT, realT>::testPath() const
835 : {
836 : return m_testPath;
837 : }
838 :
839 : template <class derivedT, typename realT>
840 : const std::string &dm<derivedT, realT>::flatDefault() const
841 : {
842 : return m_flatDefault;
843 : }
844 :
845 : template <class derivedT, typename realT>
846 : const std::string &dm<derivedT, realT>::testDefault() const
847 : {
848 : return m_testDefault;
849 : }
850 :
851 : template <class derivedT, typename realT>
852 : const std::string &dm<derivedT, realT>::shmimFlat() const
853 : {
854 : return m_shmimFlat;
855 : }
856 :
857 : template <class derivedT, typename realT>
858 : const std::string &dm<derivedT, realT>::shmimTest() const
859 : {
860 : return m_shmimTest;
861 : }
862 :
863 : template <class derivedT, typename realT>
864 : const std::string &dm<derivedT, realT>::shmimSat() const
865 : {
866 : return m_shmimSat;
867 : }
868 :
869 : template <class derivedT, typename realT>
870 : const std::string &dm<derivedT, realT>::shmimSatPerc() const
871 : {
872 : return m_shmimSatPerc;
873 : }
874 :
875 : template <class derivedT, typename realT>
876 : const std::string &dm<derivedT, realT>::shmimShape() const
877 : {
878 : return m_shmimShape;
879 : }
880 :
881 : template <class derivedT, typename realT>
882 : const std::string &dm<derivedT, realT>::shmimDelta() const
883 : {
884 : return m_shmimDelta;
885 : }
886 :
887 : template <class derivedT, typename realT>
888 : uint32_t dm<derivedT, realT>::dmWidth() const
889 : {
890 : return m_dmWidth;
891 : }
892 :
893 : template <class derivedT, typename realT>
894 : uint32_t dm<derivedT, realT>::dmHeight() const
895 : {
896 : return m_dmHeight;
897 : }
898 :
899 : template <class derivedT, typename realT>
900 : uint8_t dm<derivedT, realT>::dmDataType() const
901 : {
902 : return m_dmDataType;
903 : }
904 :
905 : template <class derivedT, typename realT>
906 : float dm<derivedT, realT>::percThreshold() const
907 : {
908 : return m_percThreshold;
909 : }
910 :
911 : template <class derivedT, typename realT>
912 : float dm<derivedT, realT>::intervalSatThreshold() const
913 : {
914 : return m_intervalSatThreshold;
915 : }
916 :
917 : template <class derivedT, typename realT>
918 : int dm<derivedT, realT>::intervalSatCountThreshold() const
919 : {
920 : return m_intervalSatCountThreshold;
921 : }
922 :
923 : template <class derivedT, typename realT>
924 : const std::vector<std::string> &dm<derivedT, realT>::satTriggerDevice() const
925 : {
926 : return m_satTriggerDevice;
927 : }
928 :
929 : template <class derivedT, typename realT>
930 : const std::vector<std::string> &dm<derivedT, realT>::satTriggerProperty() const
931 : {
932 : return m_satTriggerProperty;
933 : }
934 :
935 : template <class derivedT, typename realT>
936 : const std::string &dm<derivedT, realT>::calibRelDir() const
937 : {
938 : return m_calibRelDir;
939 : }
940 :
941 : template <class derivedT, typename realT>
942 1 : int dm<derivedT, realT>::numChannels() const
943 : {
944 1 : return m_numChannels;
945 : }
946 :
947 : template <class derivedT, typename realT>
948 2 : const mx::improc::eigenImage<uint8_t> &dm<derivedT, realT>::instSatMap() const
949 : {
950 2 : return m_instSatMap;
951 : }
952 :
953 : template <class derivedT, typename realT>
954 2 : const mx::improc::eigenImage<uint16_t> &dm<derivedT, realT>::accumSatMap() const
955 : {
956 2 : return m_accumSatMap;
957 : }
958 :
959 : template <class derivedT, typename realT>
960 2 : const mx::improc::eigenImage<float> &dm<derivedT, realT>::satPercMap() const
961 : {
962 2 : return m_satPercMap;
963 : }
964 :
965 : template <class derivedT, typename realT>
966 1 : const std::vector<std::string> &dm<derivedT, realT>::deltaChannels() const
967 : {
968 1 : return m_deltaChannels;
969 : }
970 :
971 : template <class derivedT, typename realT>
972 1 : const std::vector<size_t> &dm<derivedT, realT>::notDeltas() const
973 : {
974 1 : return m_notDeltas;
975 : }
976 :
977 : template <class derivedT, typename realT>
978 4 : const mx::improc::eigenImage<float> &dm<derivedT, realT>::totalFlat() const
979 : {
980 4 : return m_totalFlat;
981 : }
982 :
983 : template <class derivedT, typename realT>
984 3 : int dm<derivedT, realT>::setupConfig( mx::app::appConfigurator &config )
985 : {
986 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 42 : 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 39 : 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 3 : return 0;
1252 : }
1253 :
1254 : template <class derivedT, typename realT>
1255 3 : int dm<derivedT, realT>::loadConfig( mx::app::appConfigurator &config )
1256 : {
1257 :
1258 3 : m_calibPath = derived().m_calibDir + "/" + m_calibRelDir;
1259 3 : config( m_calibPath, "dm.calibPath" );
1260 :
1261 : // setup flats
1262 3 : m_flatPath = m_calibPath + "/flats";
1263 6 : config( m_flatPath, "dm.flatPath" );
1264 :
1265 3 : config( m_flatDefault, "dm.flatDefault" );
1266 3 : if( m_flatDefault != "" )
1267 : {
1268 0 : m_flatDefault = mx::ioutils::pathStem( m_flatDefault ); // strip off path and extension if provided.
1269 0 : m_flatCurrent = "default";
1270 : }
1271 :
1272 : // setup tests
1273 3 : m_testPath = m_calibPath + "/tests";
1274 6 : config( m_testPath, "dm.testPath" );
1275 :
1276 3 : config( m_testDefault, "dm.testDefault" );
1277 3 : if( m_testDefault != "" )
1278 : {
1279 0 : m_testDefault = mx::ioutils::pathStem( m_testDefault ); // strip off path and extension if provided.
1280 0 : m_testCurrent = "default";
1281 : }
1282 :
1283 6 : 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 6 : config( derived().m_smThreadPrio, "dm.threadPrio" );
1288 6 : config( derived().m_smCpuset, "dm.cpuset" );
1289 :
1290 3 : config( derived().m_shmimName, "dm.shmimName" );
1291 :
1292 3 : derived().m_getExistingFirst = true;
1293 : // end of shmimmonitor overrides
1294 :
1295 3 : if( derived().m_shmimName != "" )
1296 : {
1297 1 : m_shmimFlat = derived().m_shmimName + "00";
1298 1 : config( m_shmimFlat, "dm.shmimFlat" );
1299 :
1300 1 : m_shmimTest = derived().m_shmimName + "02";
1301 1 : config( m_shmimTest, "dm.shmimTest" );
1302 :
1303 1 : m_shmimSat = derived().m_shmimName + "ST";
1304 1 : config( m_shmimSat, "dm.shmimSat" );
1305 :
1306 1 : m_shmimSatPerc = derived().m_shmimName + "SP";
1307 2 : config( m_shmimSatPerc, "dm.shmimSatPerc" );
1308 :
1309 2 : config( m_satAvgInt, "dm.satAvgInt" );
1310 :
1311 1 : config( m_satThreadPrio, "dm.satSatThreadPrio" );
1312 :
1313 1 : m_shmimShape = derived().m_shmimName + "_shape";
1314 1 : config( m_shmimShape, "dm.shmimShape" );
1315 :
1316 1 : m_shmimDelta = derived().m_shmimName + "_delta";
1317 1 : config( m_shmimDelta, "dm.shmimDelta" );
1318 :
1319 1 : m_shmimDiff = derived().m_shmimName + "_diff";
1320 2 : config( m_shmimDiff, "dm.shmimDiff" );
1321 :
1322 2 : config( m_deltaChannels, "dm.deltaChannels" );
1323 : }
1324 : else
1325 : {
1326 : // Avoid unused error
1327 4 : config.isSet( "dm.shmimFlat" );
1328 4 : config.isSet( "dm.shmimTest" );
1329 4 : config.isSet( "dm.shmimSat" );
1330 4 : config.isSet( "dm.shmimSatPerc" );
1331 4 : config.isSet( "dm.satAvgInt" );
1332 4 : config.isSet( "dm.shmimShape" );
1333 4 : config.isSet( "dm.shmimDelta" );
1334 4 : config.isSet( "dm.deltaChannels" );
1335 : }
1336 :
1337 6 : config( m_dmWidth, "dm.width" );
1338 6 : config( m_dmHeight, "dm.height" );
1339 :
1340 6 : config( m_percThreshold, "dm.percThreshold" );
1341 6 : config( m_intervalSatThreshold, "dm.intervalSatThreshold" );
1342 6 : config( m_intervalSatCountThreshold, "dm.intervalSatCountThreshold" );
1343 6 : config( m_satTriggerDevice, "dm.satTriggerDevice" );
1344 3 : config( m_satTriggerProperty, "dm.satTriggerProperty" );
1345 :
1346 3 : if( m_dmWidth > 0 && m_dmHeight > 0 )
1347 : {
1348 : try
1349 : {
1350 1 : m_actMask.create( derived().m_shmimName + "_actmask", m_dmWidth, m_dmHeight );
1351 : }
1352 0 : catch( const std::exception &e )
1353 : {
1354 0 : derivedT::template log<text_log>( std::format( "exception caught creating actuator mask: "
1355 : "{}: {}",
1356 0 : derived().m_shmimName + "_actmask",
1357 0 : e.what(),
1358 : logPrio::LOG_ERROR ) );
1359 0 : return -1;
1360 : }
1361 :
1362 1 : if( m_actMaskPath != "" )
1363 : {
1364 0 : mx::improc::eigenImage<realT> actMask;
1365 :
1366 0 : mx::fits::fitsFile<realT> ff;
1367 :
1368 0 : mx::error_t errc = ff.read( actMask, m_calibPath + '/' + m_actMaskPath );
1369 :
1370 0 : if( errc != mx::error_t::noerror )
1371 : {
1372 0 : derivedT::template log<text_log>( std::format( "error reading actuator mask file {}: "
1373 : "{} ({})",
1374 0 : m_calibPath + '/' + m_actMaskPath,
1375 0 : mx::errorMessage( errc ),
1376 0 : mx::errorName( errc ) ),
1377 : logPrio::LOG_ERROR );
1378 0 : return -1;
1379 : }
1380 :
1381 0 : if( actMask.rows() != m_dmWidth || actMask.cols() != m_dmHeight )
1382 : {
1383 0 : derivedT::template log<text_log>( std::format( "actuaor mask {}x{} is not same size as flag {}x{}",
1384 0 : actMask.rows(),
1385 0 : actMask.cols(),
1386 0 : m_dmWidth,
1387 0 : m_dmHeight ),
1388 : logPrio::LOG_ERROR );
1389 :
1390 0 : return -1;
1391 : }
1392 :
1393 0 : m_actMask = actMask;
1394 0 : }
1395 : else
1396 : {
1397 1 : m_actMask().setConstant( 1.0 );
1398 : }
1399 : }
1400 :
1401 3 : return 0;
1402 : }
1403 :
1404 : template <class derivedT, typename realT>
1405 0 : int dm<derivedT, realT>::appStartup()
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 0 : checkFlats();
1416 :
1417 : // Register the test shmim INDI property
1418 0 : m_indiP_flatShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
1419 0 : m_indiP_flatShmim.setDevice( derived().configName() );
1420 0 : m_indiP_flatShmim.setName( "flat_shmim" );
1421 0 : m_indiP_flatShmim.setPerm( pcf::IndiProperty::ReadOnly );
1422 0 : m_indiP_flatShmim.setState( pcf::IndiProperty::Idle );
1423 0 : m_indiP_flatShmim.add( pcf::IndiElement( "channel" ) );
1424 0 : m_indiP_flatShmim["channel"] = m_shmimFlat;
1425 :
1426 0 : if( derived().registerIndiPropertyReadOnly( m_indiP_flatShmim ) < 0 )
1427 : {
1428 : #ifndef DM_TEST_NOLOG
1429 0 : derivedT::template log<software_error>( { "" } );
1430 : #endif
1431 0 : return -1;
1432 : }
1433 :
1434 : // Register the setFlat INDI property
1435 0 : derived().createStandardIndiToggleSw( m_indiP_setFlat, "flat_set" );
1436 0 : if( derived().registerIndiPropertyNew( m_indiP_setFlat, st_newCallBack_setFlat ) < 0 )
1437 : {
1438 : #ifndef DM_TEST_NOLOG
1439 0 : derivedT::template log<software_error>( { "" } );
1440 : #endif
1441 0 : return -1;
1442 : }
1443 :
1444 : //-----------------
1445 : // Get the tests
1446 0 : checkTests();
1447 :
1448 : // Register the test shmim INDI property
1449 0 : m_indiP_testShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
1450 0 : m_indiP_testShmim.setDevice( derived().configName() );
1451 0 : m_indiP_testShmim.setName( "test_shmim" );
1452 0 : m_indiP_testShmim.setPerm( pcf::IndiProperty::ReadOnly );
1453 0 : m_indiP_testShmim.setState( pcf::IndiProperty::Idle );
1454 0 : m_indiP_testShmim.add( pcf::IndiElement( "channel" ) );
1455 0 : m_indiP_testShmim["channel"] = m_shmimTest;
1456 0 : derived().createStandardIndiToggleSw( m_indiP_setTest, "test_shmim" );
1457 0 : if( derived().registerIndiPropertyReadOnly( m_indiP_testShmim ) < 0 )
1458 : {
1459 : #ifndef DM_TEST_NOLOG
1460 0 : derivedT::template log<software_error>( { "" } );
1461 : #endif
1462 0 : return -1;
1463 : }
1464 :
1465 : // Register the setTest INDI property
1466 0 : derived().createStandardIndiToggleSw( m_indiP_setTest, "test_set" );
1467 0 : if( derived().registerIndiPropertyNew( m_indiP_setTest, st_newCallBack_setTest ) < 0 )
1468 : {
1469 : #ifndef DM_TEST_NOLOG
1470 0 : derivedT::template log<software_error>( { "" } );
1471 : #endif
1472 0 : return -1;
1473 : }
1474 :
1475 : // Register the init INDI property
1476 0 : derived().createStandardIndiRequestSw( m_indiP_init, "initDM" );
1477 0 : if( derived().registerIndiPropertyNew( m_indiP_init, st_newCallBack_init ) < 0 )
1478 : {
1479 : // clang-format off
1480 : #ifndef DM_TEST_NOLOG
1481 0 : derivedT::template log<software_error>( {""} );
1482 : #endif
1483 : // clang-format on
1484 :
1485 0 : return -1;
1486 : }
1487 :
1488 : // Register the zero INDI property
1489 0 : derived().createStandardIndiRequestSw( m_indiP_zero, "zeroDM" );
1490 :
1491 0 : if( derived().registerIndiPropertyNew( m_indiP_zero, st_newCallBack_zero ) < 0 )
1492 : {
1493 : // clang-format off
1494 : #ifndef DM_TEST_NOLOG
1495 0 : derivedT::template log<software_error>( {""} );
1496 : #endif
1497 : // clang-format on
1498 :
1499 0 : return -1;
1500 : }
1501 :
1502 : // Register the release INDI property
1503 0 : derived().createStandardIndiRequestSw( m_indiP_release, "releaseDM" );
1504 0 : if( derived().registerIndiPropertyNew( m_indiP_release, st_newCallBack_release ) < 0 )
1505 : {
1506 0 : return derivedT::template log<software_error, -1>( { "" } );
1507 : }
1508 :
1509 0 : derived().createStandardIndiRequestSw( m_indiP_zeroAll, "zeroAll" );
1510 0 : if( derived().registerIndiPropertyNew( m_indiP_zeroAll, st_newCallBack_zeroAll ) < 0 )
1511 : {
1512 : #ifndef DM_TEST_NOLOG
1513 0 : derivedT::template log<software_error>( { "" } );
1514 : #endif
1515 0 : return -1;
1516 : }
1517 :
1518 0 : if( m_flatDefault != "" )
1519 : {
1520 0 : loadFlat( "default" );
1521 : }
1522 :
1523 0 : if( m_testDefault != "" )
1524 : {
1525 0 : loadTest( "default" );
1526 : }
1527 :
1528 0 : if( sem_init( &m_satSemaphore, 0, 0 ) < 0 )
1529 : {
1530 0 : return derivedT::template log<software_critical, -1>( { errno, 0, "Initializing sat semaphore" } );
1531 : }
1532 :
1533 0 : if( derived().threadStart( m_satThread,
1534 0 : m_satThreadInit,
1535 0 : m_satThreadID,
1536 0 : m_satThreadProp,
1537 : m_satThreadPrio,
1538 : "",
1539 : "saturation",
1540 : this,
1541 0 : satThreadStart ) < 0 )
1542 : {
1543 0 : derivedT::template log<software_error, -1>( { "" } );
1544 0 : return -1;
1545 : }
1546 :
1547 0 : return 0;
1548 : }
1549 :
1550 : template <class derivedT, typename realT>
1551 0 : int dm<derivedT, realT>::appLogic()
1552 : {
1553 : // do a join check to see if other threads have exited.
1554 0 : if( pthread_tryjoin_np( m_satThread.native_handle(), 0 ) == 0 )
1555 : {
1556 0 : derivedT::template log<software_error>( { "saturation thread has exited" } );
1557 :
1558 0 : return -1;
1559 : }
1560 :
1561 0 : checkFlats();
1562 :
1563 0 : checkTests();
1564 :
1565 0 : if( m_intervalSatTrip )
1566 : {
1567 0 : intervalSatTrip();
1568 0 : 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 0 : return 0;
1614 : }
1615 :
1616 : template <class derivedT, typename realT>
1617 0 : int dm<derivedT, realT>::appShutdown()
1618 : {
1619 0 : if( m_satThread.joinable() )
1620 : {
1621 0 : pthread_kill( m_satThread.native_handle(), SIGUSR1 );
1622 : try
1623 : {
1624 0 : m_satThread.join(); // this will throw if it was already joined
1625 : }
1626 0 : catch( ... )
1627 : {
1628 : }
1629 : }
1630 :
1631 0 : return 0;
1632 : }
1633 :
1634 : template <class derivedT, typename realT>
1635 : int dm<derivedT, realT>::onPowerOff()
1636 : {
1637 : baseReleaseDM();
1638 :
1639 : return 0;
1640 : }
1641 :
1642 : template <class derivedT, typename realT>
1643 : int dm<derivedT, realT>::whilePowerOff()
1644 : {
1645 : checkFlats();
1646 : checkTests();
1647 :
1648 : return 0;
1649 : }
1650 :
1651 : template <class derivedT, typename realT>
1652 1 : int dm<derivedT, realT>::findDMChannels()
1653 : {
1654 1 : std::string milkShmimDir = mx::sys::getEnv( "MILK_SHM_DIR" );
1655 1 : if( milkShmimDir == "" )
1656 : {
1657 0 : milkShmimDir = "/milk/shm";
1658 : }
1659 :
1660 1 : std::vector<std::string> dmlist;
1661 3 : mx::error_t errc = mx::ioutils::getFileNames( dmlist, milkShmimDir, derived().m_shmimName, ".im", ".shm" );
1662 :
1663 1 : mx_error_check_rv( errc, -1 );
1664 :
1665 1 : if( dmlist.size() == 0 )
1666 : {
1667 0 : derivedT::template log<software_error>( { "no dm channels found for " + derived().m_shmimName } );
1668 :
1669 0 : return -1;
1670 : }
1671 :
1672 1 : m_numChannels = -1;
1673 11 : for( size_t n = 0; n < dmlist.size(); ++n )
1674 : {
1675 : char nstr[16];
1676 10 : snprintf( nstr, sizeof( nstr ), "%02d.im.shm", (int)n );
1677 10 : std::string tgt = derived().m_shmimName;
1678 10 : tgt += nstr;
1679 :
1680 110 : for( size_t m = 0; m < dmlist.size(); ++m )
1681 : {
1682 100 : if( dmlist[m].find( tgt ) != std::string::npos )
1683 : {
1684 5 : if( (int)n > m_numChannels )
1685 : {
1686 5 : m_numChannels = n;
1687 : }
1688 : }
1689 : }
1690 : }
1691 :
1692 1 : ++m_numChannels;
1693 :
1694 1 : derivedT::template log<text_log>(
1695 1 : { std::format( "Found {} chanels for {} ", m_numChannels, derived().m_shmimName ) } );
1696 :
1697 1 : m_channels.resize( m_numChannels, nullptr );
1698 :
1699 1 : m_notDeltas.clear();
1700 1 : m_deltas.clear();
1701 :
1702 6 : for( size_t n = 0; n < m_channels.size(); ++n )
1703 : {
1704 5 : std::string sname = std::format( "{}{:02}", derived().m_shmimName, n );
1705 :
1706 : try
1707 : {
1708 5 : m_channels[n] = new mx::improc::milkImage<realT>( sname ); // this opens the channel stream
1709 : }
1710 0 : catch( const std::exception &e )
1711 : {
1712 0 : derivedT::template log<software_error>( { "exception opening " + sname + ": " + e.what() } );
1713 : }
1714 :
1715 5 : std::cerr << "looking for " << sname << '\n';
1716 5 : auto res = std::find( m_deltaChannels.begin(), m_deltaChannels.end(), sname );
1717 5 : if( res == m_deltaChannels.end() )
1718 : {
1719 3 : std::cerr << " not a delta\n";
1720 3 : m_notDeltas.push_back( n );
1721 : }
1722 : else
1723 : {
1724 2 : std::cerr << " is a delta\n";
1725 2 : m_deltas.push_back( n );
1726 : }
1727 : }
1728 :
1729 1 : std::cerr << "not deltas: ";
1730 4 : for( size_t n = 0; n < m_notDeltas.size(); ++n )
1731 : {
1732 3 : std::cerr << m_notDeltas[n] << ' ';
1733 : }
1734 1 : std::cerr << '\n';
1735 :
1736 1 : std::cerr << "deltas: ";
1737 3 : for( size_t n = 0; n < m_deltas.size(); ++n )
1738 : {
1739 2 : std::cerr << m_deltas[n] << ' ';
1740 : }
1741 1 : std::cerr << '\n';
1742 :
1743 1 : return 0;
1744 1 : }
1745 :
1746 : template <class derivedT, typename realT>
1747 1 : int dm<derivedT, realT>::allocate( const dev::shmimT &sp )
1748 : {
1749 : static_cast<void>( sp ); // be unused
1750 :
1751 1 : int err = 0;
1752 :
1753 1 : if( derived().m_width != m_dmWidth )
1754 : {
1755 0 : derivedT::template log<software_critical>( { "shmim width does not match configured DM width" } );
1756 0 : ++err;
1757 : }
1758 :
1759 1 : if( derived().m_height != m_dmHeight )
1760 : {
1761 0 : derivedT::template log<software_critical>( { "shmim height does not match configured DM height" } );
1762 0 : ++err;
1763 : }
1764 :
1765 1 : if( derived().m_dataType != m_dmDataType )
1766 : {
1767 0 : derivedT::template log<software_critical>( { "shmim data type does not match configured DM data type" } );
1768 0 : ++err;
1769 : }
1770 :
1771 1 : if( err )
1772 : {
1773 0 : return -1;
1774 : }
1775 :
1776 1 : m_instSatMap.resize( m_dmWidth, m_dmHeight );
1777 1 : m_instSatMap.setZero();
1778 :
1779 1 : m_accumSatMap.resize( m_dmWidth, m_dmHeight );
1780 1 : m_accumSatMap.setZero();
1781 :
1782 1 : m_satPercMap.resize( m_dmWidth, m_dmHeight );
1783 1 : m_satPercMap.setZero();
1784 :
1785 1 : if( findDMChannels() < 0 )
1786 : {
1787 0 : derivedT::template log<software_critical>( { "error finding DM channels" } );
1788 :
1789 0 : return -1;
1790 : }
1791 :
1792 : try
1793 : {
1794 1 : m_outputShape.create( m_shmimShape, m_dmWidth, m_dmHeight );
1795 1 : m_outputShape().setZero();
1796 : }
1797 0 : catch( const std::exception &e )
1798 : {
1799 0 : return derivedT::template log<software_error, -1>(
1800 0 : { std::string( "creating output shape shmim: " ) + e.what() } );
1801 : }
1802 :
1803 : try
1804 : {
1805 1 : m_outputDelta.create( m_shmimDelta, m_dmWidth, m_dmHeight );
1806 1 : m_outputDelta().setZero();
1807 : }
1808 0 : catch( const std::exception &e )
1809 : {
1810 0 : return derivedT::template log<software_error, -1>(
1811 0 : { std::string( "creating output delta shmim: " ) + e.what() } );
1812 : }
1813 :
1814 : try
1815 : {
1816 1 : m_outputDiff.create( m_shmimDiff, m_dmWidth, m_dmHeight );
1817 1 : m_outputDiff().setZero();
1818 : }
1819 0 : catch( const std::exception &e )
1820 : {
1821 0 : return derivedT::template log<software_error, -1>(
1822 0 : { std::string( "creating output diff shmim: " ) + e.what() } );
1823 : }
1824 :
1825 1 : m_totalFlat.resize( m_dmWidth, m_dmHeight );
1826 1 : m_totalFlat.setZero();
1827 :
1828 1 : m_totalDelta.resize( m_dmWidth, m_dmHeight );
1829 1 : 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 1 : return 0;
1842 : }
1843 :
1844 : template <class derivedT, typename realT>
1845 0 : int 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 0 : int rv = derived().commandDM( curr_src );
1855 :
1856 0 : if( rv < 0 )
1857 : {
1858 0 : derivedT::template log<software_critical>( { errno, rv, "Error from commandDM" } );
1859 0 : 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 0 : if( m_deltaChannels.size() > 0 )
1868 : {
1869 0 : rv = makeDelta();
1870 :
1871 0 : if( rv < 0 )
1872 : {
1873 0 : derivedT::template log<software_critical>( { errno, rv, "Error from makeDelta" } );
1874 0 : 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 0 : if( sem_post( &m_satSemaphore ) < 0 )
1892 : {
1893 0 : derivedT::template log<software_critical>( { errno, 0, "Error posting to semaphore" } );
1894 0 : 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 0 : return rv;
1917 : }
1918 :
1919 : template <class derivedT, typename realT>
1920 0 : int dm<derivedT, realT>::baseInitDM()
1921 : {
1922 0 : if( derived().state() != stateCodes::NOTHOMED )
1923 : {
1924 0 : derivedT::template log<software_error>( { errno, "DM is not ready to be initialized" } );
1925 0 : derived().state( stateCodes::ERROR );
1926 0 : return -1;
1927 : }
1928 :
1929 0 : derived().state( stateCodes::HOMING );
1930 :
1931 : int rv;
1932 0 : if( ( rv = derived().initDM() ) < 0 )
1933 : {
1934 0 : derivedT::template log<software_critical>( { errno, rv, "Error from initDM" } );
1935 0 : derived().state( stateCodes::ERROR );
1936 0 : return rv;
1937 : }
1938 :
1939 0 : return 0;
1940 : }
1941 :
1942 : template <class derivedT, typename realT>
1943 0 : int dm<derivedT, realT>::baseReleaseDM()
1944 : {
1945 0 : if( derived().state() != stateCodes::POWEROFF )
1946 : {
1947 0 : derived().state( stateCodes::NOTHOMED );
1948 : }
1949 :
1950 : int rv;
1951 0 : if( ( rv = derived().releaseDM() ) < 0 )
1952 : {
1953 0 : derivedT::template log<software_critical>( { errno, rv, "Error from releaseDM" } );
1954 0 : derived().state( stateCodes::ERROR );
1955 0 : return rv;
1956 : }
1957 :
1958 0 : if( ( rv = zeroAll( true ) ) < 0 )
1959 : {
1960 0 : derivedT::template log<software_error>( { errno, rv, "Error from zeroAll" } );
1961 0 : derived().state( stateCodes::ERROR );
1962 0 : return rv;
1963 : }
1964 :
1965 0 : return 0;
1966 : }
1967 :
1968 : template <class derivedT, typename realT>
1969 0 : int dm<derivedT, realT>::checkFlats()
1970 : {
1971 0 : std::vector<std::string> tfs;
1972 :
1973 0 : bool gfn_logged = false;
1974 :
1975 0 : mx::error_t errc = mx::ioutils::getFileNames( tfs, m_flatPath, "", "", ".fits" );
1976 :
1977 0 : if( errc != mx::error_t::noerror )
1978 : {
1979 0 : if( !gfn_logged )
1980 : {
1981 0 : derivedT::template log<software_error>(
1982 0 : { std::format( "error getting flat files: {}", mx::errorMessage( errc ) ) } );
1983 : }
1984 0 : gfn_logged = true;
1985 0 : return -1;
1986 : }
1987 :
1988 0 : 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 0 : for( size_t n = 0; n < tfs.size(); ++n )
1992 : {
1993 0 : if( mx::ioutils::pathStem( tfs[n] ) == "default" )
1994 : {
1995 0 : tfs.erase( tfs.begin() + n );
1996 0 : --n;
1997 : }
1998 : }
1999 :
2000 0 : unsigned m_nFlatFiles = 5;
2001 :
2002 : // Here we keep only the m_nFlatFiles most recent files
2003 0 : if( tfs.size() >= m_nFlatFiles )
2004 : {
2005 0 : std::vector<std::filesystem::file_time_type> wtimes( tfs.size() );
2006 :
2007 0 : for( size_t n = 0; n < wtimes.size(); ++n )
2008 : {
2009 0 : wtimes[n] = std::filesystem::last_write_time( tfs[n] );
2010 : }
2011 :
2012 0 : std::sort( wtimes.begin(), wtimes.end() );
2013 :
2014 0 : std::filesystem::file_time_type tn = wtimes[wtimes.size() - m_nFlatFiles];
2015 :
2016 0 : for( size_t n = 0; n < tfs.size(); ++n )
2017 : {
2018 0 : std::filesystem::file_time_type lmt = std::filesystem::last_write_time( tfs[n] );
2019 0 : if( lmt < tn )
2020 : {
2021 0 : tfs.erase( tfs.begin() + n );
2022 0 : --n;
2023 : }
2024 : }
2025 0 : }
2026 :
2027 0 : for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2028 : {
2029 0 : it->second = "";
2030 : }
2031 :
2032 0 : bool changed = false;
2033 0 : for( size_t n = 0; n < tfs.size(); ++n )
2034 : {
2035 : auto ir =
2036 0 : m_flatCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2037 0 : if( ir.second == true )
2038 0 : changed = true;
2039 : else
2040 0 : ir.first->second = tfs[n];
2041 : }
2042 :
2043 0 : for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2044 : {
2045 0 : if( it->second == "" )
2046 : {
2047 0 : changed = true;
2048 : // Erase the current iterator safely, even if the first one.
2049 0 : auto itdel = it;
2050 0 : ++it;
2051 0 : m_flatCommands.erase( itdel );
2052 0 : --it;
2053 : };
2054 : }
2055 :
2056 0 : if( changed )
2057 : {
2058 0 : if( derived().m_indiDriver )
2059 : {
2060 0 : derived().m_indiDriver->sendDelProperty( m_indiP_flats );
2061 0 : derived().m_indiNewCallBacks.erase( m_indiP_flats.createUniqueKey() );
2062 : }
2063 :
2064 0 : m_indiP_flats = pcf::IndiProperty( pcf::IndiProperty::Switch );
2065 0 : m_indiP_flats.setDevice( derived().configName() );
2066 0 : m_indiP_flats.setName( "flat" );
2067 0 : m_indiP_flats.setPerm( pcf::IndiProperty::ReadWrite );
2068 0 : m_indiP_flats.setState( pcf::IndiProperty::Idle );
2069 0 : m_indiP_flats.setRule( pcf::IndiProperty::OneOfMany );
2070 :
2071 : // Add the toggle element initialized to Off
2072 0 : for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2073 : {
2074 0 : if( it->first == m_flatCurrent || m_flatCurrent == "" )
2075 : {
2076 0 : m_indiP_flats.add( pcf::IndiElement( it->first, pcf::IndiElement::On ) );
2077 0 : m_flatCurrent = it->first; // handles the case m_flatCurrent == "" b/c it was not set in config
2078 : }
2079 : else
2080 : {
2081 0 : m_indiP_flats.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
2082 : }
2083 : }
2084 :
2085 0 : if( m_flatDefault != "" )
2086 : {
2087 0 : if( m_flatCurrent == "default" )
2088 : {
2089 0 : m_indiP_flats.add( pcf::IndiElement( "default", pcf::IndiElement::On ) );
2090 : }
2091 : else
2092 : {
2093 0 : m_indiP_flats.add( pcf::IndiElement( "default", pcf::IndiElement::Off ) );
2094 : }
2095 : }
2096 :
2097 0 : if( derived().registerIndiPropertyNew( m_indiP_flats, st_newCallBack_flats ) < 0 )
2098 : {
2099 : // clang-format off
2100 : #ifndef DM_TEST_NOLOG
2101 0 : derivedT::template log<software_error>( {""} );
2102 : #endif
2103 : // clang-format on
2104 :
2105 0 : return -1;
2106 : }
2107 :
2108 0 : if( derived().m_indiDriver )
2109 : {
2110 0 : derived().m_indiDriver->sendDefProperty( m_indiP_flats );
2111 : }
2112 : }
2113 :
2114 0 : return 0;
2115 0 : }
2116 :
2117 : template <class derivedT, typename realT>
2118 0 : int dm<derivedT, realT>::loadFlat( const std::string &intarget )
2119 : {
2120 0 : std::string target = intarget;
2121 :
2122 0 : std::string targetPath;
2123 :
2124 0 : if( target == "default" )
2125 : {
2126 0 : target = m_flatDefault;
2127 0 : targetPath = m_flatPath + "/" + m_flatDefault + ".fits";
2128 : }
2129 : else
2130 : {
2131 : try
2132 : {
2133 0 : targetPath = m_flatCommands.at( target );
2134 : }
2135 0 : catch( ... )
2136 : {
2137 0 : derivedT::template log<text_log>( "flat file " + target + " not found", logPrio::LOG_ERROR );
2138 0 : return -1;
2139 : }
2140 : }
2141 :
2142 0 : m_flatLoaded = false;
2143 :
2144 : // load into memory.
2145 0 : mx::fits::fitsFile<realT> ff;
2146 :
2147 0 : mx::error_t errc = ff.read( m_flatCommand, targetPath );
2148 :
2149 0 : if( errc != mx::error_t::noerror )
2150 : {
2151 0 : derivedT::template log<text_log>( std::format( "error reading flat file {}: "
2152 : "{} ({})",
2153 : targetPath,
2154 0 : mx::errorMessage( errc ),
2155 0 : mx::errorName( errc ) ),
2156 : logPrio::LOG_ERROR );
2157 0 : return -1;
2158 : }
2159 :
2160 0 : if( m_actMask.rows() != m_flatCommand.rows() || m_actMask.cols() != m_flatCommand.cols() )
2161 : {
2162 0 : derivedT::template log<text_log>( std::format( "actuaor mask {}x{} is not same size as flag {}x{}",
2163 0 : m_actMask.rows(),
2164 0 : m_actMask.cols(),
2165 0 : m_flatCommand.rows(),
2166 0 : m_flatCommand.cols() ),
2167 : logPrio::LOG_ERROR );
2168 :
2169 0 : return -1;
2170 : }
2171 :
2172 0 : m_flatCommand *= m_actMask();
2173 :
2174 0 : derivedT::template log<text_log>( "loaded flat file " + targetPath );
2175 0 : m_flatLoaded = true;
2176 :
2177 0 : m_flatCurrent = intarget;
2178 :
2179 0 : if( m_indiP_flats.find( "default" ) )
2180 : {
2181 0 : if( m_flatCurrent == "default" )
2182 : {
2183 0 : m_indiP_flats["default"] = pcf::IndiElement::On;
2184 : }
2185 : else
2186 : {
2187 0 : m_indiP_flats["default"] = pcf::IndiElement::Off;
2188 : }
2189 : }
2190 :
2191 0 : for( auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
2192 : {
2193 0 : if( !m_indiP_flats.find( i->first ) )
2194 : {
2195 0 : continue;
2196 : }
2197 :
2198 0 : if( i->first == m_flatCurrent )
2199 : {
2200 0 : m_indiP_flats[i->first] = pcf::IndiElement::On;
2201 : }
2202 : else
2203 : {
2204 0 : m_indiP_flats[i->first] = pcf::IndiElement::Off;
2205 : }
2206 : }
2207 :
2208 0 : if( derived().m_indiDriver )
2209 : {
2210 0 : derived().m_indiDriver->sendSetProperty( m_indiP_flats );
2211 : }
2212 :
2213 0 : if( m_flatSet )
2214 : {
2215 0 : setFlat();
2216 : }
2217 :
2218 0 : return 0;
2219 0 : }
2220 :
2221 : template <class derivedT, typename realT>
2222 0 : int dm<derivedT, realT>::setFlat( bool update )
2223 : {
2224 0 : if( m_shmimFlat == "" )
2225 : {
2226 0 : return 0;
2227 : }
2228 :
2229 0 : if( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) )
2230 : {
2231 0 : derivedT::template log<text_log>( "can not set flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2232 0 : return -1;
2233 : }
2234 :
2235 0 : if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2236 : {
2237 0 : derivedT::template log<text_log>( "could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2238 0 : return -1;
2239 : }
2240 :
2241 0 : if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2242 : {
2243 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2244 0 : derivedT::template log<text_log>( "width mismatch between " + m_shmimFlat + " and configured DM",
2245 : logPrio::LOG_ERROR );
2246 0 : return -1;
2247 : }
2248 :
2249 0 : if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2250 : {
2251 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2252 0 : derivedT::template log<text_log>( "height mismatch between " + m_shmimFlat + " and configured DM",
2253 : logPrio::LOG_ERROR );
2254 0 : return -1;
2255 : }
2256 :
2257 0 : if( !m_flatLoaded )
2258 : {
2259 0 : bool flatSet = m_flatSet;
2260 0 : m_flatSet = false; // make sure we don't loop
2261 :
2262 0 : if( loadFlat( m_flatCurrent ) < 0 )
2263 : {
2264 0 : derivedT::template log<text_log>( "error loading flat " + m_flatCurrent, logPrio::LOG_ERROR );
2265 : }
2266 0 : m_flatSet = flatSet;
2267 : }
2268 :
2269 0 : if( !m_flatLoaded )
2270 : {
2271 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2272 0 : derivedT::template log<text_log>( "no flat loaded", logPrio::LOG_ERROR );
2273 0 : return -1;
2274 : }
2275 :
2276 0 : if( m_flatCommand.rows() != m_dmWidth )
2277 : {
2278 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2279 0 : derivedT::template log<text_log>( "width mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2280 0 : return -1;
2281 : }
2282 :
2283 0 : if( m_flatCommand.cols() != m_dmHeight )
2284 : {
2285 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2286 0 : derivedT::template log<text_log>( "height mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2287 0 : return -1;
2288 : }
2289 :
2290 0 : 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 0 : memcpy( m_flatImageStream.array.raw, m_flatCommand.data(), m_dmWidth * m_dmHeight * sizeof( realT ) );
2296 :
2297 : // Set the time of last write
2298 0 : clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2299 :
2300 : // Set the image acquisition timestamp
2301 0 : m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2302 :
2303 0 : m_flatImageStream.md->cnt0++;
2304 0 : m_flatImageStream.md->write = 0;
2305 :
2306 : // Post the semaphores
2307 0 : ImageStreamIO_sempost( &m_flatImageStream, -1 );
2308 :
2309 0 : m_flatSet = true;
2310 :
2311 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2312 :
2313 0 : derived().state( stateCodes::OPERATING );
2314 :
2315 0 : if( !update )
2316 : {
2317 0 : derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2318 :
2319 0 : derivedT::template log<text_log>( "flat set" );
2320 : }
2321 :
2322 0 : return 0;
2323 : }
2324 :
2325 : template <class derivedT, typename realT>
2326 0 : int dm<derivedT, realT>::zeroFlat()
2327 : {
2328 0 : if( m_shmimFlat == "" )
2329 : {
2330 0 : return 0;
2331 : }
2332 :
2333 0 : if( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) )
2334 : {
2335 0 : derivedT::template log<text_log>( "can not zero flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2336 0 : return -1;
2337 : }
2338 :
2339 0 : if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2340 : {
2341 0 : derivedT::template log<text_log>( "could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2342 0 : return -1;
2343 : }
2344 :
2345 0 : if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2346 : {
2347 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2348 0 : derivedT::template log<text_log>( "width mismatch between " + m_shmimFlat + " and configured DM",
2349 : logPrio::LOG_ERROR );
2350 0 : return -1;
2351 : }
2352 :
2353 0 : if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2354 : {
2355 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2356 0 : derivedT::template log<text_log>( "height mismatch between " + m_shmimFlat + " and configured DM",
2357 : logPrio::LOG_ERROR );
2358 0 : return -1;
2359 : }
2360 :
2361 0 : 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 0 : memset( m_flatImageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2367 :
2368 : // Set the time of last write
2369 0 : clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2370 :
2371 : // Set the image acquisition timestamp
2372 0 : m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2373 :
2374 0 : m_flatImageStream.md->cnt0++;
2375 0 : m_flatImageStream.md->write = 0;
2376 0 : ImageStreamIO_sempost( &m_flatImageStream, -1 );
2377 :
2378 0 : m_flatSet = false;
2379 :
2380 : // Post the semaphore
2381 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2382 :
2383 0 : derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2384 :
2385 0 : derivedT::template log<text_log>( "flat zeroed" );
2386 :
2387 0 : if( derived().zeroDM() < 0 )
2388 : {
2389 0 : derivedT::template log<software_error>( { "error from zeroDM" } );
2390 : }
2391 :
2392 0 : if( clearSat() < 0 )
2393 : {
2394 0 : derivedT::template log<software_error>( { "error from clearSat" } );
2395 : }
2396 0 : derived().state( stateCodes::READY );
2397 :
2398 0 : return 0;
2399 : }
2400 :
2401 : template <class derivedT, typename realT>
2402 0 : int dm<derivedT, realT>::checkTests()
2403 : {
2404 0 : std::vector<std::string> tfs;
2405 :
2406 : static bool gfn_logged = false; // tracks if not finding the test path is logged
2407 :
2408 0 : mx::error_t errc = mx::ioutils::getFileNames( tfs, m_testPath, "", "", ".fits" );
2409 :
2410 0 : if( errc != mx::error_t::noerror )
2411 : {
2412 0 : if( !gfn_logged )
2413 : {
2414 0 : derivedT::template log<software_error>(
2415 0 : { std::format( "error getting test files: {}", mx::errorMessage( errc ) ) } );
2416 : }
2417 0 : gfn_logged = true;
2418 0 : return -1;
2419 : }
2420 :
2421 0 : gfn_logged = false;
2422 :
2423 0 : mx_error_check_rv( errc, -1 );
2424 :
2425 0 : for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2426 : {
2427 0 : it->second = "";
2428 : }
2429 :
2430 0 : bool changed = false;
2431 0 : for( size_t n = 0; n < tfs.size(); ++n )
2432 : {
2433 : auto ir =
2434 0 : m_testCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2435 0 : if( ir.second == true )
2436 0 : changed = true;
2437 : else
2438 0 : ir.first->second = tfs[n];
2439 : }
2440 :
2441 0 : for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2442 : {
2443 0 : if( it->second == "" )
2444 : {
2445 0 : changed = true;
2446 : // Erase the current iterator safely, even if the first one.
2447 0 : auto itdel = it;
2448 0 : ++it;
2449 0 : m_testCommands.erase( itdel );
2450 0 : --it;
2451 : };
2452 : }
2453 :
2454 0 : if( changed )
2455 : {
2456 0 : if( derived().m_indiDriver )
2457 : {
2458 0 : derived().m_indiDriver->sendDelProperty( m_indiP_tests );
2459 0 : derived().m_indiNewCallBacks.erase( m_indiP_tests.createUniqueKey() );
2460 : }
2461 :
2462 0 : m_indiP_tests = pcf::IndiProperty( pcf::IndiProperty::Switch );
2463 0 : m_indiP_tests.setDevice( derived().configName() );
2464 0 : m_indiP_tests.setName( "test" );
2465 0 : m_indiP_tests.setPerm( pcf::IndiProperty::ReadWrite );
2466 0 : m_indiP_tests.setState( pcf::IndiProperty::Idle );
2467 0 : m_indiP_tests.setRule( pcf::IndiProperty::OneOfMany );
2468 :
2469 : // Add the toggle element initialized to Off
2470 0 : for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2471 : {
2472 0 : if( it->first == m_testCurrent || m_testCurrent == "" )
2473 : {
2474 0 : m_indiP_tests.add( pcf::IndiElement( it->first, pcf::IndiElement::On ) );
2475 0 : m_testCurrent = it->first; // Handles the case when m_testCurrent=="" b/c it was not set in config
2476 : }
2477 : else
2478 : {
2479 0 : m_indiP_tests.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
2480 : }
2481 : }
2482 :
2483 0 : if( m_testDefault != "" )
2484 : {
2485 0 : if( m_testCurrent == "default" )
2486 : {
2487 0 : m_indiP_tests.add( pcf::IndiElement( "default", pcf::IndiElement::On ) );
2488 : }
2489 : else
2490 : {
2491 0 : m_indiP_tests.add( pcf::IndiElement( "default", pcf::IndiElement::Off ) );
2492 : }
2493 : }
2494 :
2495 0 : if( derived().registerIndiPropertyNew( m_indiP_tests, st_newCallBack_tests ) < 0 )
2496 : {
2497 : #ifndef DM_TEST_NOLOG
2498 0 : derivedT::template log<software_error>( { "" } );
2499 : #endif
2500 0 : return -1;
2501 : }
2502 :
2503 0 : if( derived().m_indiDriver )
2504 : {
2505 0 : derived().m_indiDriver->sendDefProperty( m_indiP_tests );
2506 : }
2507 : }
2508 :
2509 0 : return 0;
2510 0 : }
2511 :
2512 : template <class derivedT, typename realT>
2513 0 : int dm<derivedT, realT>::loadTest( const std::string &intarget )
2514 : {
2515 0 : std::string target = intarget; // store this for later to resolve default next:
2516 :
2517 0 : if( target == "default" )
2518 : {
2519 0 : target = m_testDefault;
2520 : }
2521 :
2522 0 : std::string targetPath;
2523 :
2524 : try
2525 : {
2526 0 : targetPath = m_testCommands.at( target );
2527 : }
2528 0 : catch( ... )
2529 : {
2530 0 : derivedT::template log<text_log>( "test file " + target + " not found", logPrio::LOG_ERROR );
2531 0 : return -1;
2532 : }
2533 :
2534 0 : m_testLoaded = false;
2535 : // load into memory.
2536 0 : mx::fits::fitsFile<realT> ff;
2537 0 : mx::error_t errc = ff.read( m_testCommand, targetPath );
2538 0 : if( errc != mx::error_t::noerror )
2539 : {
2540 0 : derivedT::template log<text_log>( std::format( "error reading test file {}: "
2541 : "{} ({})",
2542 : targetPath,
2543 0 : mx::errorMessage( errc ),
2544 0 : mx::errorName( errc ) ),
2545 : logPrio::LOG_ERROR );
2546 0 : return -1;
2547 : }
2548 :
2549 0 : derivedT::template log<text_log>( "loaded test file " + targetPath );
2550 0 : m_testLoaded = true;
2551 :
2552 0 : m_testCurrent = intarget;
2553 :
2554 0 : if( m_indiP_tests.find( "default" ) )
2555 : {
2556 0 : if( m_testCurrent == "default" )
2557 : {
2558 0 : m_indiP_tests["default"] = pcf::IndiElement::On;
2559 : }
2560 : else
2561 : {
2562 0 : m_indiP_tests["default"] = pcf::IndiElement::Off;
2563 : }
2564 : }
2565 :
2566 0 : for( auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
2567 : {
2568 0 : if( !m_indiP_tests.find( i->first ) )
2569 : {
2570 0 : continue;
2571 : }
2572 :
2573 0 : if( i->first == m_testCurrent )
2574 : {
2575 0 : m_indiP_tests[i->first] = pcf::IndiElement::On;
2576 : }
2577 : else
2578 : {
2579 0 : m_indiP_tests[i->first] = pcf::IndiElement::Off;
2580 : }
2581 : }
2582 :
2583 0 : if( derived().m_indiDriver )
2584 0 : derived().m_indiDriver->sendSetProperty( m_indiP_tests );
2585 :
2586 0 : if( m_testSet )
2587 0 : setTest();
2588 :
2589 0 : return 0;
2590 0 : }
2591 :
2592 : template <class derivedT, typename realT>
2593 0 : int dm<derivedT, realT>::setTest()
2594 : {
2595 :
2596 0 : if( m_shmimTest == "" )
2597 0 : return 0;
2598 :
2599 0 : if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2600 : {
2601 0 : derivedT::template log<text_log>( "could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2602 0 : return -1;
2603 : }
2604 :
2605 0 : if( m_testImageStream.md->size[0] != m_dmWidth )
2606 : {
2607 0 : ImageStreamIO_closeIm( &m_testImageStream );
2608 0 : derivedT::template log<text_log>( "width mismatch between " + m_shmimTest + " and configured DM",
2609 : logPrio::LOG_ERROR );
2610 0 : return -1;
2611 : }
2612 :
2613 0 : if( m_testImageStream.md->size[1] != m_dmHeight )
2614 : {
2615 0 : ImageStreamIO_closeIm( &m_testImageStream );
2616 0 : derivedT::template log<text_log>( "height mismatch between " + m_shmimTest + " and configured DM",
2617 : logPrio::LOG_ERROR );
2618 0 : return -1;
2619 : }
2620 :
2621 0 : if( !m_testLoaded )
2622 : {
2623 0 : bool testSet = m_testSet;
2624 0 : m_testSet = false; // make sure we don't loop
2625 :
2626 0 : if( loadTest( m_testCurrent ) < 0 )
2627 : {
2628 0 : derivedT::template log<text_log>( "error loading test " + m_testCurrent, logPrio::LOG_ERROR );
2629 : }
2630 0 : m_testSet = testSet;
2631 : }
2632 :
2633 0 : if( !m_testLoaded )
2634 : {
2635 0 : ImageStreamIO_closeIm( &m_testImageStream );
2636 0 : derivedT::template log<text_log>( "no test loaded", logPrio::LOG_ERROR );
2637 0 : return -1;
2638 : }
2639 :
2640 0 : if( m_testCommand.rows() != m_dmWidth )
2641 : {
2642 0 : ImageStreamIO_closeIm( &m_testImageStream );
2643 0 : derivedT::template log<text_log>( "width mismatch between test file and configured DM", logPrio::LOG_ERROR );
2644 0 : return -1;
2645 : }
2646 :
2647 0 : if( m_testCommand.cols() != m_dmHeight )
2648 : {
2649 0 : ImageStreamIO_closeIm( &m_testImageStream );
2650 0 : derivedT::template log<text_log>( "height mismatch between test file and configured DM", logPrio::LOG_ERROR );
2651 0 : return -1;
2652 : }
2653 :
2654 0 : 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 0 : memcpy( m_testImageStream.array.raw, m_testCommand.data(), m_dmWidth * m_dmHeight * sizeof( realT ) );
2660 :
2661 : // Set the time of last write
2662 0 : clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2663 :
2664 : // Set the image acquisition timestamp
2665 0 : m_testImageStream.md->atime = m_testImageStream.md->writetime;
2666 :
2667 0 : m_testImageStream.md->cnt0++;
2668 0 : m_testImageStream.md->write = 0;
2669 0 : ImageStreamIO_sempost( &m_testImageStream, -1 );
2670 :
2671 0 : m_testSet = true;
2672 :
2673 : // Post the semaphore
2674 0 : ImageStreamIO_closeIm( &m_testImageStream );
2675 :
2676 0 : derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2677 :
2678 0 : derivedT::template log<text_log>( "test set" );
2679 :
2680 0 : return 0;
2681 : }
2682 :
2683 : template <class derivedT, typename realT>
2684 0 : int dm<derivedT, realT>::zeroTest()
2685 : {
2686 0 : if( m_shmimTest == "" )
2687 0 : return 0;
2688 :
2689 0 : if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2690 : {
2691 0 : derivedT::template log<text_log>( "could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2692 0 : return -1;
2693 : }
2694 :
2695 0 : if( m_testImageStream.md[0].size[0] != m_dmWidth )
2696 : {
2697 0 : ImageStreamIO_closeIm( &m_testImageStream );
2698 0 : derivedT::template log<text_log>( "width mismatch between " + m_shmimTest + " and configured DM",
2699 : logPrio::LOG_ERROR );
2700 0 : return -1;
2701 : }
2702 :
2703 0 : if( m_testImageStream.md[0].size[1] != m_dmHeight )
2704 : {
2705 0 : ImageStreamIO_closeIm( &m_testImageStream );
2706 0 : derivedT::template log<text_log>( "height mismatch between " + m_shmimTest + " and configured DM",
2707 : logPrio::LOG_ERROR );
2708 0 : return -1;
2709 : }
2710 :
2711 0 : 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 0 : memset( m_testImageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2717 :
2718 : // Set the time of last write
2719 0 : clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2720 :
2721 : // Set the image acquisition timestamp
2722 0 : m_testImageStream.md->atime = m_testImageStream.md->writetime;
2723 :
2724 0 : m_testImageStream.md->cnt0++;
2725 0 : m_testImageStream.md->write = 0;
2726 :
2727 : // Post the semaphore
2728 0 : ImageStreamIO_sempost( &m_testImageStream, -1 );
2729 :
2730 0 : m_testSet = false;
2731 :
2732 0 : ImageStreamIO_closeIm( &m_testImageStream );
2733 :
2734 0 : derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2735 :
2736 0 : derivedT::template log<text_log>( "test zeroed" );
2737 :
2738 0 : return 0;
2739 : }
2740 :
2741 : template <class derivedT, typename realT>
2742 0 : int dm<derivedT, realT>::zeroAll( bool nosem )
2743 : {
2744 0 : if( derived().m_shmimName == "" )
2745 : {
2746 0 : return 0;
2747 : }
2748 :
2749 : IMAGE imageStream;
2750 :
2751 0 : for( int n = 0; n < m_numChannels; ++n )
2752 : {
2753 : char nstr[16];
2754 0 : snprintf( nstr, sizeof( nstr ), "%02d", n );
2755 0 : std::string shmimN = derived().m_shmimName + nstr;
2756 :
2757 0 : if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2758 : {
2759 0 : derivedT::template log<text_log>( "could not connect to channel " + shmimN, logPrio::LOG_WARNING );
2760 0 : continue;
2761 : }
2762 :
2763 0 : if( imageStream.md->size[0] != m_dmWidth )
2764 : {
2765 0 : ImageStreamIO_closeIm( &imageStream );
2766 0 : derivedT::template log<text_log>( "width mismatch between " + shmimN + " and configured DM",
2767 : logPrio::LOG_ERROR );
2768 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2769 0 : return -1;
2770 : }
2771 :
2772 0 : if( imageStream.md->size[1] != m_dmHeight )
2773 : {
2774 0 : ImageStreamIO_closeIm( &imageStream );
2775 0 : derivedT::template log<text_log>( "height mismatch between " + shmimN + " and configured DM",
2776 : logPrio::LOG_ERROR );
2777 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2778 0 : return -1;
2779 : }
2780 :
2781 0 : imageStream.md->write = 1;
2782 0 : memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2783 :
2784 0 : clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2785 :
2786 : // Set the image acquisition timestamp
2787 0 : imageStream.md->atime = imageStream.md->writetime;
2788 :
2789 0 : imageStream.md->cnt0++;
2790 0 : imageStream.md->write = 0;
2791 :
2792 : // Raise the semaphore on last one.
2793 0 : if( n == m_numChannels - 1 && !nosem )
2794 : {
2795 0 : ImageStreamIO_sempost( &imageStream, -1 );
2796 : }
2797 :
2798 0 : ImageStreamIO_closeIm( &imageStream );
2799 : }
2800 :
2801 0 : derivedT::template log<text_log>( "all channels zeroed", logPrio::LOG_NOTICE );
2802 :
2803 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2804 :
2805 : // Also cleanup flat and test
2806 0 : m_flatSet = false;
2807 0 : derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2808 0 : if( derived().state() == stateCodes::OPERATING )
2809 : {
2810 0 : derived().state( stateCodes::READY );
2811 : }
2812 :
2813 : // Also cleanup flat and test
2814 0 : m_testSet = false;
2815 0 : derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2816 :
2817 : int rv;
2818 0 : if( ( rv = clearSat() ) < 0 )
2819 : {
2820 0 : derivedT::template log<software_error>( { errno, rv, "Error from clearSat" } );
2821 0 : return rv;
2822 : }
2823 :
2824 0 : return 0;
2825 : }
2826 :
2827 : template <class derivedT, typename realT>
2828 1 : int dm<derivedT, realT>::makeDelta()
2829 : {
2830 1 : if( m_notDeltas.size() == 0 )
2831 : {
2832 0 : return 0;
2833 : }
2834 :
2835 1 : m_totalFlat = ( *m_channels[m_notDeltas[0]] )();
2836 :
2837 3 : for( size_t n = 1; n < m_notDeltas.size(); ++n )
2838 : {
2839 2 : m_totalFlat += ( *m_channels[m_notDeltas[n]] )();
2840 : }
2841 :
2842 1 : m_outputDelta = m_outputShape() - m_totalFlat; // this posts and everything
2843 :
2844 1 : m_totalDelta = ( *m_channels[m_deltas[0]] )();
2845 :
2846 2 : for( size_t n = 1; n < m_deltas.size(); ++n )
2847 : {
2848 1 : m_totalDelta += ( *m_channels[m_deltas[n]] )();
2849 : }
2850 :
2851 1 : m_outputDiff = m_totalDelta - m_outputDelta();
2852 :
2853 1 : return 0;
2854 : }
2855 :
2856 : template <class derivedT, typename realT>
2857 0 : int dm<derivedT, realT>::clearSat()
2858 : {
2859 0 : if( m_shmimSat == "" || m_dmWidth == 0 || m_dmHeight == 0 )
2860 : {
2861 0 : return 0;
2862 : }
2863 :
2864 : IMAGE imageStream;
2865 :
2866 0 : std::vector<std::string> sats = { m_shmimSat, m_shmimSatPerc };
2867 :
2868 0 : for( size_t n = 0; n < sats.size(); ++n )
2869 : {
2870 0 : std::string shmimN = sats[n];
2871 :
2872 0 : if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2873 : {
2874 0 : derivedT::template log<text_log>( "could not connect to sat map " + shmimN, logPrio::LOG_WARNING );
2875 0 : return 0;
2876 : }
2877 :
2878 0 : if( imageStream.md->size[0] != m_dmWidth )
2879 : {
2880 0 : ImageStreamIO_closeIm( &imageStream );
2881 0 : derivedT::template log<text_log>( "width mismatch between " + shmimN + " and configured DM",
2882 : logPrio::LOG_ERROR );
2883 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2884 0 : return -1;
2885 : }
2886 :
2887 0 : if( imageStream.md->size[1] != m_dmHeight )
2888 : {
2889 0 : ImageStreamIO_closeIm( &imageStream );
2890 0 : derivedT::template log<text_log>( "height mismatch between " + shmimN + " and configured DM",
2891 : logPrio::LOG_ERROR );
2892 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2893 0 : return -1;
2894 : }
2895 :
2896 0 : imageStream.md->write = 1;
2897 0 : memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * ImageStreamIO_typesize( imageStream.md->datatype ) );
2898 :
2899 0 : clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2900 :
2901 : // Set the image acquisition timestamp
2902 0 : imageStream.md->atime = imageStream.md->writetime;
2903 :
2904 0 : imageStream.md->cnt0++;
2905 0 : imageStream.md->write = 0;
2906 0 : ImageStreamIO_sempost( &imageStream, -1 );
2907 :
2908 0 : ImageStreamIO_closeIm( &imageStream );
2909 : }
2910 :
2911 0 : m_accumSatMap.setZero();
2912 0 : m_instSatMap.setZero();
2913 :
2914 0 : return 0;
2915 0 : }
2916 :
2917 : template <class derivedT, typename realT>
2918 0 : void dm<derivedT, realT>::satThreadStart( dm *d )
2919 : {
2920 0 : d->satThreadExec();
2921 0 : }
2922 :
2923 : template <class derivedT, typename realT>
2924 0 : void dm<derivedT, realT>::satThreadExec()
2925 : {
2926 : // Get the thread PID immediately so the caller can return.
2927 0 : m_satThreadID = syscall( SYS_gettid );
2928 :
2929 : // Wait for the thread starter to finish initializing this thread.
2930 0 : while( m_satThreadInit == true && derived().shutdown() == 0 )
2931 : {
2932 0 : sleep( 1 );
2933 : }
2934 :
2935 0 : if( derived().shutdown() )
2936 : {
2937 0 : return;
2938 : }
2939 :
2940 0 : uint32_t imsize[3] = { 0, 0, 0 };
2941 :
2942 : // Check for allocation to have happened.
2943 0 : while( ( m_shmimSat == "" || m_accumSatMap.rows() == 0 || m_accumSatMap.cols() == 0 ) && !derived().shutdown() )
2944 : {
2945 0 : sleep( 1 );
2946 : }
2947 :
2948 0 : if( derived().shutdown() )
2949 : {
2950 0 : return;
2951 : }
2952 :
2953 0 : imsize[0] = m_dmWidth;
2954 0 : imsize[1] = m_dmHeight;
2955 0 : imsize[2] = 1;
2956 :
2957 0 : ImageStreamIO_createIm_gpu( &m_satImageStream,
2958 : m_shmimSat.c_str(),
2959 : 3,
2960 : imsize,
2961 : IMAGESTRUCT_UINT8,
2962 : -1,
2963 : 1,
2964 : IMAGE_NB_SEMAPHORE,
2965 : 0,
2966 : CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2967 : 0 );
2968 0 : ImageStreamIO_createIm_gpu( &m_satPercImageStream,
2969 : m_shmimSatPerc.c_str(),
2970 : 3,
2971 : imsize,
2972 : IMAGESTRUCT_FLOAT,
2973 : -1,
2974 : 1,
2975 : IMAGE_NB_SEMAPHORE,
2976 : 0,
2977 : CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2978 : 0 );
2979 :
2980 0 : bool opened = true;
2981 :
2982 0 : m_satImageStream.md->cnt1 = 0;
2983 0 : m_satPercImageStream.md->cnt1 = 0;
2984 :
2985 : // This is the working memory for making the 1/0 mask out of m_accumSatMap
2986 0 : mx::improc::eigenImage<uint8_t> satmap( m_dmWidth, m_dmHeight );
2987 :
2988 0 : int naccum = 0;
2989 0 : double t_accumst = mx::sys::get_curr_time();
2990 :
2991 : // This is the main image grabbing loop.
2992 0 : while( !derived().shutdown() )
2993 : {
2994 : // Get timespec for sem_timedwait
2995 : timespec ts;
2996 0 : if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
2997 : {
2998 0 : derivedT::template log<software_critical>( { errno, 0, "clock_gettime" } );
2999 0 : return;
3000 : }
3001 0 : ts.tv_sec += 1;
3002 :
3003 : // Wait on semaphore
3004 0 : if( sem_timedwait( &m_satSemaphore, &ts ) == 0 )
3005 : {
3006 : // not a timeout -->accumulate
3007 0 : for( int rr = 0; rr < m_instSatMap.rows(); ++rr )
3008 : {
3009 0 : for( int cc = 0; cc < m_instSatMap.cols(); ++cc )
3010 : {
3011 0 : m_accumSatMap( rr, cc ) += m_instSatMap( rr, cc );
3012 : }
3013 : }
3014 0 : ++naccum;
3015 :
3016 : // If less than avg int --> go back and wait again
3017 0 : if( mx::sys::get_curr_time( ts ) - t_accumst < m_satAvgInt / 1000.0 )
3018 : {
3019 0 : continue;
3020 : }
3021 :
3022 : // If greater than avg int --> calc stats, write to streams.
3023 0 : m_overSatAct = 0;
3024 0 : for( int rr = 0; rr < m_instSatMap.rows(); ++rr )
3025 : {
3026 0 : for( int cc = 0; cc < m_instSatMap.cols(); ++cc )
3027 : {
3028 0 : m_satPercMap( rr, cc ) = m_accumSatMap( rr, cc ) / naccum;
3029 0 : if( m_satPercMap( rr, cc ) >= m_percThreshold )
3030 : {
3031 0 : ++m_overSatAct;
3032 : }
3033 0 : 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 0 : if( m_overSatAct / ( m_satPercMap.rows() * m_satPercMap.cols() * 0.75 ) > m_intervalSatThreshold )
3040 : {
3041 0 : ++m_intervalSatExceeds;
3042 : }
3043 : else
3044 : {
3045 0 : m_intervalSatExceeds = 0;
3046 : }
3047 :
3048 : // If enough consecutive intervals exceed the count threshold, we trigger
3049 0 : if( m_intervalSatExceeds >= m_intervalSatCountThreshold )
3050 : {
3051 0 : m_intervalSatTrip = true;
3052 : }
3053 :
3054 0 : m_satImageStream.md->write = 1;
3055 0 : m_satPercImageStream.md->write = 1;
3056 :
3057 0 : memcpy( m_satImageStream.array.raw, satmap.data(), m_dmWidth * m_dmHeight * sizeof( uint8_t ) );
3058 0 : memcpy( m_satPercImageStream.array.raw, m_satPercMap.data(), m_dmWidth * m_dmHeight * sizeof( float ) );
3059 :
3060 : // Set the time of last write
3061 0 : clock_gettime( CLOCK_REALTIME, &m_satImageStream.md->writetime );
3062 0 : m_satPercImageStream.md->writetime = m_satImageStream.md->writetime;
3063 :
3064 : // Set the image acquisition timestamp
3065 0 : m_satImageStream.md->atime = m_satImageStream.md->writetime;
3066 0 : m_satPercImageStream.md->atime = m_satPercImageStream.md->writetime;
3067 :
3068 : // Update cnt1
3069 0 : m_satImageStream.md->cnt1 = 0;
3070 0 : m_satPercImageStream.md->cnt1 = 0;
3071 :
3072 : // Update cnt0
3073 0 : m_satImageStream.md->cnt0++;
3074 0 : m_satPercImageStream.md->cnt0++;
3075 :
3076 0 : m_satImageStream.writetimearray[0] = m_satImageStream.md->writetime;
3077 0 : m_satImageStream.atimearray[0] = m_satImageStream.md->atime;
3078 0 : m_satImageStream.cntarray[0] = m_satImageStream.md->cnt0;
3079 :
3080 0 : m_satPercImageStream.writetimearray[0] = m_satPercImageStream.md->writetime;
3081 0 : m_satPercImageStream.atimearray[0] = m_satPercImageStream.md->atime;
3082 0 : m_satPercImageStream.cntarray[0] = m_satPercImageStream.md->cnt0;
3083 :
3084 : // And post
3085 0 : m_satImageStream.md->write = 0;
3086 0 : ImageStreamIO_sempost( &m_satImageStream, -1 );
3087 :
3088 0 : m_satPercImageStream.md->write = 0;
3089 0 : ImageStreamIO_sempost( &m_satPercImageStream, -1 );
3090 :
3091 0 : m_accumSatMap.setZero();
3092 0 : naccum = 0;
3093 0 : t_accumst = mx::sys::get_curr_time( ts );
3094 : }
3095 : else
3096 : {
3097 : // Check for why we timed out
3098 0 : if( errno == EINTR )
3099 : {
3100 0 : 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 0 : if( errno != ETIMEDOUT )
3107 : {
3108 0 : derivedT::template log<software_error>( { errno, "sem_timedwait" } );
3109 0 : break;
3110 : }
3111 : }
3112 : }
3113 :
3114 0 : if( opened )
3115 : {
3116 0 : ImageStreamIO_destroyIm( &m_satImageStream );
3117 :
3118 0 : ImageStreamIO_destroyIm( &m_satPercImageStream );
3119 : }
3120 0 : }
3121 :
3122 : template <class derivedT, typename realT>
3123 0 : void dm<derivedT, realT>::intervalSatTrip()
3124 : {
3125 0 : if( m_satTriggerDevice.size() > 0 && m_satTriggerProperty.size() == m_satTriggerDevice.size() )
3126 : {
3127 0 : for( size_t n = 0; n < m_satTriggerDevice.size(); ++n )
3128 : {
3129 : // We just silently fail
3130 : try
3131 : {
3132 0 : pcf::IndiProperty ipFreq( pcf::IndiProperty::Switch );
3133 :
3134 0 : ipFreq.setDevice( m_satTriggerDevice[n] );
3135 0 : ipFreq.setName( m_satTriggerProperty[n] );
3136 0 : ipFreq.add( pcf::IndiElement( "toggle" ) );
3137 0 : ipFreq["toggle"] = pcf::IndiElement::Off;
3138 0 : derived().sendNewProperty( ipFreq );
3139 :
3140 0 : derivedT::template log<text_log>( "DM saturation threshold exceeded. Loop opened.",
3141 : logPrio::LOG_WARNING );
3142 0 : }
3143 0 : catch( ... )
3144 : {
3145 : }
3146 : }
3147 : }
3148 0 : }
3149 :
3150 : template <class derivedT, typename realT>
3151 0 : int dm<derivedT, realT>::updateINDI()
3152 : {
3153 0 : if( !derived().m_indiDriver )
3154 : {
3155 0 : return 0;
3156 : }
3157 :
3158 0 : return 0;
3159 : }
3160 :
3161 : template <class derivedT, typename realT>
3162 0 : int dm<derivedT, realT>::st_newCallBack_init( void *app, const pcf::IndiProperty &ipRecv )
3163 : {
3164 0 : return static_cast<derivedT *>( app )->newCallBack_init( ipRecv );
3165 : }
3166 :
3167 : template <class derivedT, typename realT>
3168 0 : int dm<derivedT, realT>::newCallBack_init( const pcf::IndiProperty &ipRecv )
3169 : {
3170 0 : if( ipRecv.createUniqueKey() != m_indiP_init.createUniqueKey() )
3171 : {
3172 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3173 : }
3174 :
3175 0 : if( !ipRecv.find( "request" ) )
3176 : {
3177 0 : return 0;
3178 : }
3179 :
3180 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3181 : {
3182 0 : int rv = baseInitDM();
3183 0 : if( rv < 0 )
3184 : {
3185 0 : return derivedT::template log<software_error, -1>( { "error from initDM in INDI callback" } );
3186 : }
3187 : }
3188 :
3189 0 : return 0;
3190 : }
3191 :
3192 : template <class derivedT, typename realT>
3193 0 : int dm<derivedT, realT>::st_newCallBack_zero( void *app, const pcf::IndiProperty &ipRecv )
3194 : {
3195 0 : return static_cast<derivedT *>( app )->newCallBack_zero( ipRecv );
3196 : }
3197 :
3198 : template <class derivedT, typename realT>
3199 0 : int dm<derivedT, realT>::newCallBack_zero( const pcf::IndiProperty &ipRecv )
3200 : {
3201 0 : if( ipRecv.createUniqueKey() != m_indiP_zero.createUniqueKey() )
3202 : {
3203 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3204 : }
3205 :
3206 0 : if( !ipRecv.find( "request" ) )
3207 0 : return 0;
3208 :
3209 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3210 : {
3211 0 : return derived().zeroDM();
3212 : }
3213 0 : return 0;
3214 : }
3215 :
3216 : template <class derivedT, typename realT>
3217 0 : int dm<derivedT, realT>::st_newCallBack_release( void *app, const pcf::IndiProperty &ipRecv )
3218 : {
3219 0 : return static_cast<derivedT *>( app )->newCallBack_release( ipRecv );
3220 : }
3221 :
3222 : template <class derivedT, typename realT>
3223 0 : int dm<derivedT, realT>::newCallBack_release( const pcf::IndiProperty &ipRecv )
3224 : {
3225 0 : if( ipRecv.createUniqueKey() != m_indiP_release.createUniqueKey() )
3226 : {
3227 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3228 : }
3229 :
3230 0 : if( !ipRecv.find( "request" ) )
3231 0 : return 0;
3232 :
3233 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3234 : {
3235 0 : return baseReleaseDM();
3236 : }
3237 0 : return 0;
3238 : }
3239 :
3240 : template <class derivedT, typename realT>
3241 0 : int dm<derivedT, realT>::st_newCallBack_flats( void *app, const pcf::IndiProperty &ipRecv )
3242 : {
3243 0 : return static_cast<derivedT *>( app )->newCallBack_flats( ipRecv );
3244 : }
3245 :
3246 : template <class derivedT, typename realT>
3247 0 : int dm<derivedT, realT>::newCallBack_flats( const pcf::IndiProperty &ipRecv )
3248 : {
3249 0 : if( ipRecv.createUniqueKey() != m_indiP_flats.createUniqueKey() )
3250 : {
3251 0 : derivedT::template log<software_error>( { "invalid indi property received" } );
3252 0 : return -1;
3253 : }
3254 :
3255 0 : std::string newFlat;
3256 :
3257 0 : if( ipRecv.find( "default" ) )
3258 : {
3259 0 : if( ipRecv["default"].getSwitchState() == pcf::IndiElement::On )
3260 : {
3261 0 : newFlat = "default";
3262 : }
3263 : }
3264 :
3265 : // always do this to check for error:
3266 0 : for( auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
3267 : {
3268 0 : if( !ipRecv.find( i->first ) )
3269 0 : continue;
3270 :
3271 0 : if( ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3272 : {
3273 0 : if( newFlat != "" )
3274 : {
3275 0 : derivedT::template log<text_log>( "More than one flat selected", logPrio::LOG_ERROR );
3276 0 : return -1;
3277 : }
3278 :
3279 0 : newFlat = i->first;
3280 : }
3281 : }
3282 :
3283 0 : if( newFlat == "" )
3284 : {
3285 0 : return 0;
3286 : }
3287 :
3288 0 : return loadFlat( newFlat );
3289 0 : }
3290 :
3291 : template <class derivedT, typename realT>
3292 0 : int dm<derivedT, realT>::st_newCallBack_setFlat( void *app, const pcf::IndiProperty &ipRecv )
3293 : {
3294 0 : return static_cast<derivedT *>( app )->newCallBack_setFlat( ipRecv );
3295 : }
3296 :
3297 : template <class derivedT, typename realT>
3298 0 : int dm<derivedT, realT>::newCallBack_setFlat( const pcf::IndiProperty &ipRecv )
3299 : {
3300 0 : if( ipRecv.createUniqueKey() != m_indiP_setFlat.createUniqueKey() )
3301 : {
3302 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3303 : }
3304 :
3305 0 : if( !ipRecv.find( "toggle" ) )
3306 0 : return 0;
3307 :
3308 0 : if( ipRecv["toggle"] == pcf::IndiElement::On )
3309 : {
3310 0 : return setFlat();
3311 : }
3312 : else
3313 : {
3314 0 : return zeroFlat();
3315 : }
3316 : }
3317 :
3318 : template <class derivedT, typename realT>
3319 0 : int dm<derivedT, realT>::st_newCallBack_tests( void *app, const pcf::IndiProperty &ipRecv )
3320 : {
3321 0 : return static_cast<derivedT *>( app )->newCallBack_tests( ipRecv );
3322 : }
3323 :
3324 : template <class derivedT, typename realT>
3325 0 : int dm<derivedT, realT>::newCallBack_tests( const pcf::IndiProperty &ipRecv )
3326 : {
3327 0 : if( ipRecv.createUniqueKey() != m_indiP_tests.createUniqueKey() )
3328 : {
3329 0 : derivedT::template log<software_error>( { "invalid indi property received" } );
3330 0 : return -1;
3331 : }
3332 :
3333 0 : std::string newTest;
3334 :
3335 0 : if( ipRecv.find( "default" ) )
3336 : {
3337 0 : if( ipRecv["default"].getSwitchState() == pcf::IndiElement::On )
3338 : {
3339 0 : newTest = "default";
3340 : }
3341 : }
3342 :
3343 : // always do this to check for error:
3344 0 : for( auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
3345 : {
3346 0 : if( !ipRecv.find( i->first ) )
3347 0 : continue;
3348 :
3349 0 : if( ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3350 : {
3351 0 : if( newTest != "" )
3352 : {
3353 0 : derivedT::template log<text_log>( "More than one test selected", logPrio::LOG_ERROR );
3354 0 : return -1;
3355 : }
3356 :
3357 0 : newTest = i->first;
3358 : }
3359 : }
3360 :
3361 0 : if( newTest == "" )
3362 : {
3363 0 : return 0;
3364 : }
3365 :
3366 0 : return loadTest( newTest );
3367 0 : }
3368 :
3369 : template <class derivedT, typename realT>
3370 0 : int dm<derivedT, realT>::st_newCallBack_setTest( void *app, const pcf::IndiProperty &ipRecv )
3371 : {
3372 0 : return static_cast<derivedT *>( app )->newCallBack_setTest( ipRecv );
3373 : }
3374 :
3375 : template <class derivedT, typename realT>
3376 0 : int dm<derivedT, realT>::newCallBack_setTest( const pcf::IndiProperty &ipRecv )
3377 : {
3378 0 : if( ipRecv.createUniqueKey() != m_indiP_setTest.createUniqueKey() )
3379 : {
3380 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3381 : }
3382 :
3383 0 : if( !ipRecv.find( "toggle" ) )
3384 0 : return 0;
3385 :
3386 0 : if( ipRecv["toggle"] == pcf::IndiElement::On )
3387 : {
3388 0 : return setTest();
3389 : }
3390 : else
3391 : {
3392 0 : return zeroTest();
3393 : }
3394 : }
3395 :
3396 : template <class derivedT, typename realT>
3397 0 : int dm<derivedT, realT>::st_newCallBack_zeroAll( void *app, const pcf::IndiProperty &ipRecv )
3398 : {
3399 0 : return static_cast<derivedT *>( app )->newCallBack_zeroAll( ipRecv );
3400 : }
3401 :
3402 : template <class derivedT, typename realT>
3403 0 : int dm<derivedT, realT>::newCallBack_zeroAll( const pcf::IndiProperty &ipRecv )
3404 : {
3405 0 : if( ipRecv.createUniqueKey() != m_indiP_zeroAll.createUniqueKey() )
3406 : {
3407 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3408 : }
3409 :
3410 0 : if( !ipRecv.find( "request" ) )
3411 0 : return 0;
3412 :
3413 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3414 : {
3415 0 : indi::updateSwitchIfChanged(
3416 0 : m_indiP_zeroAll, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY );
3417 :
3418 0 : std::lock_guard<std::mutex> guard( derived().m_indiMutex );
3419 0 : return zeroAll();
3420 0 : }
3421 0 : 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
|