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 3 : dm<derivedT, realT>::~dm()
811 : {
812 8 : for( auto &mi : m_channels )
813 : {
814 5 : if( mi != nullptr )
815 : {
816 5 : delete mi;
817 : }
818 : }
819 3 : }
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>( {std::format("Found {} chanels for {} ", m_numChannels, derived().m_shmimName )} );
1695 :
1696 1 : m_channels.resize( m_numChannels, nullptr );
1697 :
1698 1 : m_notDeltas.clear();
1699 1 : m_deltas.clear();
1700 :
1701 6 : for( size_t n = 0; n < m_channels.size(); ++n )
1702 : {
1703 5 : std::string sname = std::format( "{}{:02}", derived().m_shmimName, n );
1704 :
1705 : try
1706 : {
1707 5 : m_channels[n] = new mx::improc::milkImage<realT>( sname ); // this opens the channel stream
1708 : }
1709 0 : catch( const std::exception &e )
1710 : {
1711 0 : derivedT::template log<software_error>( { "exception opening " + sname + ": " + e.what() } );
1712 : }
1713 :
1714 5 : std::cerr << "looking for " << sname << '\n';
1715 5 : auto res = std::find( m_deltaChannels.begin(), m_deltaChannels.end(), sname );
1716 5 : if( res == m_deltaChannels.end() )
1717 : {
1718 3 : std::cerr << " not a delta\n";
1719 3 : m_notDeltas.push_back( n );
1720 : }
1721 : else
1722 : {
1723 2 : std::cerr << " is a delta\n";
1724 2 : m_deltas.push_back( n );
1725 : }
1726 : }
1727 :
1728 1 : std::cerr << "not deltas: ";
1729 4 : for( size_t n = 0; n < m_notDeltas.size(); ++n )
1730 : {
1731 3 : std::cerr << m_notDeltas[n] << ' ';
1732 : }
1733 1 : std::cerr << '\n';
1734 :
1735 1 : std::cerr << "deltas: ";
1736 3 : for( size_t n = 0; n < m_deltas.size(); ++n )
1737 : {
1738 2 : std::cerr << m_deltas[n] << ' ';
1739 : }
1740 1 : std::cerr << '\n';
1741 :
1742 1 : return 0;
1743 1 : }
1744 :
1745 : template <class derivedT, typename realT>
1746 1 : int dm<derivedT, realT>::allocate( const dev::shmimT &sp )
1747 : {
1748 : static_cast<void>( sp ); // be unused
1749 :
1750 1 : int err = 0;
1751 :
1752 1 : if( derived().m_width != m_dmWidth )
1753 : {
1754 0 : derivedT::template log<software_critical>( { "shmim width does not match configured DM width" } );
1755 0 : ++err;
1756 : }
1757 :
1758 1 : if( derived().m_height != m_dmHeight )
1759 : {
1760 0 : derivedT::template log<software_critical>( { "shmim height does not match configured DM height" } );
1761 0 : ++err;
1762 : }
1763 :
1764 1 : if( derived().m_dataType != m_dmDataType )
1765 : {
1766 0 : derivedT::template log<software_critical>( { "shmim data type does not match configured DM data type" } );
1767 0 : ++err;
1768 : }
1769 :
1770 1 : if( err )
1771 : {
1772 0 : return -1;
1773 : }
1774 :
1775 1 : m_instSatMap.resize( m_dmWidth, m_dmHeight );
1776 1 : m_instSatMap.setZero();
1777 :
1778 1 : m_accumSatMap.resize( m_dmWidth, m_dmHeight );
1779 1 : m_accumSatMap.setZero();
1780 :
1781 1 : m_satPercMap.resize( m_dmWidth, m_dmHeight );
1782 1 : m_satPercMap.setZero();
1783 :
1784 1 : if( findDMChannels() < 0 )
1785 : {
1786 0 : derivedT::template log<software_critical>( { "error finding DM channels" } );
1787 :
1788 0 : return -1;
1789 : }
1790 :
1791 : try
1792 : {
1793 1 : m_outputShape.create( m_shmimShape, m_dmWidth, m_dmHeight );
1794 1 : m_outputShape().setZero();
1795 : }
1796 0 : catch( const std::exception &e )
1797 : {
1798 0 : return derivedT::template log<software_error, -1>(
1799 0 : { std::string( "creating output shape shmim: " ) + e.what() } );
1800 : }
1801 :
1802 : try
1803 : {
1804 1 : m_outputDelta.create( m_shmimDelta, m_dmWidth, m_dmHeight );
1805 1 : m_outputDelta().setZero();
1806 : }
1807 0 : catch( const std::exception &e )
1808 : {
1809 0 : return derivedT::template log<software_error, -1>(
1810 0 : { std::string( "creating output delta shmim: " ) + e.what() } );
1811 : }
1812 :
1813 : try
1814 : {
1815 1 : m_outputDiff.create( m_shmimDiff, m_dmWidth, m_dmHeight );
1816 1 : m_outputDiff().setZero();
1817 : }
1818 0 : catch( const std::exception &e )
1819 : {
1820 0 : return derivedT::template log<software_error, -1>(
1821 0 : { std::string( "creating output diff shmim: " ) + e.what() } );
1822 : }
1823 :
1824 1 : m_totalFlat.resize( m_dmWidth, m_dmHeight );
1825 1 : m_totalFlat.setZero();
1826 :
1827 1 : m_totalDelta.resize( m_dmWidth, m_dmHeight );
1828 1 : m_totalDelta.setZero();
1829 :
1830 : // clang-format off
1831 : #ifdef XWC_DMTIMINGS
1832 : m_piTimes.maxEntries( 2000 );
1833 : m_satSem.maxEntries( 2000 );
1834 : m_actProc.maxEntries( 2000 );
1835 : m_actCom.maxEntries( 2000 );
1836 : m_satUp.maxEntries( 2000 );
1837 : m_deltaUp.maxEntries( 2000 );
1838 : #endif // clang-format on
1839 :
1840 1 : return 0;
1841 : }
1842 :
1843 : template <class derivedT, typename realT>
1844 0 : int dm<derivedT, realT>::processImage( void *curr_src, const dev::shmimT &sp )
1845 : {
1846 : static_cast<void>( sp ); // be unused
1847 :
1848 : // clang-format off
1849 : #ifdef XWC_DMTIMINGS
1850 : m_t0 = mx::sys::get_curr_time();
1851 : #endif // clang-format on
1852 :
1853 0 : int rv = derived().commandDM( curr_src );
1854 :
1855 0 : if( rv < 0 )
1856 : {
1857 0 : derivedT::template log<software_critical>( { errno, rv, "Error from commandDM" } );
1858 0 : return rv;
1859 : }
1860 :
1861 : // clang-format off
1862 : #ifdef XWC_DMTIMINGS
1863 : m_tdelta0 = mx::sys::get_curr_time();
1864 : #endif // clang-format on
1865 :
1866 0 : if( m_deltaChannels.size() > 0 )
1867 : {
1868 0 : rv = makeDelta();
1869 :
1870 0 : if( rv < 0 )
1871 : {
1872 0 : derivedT::template log<software_critical>( { errno, rv, "Error from makeDelta" } );
1873 0 : return rv;
1874 : }
1875 : }
1876 :
1877 : // clang-format off
1878 : #ifdef XWC_DMTIMINGS
1879 : m_tdeltaf = mx::sys::get_curr_time();
1880 :
1881 : m_tf = m_tdeltaf;
1882 : #endif // clang-format on
1883 :
1884 : // clang-format off
1885 : #ifdef XWC_DMTIMINGS
1886 : m_tsat0 = mx::sys::get_curr_time();
1887 : #endif // clang-format on
1888 :
1889 : // Tell the sat thread to get going
1890 0 : if( sem_post( &m_satSemaphore ) < 0 )
1891 : {
1892 0 : derivedT::template log<software_critical>( { errno, 0, "Error posting to semaphore" } );
1893 0 : return -1;
1894 : }
1895 :
1896 : // clang-format off
1897 : #ifdef XWC_DMTIMINGS // clang-format on
1898 :
1899 : m_tsatf = mx::sys::get_curr_time();
1900 :
1901 : // Update the latency circ. buffs
1902 : if( m_piTimes.maxEntries() > 0 )
1903 : {
1904 : m_piTimes.nextEntry( m_tf - m_t0 );
1905 : m_satSem.nextEntry( m_tsatf - m_tsat0 );
1906 : m_actProc.nextEntry( m_tact1 - m_tact0 );
1907 : m_actCom.nextEntry( m_tact2 - m_tact1 );
1908 : m_satUp.nextEntry( m_tact4 - m_tact3 );
1909 : m_deltaUp.nextEntry( m_tdeltaf - m_tdelta0 );
1910 : }
1911 :
1912 : // clang-format off
1913 : #endif // clang-format on
1914 :
1915 0 : return rv;
1916 : }
1917 :
1918 : template <class derivedT, typename realT>
1919 0 : int dm<derivedT, realT>::baseInitDM()
1920 : {
1921 0 : if( derived().state() != stateCodes::NOTHOMED )
1922 : {
1923 0 : derivedT::template log<software_error>( { errno, "DM is not ready to be initialized" } );
1924 0 : derived().state( stateCodes::ERROR );
1925 0 : return -1;
1926 : }
1927 :
1928 0 : derived().state( stateCodes::HOMING );
1929 :
1930 : int rv;
1931 0 : if( ( rv = derived().initDM() ) < 0 )
1932 : {
1933 0 : derivedT::template log<software_critical>( { errno, rv, "Error from initDM" } );
1934 0 : derived().state( stateCodes::ERROR );
1935 0 : return rv;
1936 : }
1937 :
1938 0 : return 0;
1939 : }
1940 :
1941 : template <class derivedT, typename realT>
1942 0 : int dm<derivedT, realT>::baseReleaseDM()
1943 : {
1944 0 : if( derived().state() != stateCodes::POWEROFF )
1945 : {
1946 0 : derived().state( stateCodes::NOTHOMED );
1947 : }
1948 :
1949 : int rv;
1950 0 : if( ( rv = derived().releaseDM() ) < 0 )
1951 : {
1952 0 : derivedT::template log<software_critical>( { errno, rv, "Error from releaseDM" } );
1953 0 : derived().state( stateCodes::ERROR );
1954 0 : return rv;
1955 : }
1956 :
1957 0 : if( ( rv = zeroAll( true ) ) < 0 )
1958 : {
1959 0 : derivedT::template log<software_error>( { errno, rv, "Error from zeroAll" } );
1960 0 : derived().state( stateCodes::ERROR );
1961 0 : return rv;
1962 : }
1963 :
1964 0 : return 0;
1965 : }
1966 :
1967 : template <class derivedT, typename realT>
1968 0 : int dm<derivedT, realT>::checkFlats()
1969 : {
1970 0 : std::vector<std::string> tfs;
1971 0 : mx::error_t errc = mx::ioutils::getFileNames( tfs, m_flatPath, "", "", ".fits" );
1972 :
1973 0 : mx_error_check_rv( errc, -1 );
1974 :
1975 : // First remove default, b/c we always add it and don't want to include it in timestamp selected ones
1976 0 : for( size_t n = 0; n < tfs.size(); ++n )
1977 : {
1978 0 : if( mx::ioutils::pathStem( tfs[n] ) == "default" )
1979 : {
1980 0 : tfs.erase( tfs.begin() + n );
1981 0 : --n;
1982 : }
1983 : }
1984 :
1985 0 : unsigned m_nFlatFiles = 5;
1986 :
1987 : // Here we keep only the m_nFlatFiles most recent files
1988 0 : if( tfs.size() >= m_nFlatFiles )
1989 : {
1990 0 : std::vector<std::filesystem::file_time_type> wtimes( tfs.size() );
1991 :
1992 0 : for( size_t n = 0; n < wtimes.size(); ++n )
1993 : {
1994 0 : wtimes[n] = std::filesystem::last_write_time( tfs[n] );
1995 : }
1996 :
1997 0 : std::sort( wtimes.begin(), wtimes.end() );
1998 :
1999 0 : std::filesystem::file_time_type tn = wtimes[wtimes.size() - m_nFlatFiles];
2000 :
2001 0 : for( size_t n = 0; n < tfs.size(); ++n )
2002 : {
2003 0 : std::filesystem::file_time_type lmt = std::filesystem::last_write_time( tfs[n] );
2004 0 : if( lmt < tn )
2005 : {
2006 0 : tfs.erase( tfs.begin() + n );
2007 0 : --n;
2008 : }
2009 : }
2010 0 : }
2011 :
2012 0 : for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2013 : {
2014 0 : it->second = "";
2015 : }
2016 :
2017 0 : bool changed = false;
2018 0 : for( size_t n = 0; n < tfs.size(); ++n )
2019 : {
2020 : auto ir =
2021 0 : m_flatCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2022 0 : if( ir.second == true )
2023 0 : changed = true;
2024 : else
2025 0 : ir.first->second = tfs[n];
2026 : }
2027 :
2028 0 : for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2029 : {
2030 0 : if( it->second == "" )
2031 : {
2032 0 : changed = true;
2033 : // Erase the current iterator safely, even if the first one.
2034 0 : auto itdel = it;
2035 0 : ++it;
2036 0 : m_flatCommands.erase( itdel );
2037 0 : --it;
2038 : };
2039 : }
2040 :
2041 0 : if( changed )
2042 : {
2043 0 : if( derived().m_indiDriver )
2044 : {
2045 0 : derived().m_indiDriver->sendDelProperty( m_indiP_flats );
2046 0 : derived().m_indiNewCallBacks.erase( m_indiP_flats.createUniqueKey() );
2047 : }
2048 :
2049 0 : m_indiP_flats = pcf::IndiProperty( pcf::IndiProperty::Switch );
2050 0 : m_indiP_flats.setDevice( derived().configName() );
2051 0 : m_indiP_flats.setName( "flat" );
2052 0 : m_indiP_flats.setPerm( pcf::IndiProperty::ReadWrite );
2053 0 : m_indiP_flats.setState( pcf::IndiProperty::Idle );
2054 0 : m_indiP_flats.setRule( pcf::IndiProperty::OneOfMany );
2055 :
2056 : // Add the toggle element initialized to Off
2057 0 : for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
2058 : {
2059 0 : if( it->first == m_flatCurrent || m_flatCurrent == "" )
2060 : {
2061 0 : m_indiP_flats.add( pcf::IndiElement( it->first, pcf::IndiElement::On ) );
2062 0 : m_flatCurrent = it->first; // handles the case m_flatCurrent == "" b/c it was not set in config
2063 : }
2064 : else
2065 : {
2066 0 : m_indiP_flats.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
2067 : }
2068 : }
2069 :
2070 0 : if( m_flatDefault != "" )
2071 : {
2072 0 : if( m_flatCurrent == "default" )
2073 : {
2074 0 : m_indiP_flats.add( pcf::IndiElement( "default", pcf::IndiElement::On ) );
2075 : }
2076 : else
2077 : {
2078 0 : m_indiP_flats.add( pcf::IndiElement( "default", pcf::IndiElement::Off ) );
2079 : }
2080 : }
2081 :
2082 0 : if( derived().registerIndiPropertyNew( m_indiP_flats, st_newCallBack_flats ) < 0 )
2083 : {
2084 : // clang-format off
2085 : #ifndef DM_TEST_NOLOG
2086 0 : derivedT::template log<software_error>( {""} );
2087 : #endif
2088 : // clang-format on
2089 :
2090 0 : return -1;
2091 : }
2092 :
2093 0 : if( derived().m_indiDriver )
2094 : {
2095 0 : derived().m_indiDriver->sendDefProperty( m_indiP_flats );
2096 : }
2097 : }
2098 :
2099 0 : return 0;
2100 0 : }
2101 :
2102 : template <class derivedT, typename realT>
2103 0 : int dm<derivedT, realT>::loadFlat( const std::string &intarget )
2104 : {
2105 0 : std::string target = intarget;
2106 :
2107 0 : std::string targetPath;
2108 :
2109 0 : if( target == "default" )
2110 : {
2111 0 : target = m_flatDefault;
2112 0 : targetPath = m_flatPath + "/" + m_flatDefault + ".fits";
2113 : }
2114 : else
2115 : {
2116 : try
2117 : {
2118 0 : targetPath = m_flatCommands.at( target );
2119 : }
2120 0 : catch( ... )
2121 : {
2122 0 : derivedT::template log<text_log>( "flat file " + target + " not found", logPrio::LOG_ERROR );
2123 0 : return -1;
2124 : }
2125 : }
2126 :
2127 0 : m_flatLoaded = false;
2128 :
2129 : // load into memory.
2130 0 : mx::fits::fitsFile<realT> ff;
2131 :
2132 0 : mx::error_t errc = ff.read( m_flatCommand, targetPath );
2133 :
2134 0 : if( errc != mx::error_t::noerror )
2135 : {
2136 0 : derivedT::template log<text_log>( std::format( "error reading flat file {}: "
2137 : "{} ({})",
2138 : targetPath,
2139 0 : mx::errorMessage( errc ),
2140 0 : mx::errorName( errc ) ),
2141 : logPrio::LOG_ERROR );
2142 0 : return -1;
2143 : }
2144 :
2145 0 : if( m_actMask.rows() != m_flatCommand.rows() || m_actMask.cols() != m_flatCommand.cols() )
2146 : {
2147 0 : derivedT::template log<text_log>( std::format( "actuaor mask {}x{} is not same size as flag {}x{}",
2148 0 : m_actMask.rows(),
2149 0 : m_actMask.cols(),
2150 0 : m_flatCommand.rows(),
2151 0 : m_flatCommand.cols() ),
2152 : logPrio::LOG_ERROR );
2153 :
2154 0 : return -1;
2155 : }
2156 :
2157 0 : m_flatCommand *= m_actMask();
2158 :
2159 0 : derivedT::template log<text_log>( "loaded flat file " + targetPath );
2160 0 : m_flatLoaded = true;
2161 :
2162 0 : m_flatCurrent = intarget;
2163 :
2164 0 : if( m_indiP_flats.find( "default" ) )
2165 : {
2166 0 : if( m_flatCurrent == "default" )
2167 : {
2168 0 : m_indiP_flats["default"] = pcf::IndiElement::On;
2169 : }
2170 : else
2171 : {
2172 0 : m_indiP_flats["default"] = pcf::IndiElement::Off;
2173 : }
2174 : }
2175 :
2176 0 : for( auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
2177 : {
2178 0 : if( !m_indiP_flats.find( i->first ) )
2179 : {
2180 0 : continue;
2181 : }
2182 :
2183 0 : if( i->first == m_flatCurrent )
2184 : {
2185 0 : m_indiP_flats[i->first] = pcf::IndiElement::On;
2186 : }
2187 : else
2188 : {
2189 0 : m_indiP_flats[i->first] = pcf::IndiElement::Off;
2190 : }
2191 : }
2192 :
2193 0 : if( derived().m_indiDriver )
2194 : {
2195 0 : derived().m_indiDriver->sendSetProperty( m_indiP_flats );
2196 : }
2197 :
2198 0 : if( m_flatSet )
2199 : {
2200 0 : setFlat();
2201 : }
2202 :
2203 0 : return 0;
2204 0 : }
2205 :
2206 : template <class derivedT, typename realT>
2207 0 : int dm<derivedT, realT>::setFlat( bool update )
2208 : {
2209 0 : if( m_shmimFlat == "" )
2210 : {
2211 0 : return 0;
2212 : }
2213 :
2214 0 : if( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) )
2215 : {
2216 0 : derivedT::template log<text_log>( "can not set flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2217 0 : return -1;
2218 : }
2219 :
2220 0 : if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2221 : {
2222 0 : derivedT::template log<text_log>( "could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2223 0 : return -1;
2224 : }
2225 :
2226 0 : if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2227 : {
2228 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2229 0 : derivedT::template log<text_log>( "width mismatch between " + m_shmimFlat + " and configured DM",
2230 : logPrio::LOG_ERROR );
2231 0 : return -1;
2232 : }
2233 :
2234 0 : if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2235 : {
2236 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2237 0 : derivedT::template log<text_log>( "height mismatch between " + m_shmimFlat + " and configured DM",
2238 : logPrio::LOG_ERROR );
2239 0 : return -1;
2240 : }
2241 :
2242 0 : if( !m_flatLoaded )
2243 : {
2244 0 : bool flatSet = m_flatSet;
2245 0 : m_flatSet = false; // make sure we don't loop
2246 :
2247 0 : if( loadFlat( m_flatCurrent ) < 0 )
2248 : {
2249 0 : derivedT::template log<text_log>( "error loading flat " + m_flatCurrent, logPrio::LOG_ERROR );
2250 : }
2251 0 : m_flatSet = flatSet;
2252 : }
2253 :
2254 0 : if( !m_flatLoaded )
2255 : {
2256 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2257 0 : derivedT::template log<text_log>( "no flat loaded", logPrio::LOG_ERROR );
2258 0 : return -1;
2259 : }
2260 :
2261 0 : if( m_flatCommand.rows() != m_dmWidth )
2262 : {
2263 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2264 0 : derivedT::template log<text_log>( "width mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2265 0 : return -1;
2266 : }
2267 :
2268 0 : if( m_flatCommand.cols() != m_dmHeight )
2269 : {
2270 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2271 0 : derivedT::template log<text_log>( "height mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2272 0 : return -1;
2273 : }
2274 :
2275 0 : m_flatImageStream.md->write = 1;
2276 :
2277 : ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here
2278 : /// anyway. With bounds checks b/c not everyone handles cnt1 properly.
2279 : // Copy
2280 0 : memcpy( m_flatImageStream.array.raw, m_flatCommand.data(), m_dmWidth * m_dmHeight * sizeof( realT ) );
2281 :
2282 : // Set the time of last write
2283 0 : clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2284 :
2285 : // Set the image acquisition timestamp
2286 0 : m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2287 :
2288 0 : m_flatImageStream.md->cnt0++;
2289 0 : m_flatImageStream.md->write = 0;
2290 :
2291 : // Post the semaphores
2292 0 : ImageStreamIO_sempost( &m_flatImageStream, -1 );
2293 :
2294 0 : m_flatSet = true;
2295 :
2296 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2297 :
2298 0 : derived().state( stateCodes::OPERATING );
2299 :
2300 0 : if( !update )
2301 : {
2302 0 : derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2303 :
2304 0 : derivedT::template log<text_log>( "flat set" );
2305 : }
2306 :
2307 0 : return 0;
2308 : }
2309 :
2310 : template <class derivedT, typename realT>
2311 0 : int dm<derivedT, realT>::zeroFlat()
2312 : {
2313 0 : if( m_shmimFlat == "" )
2314 : {
2315 0 : return 0;
2316 : }
2317 :
2318 0 : if( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) )
2319 : {
2320 0 : derivedT::template log<text_log>( "can not zero flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2321 0 : return -1;
2322 : }
2323 :
2324 0 : if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2325 : {
2326 0 : derivedT::template log<text_log>( "could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2327 0 : return -1;
2328 : }
2329 :
2330 0 : if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2331 : {
2332 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2333 0 : derivedT::template log<text_log>( "width mismatch between " + m_shmimFlat + " and configured DM",
2334 : logPrio::LOG_ERROR );
2335 0 : return -1;
2336 : }
2337 :
2338 0 : if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2339 : {
2340 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2341 0 : derivedT::template log<text_log>( "height mismatch between " + m_shmimFlat + " and configured DM",
2342 : logPrio::LOG_ERROR );
2343 0 : return -1;
2344 : }
2345 :
2346 0 : m_flatImageStream.md->write = 1;
2347 :
2348 : ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here
2349 : /// anyway. With bounds checks b/c not everyone handles cnt1 properly.
2350 : // Zero
2351 0 : memset( m_flatImageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2352 :
2353 : // Set the time of last write
2354 0 : clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2355 :
2356 : // Set the image acquisition timestamp
2357 0 : m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2358 :
2359 0 : m_flatImageStream.md->cnt0++;
2360 0 : m_flatImageStream.md->write = 0;
2361 0 : ImageStreamIO_sempost( &m_flatImageStream, -1 );
2362 :
2363 0 : m_flatSet = false;
2364 :
2365 : // Post the semaphore
2366 0 : ImageStreamIO_closeIm( &m_flatImageStream );
2367 :
2368 0 : derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2369 :
2370 0 : derivedT::template log<text_log>( "flat zeroed" );
2371 :
2372 0 : if( derived().zeroDM() < 0 )
2373 : {
2374 0 : derivedT::template log<software_error>( { "error from zeroDM" } );
2375 : }
2376 :
2377 0 : if( clearSat() < 0 )
2378 : {
2379 0 : derivedT::template log<software_error>( { "error from clearSat" } );
2380 : }
2381 0 : derived().state( stateCodes::READY );
2382 :
2383 0 : return 0;
2384 : }
2385 :
2386 : template <class derivedT, typename realT>
2387 0 : int dm<derivedT, realT>::checkTests()
2388 : {
2389 0 : std::vector<std::string> tfs;
2390 0 : mx::error_t errc = mx::ioutils::getFileNames( tfs, m_testPath, "", "", ".fits" );
2391 :
2392 0 : mx_error_check_rv( errc, -1 );
2393 :
2394 0 : for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2395 : {
2396 0 : it->second = "";
2397 : }
2398 :
2399 0 : bool changed = false;
2400 0 : for( size_t n = 0; n < tfs.size(); ++n )
2401 : {
2402 : auto ir =
2403 0 : m_testCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2404 0 : if( ir.second == true )
2405 0 : changed = true;
2406 : else
2407 0 : ir.first->second = tfs[n];
2408 : }
2409 :
2410 0 : for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2411 : {
2412 0 : if( it->second == "" )
2413 : {
2414 0 : changed = true;
2415 : // Erase the current iterator safely, even if the first one.
2416 0 : auto itdel = it;
2417 0 : ++it;
2418 0 : m_testCommands.erase( itdel );
2419 0 : --it;
2420 : };
2421 : }
2422 :
2423 0 : if( changed )
2424 : {
2425 0 : if( derived().m_indiDriver )
2426 : {
2427 0 : derived().m_indiDriver->sendDelProperty( m_indiP_tests );
2428 0 : derived().m_indiNewCallBacks.erase( m_indiP_tests.createUniqueKey() );
2429 : }
2430 :
2431 0 : m_indiP_tests = pcf::IndiProperty( pcf::IndiProperty::Switch );
2432 0 : m_indiP_tests.setDevice( derived().configName() );
2433 0 : m_indiP_tests.setName( "test" );
2434 0 : m_indiP_tests.setPerm( pcf::IndiProperty::ReadWrite );
2435 0 : m_indiP_tests.setState( pcf::IndiProperty::Idle );
2436 0 : m_indiP_tests.setRule( pcf::IndiProperty::OneOfMany );
2437 :
2438 : // Add the toggle element initialized to Off
2439 0 : for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
2440 : {
2441 0 : if( it->first == m_testCurrent || m_testCurrent == "" )
2442 : {
2443 0 : m_indiP_tests.add( pcf::IndiElement( it->first, pcf::IndiElement::On ) );
2444 0 : m_testCurrent = it->first; // Handles the case when m_testCurrent=="" b/c it was not set in config
2445 : }
2446 : else
2447 : {
2448 0 : m_indiP_tests.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
2449 : }
2450 : }
2451 :
2452 0 : if( m_testDefault != "" )
2453 : {
2454 0 : if( m_testCurrent == "default" )
2455 : {
2456 0 : m_indiP_tests.add( pcf::IndiElement( "default", pcf::IndiElement::On ) );
2457 : }
2458 : else
2459 : {
2460 0 : m_indiP_tests.add( pcf::IndiElement( "default", pcf::IndiElement::Off ) );
2461 : }
2462 : }
2463 :
2464 0 : if( derived().registerIndiPropertyNew( m_indiP_tests, st_newCallBack_tests ) < 0 )
2465 : {
2466 : #ifndef DM_TEST_NOLOG
2467 0 : derivedT::template log<software_error>( { "" } );
2468 : #endif
2469 0 : return -1;
2470 : }
2471 :
2472 0 : if( derived().m_indiDriver )
2473 : {
2474 0 : derived().m_indiDriver->sendDefProperty( m_indiP_tests );
2475 : }
2476 : }
2477 :
2478 0 : return 0;
2479 0 : }
2480 :
2481 : template <class derivedT, typename realT>
2482 0 : int dm<derivedT, realT>::loadTest( const std::string &intarget )
2483 : {
2484 0 : std::string target = intarget; // store this for later to resolve default next:
2485 :
2486 0 : if( target == "default" )
2487 : {
2488 0 : target = m_testDefault;
2489 : }
2490 :
2491 0 : std::string targetPath;
2492 :
2493 : try
2494 : {
2495 0 : targetPath = m_testCommands.at( target );
2496 : }
2497 0 : catch( ... )
2498 : {
2499 0 : derivedT::template log<text_log>( "test file " + target + " not found", logPrio::LOG_ERROR );
2500 0 : return -1;
2501 : }
2502 :
2503 0 : m_testLoaded = false;
2504 : // load into memory.
2505 0 : mx::fits::fitsFile<realT> ff;
2506 0 : mx::error_t errc = ff.read( m_testCommand, targetPath );
2507 0 : if( errc != mx::error_t::noerror )
2508 : {
2509 0 : derivedT::template log<text_log>( std::format( "error reading test file {}: "
2510 : "{} ({})",
2511 : targetPath,
2512 0 : mx::errorMessage( errc ),
2513 0 : mx::errorName( errc ) ),
2514 : logPrio::LOG_ERROR );
2515 0 : return -1;
2516 : }
2517 :
2518 0 : derivedT::template log<text_log>( "loaded test file " + targetPath );
2519 0 : m_testLoaded = true;
2520 :
2521 0 : m_testCurrent = intarget;
2522 :
2523 0 : if( m_indiP_tests.find( "default" ) )
2524 : {
2525 0 : if( m_testCurrent == "default" )
2526 : {
2527 0 : m_indiP_tests["default"] = pcf::IndiElement::On;
2528 : }
2529 : else
2530 : {
2531 0 : m_indiP_tests["default"] = pcf::IndiElement::Off;
2532 : }
2533 : }
2534 :
2535 0 : for( auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
2536 : {
2537 0 : if( !m_indiP_tests.find( i->first ) )
2538 : {
2539 0 : continue;
2540 : }
2541 :
2542 0 : if( i->first == m_testCurrent )
2543 : {
2544 0 : m_indiP_tests[i->first] = pcf::IndiElement::On;
2545 : }
2546 : else
2547 : {
2548 0 : m_indiP_tests[i->first] = pcf::IndiElement::Off;
2549 : }
2550 : }
2551 :
2552 0 : if( derived().m_indiDriver )
2553 0 : derived().m_indiDriver->sendSetProperty( m_indiP_tests );
2554 :
2555 0 : if( m_testSet )
2556 0 : setTest();
2557 :
2558 0 : return 0;
2559 0 : }
2560 :
2561 : template <class derivedT, typename realT>
2562 0 : int dm<derivedT, realT>::setTest()
2563 : {
2564 :
2565 0 : if( m_shmimTest == "" )
2566 0 : return 0;
2567 :
2568 0 : if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2569 : {
2570 0 : derivedT::template log<text_log>( "could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2571 0 : return -1;
2572 : }
2573 :
2574 0 : if( m_testImageStream.md->size[0] != m_dmWidth )
2575 : {
2576 0 : ImageStreamIO_closeIm( &m_testImageStream );
2577 0 : derivedT::template log<text_log>( "width mismatch between " + m_shmimTest + " and configured DM",
2578 : logPrio::LOG_ERROR );
2579 0 : return -1;
2580 : }
2581 :
2582 0 : if( m_testImageStream.md->size[1] != m_dmHeight )
2583 : {
2584 0 : ImageStreamIO_closeIm( &m_testImageStream );
2585 0 : derivedT::template log<text_log>( "height mismatch between " + m_shmimTest + " and configured DM",
2586 : logPrio::LOG_ERROR );
2587 0 : return -1;
2588 : }
2589 :
2590 0 : if( !m_testLoaded )
2591 : {
2592 0 : bool testSet = m_testSet;
2593 0 : m_testSet = false; // make sure we don't loop
2594 :
2595 0 : if( loadTest( m_testCurrent ) < 0 )
2596 : {
2597 0 : derivedT::template log<text_log>( "error loading test " + m_testCurrent, logPrio::LOG_ERROR );
2598 : }
2599 0 : m_testSet = testSet;
2600 : }
2601 :
2602 0 : if( !m_testLoaded )
2603 : {
2604 0 : ImageStreamIO_closeIm( &m_testImageStream );
2605 0 : derivedT::template log<text_log>( "no test loaded", logPrio::LOG_ERROR );
2606 0 : return -1;
2607 : }
2608 :
2609 0 : if( m_testCommand.rows() != m_dmWidth )
2610 : {
2611 0 : ImageStreamIO_closeIm( &m_testImageStream );
2612 0 : derivedT::template log<text_log>( "width mismatch between test file and configured DM", logPrio::LOG_ERROR );
2613 0 : return -1;
2614 : }
2615 :
2616 0 : if( m_testCommand.cols() != m_dmHeight )
2617 : {
2618 0 : ImageStreamIO_closeIm( &m_testImageStream );
2619 0 : derivedT::template log<text_log>( "height mismatch between test file and configured DM", logPrio::LOG_ERROR );
2620 0 : return -1;
2621 : }
2622 :
2623 0 : m_testImageStream.md->write = 1;
2624 :
2625 : ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here
2626 : /// anyway. With bounds checks b/c not everyone handles cnt1 properly.
2627 : // Copy
2628 0 : memcpy( m_testImageStream.array.raw, m_testCommand.data(), m_dmWidth * m_dmHeight * sizeof( realT ) );
2629 :
2630 : // Set the time of last write
2631 0 : clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2632 :
2633 : // Set the image acquisition timestamp
2634 0 : m_testImageStream.md->atime = m_testImageStream.md->writetime;
2635 :
2636 0 : m_testImageStream.md->cnt0++;
2637 0 : m_testImageStream.md->write = 0;
2638 0 : ImageStreamIO_sempost( &m_testImageStream, -1 );
2639 :
2640 0 : m_testSet = true;
2641 :
2642 : // Post the semaphore
2643 0 : ImageStreamIO_closeIm( &m_testImageStream );
2644 :
2645 0 : derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2646 :
2647 0 : derivedT::template log<text_log>( "test set" );
2648 :
2649 0 : return 0;
2650 : }
2651 :
2652 : template <class derivedT, typename realT>
2653 0 : int dm<derivedT, realT>::zeroTest()
2654 : {
2655 0 : if( m_shmimTest == "" )
2656 0 : return 0;
2657 :
2658 0 : if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2659 : {
2660 0 : derivedT::template log<text_log>( "could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2661 0 : return -1;
2662 : }
2663 :
2664 0 : if( m_testImageStream.md[0].size[0] != m_dmWidth )
2665 : {
2666 0 : ImageStreamIO_closeIm( &m_testImageStream );
2667 0 : derivedT::template log<text_log>( "width mismatch between " + m_shmimTest + " and configured DM",
2668 : logPrio::LOG_ERROR );
2669 0 : return -1;
2670 : }
2671 :
2672 0 : if( m_testImageStream.md[0].size[1] != m_dmHeight )
2673 : {
2674 0 : ImageStreamIO_closeIm( &m_testImageStream );
2675 0 : derivedT::template log<text_log>( "height mismatch between " + m_shmimTest + " and configured DM",
2676 : logPrio::LOG_ERROR );
2677 0 : return -1;
2678 : }
2679 :
2680 0 : m_testImageStream.md->write = 1;
2681 :
2682 : ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here
2683 : /// anyway. With bounds checks b/c not everyone handles cnt1 properly.
2684 : // Zero
2685 0 : memset( m_testImageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2686 :
2687 : // Set the time of last write
2688 0 : clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2689 :
2690 : // Set the image acquisition timestamp
2691 0 : m_testImageStream.md->atime = m_testImageStream.md->writetime;
2692 :
2693 0 : m_testImageStream.md->cnt0++;
2694 0 : m_testImageStream.md->write = 0;
2695 :
2696 : // Post the semaphore
2697 0 : ImageStreamIO_sempost( &m_testImageStream, -1 );
2698 :
2699 0 : m_testSet = false;
2700 :
2701 0 : ImageStreamIO_closeIm( &m_testImageStream );
2702 :
2703 0 : derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2704 :
2705 0 : derivedT::template log<text_log>( "test zeroed" );
2706 :
2707 0 : return 0;
2708 : }
2709 :
2710 : template <class derivedT, typename realT>
2711 0 : int dm<derivedT, realT>::zeroAll( bool nosem )
2712 : {
2713 0 : if( derived().m_shmimName == "" )
2714 : {
2715 0 : return 0;
2716 : }
2717 :
2718 : IMAGE imageStream;
2719 :
2720 0 : for( int n = 0; n < m_numChannels; ++n )
2721 : {
2722 : char nstr[16];
2723 0 : snprintf( nstr, sizeof( nstr ), "%02d", n );
2724 0 : std::string shmimN = derived().m_shmimName + nstr;
2725 :
2726 0 : if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2727 : {
2728 0 : derivedT::template log<text_log>( "could not connect to channel " + shmimN, logPrio::LOG_WARNING );
2729 0 : continue;
2730 : }
2731 :
2732 0 : if( imageStream.md->size[0] != m_dmWidth )
2733 : {
2734 0 : ImageStreamIO_closeIm( &imageStream );
2735 0 : derivedT::template log<text_log>( "width mismatch between " + shmimN + " and configured DM",
2736 : logPrio::LOG_ERROR );
2737 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2738 0 : return -1;
2739 : }
2740 :
2741 0 : if( imageStream.md->size[1] != m_dmHeight )
2742 : {
2743 0 : ImageStreamIO_closeIm( &imageStream );
2744 0 : derivedT::template log<text_log>( "height mismatch between " + shmimN + " and configured DM",
2745 : logPrio::LOG_ERROR );
2746 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2747 0 : return -1;
2748 : }
2749 :
2750 0 : imageStream.md->write = 1;
2751 0 : memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
2752 :
2753 0 : clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2754 :
2755 : // Set the image acquisition timestamp
2756 0 : imageStream.md->atime = imageStream.md->writetime;
2757 :
2758 0 : imageStream.md->cnt0++;
2759 0 : imageStream.md->write = 0;
2760 :
2761 : // Raise the semaphore on last one.
2762 0 : if( n == m_numChannels - 1 && !nosem )
2763 : {
2764 0 : ImageStreamIO_sempost( &imageStream, -1 );
2765 : }
2766 :
2767 0 : ImageStreamIO_closeIm( &imageStream );
2768 : }
2769 :
2770 0 : derivedT::template log<text_log>( "all channels zeroed", logPrio::LOG_NOTICE );
2771 :
2772 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2773 :
2774 : // Also cleanup flat and test
2775 0 : m_flatSet = false;
2776 0 : derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2777 0 : if( derived().state() == stateCodes::OPERATING )
2778 : {
2779 0 : derived().state( stateCodes::READY );
2780 : }
2781 :
2782 : // Also cleanup flat and test
2783 0 : m_testSet = false;
2784 0 : derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2785 :
2786 : int rv;
2787 0 : if( ( rv = clearSat() ) < 0 )
2788 : {
2789 0 : derivedT::template log<software_error>( { errno, rv, "Error from clearSat" } );
2790 0 : return rv;
2791 : }
2792 :
2793 0 : return 0;
2794 : }
2795 :
2796 : template <class derivedT, typename realT>
2797 1 : int dm<derivedT, realT>::makeDelta()
2798 : {
2799 1 : if( m_notDeltas.size() == 0 )
2800 : {
2801 0 : return 0;
2802 : }
2803 :
2804 1 : m_totalFlat = ( *m_channels[m_notDeltas[0]] )();
2805 :
2806 3 : for( size_t n = 1; n < m_notDeltas.size(); ++n )
2807 : {
2808 2 : m_totalFlat += ( *m_channels[m_notDeltas[n]] )();
2809 : }
2810 :
2811 1 : m_outputDelta = m_outputShape() - m_totalFlat; // this posts and everything
2812 :
2813 1 : m_totalDelta = ( *m_channels[m_deltas[0]] )();
2814 :
2815 2 : for( size_t n = 1; n < m_deltas.size(); ++n )
2816 : {
2817 1 : m_totalDelta += ( *m_channels[m_deltas[n]] )();
2818 : }
2819 :
2820 1 : m_outputDiff = m_totalDelta - m_outputDelta();
2821 :
2822 1 : return 0;
2823 : }
2824 :
2825 : template <class derivedT, typename realT>
2826 0 : int dm<derivedT, realT>::clearSat()
2827 : {
2828 0 : if( m_shmimSat == "" || m_dmWidth == 0 || m_dmHeight == 0 )
2829 : {
2830 0 : return 0;
2831 : }
2832 :
2833 : IMAGE imageStream;
2834 :
2835 0 : std::vector<std::string> sats = { m_shmimSat, m_shmimSatPerc };
2836 :
2837 0 : for( size_t n = 0; n < sats.size(); ++n )
2838 : {
2839 0 : std::string shmimN = sats[n];
2840 :
2841 0 : if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2842 : {
2843 0 : derivedT::template log<text_log>( "could not connect to sat map " + shmimN, logPrio::LOG_WARNING );
2844 0 : return 0;
2845 : }
2846 :
2847 0 : if( imageStream.md->size[0] != m_dmWidth )
2848 : {
2849 0 : ImageStreamIO_closeIm( &imageStream );
2850 0 : derivedT::template log<text_log>( "width mismatch between " + shmimN + " and configured DM",
2851 : logPrio::LOG_ERROR );
2852 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2853 0 : return -1;
2854 : }
2855 :
2856 0 : if( imageStream.md->size[1] != m_dmHeight )
2857 : {
2858 0 : ImageStreamIO_closeIm( &imageStream );
2859 0 : derivedT::template log<text_log>( "height mismatch between " + shmimN + " and configured DM",
2860 : logPrio::LOG_ERROR );
2861 0 : derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
2862 0 : return -1;
2863 : }
2864 :
2865 0 : imageStream.md->write = 1;
2866 0 : memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * ImageStreamIO_typesize( imageStream.md->datatype ) );
2867 :
2868 0 : clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2869 :
2870 : // Set the image acquisition timestamp
2871 0 : imageStream.md->atime = imageStream.md->writetime;
2872 :
2873 0 : imageStream.md->cnt0++;
2874 0 : imageStream.md->write = 0;
2875 0 : ImageStreamIO_sempost( &imageStream, -1 );
2876 :
2877 0 : ImageStreamIO_closeIm( &imageStream );
2878 : }
2879 :
2880 0 : m_accumSatMap.setZero();
2881 0 : m_instSatMap.setZero();
2882 :
2883 0 : return 0;
2884 0 : }
2885 :
2886 : template <class derivedT, typename realT>
2887 0 : void dm<derivedT, realT>::satThreadStart( dm *d )
2888 : {
2889 0 : d->satThreadExec();
2890 0 : }
2891 :
2892 : template <class derivedT, typename realT>
2893 0 : void dm<derivedT, realT>::satThreadExec()
2894 : {
2895 : // Get the thread PID immediately so the caller can return.
2896 0 : m_satThreadID = syscall( SYS_gettid );
2897 :
2898 : // Wait for the thread starter to finish initializing this thread.
2899 0 : while( m_satThreadInit == true && derived().shutdown() == 0 )
2900 : {
2901 0 : sleep( 1 );
2902 : }
2903 :
2904 0 : if( derived().shutdown() )
2905 : {
2906 0 : return;
2907 : }
2908 :
2909 0 : uint32_t imsize[3] = { 0, 0, 0 };
2910 :
2911 : // Check for allocation to have happened.
2912 0 : while( ( m_shmimSat == "" || m_accumSatMap.rows() == 0 || m_accumSatMap.cols() == 0 ) && !derived().shutdown() )
2913 : {
2914 0 : sleep( 1 );
2915 : }
2916 :
2917 0 : if( derived().shutdown() )
2918 : {
2919 0 : return;
2920 : }
2921 :
2922 0 : imsize[0] = m_dmWidth;
2923 0 : imsize[1] = m_dmHeight;
2924 0 : imsize[2] = 1;
2925 :
2926 0 : ImageStreamIO_createIm_gpu( &m_satImageStream,
2927 : m_shmimSat.c_str(),
2928 : 3,
2929 : imsize,
2930 : IMAGESTRUCT_UINT8,
2931 : -1,
2932 : 1,
2933 : IMAGE_NB_SEMAPHORE,
2934 : 0,
2935 : CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2936 : 0 );
2937 0 : ImageStreamIO_createIm_gpu( &m_satPercImageStream,
2938 : m_shmimSatPerc.c_str(),
2939 : 3,
2940 : imsize,
2941 : IMAGESTRUCT_FLOAT,
2942 : -1,
2943 : 1,
2944 : IMAGE_NB_SEMAPHORE,
2945 : 0,
2946 : CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2947 : 0 );
2948 :
2949 0 : bool opened = true;
2950 :
2951 0 : m_satImageStream.md->cnt1 = 0;
2952 0 : m_satPercImageStream.md->cnt1 = 0;
2953 :
2954 : // This is the working memory for making the 1/0 mask out of m_accumSatMap
2955 0 : mx::improc::eigenImage<uint8_t> satmap( m_dmWidth, m_dmHeight );
2956 :
2957 0 : int naccum = 0;
2958 0 : double t_accumst = mx::sys::get_curr_time();
2959 :
2960 : // This is the main image grabbing loop.
2961 0 : while( !derived().shutdown() )
2962 : {
2963 : // Get timespec for sem_timedwait
2964 : timespec ts;
2965 0 : if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
2966 : {
2967 0 : derivedT::template log<software_critical>( { errno, 0, "clock_gettime" } );
2968 0 : return;
2969 : }
2970 0 : ts.tv_sec += 1;
2971 :
2972 : // Wait on semaphore
2973 0 : if( sem_timedwait( &m_satSemaphore, &ts ) == 0 )
2974 : {
2975 : // not a timeout -->accumulate
2976 0 : for( int rr = 0; rr < m_instSatMap.rows(); ++rr )
2977 : {
2978 0 : for( int cc = 0; cc < m_instSatMap.cols(); ++cc )
2979 : {
2980 0 : m_accumSatMap( rr, cc ) += m_instSatMap( rr, cc );
2981 : }
2982 : }
2983 0 : ++naccum;
2984 :
2985 : // If less than avg int --> go back and wait again
2986 0 : if( mx::sys::get_curr_time( ts ) - t_accumst < m_satAvgInt / 1000.0 )
2987 : {
2988 0 : continue;
2989 : }
2990 :
2991 : // If greater than avg int --> calc stats, write to streams.
2992 0 : m_overSatAct = 0;
2993 0 : for( int rr = 0; rr < m_instSatMap.rows(); ++rr )
2994 : {
2995 0 : for( int cc = 0; cc < m_instSatMap.cols(); ++cc )
2996 : {
2997 0 : m_satPercMap( rr, cc ) = m_accumSatMap( rr, cc ) / naccum;
2998 0 : if( m_satPercMap( rr, cc ) >= m_percThreshold )
2999 : {
3000 0 : ++m_overSatAct;
3001 : }
3002 0 : satmap( rr, cc ) = ( m_accumSatMap( rr, cc ) > 0 ); // it's 1/0 map
3003 : }
3004 : }
3005 :
3006 : // Check of the number of actuators saturated above the percent threshold is greater than the number
3007 : // threshold if it is, increment the counter
3008 0 : if( m_overSatAct / ( m_satPercMap.rows() * m_satPercMap.cols() * 0.75 ) > m_intervalSatThreshold )
3009 : {
3010 0 : ++m_intervalSatExceeds;
3011 : }
3012 : else
3013 : {
3014 0 : m_intervalSatExceeds = 0;
3015 : }
3016 :
3017 : // If enough consecutive intervals exceed the count threshold, we trigger
3018 0 : if( m_intervalSatExceeds >= m_intervalSatCountThreshold )
3019 : {
3020 0 : m_intervalSatTrip = true;
3021 : }
3022 :
3023 0 : m_satImageStream.md->write = 1;
3024 0 : m_satPercImageStream.md->write = 1;
3025 :
3026 0 : memcpy( m_satImageStream.array.raw, satmap.data(), m_dmWidth * m_dmHeight * sizeof( uint8_t ) );
3027 0 : memcpy( m_satPercImageStream.array.raw, m_satPercMap.data(), m_dmWidth * m_dmHeight * sizeof( float ) );
3028 :
3029 : // Set the time of last write
3030 0 : clock_gettime( CLOCK_REALTIME, &m_satImageStream.md->writetime );
3031 0 : m_satPercImageStream.md->writetime = m_satImageStream.md->writetime;
3032 :
3033 : // Set the image acquisition timestamp
3034 0 : m_satImageStream.md->atime = m_satImageStream.md->writetime;
3035 0 : m_satPercImageStream.md->atime = m_satPercImageStream.md->writetime;
3036 :
3037 : // Update cnt1
3038 0 : m_satImageStream.md->cnt1 = 0;
3039 0 : m_satPercImageStream.md->cnt1 = 0;
3040 :
3041 : // Update cnt0
3042 0 : m_satImageStream.md->cnt0++;
3043 0 : m_satPercImageStream.md->cnt0++;
3044 :
3045 0 : m_satImageStream.writetimearray[0] = m_satImageStream.md->writetime;
3046 0 : m_satImageStream.atimearray[0] = m_satImageStream.md->atime;
3047 0 : m_satImageStream.cntarray[0] = m_satImageStream.md->cnt0;
3048 :
3049 0 : m_satPercImageStream.writetimearray[0] = m_satPercImageStream.md->writetime;
3050 0 : m_satPercImageStream.atimearray[0] = m_satPercImageStream.md->atime;
3051 0 : m_satPercImageStream.cntarray[0] = m_satPercImageStream.md->cnt0;
3052 :
3053 : // And post
3054 0 : m_satImageStream.md->write = 0;
3055 0 : ImageStreamIO_sempost( &m_satImageStream, -1 );
3056 :
3057 0 : m_satPercImageStream.md->write = 0;
3058 0 : ImageStreamIO_sempost( &m_satPercImageStream, -1 );
3059 :
3060 0 : m_accumSatMap.setZero();
3061 0 : naccum = 0;
3062 0 : t_accumst = mx::sys::get_curr_time( ts );
3063 : }
3064 : else
3065 : {
3066 : // Check for why we timed out
3067 0 : if( errno == EINTR )
3068 : {
3069 0 : break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit normally if
3070 : // flags set.
3071 : }
3072 :
3073 : // ETIMEDOUT just means we should wait more.
3074 : // Otherwise, report an error.
3075 0 : if( errno != ETIMEDOUT )
3076 : {
3077 0 : derivedT::template log<software_error>( { errno, "sem_timedwait" } );
3078 0 : break;
3079 : }
3080 : }
3081 : }
3082 :
3083 0 : if( opened )
3084 : {
3085 0 : ImageStreamIO_destroyIm( &m_satImageStream );
3086 :
3087 0 : ImageStreamIO_destroyIm( &m_satPercImageStream );
3088 : }
3089 0 : }
3090 :
3091 : template <class derivedT, typename realT>
3092 0 : void dm<derivedT, realT>::intervalSatTrip()
3093 : {
3094 0 : if( m_satTriggerDevice.size() > 0 && m_satTriggerProperty.size() == m_satTriggerDevice.size() )
3095 : {
3096 0 : for( size_t n = 0; n < m_satTriggerDevice.size(); ++n )
3097 : {
3098 : // We just silently fail
3099 : try
3100 : {
3101 0 : pcf::IndiProperty ipFreq( pcf::IndiProperty::Switch );
3102 :
3103 0 : ipFreq.setDevice( m_satTriggerDevice[n] );
3104 0 : ipFreq.setName( m_satTriggerProperty[n] );
3105 0 : ipFreq.add( pcf::IndiElement( "toggle" ) );
3106 0 : ipFreq["toggle"] = pcf::IndiElement::Off;
3107 0 : derived().sendNewProperty( ipFreq );
3108 :
3109 0 : derivedT::template log<text_log>( "DM saturation threshold exceeded. Loop opened.",
3110 : logPrio::LOG_WARNING );
3111 0 : }
3112 0 : catch( ... )
3113 : {
3114 : }
3115 : }
3116 : }
3117 0 : }
3118 :
3119 : template <class derivedT, typename realT>
3120 0 : int dm<derivedT, realT>::updateINDI()
3121 : {
3122 0 : if( !derived().m_indiDriver )
3123 : {
3124 0 : return 0;
3125 : }
3126 :
3127 0 : return 0;
3128 : }
3129 :
3130 : template <class derivedT, typename realT>
3131 0 : int dm<derivedT, realT>::st_newCallBack_init( void *app, const pcf::IndiProperty &ipRecv )
3132 : {
3133 0 : return static_cast<derivedT *>( app )->newCallBack_init( ipRecv );
3134 : }
3135 :
3136 : template <class derivedT, typename realT>
3137 0 : int dm<derivedT, realT>::newCallBack_init( const pcf::IndiProperty &ipRecv )
3138 : {
3139 0 : if( ipRecv.createUniqueKey() != m_indiP_init.createUniqueKey() )
3140 : {
3141 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3142 : }
3143 :
3144 0 : if( !ipRecv.find( "request" ) )
3145 : {
3146 0 : return 0;
3147 : }
3148 :
3149 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3150 : {
3151 0 : int rv = baseInitDM();
3152 0 : if( rv < 0 )
3153 : {
3154 0 : return derivedT::template log<software_error, -1>( { "error from initDM in INDI callback" } );
3155 : }
3156 : }
3157 :
3158 0 : return 0;
3159 : }
3160 :
3161 : template <class derivedT, typename realT>
3162 0 : int dm<derivedT, realT>::st_newCallBack_zero( void *app, const pcf::IndiProperty &ipRecv )
3163 : {
3164 0 : return static_cast<derivedT *>( app )->newCallBack_zero( ipRecv );
3165 : }
3166 :
3167 : template <class derivedT, typename realT>
3168 0 : int dm<derivedT, realT>::newCallBack_zero( const pcf::IndiProperty &ipRecv )
3169 : {
3170 0 : if( ipRecv.createUniqueKey() != m_indiP_zero.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 0 : return 0;
3177 :
3178 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3179 : {
3180 0 : return derived().zeroDM();
3181 : }
3182 0 : return 0;
3183 : }
3184 :
3185 : template <class derivedT, typename realT>
3186 0 : int dm<derivedT, realT>::st_newCallBack_release( void *app, const pcf::IndiProperty &ipRecv )
3187 : {
3188 0 : return static_cast<derivedT *>( app )->newCallBack_release( ipRecv );
3189 : }
3190 :
3191 : template <class derivedT, typename realT>
3192 0 : int dm<derivedT, realT>::newCallBack_release( const pcf::IndiProperty &ipRecv )
3193 : {
3194 0 : if( ipRecv.createUniqueKey() != m_indiP_release.createUniqueKey() )
3195 : {
3196 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3197 : }
3198 :
3199 0 : if( !ipRecv.find( "request" ) )
3200 0 : return 0;
3201 :
3202 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3203 : {
3204 0 : return baseReleaseDM();
3205 : }
3206 0 : return 0;
3207 : }
3208 :
3209 : template <class derivedT, typename realT>
3210 0 : int dm<derivedT, realT>::st_newCallBack_flats( void *app, const pcf::IndiProperty &ipRecv )
3211 : {
3212 0 : return static_cast<derivedT *>( app )->newCallBack_flats( ipRecv );
3213 : }
3214 :
3215 : template <class derivedT, typename realT>
3216 0 : int dm<derivedT, realT>::newCallBack_flats( const pcf::IndiProperty &ipRecv )
3217 : {
3218 0 : if( ipRecv.createUniqueKey() != m_indiP_flats.createUniqueKey() )
3219 : {
3220 0 : derivedT::template log<software_error>( { "invalid indi property received" } );
3221 0 : return -1;
3222 : }
3223 :
3224 0 : std::string newFlat;
3225 :
3226 0 : if( ipRecv.find( "default" ) )
3227 : {
3228 0 : if( ipRecv["default"].getSwitchState() == pcf::IndiElement::On )
3229 : {
3230 0 : newFlat = "default";
3231 : }
3232 : }
3233 :
3234 : // always do this to check for error:
3235 0 : for( auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
3236 : {
3237 0 : if( !ipRecv.find( i->first ) )
3238 0 : continue;
3239 :
3240 0 : if( ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3241 : {
3242 0 : if( newFlat != "" )
3243 : {
3244 0 : derivedT::template log<text_log>( "More than one flat selected", logPrio::LOG_ERROR );
3245 0 : return -1;
3246 : }
3247 :
3248 0 : newFlat = i->first;
3249 : }
3250 : }
3251 :
3252 0 : if( newFlat == "" )
3253 : {
3254 0 : return 0;
3255 : }
3256 :
3257 0 : return loadFlat( newFlat );
3258 0 : }
3259 :
3260 : template <class derivedT, typename realT>
3261 0 : int dm<derivedT, realT>::st_newCallBack_setFlat( void *app, const pcf::IndiProperty &ipRecv )
3262 : {
3263 0 : return static_cast<derivedT *>( app )->newCallBack_setFlat( ipRecv );
3264 : }
3265 :
3266 : template <class derivedT, typename realT>
3267 0 : int dm<derivedT, realT>::newCallBack_setFlat( const pcf::IndiProperty &ipRecv )
3268 : {
3269 0 : if( ipRecv.createUniqueKey() != m_indiP_setFlat.createUniqueKey() )
3270 : {
3271 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3272 : }
3273 :
3274 0 : if( !ipRecv.find( "toggle" ) )
3275 0 : return 0;
3276 :
3277 0 : if( ipRecv["toggle"] == pcf::IndiElement::On )
3278 : {
3279 0 : return setFlat();
3280 : }
3281 : else
3282 : {
3283 0 : return zeroFlat();
3284 : }
3285 : }
3286 :
3287 : template <class derivedT, typename realT>
3288 0 : int dm<derivedT, realT>::st_newCallBack_tests( void *app, const pcf::IndiProperty &ipRecv )
3289 : {
3290 0 : return static_cast<derivedT *>( app )->newCallBack_tests( ipRecv );
3291 : }
3292 :
3293 : template <class derivedT, typename realT>
3294 0 : int dm<derivedT, realT>::newCallBack_tests( const pcf::IndiProperty &ipRecv )
3295 : {
3296 0 : if( ipRecv.createUniqueKey() != m_indiP_tests.createUniqueKey() )
3297 : {
3298 0 : derivedT::template log<software_error>( { "invalid indi property received" } );
3299 0 : return -1;
3300 : }
3301 :
3302 0 : std::string newTest;
3303 :
3304 0 : if( ipRecv.find( "default" ) )
3305 : {
3306 0 : if( ipRecv["default"].getSwitchState() == pcf::IndiElement::On )
3307 : {
3308 0 : newTest = "default";
3309 : }
3310 : }
3311 :
3312 : // always do this to check for error:
3313 0 : for( auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
3314 : {
3315 0 : if( !ipRecv.find( i->first ) )
3316 0 : continue;
3317 :
3318 0 : if( ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3319 : {
3320 0 : if( newTest != "" )
3321 : {
3322 0 : derivedT::template log<text_log>( "More than one test selected", logPrio::LOG_ERROR );
3323 0 : return -1;
3324 : }
3325 :
3326 0 : newTest = i->first;
3327 : }
3328 : }
3329 :
3330 0 : if( newTest == "" )
3331 : {
3332 0 : return 0;
3333 : }
3334 :
3335 0 : return loadTest( newTest );
3336 0 : }
3337 :
3338 : template <class derivedT, typename realT>
3339 0 : int dm<derivedT, realT>::st_newCallBack_setTest( void *app, const pcf::IndiProperty &ipRecv )
3340 : {
3341 0 : return static_cast<derivedT *>( app )->newCallBack_setTest( ipRecv );
3342 : }
3343 :
3344 : template <class derivedT, typename realT>
3345 0 : int dm<derivedT, realT>::newCallBack_setTest( const pcf::IndiProperty &ipRecv )
3346 : {
3347 0 : if( ipRecv.createUniqueKey() != m_indiP_setTest.createUniqueKey() )
3348 : {
3349 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3350 : }
3351 :
3352 0 : if( !ipRecv.find( "toggle" ) )
3353 0 : return 0;
3354 :
3355 0 : if( ipRecv["toggle"] == pcf::IndiElement::On )
3356 : {
3357 0 : return setTest();
3358 : }
3359 : else
3360 : {
3361 0 : return zeroTest();
3362 : }
3363 : }
3364 :
3365 : template <class derivedT, typename realT>
3366 0 : int dm<derivedT, realT>::st_newCallBack_zeroAll( void *app, const pcf::IndiProperty &ipRecv )
3367 : {
3368 0 : return static_cast<derivedT *>( app )->newCallBack_zeroAll( ipRecv );
3369 : }
3370 :
3371 : template <class derivedT, typename realT>
3372 0 : int dm<derivedT, realT>::newCallBack_zeroAll( const pcf::IndiProperty &ipRecv )
3373 : {
3374 0 : if( ipRecv.createUniqueKey() != m_indiP_zeroAll.createUniqueKey() )
3375 : {
3376 0 : return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
3377 : }
3378 :
3379 0 : if( !ipRecv.find( "request" ) )
3380 0 : return 0;
3381 :
3382 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
3383 : {
3384 0 : indi::updateSwitchIfChanged(
3385 0 : m_indiP_zeroAll, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY );
3386 :
3387 0 : std::lock_guard<std::mutex> guard( derived().m_indiMutex );
3388 0 : return zeroAll();
3389 0 : }
3390 0 : return 0;
3391 : }
3392 :
3393 : /// Call dmT::setupConfig with error checking for dm
3394 : /**
3395 : * \param cfig the application configurator
3396 : */
3397 : #define DM_SETUP_CONFIG( cfig ) \
3398 : if( dmT::setupConfig( cfig ) < 0 ) \
3399 : { \
3400 : log<software_error>( { "Error from dmT::setupConfig" } ); \
3401 : m_shutdown = true; \
3402 : return; \
3403 : }
3404 :
3405 : /// Call dmT::loadConfig with error checking for dm
3406 : /** This must be inside a function that returns int, e.g. the standard loadConfigImpl.
3407 : * \param cfig the application configurator
3408 : */
3409 : #define DM_LOAD_CONFIG( cfig ) \
3410 : if( dmT::loadConfig( cfig ) < 0 ) \
3411 : { \
3412 : return log<software_error, -1>( { "Error from dmT::loadConfig" } ); \
3413 : }
3414 :
3415 : /// Call shmimMonitorT::appStartup with error checking for dm
3416 : #define DM_APP_STARTUP \
3417 : if( dmT::appStartup() < 0 ) \
3418 : { \
3419 : return log<software_error, -1>( { "Error from dmT::appStartup" } ); \
3420 : }
3421 :
3422 : /// Call dmT::appLogic with error checking for dm
3423 : #define DM_APP_LOGIC \
3424 : if( dmT::appLogic() < 0 ) \
3425 : { \
3426 : return log<software_error, -1>( { "Error from dmT::appLogic" } ); \
3427 : }
3428 :
3429 : /// Call dmT::updateINDI with error checking for dm
3430 : #define DM_UPDATE_INDI \
3431 : if( dmT::updateINDI() < 0 ) \
3432 : { \
3433 : return log<software_error, -1>( { "Error from dmT::updateINDI" } ); \
3434 : }
3435 :
3436 : /// Call dmT::appShutdown with error checking for dm
3437 : #define DM_APP_SHUTDOWN \
3438 : if( dmT::appShutdown() < 0 ) \
3439 : { \
3440 : return log<software_error, -1>( { "Error from dmT::appShutdown" } ); \
3441 : }
3442 :
3443 : } // namespace dev
3444 : } // namespace app
3445 : } // namespace MagAOX
3446 : #endif
|