LCOV - code coverage report
Current view: top level - libMagAOX/app/dev - dm.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 18.6 % 967 180
Test Date: 2026-01-03 21:03:39 Functions: 29.4 % 51 15

            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> &notDeltas() const;
     363              : 
     364              :     const mx::improc::eigenImage<float> &totalFlat() const;
     365              : 
     366              :     /// Setup the configuration system
     367              :     /**
     368              :       * This should be called in `derivedT::setupConfig` as
     369              :       * \code
     370              :         dm<derivedT,realT>::setupConfig(config);
     371              :         \endcode
     372              :       * with appropriate error checking.
     373              :       */
     374              :     int setupConfig( mx::app::appConfigurator &config /**< [out] the derived classes configurator*/ );
     375              : 
     376              :     /// load the configuration system results
     377              :     /**
     378              :       * This should be called in `derivedT::loadConfig` as
     379              :       * \code
     380              :         dm<derivedT,realT>::loadConfig(config);
     381              :         \endcode
     382              :       * with appropriate error checking.
     383              :       */
     384              :     int loadConfig( mx::app::appConfigurator &config /**< [in] the derived classes configurator*/ );
     385              : 
     386              :     /// Startup function
     387              :     /**
     388              :       * This should be called in `derivedT::appStartup` as
     389              :       * \code
     390              :         dm<derivedT,realT>::appStartup();
     391              :         \endcode
     392              :       * with appropriate error checking.
     393              :       *
     394              :       * \returns 0 on success
     395              :       * \returns -1 on error, which is logged.
     396              :       */
     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
        

Generated by: LCOV version 2.0-1