LCOV - code coverage report
Current view: top level - libMagAOX/app/dev - dm.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 18.4 % 982 181
Test Date: 2026-04-15 19:34:29 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            4 : dm<derivedT, realT>::~dm()
     811              : {
     812            9 :     for( auto &mi : m_channels )
     813              :     {
     814            5 :         if( mi != nullptr )
     815              :         {
     816            5 :             delete mi;
     817              :         }
     818              :     }
     819            4 : }
     820              : 
     821              : template <class derivedT, typename realT>
     822            2 : const std::string &dm<derivedT, realT>::calibPath() const
     823              : {
     824            2 :     return m_calibPath;
     825              : }
     826              : 
     827              : template <class derivedT, typename realT>
     828              : const std::string &dm<derivedT, realT>::flatPath() const
     829              : {
     830              :     return m_flatPath;
     831              : }
     832              : 
     833              : template <class derivedT, typename realT>
     834              : const std::string &dm<derivedT, realT>::testPath() const
     835              : {
     836              :     return m_testPath;
     837              : }
     838              : 
     839              : template <class derivedT, typename realT>
     840              : const std::string &dm<derivedT, realT>::flatDefault() const
     841              : {
     842              :     return m_flatDefault;
     843              : }
     844              : 
     845              : template <class derivedT, typename realT>
     846              : const std::string &dm<derivedT, realT>::testDefault() const
     847              : {
     848              :     return m_testDefault;
     849              : }
     850              : 
     851              : template <class derivedT, typename realT>
     852              : const std::string &dm<derivedT, realT>::shmimFlat() const
     853              : {
     854              :     return m_shmimFlat;
     855              : }
     856              : 
     857              : template <class derivedT, typename realT>
     858              : const std::string &dm<derivedT, realT>::shmimTest() const
     859              : {
     860              :     return m_shmimTest;
     861              : }
     862              : 
     863              : template <class derivedT, typename realT>
     864              : const std::string &dm<derivedT, realT>::shmimSat() const
     865              : {
     866              :     return m_shmimSat;
     867              : }
     868              : 
     869              : template <class derivedT, typename realT>
     870              : const std::string &dm<derivedT, realT>::shmimSatPerc() const
     871              : {
     872              :     return m_shmimSatPerc;
     873              : }
     874              : 
     875              : template <class derivedT, typename realT>
     876              : const std::string &dm<derivedT, realT>::shmimShape() const
     877              : {
     878              :     return m_shmimShape;
     879              : }
     880              : 
     881              : template <class derivedT, typename realT>
     882              : const std::string &dm<derivedT, realT>::shmimDelta() const
     883              : {
     884              :     return m_shmimDelta;
     885              : }
     886              : 
     887              : template <class derivedT, typename realT>
     888              : uint32_t dm<derivedT, realT>::dmWidth() const
     889              : {
     890              :     return m_dmWidth;
     891              : }
     892              : 
     893              : template <class derivedT, typename realT>
     894              : uint32_t dm<derivedT, realT>::dmHeight() const
     895              : {
     896              :     return m_dmHeight;
     897              : }
     898              : 
     899              : template <class derivedT, typename realT>
     900              : uint8_t dm<derivedT, realT>::dmDataType() const
     901              : {
     902              :     return m_dmDataType;
     903              : }
     904              : 
     905              : template <class derivedT, typename realT>
     906              : float dm<derivedT, realT>::percThreshold() const
     907              : {
     908              :     return m_percThreshold;
     909              : }
     910              : 
     911              : template <class derivedT, typename realT>
     912              : float dm<derivedT, realT>::intervalSatThreshold() const
     913              : {
     914              :     return m_intervalSatThreshold;
     915              : }
     916              : 
     917              : template <class derivedT, typename realT>
     918              : int dm<derivedT, realT>::intervalSatCountThreshold() const
     919              : {
     920              :     return m_intervalSatCountThreshold;
     921              : }
     922              : 
     923              : template <class derivedT, typename realT>
     924              : const std::vector<std::string> &dm<derivedT, realT>::satTriggerDevice() const
     925              : {
     926              :     return m_satTriggerDevice;
     927              : }
     928              : 
     929              : template <class derivedT, typename realT>
     930              : const std::vector<std::string> &dm<derivedT, realT>::satTriggerProperty() const
     931              : {
     932              :     return m_satTriggerProperty;
     933              : }
     934              : 
     935              : template <class derivedT, typename realT>
     936              : const std::string &dm<derivedT, realT>::calibRelDir() const
     937              : {
     938              :     return m_calibRelDir;
     939              : }
     940              : 
     941              : template <class derivedT, typename realT>
     942            1 : int dm<derivedT, realT>::numChannels() const
     943              : {
     944            1 :     return m_numChannels;
     945              : }
     946              : 
     947              : template <class derivedT, typename realT>
     948            2 : const mx::improc::eigenImage<uint8_t> &dm<derivedT, realT>::instSatMap() const
     949              : {
     950            2 :     return m_instSatMap;
     951              : }
     952              : 
     953              : template <class derivedT, typename realT>
     954            2 : const mx::improc::eigenImage<uint16_t> &dm<derivedT, realT>::accumSatMap() const
     955              : {
     956            2 :     return m_accumSatMap;
     957              : }
     958              : 
     959              : template <class derivedT, typename realT>
     960            2 : const mx::improc::eigenImage<float> &dm<derivedT, realT>::satPercMap() const
     961              : {
     962            2 :     return m_satPercMap;
     963              : }
     964              : 
     965              : template <class derivedT, typename realT>
     966            1 : const std::vector<std::string> &dm<derivedT, realT>::deltaChannels() const
     967              : {
     968            1 :     return m_deltaChannels;
     969              : }
     970              : 
     971              : template <class derivedT, typename realT>
     972            1 : const std::vector<size_t> &dm<derivedT, realT>::notDeltas() const
     973              : {
     974            1 :     return m_notDeltas;
     975              : }
     976              : 
     977              : template <class derivedT, typename realT>
     978            4 : const mx::improc::eigenImage<float> &dm<derivedT, realT>::totalFlat() const
     979              : {
     980            4 :     return m_totalFlat;
     981              : }
     982              : 
     983              : template <class derivedT, typename realT>
     984            3 : int dm<derivedT, realT>::setupConfig( mx::app::appConfigurator &config )
     985              : {
     986           42 :     config.add( "dm.calibPath",
     987              :                 "",
     988              :                 "dm.calibPath",
     989              :                 argType::Required,
     990              :                 "dm",
     991              :                 "calibPath",
     992              :                 false,
     993              :                 "string",
     994              :                 "The path to calibration files, relative to the MagAO-X calibration path." );
     995              : 
     996           42 :     config.add( "dm.flatPath",
     997              :                 "",
     998              :                 "dm.flatPath",
     999              :                 argType::Required,
    1000              :                 "dm",
    1001              :                 "flatPath",
    1002              :                 false,
    1003              :                 "string",
    1004              :                 "The path to flat files.  Default is the calibration path." );
    1005              : 
    1006           42 :     config.add( "dm.flatDefault",
    1007              :                 "",
    1008              :                 "dm.flatDefault",
    1009              :                 argType::Required,
    1010              :                 "dm",
    1011              :                 "flatDefault",
    1012              :                 false,
    1013              :                 "string",
    1014              :                 "The default flat file (path and extension are not required)." );
    1015              : 
    1016           42 :     config.add( "dm.testPath",
    1017              :                 "",
    1018              :                 "dm.testPath",
    1019              :                 argType::Required,
    1020              :                 "dm",
    1021              :                 "testPath",
    1022              :                 false,
    1023              :                 "string",
    1024              :                 "The path to test files.  Default is the calibration path plus /tests." );
    1025              : 
    1026           42 :     config.add( "dm.testDefault",
    1027              :                 "",
    1028              :                 "dm.testDefault",
    1029              :                 argType::Required,
    1030              :                 "dm",
    1031              :                 "testDefault",
    1032              :                 false,
    1033              :                 "string",
    1034              :                 "The default test file (path and extension are not required)." );
    1035              : 
    1036           42 :     config.add( "dm.actMaskPath",
    1037              :                 "",
    1038              :                 "dm.actMaskPath",
    1039              :                 argType::Required,
    1040              :                 "dm",
    1041              :                 "actMaskPath",
    1042              :                 false,
    1043              :                 "string",
    1044              :                 "The path to the actuator mask for this DM, relative to the calib path." );
    1045              : 
    1046              :     // Overriding the shmimMonitor setup so that these all go in the dm section
    1047              :     // Otherwise, would call shmimMonitor<dm<derivedT,realT>>::setupConfig();
    1048              :     ///\todo shmimMonitor now has configSection so this isn't necessary.
    1049           42 :     config.add( "dm.threadPrio",
    1050              :                 "",
    1051              :                 "dm.threadPrio",
    1052              :                 argType::Required,
    1053              :                 "dm",
    1054              :                 "threadPrio",
    1055              :                 false,
    1056              :                 "int",
    1057              :                 "The real-time priority of the dm control thread." );
    1058              : 
    1059           42 :     config.add( "dm.cpuset",
    1060              :                 "",
    1061              :                 "dm.cpuset",
    1062              :                 argType::Required,
    1063              :                 "dm",
    1064              :                 "cpuset",
    1065              :                 false,
    1066              :                 "int",
    1067              :                 "The cpuset for the dm control thread." );
    1068              : 
    1069           42 :     config.add( "dm.shmimName",
    1070              :                 "",
    1071              :                 "dm.shmimName",
    1072              :                 argType::Required,
    1073              :                 "dm",
    1074              :                 "shmimName",
    1075              :                 false,
    1076              :                 "string",
    1077              :                 "The name of the ImageStreamIO shared memory image to monitor for DM comands. Will be used as "
    1078              :                 "/tmp/<shmimName>.im.shm." );
    1079              : 
    1080              :     // end of shmimmonitor overrides
    1081              : 
    1082           42 :     config.add( "dm.shmimFlat",
    1083              :                 "",
    1084              :                 "dm.shmimFlat",
    1085              :                 argType::Required,
    1086              :                 "dm",
    1087              :                 "shmimFlat",
    1088              :                 false,
    1089              :                 "string",
    1090              :                 "The name of the ImageStreamIO shared memory image to write the flat command to.  Default is shmimName "
    1091              :                 "with 00 apended (i.e. dm00disp -> dm00disp00). " );
    1092              : 
    1093           42 :     config.add( "dm.shmimTest",
    1094              :                 "",
    1095              :                 "dm.shmimTest",
    1096              :                 argType::Required,
    1097              :                 "dm",
    1098              :                 "shmimTest",
    1099              :                 false,
    1100              :                 "string",
    1101              :                 "The name of the ImageStreamIO shared memory image to write the test command to.  Default is shmimName "
    1102              :                 "with 01 apended (i.e. dm00disp -> dm00disp01). " );
    1103              : 
    1104           42 :     config.add( "dm.shmimSat",
    1105              :                 "",
    1106              :                 "dm.shmimSat",
    1107              :                 argType::Required,
    1108              :                 "dm",
    1109              :                 "shmimSat",
    1110              :                 false,
    1111              :                 "string",
    1112              :                 "The name of the ImageStreamIO shared memory image to write the saturation map to.  Default is "
    1113              :                 "shmimName with SA apended (i.e. dm00disp -> dm00dispSA).  This is created." );
    1114              : 
    1115           42 :     config.add( "dm.shmimSatPerc",
    1116              :                 "",
    1117              :                 "dm.shmimSatPerc",
    1118              :                 argType::Required,
    1119              :                 "dm",
    1120              :                 "shmimSatPerc",
    1121              :                 false,
    1122              :                 "string",
    1123              :                 "The name of the ImageStreamIO shared memory image to write the saturation percentage map to.  Default "
    1124              :                 "is shmimName with SP apended (i.e. dm00disp -> dm00dispSP).  This is created." );
    1125              : 
    1126           42 :     config.add( "dm.satAvgInt",
    1127              :                 "",
    1128              :                 "dm.satAvgInt",
    1129              :                 argType::Required,
    1130              :                 "dm",
    1131              :                 "satAvgInt",
    1132              :                 false,
    1133              :                 "int",
    1134              :                 "The interval in milliseconds over which saturation "
    1135              :                 "is accumulated before updating.  Default is 100 ms." );
    1136              : 
    1137           42 :     config.add( "dm.satThreadPrio",
    1138              :                 "",
    1139              :                 "dm.satThreadPrio",
    1140              :                 argType::Required,
    1141              :                 "dm",
    1142              :                 "satThreadPrio",
    1143              :                 false,
    1144              :                 "int",
    1145              :                 "The priority for the saturation thread. "
    1146              :                 "Usually ok to be 0." );
    1147              : 
    1148           42 :     config.add( "dm.shmimShape",
    1149              :                 "",
    1150              :                 "dm.shmimShape",
    1151              :                 argType::Required,
    1152              :                 "dm",
    1153              :                 "shmimShape",
    1154              :                 false,
    1155              :                 "string",
    1156              :                 "The name of the ImageStreamIO shared memory image to write the desaturated shape to.  Default is "
    1157              :                 "shmimName with _shape apended (i.e. dm00disp -> dm00disp_shape).  This is created." );
    1158              : 
    1159           42 :     config.add( "dm.shmimDelta",
    1160              :                 "",
    1161              :                 "dm.shmimDelta",
    1162              :                 argType::Required,
    1163              :                 "dm",
    1164              :                 "shmimDelta",
    1165              :                 false,
    1166              :                 "string",
    1167              :                 "The name of the ImageStreamIO shared memory image to write the "
    1168              :                 "desaturated delta-shape to.  Default is "
    1169              :                 "shmimName with _delta apended (i.e. dm00disp -> dm00disp_delta).  This is created." );
    1170              : 
    1171           42 :     config.add( "dm.deltaChannels",
    1172              :                 "",
    1173              :                 "dm.deltaChannels",
    1174              :                 argType::Required,
    1175              :                 "dm",
    1176              :                 "deltaChannels",
    1177              :                 false,
    1178              :                 "vector<string>",
    1179              :                 "The names of the DM channels which are delta commands to be excluded from the total flat." );
    1180              : 
    1181           42 :     config.add( "dm.width",
    1182              :                 "",
    1183              :                 "dm.width",
    1184              :                 argType::Required,
    1185              :                 "dm",
    1186              :                 "width",
    1187              :                 false,
    1188              :                 "string",
    1189              :                 "The width of the DM in actuators." );
    1190              : 
    1191           42 :     config.add( "dm.height",
    1192              :                 "",
    1193              :                 "dm.height",
    1194              :                 argType::Required,
    1195              :                 "dm",
    1196              :                 "height",
    1197              :                 false,
    1198              :                 "string",
    1199              :                 "The height of the DM in actuators." );
    1200              : 
    1201           42 :     config.add( "dm.percThreshold",
    1202              :                 "",
    1203              :                 "dm.percThreshold",
    1204              :                 argType::Required,
    1205              :                 "dm",
    1206              :                 "percThreshold",
    1207              :                 false,
    1208              :                 "float",
    1209              :                 "Threshold on percentage of frames an actuator is saturated over an interval.  Default is 0.98." );
    1210              : 
    1211           42 :     config.add( "dm.intervalSatThreshold",
    1212              :                 "",
    1213              :                 "dm.intervalSatThreshold",
    1214              :                 argType::Required,
    1215              :                 "dm",
    1216              :                 "intervalSatThreshold",
    1217              :                 false,
    1218              :                 "float",
    1219              :                 "Threshold on percentage of actuators which exceed percThreshold in an interval.  Default is 0.5." );
    1220              : 
    1221           42 :     config.add( "dm.intervalSatCountThreshold",
    1222              :                 "",
    1223              :                 "dm.intervalSatCountThreshold",
    1224              :                 argType::Required,
    1225              :                 "dm",
    1226              :                 "intervalSatCountThreshold",
    1227              :                 false,
    1228              :                 "float",
    1229              :                 "Threshold on number of consecutive intervals the intervalSatThreshold is exceeded.  Default is 10." );
    1230              : 
    1231           42 :     config.add( "dm.satTriggerDevice",
    1232              :                 "",
    1233              :                 "dm.satTriggerDevice",
    1234              :                 argType::Required,
    1235              :                 "dm",
    1236              :                 "satTriggerDevice",
    1237              :                 false,
    1238              :                 "vector<string>",
    1239              :                 "Device(s) with a toggle switch to toggle on saturation trigger." );
    1240              : 
    1241           39 :     config.add( "dm.satTriggerProperty",
    1242              :                 "",
    1243              :                 "dm.satTriggerProperty",
    1244              :                 argType::Required,
    1245              :                 "dm",
    1246              :                 "satTriggerProperty",
    1247              :                 false,
    1248              :                 "vector<string>",
    1249              :                 "Property with a toggle switch to toggle on saturation trigger, one per entry in satTriggerDevice." );
    1250              : 
    1251            3 :     return 0;
    1252              : }
    1253              : 
    1254              : template <class derivedT, typename realT>
    1255            3 : int dm<derivedT, realT>::loadConfig( mx::app::appConfigurator &config )
    1256              : {
    1257              : 
    1258            3 :     m_calibPath = derived().m_calibDir + "/" + m_calibRelDir;
    1259            3 :     config( m_calibPath, "dm.calibPath" );
    1260              : 
    1261              :     // setup flats
    1262            3 :     m_flatPath = m_calibPath + "/flats";
    1263            6 :     config( m_flatPath, "dm.flatPath" );
    1264              : 
    1265            3 :     config( m_flatDefault, "dm.flatDefault" );
    1266            3 :     if( m_flatDefault != "" )
    1267              :     {
    1268            0 :         m_flatDefault = mx::ioutils::pathStem( m_flatDefault ); // strip off path and extension if provided.
    1269            0 :         m_flatCurrent = "default";
    1270              :     }
    1271              : 
    1272              :     // setup tests
    1273            3 :     m_testPath = m_calibPath + "/tests";
    1274            6 :     config( m_testPath, "dm.testPath" );
    1275              : 
    1276            3 :     config( m_testDefault, "dm.testDefault" );
    1277            3 :     if( m_testDefault != "" )
    1278              :     {
    1279            0 :         m_testDefault = mx::ioutils::pathStem( m_testDefault ); // strip off path and extension if provided.
    1280            0 :         m_testCurrent = "default";
    1281              :     }
    1282              : 
    1283            6 :     config( m_actMaskPath, "dm.actMaskPath" );
    1284              : 
    1285              :     // Overriding the shmimMonitor setup so that these all go in the dm section
    1286              :     // Otherwise, would call shmimMonitor<dm<derivedT,realT>>::loadConfig(config);
    1287            6 :     config( derived().m_smThreadPrio, "dm.threadPrio" );
    1288            6 :     config( derived().m_smCpuset, "dm.cpuset" );
    1289              : 
    1290            3 :     config( derived().m_shmimName, "dm.shmimName" );
    1291              : 
    1292            3 :     derived().m_getExistingFirst = true;
    1293              :     // end of shmimmonitor overrides
    1294              : 
    1295            3 :     if( derived().m_shmimName != "" )
    1296              :     {
    1297            1 :         m_shmimFlat = derived().m_shmimName + "00";
    1298            1 :         config( m_shmimFlat, "dm.shmimFlat" );
    1299              : 
    1300            1 :         m_shmimTest = derived().m_shmimName + "02";
    1301            1 :         config( m_shmimTest, "dm.shmimTest" );
    1302              : 
    1303            1 :         m_shmimSat = derived().m_shmimName + "ST";
    1304            1 :         config( m_shmimSat, "dm.shmimSat" );
    1305              : 
    1306            1 :         m_shmimSatPerc = derived().m_shmimName + "SP";
    1307            2 :         config( m_shmimSatPerc, "dm.shmimSatPerc" );
    1308              : 
    1309            2 :         config( m_satAvgInt, "dm.satAvgInt" );
    1310              : 
    1311            1 :         config( m_satThreadPrio, "dm.satSatThreadPrio" );
    1312              : 
    1313            1 :         m_shmimShape = derived().m_shmimName + "_shape";
    1314            1 :         config( m_shmimShape, "dm.shmimShape" );
    1315              : 
    1316            1 :         m_shmimDelta = derived().m_shmimName + "_delta";
    1317            1 :         config( m_shmimDelta, "dm.shmimDelta" );
    1318              : 
    1319            1 :         m_shmimDiff = derived().m_shmimName + "_diff";
    1320            2 :         config( m_shmimDiff, "dm.shmimDiff" );
    1321              : 
    1322            2 :         config( m_deltaChannels, "dm.deltaChannels" );
    1323              :     }
    1324              :     else
    1325              :     {
    1326              :         // Avoid unused error
    1327            4 :         config.isSet( "dm.shmimFlat" );
    1328            4 :         config.isSet( "dm.shmimTest" );
    1329            4 :         config.isSet( "dm.shmimSat" );
    1330            4 :         config.isSet( "dm.shmimSatPerc" );
    1331            4 :         config.isSet( "dm.satAvgInt" );
    1332            4 :         config.isSet( "dm.shmimShape" );
    1333            4 :         config.isSet( "dm.shmimDelta" );
    1334            4 :         config.isSet( "dm.deltaChannels" );
    1335              :     }
    1336              : 
    1337            6 :     config( m_dmWidth, "dm.width" );
    1338            6 :     config( m_dmHeight, "dm.height" );
    1339              : 
    1340            6 :     config( m_percThreshold, "dm.percThreshold" );
    1341            6 :     config( m_intervalSatThreshold, "dm.intervalSatThreshold" );
    1342            6 :     config( m_intervalSatCountThreshold, "dm.intervalSatCountThreshold" );
    1343            6 :     config( m_satTriggerDevice, "dm.satTriggerDevice" );
    1344            3 :     config( m_satTriggerProperty, "dm.satTriggerProperty" );
    1345              : 
    1346            3 :     if( m_dmWidth > 0 && m_dmHeight > 0 )
    1347              :     {
    1348              :         try
    1349              :         {
    1350            1 :             m_actMask.create( derived().m_shmimName + "_actmask", m_dmWidth, m_dmHeight );
    1351              :         }
    1352            0 :         catch( const std::exception &e )
    1353              :         {
    1354            0 :             derivedT::template log<text_log>( std::format( "exception caught creating actuator mask: "
    1355              :                                                            "{}: {}",
    1356            0 :                                                            derived().m_shmimName + "_actmask",
    1357            0 :                                                            e.what(),
    1358              :                                                            logPrio::LOG_ERROR ) );
    1359            0 :             return -1;
    1360              :         }
    1361              : 
    1362            1 :         if( m_actMaskPath != "" )
    1363              :         {
    1364            0 :             mx::improc::eigenImage<realT> actMask;
    1365              : 
    1366            0 :             mx::fits::fitsFile<realT> ff;
    1367              : 
    1368            0 :             mx::error_t errc = ff.read( actMask, m_calibPath + '/' + m_actMaskPath );
    1369              : 
    1370            0 :             if( errc != mx::error_t::noerror )
    1371              :             {
    1372            0 :                 derivedT::template log<text_log>( std::format( "error reading actuator mask file {}: "
    1373              :                                                                "{} ({})",
    1374            0 :                                                                m_calibPath + '/' + m_actMaskPath,
    1375            0 :                                                                mx::errorMessage( errc ),
    1376            0 :                                                                mx::errorName( errc ) ),
    1377              :                                                   logPrio::LOG_ERROR );
    1378            0 :                 return -1;
    1379              :             }
    1380              : 
    1381            0 :             if( actMask.rows() != m_dmWidth || actMask.cols() != m_dmHeight )
    1382              :             {
    1383            0 :                 derivedT::template log<text_log>( std::format( "actuaor mask {}x{} is not same size as flag {}x{}",
    1384            0 :                                                                actMask.rows(),
    1385            0 :                                                                actMask.cols(),
    1386            0 :                                                                m_dmWidth,
    1387            0 :                                                                m_dmHeight ),
    1388              :                                                   logPrio::LOG_ERROR );
    1389              : 
    1390            0 :                 return -1;
    1391              :             }
    1392              : 
    1393            0 :             m_actMask = actMask;
    1394            0 :         }
    1395              :         else
    1396              :         {
    1397            1 :             m_actMask().setConstant( 1.0 );
    1398              :         }
    1399              :     }
    1400              : 
    1401            3 :     return 0;
    1402              : }
    1403              : 
    1404              : template <class derivedT, typename realT>
    1405            0 : int dm<derivedT, realT>::appStartup()
    1406              : {
    1407              :     if( m_dmDataType == 0 )
    1408              :     {
    1409              :         derivedT::template log<software_error>( { "unsupported DM data type" } );
    1410              :         return -1;
    1411              :     }
    1412              : 
    1413              :     //-----------------
    1414              :     // Get the flats
    1415            0 :     checkFlats();
    1416              : 
    1417              :     // Register the test shmim INDI property
    1418            0 :     m_indiP_flatShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
    1419            0 :     m_indiP_flatShmim.setDevice( derived().configName() );
    1420            0 :     m_indiP_flatShmim.setName( "flat_shmim" );
    1421            0 :     m_indiP_flatShmim.setPerm( pcf::IndiProperty::ReadOnly );
    1422            0 :     m_indiP_flatShmim.setState( pcf::IndiProperty::Idle );
    1423            0 :     m_indiP_flatShmim.add( pcf::IndiElement( "channel" ) );
    1424            0 :     m_indiP_flatShmim["channel"] = m_shmimFlat;
    1425              : 
    1426            0 :     if( derived().registerIndiPropertyReadOnly( m_indiP_flatShmim ) < 0 )
    1427              :     {
    1428              : #ifndef DM_TEST_NOLOG
    1429            0 :         derivedT::template log<software_error>( { "" } );
    1430              : #endif
    1431            0 :         return -1;
    1432              :     }
    1433              : 
    1434              :     // Register the setFlat INDI property
    1435            0 :     derived().createStandardIndiToggleSw( m_indiP_setFlat, "flat_set" );
    1436            0 :     if( derived().registerIndiPropertyNew( m_indiP_setFlat, st_newCallBack_setFlat ) < 0 )
    1437              :     {
    1438              : #ifndef DM_TEST_NOLOG
    1439            0 :         derivedT::template log<software_error>( { "" } );
    1440              : #endif
    1441            0 :         return -1;
    1442              :     }
    1443              : 
    1444              :     //-----------------
    1445              :     // Get the tests
    1446            0 :     checkTests();
    1447              : 
    1448              :     // Register the test shmim INDI property
    1449            0 :     m_indiP_testShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
    1450            0 :     m_indiP_testShmim.setDevice( derived().configName() );
    1451            0 :     m_indiP_testShmim.setName( "test_shmim" );
    1452            0 :     m_indiP_testShmim.setPerm( pcf::IndiProperty::ReadOnly );
    1453            0 :     m_indiP_testShmim.setState( pcf::IndiProperty::Idle );
    1454            0 :     m_indiP_testShmim.add( pcf::IndiElement( "channel" ) );
    1455            0 :     m_indiP_testShmim["channel"] = m_shmimTest;
    1456            0 :     derived().createStandardIndiToggleSw( m_indiP_setTest, "test_shmim" );
    1457            0 :     if( derived().registerIndiPropertyReadOnly( m_indiP_testShmim ) < 0 )
    1458              :     {
    1459              : #ifndef DM_TEST_NOLOG
    1460            0 :         derivedT::template log<software_error>( { "" } );
    1461              : #endif
    1462            0 :         return -1;
    1463              :     }
    1464              : 
    1465              :     // Register the setTest INDI property
    1466            0 :     derived().createStandardIndiToggleSw( m_indiP_setTest, "test_set" );
    1467            0 :     if( derived().registerIndiPropertyNew( m_indiP_setTest, st_newCallBack_setTest ) < 0 )
    1468              :     {
    1469              : #ifndef DM_TEST_NOLOG
    1470            0 :         derivedT::template log<software_error>( { "" } );
    1471              : #endif
    1472            0 :         return -1;
    1473              :     }
    1474              : 
    1475              :     // Register the init INDI property
    1476            0 :     derived().createStandardIndiRequestSw( m_indiP_init, "initDM" );
    1477            0 :     if( derived().registerIndiPropertyNew( m_indiP_init, st_newCallBack_init ) < 0 )
    1478              :     {
    1479              :         // clang-format off
    1480              :         #ifndef DM_TEST_NOLOG
    1481            0 :         derivedT::template log<software_error>( {""} );
    1482              :         #endif
    1483              :         // clang-format on
    1484              : 
    1485            0 :         return -1;
    1486              :     }
    1487              : 
    1488              :     // Register the zero INDI property
    1489            0 :     derived().createStandardIndiRequestSw( m_indiP_zero, "zeroDM" );
    1490              : 
    1491            0 :     if( derived().registerIndiPropertyNew( m_indiP_zero, st_newCallBack_zero ) < 0 )
    1492              :     {
    1493              :         // clang-format off
    1494              :         #ifndef DM_TEST_NOLOG
    1495            0 :         derivedT::template log<software_error>( {""} );
    1496              :         #endif
    1497              :         // clang-format on
    1498              : 
    1499            0 :         return -1;
    1500              :     }
    1501              : 
    1502              :     // Register the release INDI property
    1503            0 :     derived().createStandardIndiRequestSw( m_indiP_release, "releaseDM" );
    1504            0 :     if( derived().registerIndiPropertyNew( m_indiP_release, st_newCallBack_release ) < 0 )
    1505              :     {
    1506            0 :         return derivedT::template log<software_error, -1>( { "" } );
    1507              :     }
    1508              : 
    1509            0 :     derived().createStandardIndiRequestSw( m_indiP_zeroAll, "zeroAll" );
    1510            0 :     if( derived().registerIndiPropertyNew( m_indiP_zeroAll, st_newCallBack_zeroAll ) < 0 )
    1511              :     {
    1512              : #ifndef DM_TEST_NOLOG
    1513            0 :         derivedT::template log<software_error>( { "" } );
    1514              : #endif
    1515            0 :         return -1;
    1516              :     }
    1517              : 
    1518            0 :     if( m_flatDefault != "" )
    1519              :     {
    1520            0 :         loadFlat( "default" );
    1521              :     }
    1522              : 
    1523            0 :     if( m_testDefault != "" )
    1524              :     {
    1525            0 :         loadTest( "default" );
    1526              :     }
    1527              : 
    1528            0 :     if( sem_init( &m_satSemaphore, 0, 0 ) < 0 )
    1529              :     {
    1530            0 :         return derivedT::template log<software_critical, -1>( { errno, 0, "Initializing sat semaphore" } );
    1531              :     }
    1532              : 
    1533            0 :     if( derived().threadStart( m_satThread,
    1534            0 :                                m_satThreadInit,
    1535            0 :                                m_satThreadID,
    1536            0 :                                m_satThreadProp,
    1537              :                                m_satThreadPrio,
    1538              :                                "",
    1539              :                                "saturation",
    1540              :                                this,
    1541            0 :                                satThreadStart ) < 0 )
    1542              :     {
    1543            0 :         derivedT::template log<software_error, -1>( { "" } );
    1544            0 :         return -1;
    1545              :     }
    1546              : 
    1547            0 :     return 0;
    1548              : }
    1549              : 
    1550              : template <class derivedT, typename realT>
    1551            0 : int dm<derivedT, realT>::appLogic()
    1552              : {
    1553              :     // do a join check to see if other threads have exited.
    1554            0 :     if( pthread_tryjoin_np( m_satThread.native_handle(), 0 ) == 0 )
    1555              :     {
    1556            0 :         derivedT::template log<software_error>( { "saturation thread has exited" } );
    1557              : 
    1558            0 :         return -1;
    1559              :     }
    1560              : 
    1561            0 :     checkFlats();
    1562              : 
    1563            0 :     checkTests();
    1564              : 
    1565            0 :     if( m_intervalSatTrip )
    1566              :     {
    1567            0 :         intervalSatTrip();
    1568            0 :         m_intervalSatTrip = false;
    1569              :     }
    1570              : 
    1571              : #ifdef XWC_DMTIMINGS
    1572              :     static uint64_t lastMono = 0;
    1573              : 
    1574              :     if( m_piTimes.size() >= m_piTimes.maxEntries() && m_piTimes.maxEntries() > 0 && m_piTimes.mono() != lastMono )
    1575              :     {
    1576              :         cbIndexT refEntry = m_piTimes.earliest();
    1577              : 
    1578              :         m_piTimesD.resize( m_piTimes.maxEntries() );
    1579              :         m_satSemD.resize( m_satSem.maxEntries() );
    1580              :         m_actProcD.resize( m_actProc.maxEntries() );
    1581              :         m_actComD.resize( m_actCom.maxEntries() );
    1582              :         m_satUpD.resize( m_satUp.maxEntries() );
    1583              :         m_deltaUpD.resize( m_deltaUp.maxEntries() );
    1584              : 
    1585              :         for( size_t n = 0; n < m_piTimesD.size(); ++n )
    1586              :         {
    1587              :             m_piTimesD[n] = m_piTimes.at( refEntry, n );
    1588              :             m_satSemD[n]  = m_satSem.at( refEntry, n );
    1589              :             m_actProcD[n] = m_actProc.at( refEntry, n );
    1590              :             m_actComD[n]  = m_actCom.at( refEntry, n );
    1591              :             m_satUpD[n]   = m_satUp.at( refEntry, n );
    1592              :             m_deltaUpD[n] = m_deltaUp.at( refEntry, n );
    1593              :         }
    1594              : 
    1595              :         std::cerr << "Act. Process:   " << mx::math::vectorMean( m_actProcD ) << " +/- "
    1596              :                   << sqrt( mx::math::vectorVariance( m_actProcD ) ) << "\n";
    1597              :         std::cerr << "Act. Command:   " << mx::math::vectorMean( m_actComD ) << " +/- "
    1598              :                   << sqrt( mx::math::vectorVariance( m_actComD ) ) << "\n";
    1599              :         std::cerr << "Sat. Update:    " << mx::math::vectorMean( m_satUpD ) << " +/- "
    1600              :                   << sqrt( mx::math::vectorVariance( m_satUpD ) ) << "\n";
    1601              :         std::cerr << "Delta Update:   " << mx::math::vectorMean( m_deltaUpD ) << " +/- "
    1602              :                   << sqrt( mx::math::vectorVariance( m_deltaUpD ) ) << "\n";
    1603              :         std::cerr << "Tot. CommandDM: " << mx::math::vectorMean( m_piTimesD ) << " +/- "
    1604              :                   << sqrt( mx::math::vectorVariance( m_piTimesD ) ) << "\n";
    1605              :         std::cerr << "Sat. Semaphore: " << mx::math::vectorMean( m_satSemD ) << " +/- "
    1606              :                   << sqrt( mx::math::vectorVariance( m_satSemD ) ) << "\n";
    1607              :         std::cerr << "\n";
    1608              : 
    1609              :         lastMono = m_piTimes.mono();
    1610              :     }
    1611              : #endif // XWC_DMTIMINGS
    1612              : 
    1613            0 :     return 0;
    1614              : }
    1615              : 
    1616              : template <class derivedT, typename realT>
    1617            0 : int dm<derivedT, realT>::appShutdown()
    1618              : {
    1619            0 :     if( m_satThread.joinable() )
    1620              :     {
    1621            0 :         pthread_kill( m_satThread.native_handle(), SIGUSR1 );
    1622              :         try
    1623              :         {
    1624            0 :             m_satThread.join(); // this will throw if it was already joined
    1625              :         }
    1626            0 :         catch( ... )
    1627              :         {
    1628              :         }
    1629              :     }
    1630              : 
    1631            0 :     return 0;
    1632              : }
    1633              : 
    1634              : template <class derivedT, typename realT>
    1635              : int dm<derivedT, realT>::onPowerOff()
    1636              : {
    1637              :     baseReleaseDM();
    1638              : 
    1639              :     return 0;
    1640              : }
    1641              : 
    1642              : template <class derivedT, typename realT>
    1643              : int dm<derivedT, realT>::whilePowerOff()
    1644              : {
    1645              :     checkFlats();
    1646              :     checkTests();
    1647              : 
    1648              :     return 0;
    1649              : }
    1650              : 
    1651              : template <class derivedT, typename realT>
    1652            1 : int dm<derivedT, realT>::findDMChannels()
    1653              : {
    1654            1 :     std::string milkShmimDir = mx::sys::getEnv( "MILK_SHM_DIR" );
    1655            1 :     if( milkShmimDir == "" )
    1656              :     {
    1657            0 :         milkShmimDir = "/milk/shm";
    1658              :     }
    1659              : 
    1660            1 :     std::vector<std::string> dmlist;
    1661            3 :     mx::error_t errc = mx::ioutils::getFileNames( dmlist, milkShmimDir, derived().m_shmimName, ".im", ".shm" );
    1662              : 
    1663            1 :     mx_error_check_rv( errc, -1 );
    1664              : 
    1665            1 :     if( dmlist.size() == 0 )
    1666              :     {
    1667            0 :         derivedT::template log<software_error>( { "no dm channels found for " + derived().m_shmimName } );
    1668              : 
    1669            0 :         return -1;
    1670              :     }
    1671              : 
    1672            1 :     m_numChannels = -1;
    1673           11 :     for( size_t n = 0; n < dmlist.size(); ++n )
    1674              :     {
    1675              :         char nstr[16];
    1676           10 :         snprintf( nstr, sizeof( nstr ), "%02d.im.shm", (int)n );
    1677           10 :         std::string tgt = derived().m_shmimName;
    1678           10 :         tgt += nstr;
    1679              : 
    1680          110 :         for( size_t m = 0; m < dmlist.size(); ++m )
    1681              :         {
    1682          100 :             if( dmlist[m].find( tgt ) != std::string::npos )
    1683              :             {
    1684            5 :                 if( (int)n > m_numChannels )
    1685              :                 {
    1686            5 :                     m_numChannels = n;
    1687              :                 }
    1688              :             }
    1689              :         }
    1690              :     }
    1691              : 
    1692            1 :     ++m_numChannels;
    1693              : 
    1694            1 :     derivedT::template log<text_log>(
    1695            1 :         { std::format( "Found {} chanels for {} ", m_numChannels, derived().m_shmimName ) } );
    1696              : 
    1697            1 :     m_channels.resize( m_numChannels, nullptr );
    1698              : 
    1699            1 :     m_notDeltas.clear();
    1700            1 :     m_deltas.clear();
    1701              : 
    1702            6 :     for( size_t n = 0; n < m_channels.size(); ++n )
    1703              :     {
    1704            5 :         std::string sname = std::format( "{}{:02}", derived().m_shmimName, n );
    1705              : 
    1706              :         try
    1707              :         {
    1708            5 :             m_channels[n] = new mx::improc::milkImage<realT>( sname ); // this opens the channel stream
    1709              :         }
    1710            0 :         catch( const std::exception &e )
    1711              :         {
    1712            0 :             derivedT::template log<software_error>( { "exception opening " + sname + ": " + e.what() } );
    1713              :         }
    1714              : 
    1715            5 :         std::cerr << "looking for " << sname << '\n';
    1716            5 :         auto res = std::find( m_deltaChannels.begin(), m_deltaChannels.end(), sname );
    1717            5 :         if( res == m_deltaChannels.end() )
    1718              :         {
    1719            3 :             std::cerr << "  not a delta\n";
    1720            3 :             m_notDeltas.push_back( n );
    1721              :         }
    1722              :         else
    1723              :         {
    1724            2 :             std::cerr << "  is a delta\n";
    1725            2 :             m_deltas.push_back( n );
    1726              :         }
    1727              :     }
    1728              : 
    1729            1 :     std::cerr << "not deltas: ";
    1730            4 :     for( size_t n = 0; n < m_notDeltas.size(); ++n )
    1731              :     {
    1732            3 :         std::cerr << m_notDeltas[n] << ' ';
    1733              :     }
    1734            1 :     std::cerr << '\n';
    1735              : 
    1736            1 :     std::cerr << "deltas: ";
    1737            3 :     for( size_t n = 0; n < m_deltas.size(); ++n )
    1738              :     {
    1739            2 :         std::cerr << m_deltas[n] << ' ';
    1740              :     }
    1741            1 :     std::cerr << '\n';
    1742              : 
    1743            1 :     return 0;
    1744            1 : }
    1745              : 
    1746              : template <class derivedT, typename realT>
    1747            1 : int dm<derivedT, realT>::allocate( const dev::shmimT &sp )
    1748              : {
    1749              :     static_cast<void>( sp ); // be unused
    1750              : 
    1751            1 :     int err = 0;
    1752              : 
    1753            1 :     if( derived().m_width != m_dmWidth )
    1754              :     {
    1755            0 :         derivedT::template log<software_critical>( { "shmim width does not match configured DM width" } );
    1756            0 :         ++err;
    1757              :     }
    1758              : 
    1759            1 :     if( derived().m_height != m_dmHeight )
    1760              :     {
    1761            0 :         derivedT::template log<software_critical>( { "shmim height does not match configured DM height" } );
    1762            0 :         ++err;
    1763              :     }
    1764              : 
    1765            1 :     if( derived().m_dataType != m_dmDataType )
    1766              :     {
    1767            0 :         derivedT::template log<software_critical>( { "shmim data type does not match configured DM data type" } );
    1768            0 :         ++err;
    1769              :     }
    1770              : 
    1771            1 :     if( err )
    1772              :     {
    1773            0 :         return -1;
    1774              :     }
    1775              : 
    1776            1 :     m_instSatMap.resize( m_dmWidth, m_dmHeight );
    1777            1 :     m_instSatMap.setZero();
    1778              : 
    1779            1 :     m_accumSatMap.resize( m_dmWidth, m_dmHeight );
    1780            1 :     m_accumSatMap.setZero();
    1781              : 
    1782            1 :     m_satPercMap.resize( m_dmWidth, m_dmHeight );
    1783            1 :     m_satPercMap.setZero();
    1784              : 
    1785            1 :     if( findDMChannels() < 0 )
    1786              :     {
    1787            0 :         derivedT::template log<software_critical>( { "error finding DM channels" } );
    1788              : 
    1789            0 :         return -1;
    1790              :     }
    1791              : 
    1792              :     try
    1793              :     {
    1794            1 :         m_outputShape.create( m_shmimShape, m_dmWidth, m_dmHeight );
    1795            1 :         m_outputShape().setZero();
    1796              :     }
    1797            0 :     catch( const std::exception &e )
    1798              :     {
    1799            0 :         return derivedT::template log<software_error, -1>(
    1800            0 :             { std::string( "creating output shape shmim: " ) + e.what() } );
    1801              :     }
    1802              : 
    1803              :     try
    1804              :     {
    1805            1 :         m_outputDelta.create( m_shmimDelta, m_dmWidth, m_dmHeight );
    1806            1 :         m_outputDelta().setZero();
    1807              :     }
    1808            0 :     catch( const std::exception &e )
    1809              :     {
    1810            0 :         return derivedT::template log<software_error, -1>(
    1811            0 :             { std::string( "creating output delta shmim: " ) + e.what() } );
    1812              :     }
    1813              : 
    1814              :     try
    1815              :     {
    1816            1 :         m_outputDiff.create( m_shmimDiff, m_dmWidth, m_dmHeight );
    1817            1 :         m_outputDiff().setZero();
    1818              :     }
    1819            0 :     catch( const std::exception &e )
    1820              :     {
    1821            0 :         return derivedT::template log<software_error, -1>(
    1822            0 :             { std::string( "creating output diff shmim: " ) + e.what() } );
    1823              :     }
    1824              : 
    1825            1 :     m_totalFlat.resize( m_dmWidth, m_dmHeight );
    1826            1 :     m_totalFlat.setZero();
    1827              : 
    1828            1 :     m_totalDelta.resize( m_dmWidth, m_dmHeight );
    1829            1 :     m_totalDelta.setZero();
    1830              : 
    1831              :     // clang-format off
    1832              :     #ifdef XWC_DMTIMINGS
    1833              :     m_piTimes.maxEntries( 2000 );
    1834              :     m_satSem.maxEntries( 2000 );
    1835              :     m_actProc.maxEntries( 2000 );
    1836              :     m_actCom.maxEntries( 2000 );
    1837              :     m_satUp.maxEntries( 2000 );
    1838              :     m_deltaUp.maxEntries( 2000 );
    1839              :     #endif // clang-format on
    1840              : 
    1841            1 :     return 0;
    1842              : }
    1843              : 
    1844              : template <class derivedT, typename realT>
    1845            0 : int dm<derivedT, realT>::processImage( void *curr_src, const dev::shmimT &sp )
    1846              : {
    1847              :     static_cast<void>( sp ); // be unused
    1848              : 
    1849              :     // clang-format off
    1850              :     #ifdef XWC_DMTIMINGS
    1851              :     m_t0 = mx::sys::get_curr_time();
    1852              :     #endif // clang-format on
    1853              : 
    1854            0 :     int rv = derived().commandDM( curr_src );
    1855              : 
    1856            0 :     if( rv < 0 )
    1857              :     {
    1858            0 :         derivedT::template log<software_critical>( { errno, rv, "Error from commandDM" } );
    1859            0 :         return rv;
    1860              :     }
    1861              : 
    1862              :     // clang-format off
    1863              :     #ifdef XWC_DMTIMINGS
    1864              :     m_tdelta0 = mx::sys::get_curr_time();
    1865              :     #endif // clang-format on
    1866              : 
    1867            0 :     if( m_deltaChannels.size() > 0 )
    1868              :     {
    1869            0 :         rv = makeDelta();
    1870              : 
    1871            0 :         if( rv < 0 )
    1872              :         {
    1873            0 :             derivedT::template log<software_critical>( { errno, rv, "Error from makeDelta" } );
    1874            0 :             return rv;
    1875              :         }
    1876              :     }
    1877              : 
    1878              :     // clang-format off
    1879              :     #ifdef XWC_DMTIMINGS
    1880              :     m_tdeltaf = mx::sys::get_curr_time();
    1881              : 
    1882              :     m_tf = m_tdeltaf;
    1883              :     #endif // clang-format on
    1884              : 
    1885              :     // clang-format off
    1886              :     #ifdef XWC_DMTIMINGS
    1887              :     m_tsat0 = mx::sys::get_curr_time();
    1888              :     #endif // clang-format on
    1889              : 
    1890              :     // Tell the sat thread to get going
    1891            0 :     if( sem_post( &m_satSemaphore ) < 0 )
    1892              :     {
    1893            0 :         derivedT::template log<software_critical>( { errno, 0, "Error posting to semaphore" } );
    1894            0 :         return -1;
    1895              :     }
    1896              : 
    1897              :     // clang-format off
    1898              :     #ifdef XWC_DMTIMINGS // clang-format on
    1899              : 
    1900              :     m_tsatf = mx::sys::get_curr_time();
    1901              : 
    1902              :     // Update the latency circ. buffs
    1903              :     if( m_piTimes.maxEntries() > 0 )
    1904              :     {
    1905              :         m_piTimes.nextEntry( m_tf - m_t0 );
    1906              :         m_satSem.nextEntry( m_tsatf - m_tsat0 );
    1907              :         m_actProc.nextEntry( m_tact1 - m_tact0 );
    1908              :         m_actCom.nextEntry( m_tact2 - m_tact1 );
    1909              :         m_satUp.nextEntry( m_tact4 - m_tact3 );
    1910              :         m_deltaUp.nextEntry( m_tdeltaf - m_tdelta0 );
    1911              :     }
    1912              : 
    1913              :         // clang-format off
    1914              :     #endif // clang-format on
    1915              : 
    1916            0 :     return rv;
    1917              : }
    1918              : 
    1919              : template <class derivedT, typename realT>
    1920            0 : int dm<derivedT, realT>::baseInitDM()
    1921              : {
    1922            0 :     if( derived().state() != stateCodes::NOTHOMED )
    1923              :     {
    1924            0 :         derivedT::template log<software_error>( { errno, "DM is not ready to be initialized" } );
    1925            0 :         derived().state( stateCodes::ERROR );
    1926            0 :         return -1;
    1927              :     }
    1928              : 
    1929            0 :     derived().state( stateCodes::HOMING );
    1930              : 
    1931              :     int rv;
    1932            0 :     if( ( rv = derived().initDM() ) < 0 )
    1933              :     {
    1934            0 :         derivedT::template log<software_critical>( { errno, rv, "Error from initDM" } );
    1935            0 :         derived().state( stateCodes::ERROR );
    1936            0 :         return rv;
    1937              :     }
    1938              : 
    1939            0 :     return 0;
    1940              : }
    1941              : 
    1942              : template <class derivedT, typename realT>
    1943            0 : int dm<derivedT, realT>::baseReleaseDM()
    1944              : {
    1945            0 :     if( derived().state() != stateCodes::POWEROFF )
    1946              :     {
    1947            0 :         derived().state( stateCodes::NOTHOMED );
    1948              :     }
    1949              : 
    1950              :     int rv;
    1951            0 :     if( ( rv = derived().releaseDM() ) < 0 )
    1952              :     {
    1953            0 :         derivedT::template log<software_critical>( { errno, rv, "Error from releaseDM" } );
    1954            0 :         derived().state( stateCodes::ERROR );
    1955            0 :         return rv;
    1956              :     }
    1957              : 
    1958            0 :     if( ( rv = zeroAll( true ) ) < 0 )
    1959              :     {
    1960            0 :         derivedT::template log<software_error>( { errno, rv, "Error from zeroAll" } );
    1961            0 :         derived().state( stateCodes::ERROR );
    1962            0 :         return rv;
    1963              :     }
    1964              : 
    1965            0 :     return 0;
    1966              : }
    1967              : 
    1968              : template <class derivedT, typename realT>
    1969            0 : int dm<derivedT, realT>::checkFlats()
    1970              : {
    1971            0 :     std::vector<std::string> tfs;
    1972              : 
    1973            0 :     bool gfn_logged = false;
    1974              : 
    1975            0 :     mx::error_t errc = mx::ioutils::getFileNames( tfs, m_flatPath, "", "", ".fits" );
    1976              : 
    1977            0 :     if( errc != mx::error_t::noerror )
    1978              :     {
    1979            0 :         if( !gfn_logged )
    1980              :         {
    1981            0 :             derivedT::template log<software_error>(
    1982            0 :                 { std::format( "error getting flat files: {}", mx::errorMessage( errc ) ) } );
    1983              :         }
    1984            0 :         gfn_logged = true;
    1985            0 :         return -1;
    1986              :     }
    1987              : 
    1988            0 :     gfn_logged = false;
    1989              : 
    1990              :     // First remove default, b/c we always add it and don't want to include it in timestamp selected ones
    1991            0 :     for( size_t n = 0; n < tfs.size(); ++n )
    1992              :     {
    1993            0 :         if( mx::ioutils::pathStem( tfs[n] ) == "default" )
    1994              :         {
    1995            0 :             tfs.erase( tfs.begin() + n );
    1996            0 :             --n;
    1997              :         }
    1998              :     }
    1999              : 
    2000            0 :     unsigned m_nFlatFiles = 5;
    2001              : 
    2002              :     // Here we keep only the m_nFlatFiles most recent files
    2003            0 :     if( tfs.size() >= m_nFlatFiles )
    2004              :     {
    2005            0 :         std::vector<std::filesystem::file_time_type> wtimes( tfs.size() );
    2006              : 
    2007            0 :         for( size_t n = 0; n < wtimes.size(); ++n )
    2008              :         {
    2009            0 :             wtimes[n] = std::filesystem::last_write_time( tfs[n] );
    2010              :         }
    2011              : 
    2012            0 :         std::sort( wtimes.begin(), wtimes.end() );
    2013              : 
    2014            0 :         std::filesystem::file_time_type tn = wtimes[wtimes.size() - m_nFlatFiles];
    2015              : 
    2016            0 :         for( size_t n = 0; n < tfs.size(); ++n )
    2017              :         {
    2018            0 :             std::filesystem::file_time_type lmt = std::filesystem::last_write_time( tfs[n] );
    2019            0 :             if( lmt < tn )
    2020              :             {
    2021            0 :                 tfs.erase( tfs.begin() + n );
    2022            0 :                 --n;
    2023              :             }
    2024              :         }
    2025            0 :     }
    2026              : 
    2027            0 :     for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
    2028              :     {
    2029            0 :         it->second = "";
    2030              :     }
    2031              : 
    2032            0 :     bool changed = false;
    2033            0 :     for( size_t n = 0; n < tfs.size(); ++n )
    2034              :     {
    2035              :         auto ir =
    2036            0 :             m_flatCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
    2037            0 :         if( ir.second == true )
    2038            0 :             changed = true;
    2039              :         else
    2040            0 :             ir.first->second = tfs[n];
    2041              :     }
    2042              : 
    2043            0 :     for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
    2044              :     {
    2045            0 :         if( it->second == "" )
    2046              :         {
    2047            0 :             changed = true;
    2048              :             // Erase the current iterator safely, even if the first one.
    2049            0 :             auto itdel = it;
    2050            0 :             ++it;
    2051            0 :             m_flatCommands.erase( itdel );
    2052            0 :             --it;
    2053              :         };
    2054              :     }
    2055              : 
    2056            0 :     if( changed )
    2057              :     {
    2058            0 :         if( derived().m_indiDriver )
    2059              :         {
    2060            0 :             derived().m_indiDriver->sendDelProperty( m_indiP_flats );
    2061            0 :             derived().m_indiNewCallBacks.erase( m_indiP_flats.createUniqueKey() );
    2062              :         }
    2063              : 
    2064            0 :         m_indiP_flats = pcf::IndiProperty( pcf::IndiProperty::Switch );
    2065            0 :         m_indiP_flats.setDevice( derived().configName() );
    2066            0 :         m_indiP_flats.setName( "flat" );
    2067            0 :         m_indiP_flats.setPerm( pcf::IndiProperty::ReadWrite );
    2068            0 :         m_indiP_flats.setState( pcf::IndiProperty::Idle );
    2069            0 :         m_indiP_flats.setRule( pcf::IndiProperty::OneOfMany );
    2070              : 
    2071              :         // Add the toggle element initialized to Off
    2072            0 :         for( auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it )
    2073              :         {
    2074            0 :             if( it->first == m_flatCurrent || m_flatCurrent == "" )
    2075              :             {
    2076            0 :                 m_indiP_flats.add( pcf::IndiElement( it->first, pcf::IndiElement::On ) );
    2077            0 :                 m_flatCurrent = it->first; // handles the case m_flatCurrent == "" b/c it was not set in config
    2078              :             }
    2079              :             else
    2080              :             {
    2081            0 :                 m_indiP_flats.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
    2082              :             }
    2083              :         }
    2084              : 
    2085            0 :         if( m_flatDefault != "" )
    2086              :         {
    2087            0 :             if( m_flatCurrent == "default" )
    2088              :             {
    2089            0 :                 m_indiP_flats.add( pcf::IndiElement( "default", pcf::IndiElement::On ) );
    2090              :             }
    2091              :             else
    2092              :             {
    2093            0 :                 m_indiP_flats.add( pcf::IndiElement( "default", pcf::IndiElement::Off ) );
    2094              :             }
    2095              :         }
    2096              : 
    2097            0 :         if( derived().registerIndiPropertyNew( m_indiP_flats, st_newCallBack_flats ) < 0 )
    2098              :         {
    2099              :             // clang-format off
    2100              :             #ifndef DM_TEST_NOLOG
    2101            0 :             derivedT::template log<software_error>( {""} );
    2102              :             #endif
    2103              :             // clang-format on
    2104              : 
    2105            0 :             return -1;
    2106              :         }
    2107              : 
    2108            0 :         if( derived().m_indiDriver )
    2109              :         {
    2110            0 :             derived().m_indiDriver->sendDefProperty( m_indiP_flats );
    2111              :         }
    2112              :     }
    2113              : 
    2114            0 :     return 0;
    2115            0 : }
    2116              : 
    2117              : template <class derivedT, typename realT>
    2118            0 : int dm<derivedT, realT>::loadFlat( const std::string &intarget )
    2119              : {
    2120            0 :     std::string target = intarget;
    2121              : 
    2122            0 :     std::string targetPath;
    2123              : 
    2124            0 :     if( target == "default" )
    2125              :     {
    2126            0 :         target     = m_flatDefault;
    2127            0 :         targetPath = m_flatPath + "/" + m_flatDefault + ".fits";
    2128              :     }
    2129              :     else
    2130              :     {
    2131              :         try
    2132              :         {
    2133            0 :             targetPath = m_flatCommands.at( target );
    2134              :         }
    2135            0 :         catch( ... )
    2136              :         {
    2137            0 :             derivedT::template log<text_log>( "flat file " + target + " not found", logPrio::LOG_ERROR );
    2138            0 :             return -1;
    2139              :         }
    2140              :     }
    2141              : 
    2142            0 :     m_flatLoaded = false;
    2143              : 
    2144              :     // load into memory.
    2145            0 :     mx::fits::fitsFile<realT> ff;
    2146              : 
    2147            0 :     mx::error_t errc = ff.read( m_flatCommand, targetPath );
    2148              : 
    2149            0 :     if( errc != mx::error_t::noerror )
    2150              :     {
    2151            0 :         derivedT::template log<text_log>( std::format( "error reading flat file {}: "
    2152              :                                                        "{} ({})",
    2153              :                                                        targetPath,
    2154            0 :                                                        mx::errorMessage( errc ),
    2155            0 :                                                        mx::errorName( errc ) ),
    2156              :                                           logPrio::LOG_ERROR );
    2157            0 :         return -1;
    2158              :     }
    2159              : 
    2160            0 :     if( m_actMask.rows() != m_flatCommand.rows() || m_actMask.cols() != m_flatCommand.cols() )
    2161              :     {
    2162            0 :         derivedT::template log<text_log>( std::format( "actuaor mask {}x{} is not same size as flag {}x{}",
    2163            0 :                                                        m_actMask.rows(),
    2164            0 :                                                        m_actMask.cols(),
    2165            0 :                                                        m_flatCommand.rows(),
    2166            0 :                                                        m_flatCommand.cols() ),
    2167              :                                           logPrio::LOG_ERROR );
    2168              : 
    2169            0 :         return -1;
    2170              :     }
    2171              : 
    2172            0 :     m_flatCommand *= m_actMask();
    2173              : 
    2174            0 :     derivedT::template log<text_log>( "loaded flat file " + targetPath );
    2175            0 :     m_flatLoaded = true;
    2176              : 
    2177            0 :     m_flatCurrent = intarget;
    2178              : 
    2179            0 :     if( m_indiP_flats.find( "default" ) )
    2180              :     {
    2181            0 :         if( m_flatCurrent == "default" )
    2182              :         {
    2183            0 :             m_indiP_flats["default"] = pcf::IndiElement::On;
    2184              :         }
    2185              :         else
    2186              :         {
    2187            0 :             m_indiP_flats["default"] = pcf::IndiElement::Off;
    2188              :         }
    2189              :     }
    2190              : 
    2191            0 :     for( auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
    2192              :     {
    2193            0 :         if( !m_indiP_flats.find( i->first ) )
    2194              :         {
    2195            0 :             continue;
    2196              :         }
    2197              : 
    2198            0 :         if( i->first == m_flatCurrent )
    2199              :         {
    2200            0 :             m_indiP_flats[i->first] = pcf::IndiElement::On;
    2201              :         }
    2202              :         else
    2203              :         {
    2204            0 :             m_indiP_flats[i->first] = pcf::IndiElement::Off;
    2205              :         }
    2206              :     }
    2207              : 
    2208            0 :     if( derived().m_indiDriver )
    2209              :     {
    2210            0 :         derived().m_indiDriver->sendSetProperty( m_indiP_flats );
    2211              :     }
    2212              : 
    2213            0 :     if( m_flatSet )
    2214              :     {
    2215            0 :         setFlat();
    2216              :     }
    2217              : 
    2218            0 :     return 0;
    2219            0 : }
    2220              : 
    2221              : template <class derivedT, typename realT>
    2222            0 : int dm<derivedT, realT>::setFlat( bool update )
    2223              : {
    2224            0 :     if( m_shmimFlat == "" )
    2225              :     {
    2226            0 :         return 0;
    2227              :     }
    2228              : 
    2229            0 :     if( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) )
    2230              :     {
    2231            0 :         derivedT::template log<text_log>( "can not set flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
    2232            0 :         return -1;
    2233              :     }
    2234              : 
    2235            0 :     if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
    2236              :     {
    2237            0 :         derivedT::template log<text_log>( "could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
    2238            0 :         return -1;
    2239              :     }
    2240              : 
    2241            0 :     if( m_flatImageStream.md[0].size[0] != m_dmWidth )
    2242              :     {
    2243            0 :         ImageStreamIO_closeIm( &m_flatImageStream );
    2244            0 :         derivedT::template log<text_log>( "width mismatch between " + m_shmimFlat + " and configured DM",
    2245              :                                           logPrio::LOG_ERROR );
    2246            0 :         return -1;
    2247              :     }
    2248              : 
    2249            0 :     if( m_flatImageStream.md[0].size[1] != m_dmHeight )
    2250              :     {
    2251            0 :         ImageStreamIO_closeIm( &m_flatImageStream );
    2252            0 :         derivedT::template log<text_log>( "height mismatch between " + m_shmimFlat + " and configured DM",
    2253              :                                           logPrio::LOG_ERROR );
    2254            0 :         return -1;
    2255              :     }
    2256              : 
    2257            0 :     if( !m_flatLoaded )
    2258              :     {
    2259            0 :         bool flatSet = m_flatSet;
    2260            0 :         m_flatSet    = false; // make sure we don't loop
    2261              : 
    2262            0 :         if( loadFlat( m_flatCurrent ) < 0 )
    2263              :         {
    2264            0 :             derivedT::template log<text_log>( "error loading flat " + m_flatCurrent, logPrio::LOG_ERROR );
    2265              :         }
    2266            0 :         m_flatSet = flatSet;
    2267              :     }
    2268              : 
    2269            0 :     if( !m_flatLoaded )
    2270              :     {
    2271            0 :         ImageStreamIO_closeIm( &m_flatImageStream );
    2272            0 :         derivedT::template log<text_log>( "no flat loaded", logPrio::LOG_ERROR );
    2273            0 :         return -1;
    2274              :     }
    2275              : 
    2276            0 :     if( m_flatCommand.rows() != m_dmWidth )
    2277              :     {
    2278            0 :         ImageStreamIO_closeIm( &m_flatImageStream );
    2279            0 :         derivedT::template log<text_log>( "width mismatch between flat file and configured DM", logPrio::LOG_ERROR );
    2280            0 :         return -1;
    2281              :     }
    2282              : 
    2283            0 :     if( m_flatCommand.cols() != m_dmHeight )
    2284              :     {
    2285            0 :         ImageStreamIO_closeIm( &m_flatImageStream );
    2286            0 :         derivedT::template log<text_log>( "height mismatch between flat file and configured DM", logPrio::LOG_ERROR );
    2287            0 :         return -1;
    2288              :     }
    2289              : 
    2290            0 :     m_flatImageStream.md->write = 1;
    2291              : 
    2292              :     ///\todo we are assuming that dmXXcomYY is not a cube.  This might be true, but we should add cnt1 handling here
    2293              :     /// anyway.  With bounds checks b/c not everyone handles cnt1 properly.
    2294              :     // Copy
    2295            0 :     memcpy( m_flatImageStream.array.raw, m_flatCommand.data(), m_dmWidth * m_dmHeight * sizeof( realT ) );
    2296              : 
    2297              :     // Set the time of last write
    2298            0 :     clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
    2299              : 
    2300              :     // Set the image acquisition timestamp
    2301            0 :     m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
    2302              : 
    2303            0 :     m_flatImageStream.md->cnt0++;
    2304            0 :     m_flatImageStream.md->write = 0;
    2305              : 
    2306              :     // Post the semaphores
    2307            0 :     ImageStreamIO_sempost( &m_flatImageStream, -1 );
    2308              : 
    2309            0 :     m_flatSet = true;
    2310              : 
    2311            0 :     ImageStreamIO_closeIm( &m_flatImageStream );
    2312              : 
    2313            0 :     derived().state( stateCodes::OPERATING );
    2314              : 
    2315            0 :     if( !update )
    2316              :     {
    2317            0 :         derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
    2318              : 
    2319            0 :         derivedT::template log<text_log>( "flat set" );
    2320              :     }
    2321              : 
    2322            0 :     return 0;
    2323              : }
    2324              : 
    2325              : template <class derivedT, typename realT>
    2326            0 : int dm<derivedT, realT>::zeroFlat()
    2327              : {
    2328            0 :     if( m_shmimFlat == "" )
    2329              :     {
    2330            0 :         return 0;
    2331              :     }
    2332              : 
    2333            0 :     if( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) )
    2334              :     {
    2335            0 :         derivedT::template log<text_log>( "can not zero flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
    2336            0 :         return -1;
    2337              :     }
    2338              : 
    2339            0 :     if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
    2340              :     {
    2341            0 :         derivedT::template log<text_log>( "could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
    2342            0 :         return -1;
    2343              :     }
    2344              : 
    2345            0 :     if( m_flatImageStream.md[0].size[0] != m_dmWidth )
    2346              :     {
    2347            0 :         ImageStreamIO_closeIm( &m_flatImageStream );
    2348            0 :         derivedT::template log<text_log>( "width mismatch between " + m_shmimFlat + " and configured DM",
    2349              :                                           logPrio::LOG_ERROR );
    2350            0 :         return -1;
    2351              :     }
    2352              : 
    2353            0 :     if( m_flatImageStream.md[0].size[1] != m_dmHeight )
    2354              :     {
    2355            0 :         ImageStreamIO_closeIm( &m_flatImageStream );
    2356            0 :         derivedT::template log<text_log>( "height mismatch between " + m_shmimFlat + " and configured DM",
    2357              :                                           logPrio::LOG_ERROR );
    2358            0 :         return -1;
    2359              :     }
    2360              : 
    2361            0 :     m_flatImageStream.md->write = 1;
    2362              : 
    2363              :     ///\todo we are assuming that dmXXcomYY is not a cube.  This might be true, but we should add cnt1 handling here
    2364              :     /// anyway.  With bounds checks b/c not everyone handles cnt1 properly.
    2365              :     // Zero
    2366            0 :     memset( m_flatImageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
    2367              : 
    2368              :     // Set the time of last write
    2369            0 :     clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
    2370              : 
    2371              :     // Set the image acquisition timestamp
    2372            0 :     m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
    2373              : 
    2374            0 :     m_flatImageStream.md->cnt0++;
    2375            0 :     m_flatImageStream.md->write = 0;
    2376            0 :     ImageStreamIO_sempost( &m_flatImageStream, -1 );
    2377              : 
    2378            0 :     m_flatSet = false;
    2379              : 
    2380              :     // Post the semaphore
    2381            0 :     ImageStreamIO_closeIm( &m_flatImageStream );
    2382              : 
    2383            0 :     derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
    2384              : 
    2385            0 :     derivedT::template log<text_log>( "flat zeroed" );
    2386              : 
    2387            0 :     if( derived().zeroDM() < 0 )
    2388              :     {
    2389            0 :         derivedT::template log<software_error>( { "error from zeroDM" } );
    2390              :     }
    2391              : 
    2392            0 :     if( clearSat() < 0 )
    2393              :     {
    2394            0 :         derivedT::template log<software_error>( { "error from clearSat" } );
    2395              :     }
    2396            0 :     derived().state( stateCodes::READY );
    2397              : 
    2398            0 :     return 0;
    2399              : }
    2400              : 
    2401              : template <class derivedT, typename realT>
    2402            0 : int dm<derivedT, realT>::checkTests()
    2403              : {
    2404            0 :     std::vector<std::string> tfs;
    2405              : 
    2406              :     static bool gfn_logged = false; // tracks if not finding the test path is logged
    2407              : 
    2408            0 :     mx::error_t errc = mx::ioutils::getFileNames( tfs, m_testPath, "", "", ".fits" );
    2409              : 
    2410            0 :     if( errc != mx::error_t::noerror )
    2411              :     {
    2412            0 :         if( !gfn_logged )
    2413              :         {
    2414            0 :             derivedT::template log<software_error>(
    2415            0 :                 { std::format( "error getting test files: {}", mx::errorMessage( errc ) ) } );
    2416              :         }
    2417            0 :         gfn_logged = true;
    2418            0 :         return -1;
    2419              :     }
    2420              : 
    2421            0 :     gfn_logged = false;
    2422              : 
    2423            0 :     mx_error_check_rv( errc, -1 );
    2424              : 
    2425            0 :     for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
    2426              :     {
    2427            0 :         it->second = "";
    2428              :     }
    2429              : 
    2430            0 :     bool changed = false;
    2431            0 :     for( size_t n = 0; n < tfs.size(); ++n )
    2432              :     {
    2433              :         auto ir =
    2434            0 :             m_testCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
    2435            0 :         if( ir.second == true )
    2436            0 :             changed = true;
    2437              :         else
    2438            0 :             ir.first->second = tfs[n];
    2439              :     }
    2440              : 
    2441            0 :     for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
    2442              :     {
    2443            0 :         if( it->second == "" )
    2444              :         {
    2445            0 :             changed = true;
    2446              :             // Erase the current iterator safely, even if the first one.
    2447            0 :             auto itdel = it;
    2448            0 :             ++it;
    2449            0 :             m_testCommands.erase( itdel );
    2450            0 :             --it;
    2451              :         };
    2452              :     }
    2453              : 
    2454            0 :     if( changed )
    2455              :     {
    2456            0 :         if( derived().m_indiDriver )
    2457              :         {
    2458            0 :             derived().m_indiDriver->sendDelProperty( m_indiP_tests );
    2459            0 :             derived().m_indiNewCallBacks.erase( m_indiP_tests.createUniqueKey() );
    2460              :         }
    2461              : 
    2462            0 :         m_indiP_tests = pcf::IndiProperty( pcf::IndiProperty::Switch );
    2463            0 :         m_indiP_tests.setDevice( derived().configName() );
    2464            0 :         m_indiP_tests.setName( "test" );
    2465            0 :         m_indiP_tests.setPerm( pcf::IndiProperty::ReadWrite );
    2466            0 :         m_indiP_tests.setState( pcf::IndiProperty::Idle );
    2467            0 :         m_indiP_tests.setRule( pcf::IndiProperty::OneOfMany );
    2468              : 
    2469              :         // Add the toggle element initialized to Off
    2470            0 :         for( auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it )
    2471              :         {
    2472            0 :             if( it->first == m_testCurrent || m_testCurrent == "" )
    2473              :             {
    2474            0 :                 m_indiP_tests.add( pcf::IndiElement( it->first, pcf::IndiElement::On ) );
    2475            0 :                 m_testCurrent = it->first; // Handles the case when m_testCurrent=="" b/c it was not set in config
    2476              :             }
    2477              :             else
    2478              :             {
    2479            0 :                 m_indiP_tests.add( pcf::IndiElement( it->first, pcf::IndiElement::Off ) );
    2480              :             }
    2481              :         }
    2482              : 
    2483            0 :         if( m_testDefault != "" )
    2484              :         {
    2485            0 :             if( m_testCurrent == "default" )
    2486              :             {
    2487            0 :                 m_indiP_tests.add( pcf::IndiElement( "default", pcf::IndiElement::On ) );
    2488              :             }
    2489              :             else
    2490              :             {
    2491            0 :                 m_indiP_tests.add( pcf::IndiElement( "default", pcf::IndiElement::Off ) );
    2492              :             }
    2493              :         }
    2494              : 
    2495            0 :         if( derived().registerIndiPropertyNew( m_indiP_tests, st_newCallBack_tests ) < 0 )
    2496              :         {
    2497              : #ifndef DM_TEST_NOLOG
    2498            0 :             derivedT::template log<software_error>( { "" } );
    2499              : #endif
    2500            0 :             return -1;
    2501              :         }
    2502              : 
    2503            0 :         if( derived().m_indiDriver )
    2504              :         {
    2505            0 :             derived().m_indiDriver->sendDefProperty( m_indiP_tests );
    2506              :         }
    2507              :     }
    2508              : 
    2509            0 :     return 0;
    2510            0 : }
    2511              : 
    2512              : template <class derivedT, typename realT>
    2513            0 : int dm<derivedT, realT>::loadTest( const std::string &intarget )
    2514              : {
    2515            0 :     std::string target = intarget; // store this for later to resolve default next:
    2516              : 
    2517            0 :     if( target == "default" )
    2518              :     {
    2519            0 :         target = m_testDefault;
    2520              :     }
    2521              : 
    2522            0 :     std::string targetPath;
    2523              : 
    2524              :     try
    2525              :     {
    2526            0 :         targetPath = m_testCommands.at( target );
    2527              :     }
    2528            0 :     catch( ... )
    2529              :     {
    2530            0 :         derivedT::template log<text_log>( "test file " + target + " not found", logPrio::LOG_ERROR );
    2531            0 :         return -1;
    2532              :     }
    2533              : 
    2534            0 :     m_testLoaded = false;
    2535              :     // load into memory.
    2536            0 :     mx::fits::fitsFile<realT> ff;
    2537            0 :     mx::error_t               errc = ff.read( m_testCommand, targetPath );
    2538            0 :     if( errc != mx::error_t::noerror )
    2539              :     {
    2540            0 :         derivedT::template log<text_log>( std::format( "error reading test file {}: "
    2541              :                                                        "{} ({})",
    2542              :                                                        targetPath,
    2543            0 :                                                        mx::errorMessage( errc ),
    2544            0 :                                                        mx::errorName( errc ) ),
    2545              :                                           logPrio::LOG_ERROR );
    2546            0 :         return -1;
    2547              :     }
    2548              : 
    2549            0 :     derivedT::template log<text_log>( "loaded test file " + targetPath );
    2550            0 :     m_testLoaded = true;
    2551              : 
    2552            0 :     m_testCurrent = intarget;
    2553              : 
    2554            0 :     if( m_indiP_tests.find( "default" ) )
    2555              :     {
    2556            0 :         if( m_testCurrent == "default" )
    2557              :         {
    2558            0 :             m_indiP_tests["default"] = pcf::IndiElement::On;
    2559              :         }
    2560              :         else
    2561              :         {
    2562            0 :             m_indiP_tests["default"] = pcf::IndiElement::Off;
    2563              :         }
    2564              :     }
    2565              : 
    2566            0 :     for( auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
    2567              :     {
    2568            0 :         if( !m_indiP_tests.find( i->first ) )
    2569              :         {
    2570            0 :             continue;
    2571              :         }
    2572              : 
    2573            0 :         if( i->first == m_testCurrent )
    2574              :         {
    2575            0 :             m_indiP_tests[i->first] = pcf::IndiElement::On;
    2576              :         }
    2577              :         else
    2578              :         {
    2579            0 :             m_indiP_tests[i->first] = pcf::IndiElement::Off;
    2580              :         }
    2581              :     }
    2582              : 
    2583            0 :     if( derived().m_indiDriver )
    2584            0 :         derived().m_indiDriver->sendSetProperty( m_indiP_tests );
    2585              : 
    2586            0 :     if( m_testSet )
    2587            0 :         setTest();
    2588              : 
    2589            0 :     return 0;
    2590            0 : }
    2591              : 
    2592              : template <class derivedT, typename realT>
    2593            0 : int dm<derivedT, realT>::setTest()
    2594              : {
    2595              : 
    2596            0 :     if( m_shmimTest == "" )
    2597            0 :         return 0;
    2598              : 
    2599            0 :     if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
    2600              :     {
    2601            0 :         derivedT::template log<text_log>( "could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
    2602            0 :         return -1;
    2603              :     }
    2604              : 
    2605            0 :     if( m_testImageStream.md->size[0] != m_dmWidth )
    2606              :     {
    2607            0 :         ImageStreamIO_closeIm( &m_testImageStream );
    2608            0 :         derivedT::template log<text_log>( "width mismatch between " + m_shmimTest + " and configured DM",
    2609              :                                           logPrio::LOG_ERROR );
    2610            0 :         return -1;
    2611              :     }
    2612              : 
    2613            0 :     if( m_testImageStream.md->size[1] != m_dmHeight )
    2614              :     {
    2615            0 :         ImageStreamIO_closeIm( &m_testImageStream );
    2616            0 :         derivedT::template log<text_log>( "height mismatch between " + m_shmimTest + " and configured DM",
    2617              :                                           logPrio::LOG_ERROR );
    2618            0 :         return -1;
    2619              :     }
    2620              : 
    2621            0 :     if( !m_testLoaded )
    2622              :     {
    2623            0 :         bool testSet = m_testSet;
    2624            0 :         m_testSet    = false; // make sure we don't loop
    2625              : 
    2626            0 :         if( loadTest( m_testCurrent ) < 0 )
    2627              :         {
    2628            0 :             derivedT::template log<text_log>( "error loading test " + m_testCurrent, logPrio::LOG_ERROR );
    2629              :         }
    2630            0 :         m_testSet = testSet;
    2631              :     }
    2632              : 
    2633            0 :     if( !m_testLoaded )
    2634              :     {
    2635            0 :         ImageStreamIO_closeIm( &m_testImageStream );
    2636            0 :         derivedT::template log<text_log>( "no test loaded", logPrio::LOG_ERROR );
    2637            0 :         return -1;
    2638              :     }
    2639              : 
    2640            0 :     if( m_testCommand.rows() != m_dmWidth )
    2641              :     {
    2642            0 :         ImageStreamIO_closeIm( &m_testImageStream );
    2643            0 :         derivedT::template log<text_log>( "width mismatch between test file and configured DM", logPrio::LOG_ERROR );
    2644            0 :         return -1;
    2645              :     }
    2646              : 
    2647            0 :     if( m_testCommand.cols() != m_dmHeight )
    2648              :     {
    2649            0 :         ImageStreamIO_closeIm( &m_testImageStream );
    2650            0 :         derivedT::template log<text_log>( "height mismatch between test file and configured DM", logPrio::LOG_ERROR );
    2651            0 :         return -1;
    2652              :     }
    2653              : 
    2654            0 :     m_testImageStream.md->write = 1;
    2655              : 
    2656              :     ///\todo we are assuming that dmXXcomYY is not a cube.  This might be true, but we should add cnt1 handling here
    2657              :     /// anyway.  With bounds checks b/c not everyone handles cnt1 properly.
    2658              :     // Copy
    2659            0 :     memcpy( m_testImageStream.array.raw, m_testCommand.data(), m_dmWidth * m_dmHeight * sizeof( realT ) );
    2660              : 
    2661              :     // Set the time of last write
    2662            0 :     clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
    2663              : 
    2664              :     // Set the image acquisition timestamp
    2665            0 :     m_testImageStream.md->atime = m_testImageStream.md->writetime;
    2666              : 
    2667            0 :     m_testImageStream.md->cnt0++;
    2668            0 :     m_testImageStream.md->write = 0;
    2669            0 :     ImageStreamIO_sempost( &m_testImageStream, -1 );
    2670              : 
    2671            0 :     m_testSet = true;
    2672              : 
    2673              :     // Post the semaphore
    2674            0 :     ImageStreamIO_closeIm( &m_testImageStream );
    2675              : 
    2676            0 :     derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
    2677              : 
    2678            0 :     derivedT::template log<text_log>( "test set" );
    2679              : 
    2680            0 :     return 0;
    2681              : }
    2682              : 
    2683              : template <class derivedT, typename realT>
    2684            0 : int dm<derivedT, realT>::zeroTest()
    2685              : {
    2686            0 :     if( m_shmimTest == "" )
    2687            0 :         return 0;
    2688              : 
    2689            0 :     if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
    2690              :     {
    2691            0 :         derivedT::template log<text_log>( "could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
    2692            0 :         return -1;
    2693              :     }
    2694              : 
    2695            0 :     if( m_testImageStream.md[0].size[0] != m_dmWidth )
    2696              :     {
    2697            0 :         ImageStreamIO_closeIm( &m_testImageStream );
    2698            0 :         derivedT::template log<text_log>( "width mismatch between " + m_shmimTest + " and configured DM",
    2699              :                                           logPrio::LOG_ERROR );
    2700            0 :         return -1;
    2701              :     }
    2702              : 
    2703            0 :     if( m_testImageStream.md[0].size[1] != m_dmHeight )
    2704              :     {
    2705            0 :         ImageStreamIO_closeIm( &m_testImageStream );
    2706            0 :         derivedT::template log<text_log>( "height mismatch between " + m_shmimTest + " and configured DM",
    2707              :                                           logPrio::LOG_ERROR );
    2708            0 :         return -1;
    2709              :     }
    2710              : 
    2711            0 :     m_testImageStream.md->write = 1;
    2712              : 
    2713              :     ///\todo we are assuming that dmXXcomYY is not a cube.  This might be true, but we should add cnt1 handling here
    2714              :     /// anyway.  With bounds checks b/c not everyone handles cnt1 properly.
    2715              :     // Zero
    2716            0 :     memset( m_testImageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
    2717              : 
    2718              :     // Set the time of last write
    2719            0 :     clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
    2720              : 
    2721              :     // Set the image acquisition timestamp
    2722            0 :     m_testImageStream.md->atime = m_testImageStream.md->writetime;
    2723              : 
    2724            0 :     m_testImageStream.md->cnt0++;
    2725            0 :     m_testImageStream.md->write = 0;
    2726              : 
    2727              :     // Post the semaphore
    2728            0 :     ImageStreamIO_sempost( &m_testImageStream, -1 );
    2729              : 
    2730            0 :     m_testSet = false;
    2731              : 
    2732            0 :     ImageStreamIO_closeIm( &m_testImageStream );
    2733              : 
    2734            0 :     derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
    2735              : 
    2736            0 :     derivedT::template log<text_log>( "test zeroed" );
    2737              : 
    2738            0 :     return 0;
    2739              : }
    2740              : 
    2741              : template <class derivedT, typename realT>
    2742            0 : int dm<derivedT, realT>::zeroAll( bool nosem )
    2743              : {
    2744            0 :     if( derived().m_shmimName == "" )
    2745              :     {
    2746            0 :         return 0;
    2747              :     }
    2748              : 
    2749              :     IMAGE imageStream;
    2750              : 
    2751            0 :     for( int n = 0; n < m_numChannels; ++n )
    2752              :     {
    2753              :         char nstr[16];
    2754            0 :         snprintf( nstr, sizeof( nstr ), "%02d", n );
    2755            0 :         std::string shmimN = derived().m_shmimName + nstr;
    2756              : 
    2757            0 :         if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
    2758              :         {
    2759            0 :             derivedT::template log<text_log>( "could not connect to channel " + shmimN, logPrio::LOG_WARNING );
    2760            0 :             continue;
    2761              :         }
    2762              : 
    2763            0 :         if( imageStream.md->size[0] != m_dmWidth )
    2764              :         {
    2765            0 :             ImageStreamIO_closeIm( &imageStream );
    2766            0 :             derivedT::template log<text_log>( "width mismatch between " + shmimN + " and configured DM",
    2767              :                                               logPrio::LOG_ERROR );
    2768            0 :             derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
    2769            0 :             return -1;
    2770              :         }
    2771              : 
    2772            0 :         if( imageStream.md->size[1] != m_dmHeight )
    2773              :         {
    2774            0 :             ImageStreamIO_closeIm( &imageStream );
    2775            0 :             derivedT::template log<text_log>( "height mismatch between " + shmimN + " and configured DM",
    2776              :                                               logPrio::LOG_ERROR );
    2777            0 :             derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
    2778            0 :             return -1;
    2779              :         }
    2780              : 
    2781            0 :         imageStream.md->write = 1;
    2782            0 :         memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * sizeof( realT ) );
    2783              : 
    2784            0 :         clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
    2785              : 
    2786              :         // Set the image acquisition timestamp
    2787            0 :         imageStream.md->atime = imageStream.md->writetime;
    2788              : 
    2789            0 :         imageStream.md->cnt0++;
    2790            0 :         imageStream.md->write = 0;
    2791              : 
    2792              :         // Raise the semaphore on last one.
    2793            0 :         if( n == m_numChannels - 1 && !nosem )
    2794              :         {
    2795            0 :             ImageStreamIO_sempost( &imageStream, -1 );
    2796              :         }
    2797              : 
    2798            0 :         ImageStreamIO_closeIm( &imageStream );
    2799              :     }
    2800              : 
    2801            0 :     derivedT::template log<text_log>( "all channels zeroed", logPrio::LOG_NOTICE );
    2802              : 
    2803            0 :     derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
    2804              : 
    2805              :     // Also cleanup flat and test
    2806            0 :     m_flatSet = false;
    2807            0 :     derived().updateSwitchIfChanged( m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
    2808            0 :     if( derived().state() == stateCodes::OPERATING )
    2809              :     {
    2810            0 :         derived().state( stateCodes::READY );
    2811              :     }
    2812              : 
    2813              :     // Also cleanup flat and test
    2814            0 :     m_testSet = false;
    2815            0 :     derived().updateSwitchIfChanged( m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
    2816              : 
    2817              :     int rv;
    2818            0 :     if( ( rv = clearSat() ) < 0 )
    2819              :     {
    2820            0 :         derivedT::template log<software_error>( { errno, rv, "Error from clearSat" } );
    2821            0 :         return rv;
    2822              :     }
    2823              : 
    2824            0 :     return 0;
    2825              : }
    2826              : 
    2827              : template <class derivedT, typename realT>
    2828            1 : int dm<derivedT, realT>::makeDelta()
    2829              : {
    2830            1 :     if( m_notDeltas.size() == 0 )
    2831              :     {
    2832            0 :         return 0;
    2833              :     }
    2834              : 
    2835            1 :     m_totalFlat = ( *m_channels[m_notDeltas[0]] )();
    2836              : 
    2837            3 :     for( size_t n = 1; n < m_notDeltas.size(); ++n )
    2838              :     {
    2839            2 :         m_totalFlat += ( *m_channels[m_notDeltas[n]] )();
    2840              :     }
    2841              : 
    2842            1 :     m_outputDelta = m_outputShape() - m_totalFlat; // this posts and everything
    2843              : 
    2844            1 :     m_totalDelta = ( *m_channels[m_deltas[0]] )();
    2845              : 
    2846            2 :     for( size_t n = 1; n < m_deltas.size(); ++n )
    2847              :     {
    2848            1 :         m_totalDelta += ( *m_channels[m_deltas[n]] )();
    2849              :     }
    2850              : 
    2851            1 :     m_outputDiff = m_totalDelta - m_outputDelta();
    2852              : 
    2853            1 :     return 0;
    2854              : }
    2855              : 
    2856              : template <class derivedT, typename realT>
    2857            0 : int dm<derivedT, realT>::clearSat()
    2858              : {
    2859            0 :     if( m_shmimSat == "" || m_dmWidth == 0 || m_dmHeight == 0 )
    2860              :     {
    2861            0 :         return 0;
    2862              :     }
    2863              : 
    2864              :     IMAGE imageStream;
    2865              : 
    2866            0 :     std::vector<std::string> sats = { m_shmimSat, m_shmimSatPerc };
    2867              : 
    2868            0 :     for( size_t n = 0; n < sats.size(); ++n )
    2869              :     {
    2870            0 :         std::string shmimN = sats[n];
    2871              : 
    2872            0 :         if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
    2873              :         {
    2874            0 :             derivedT::template log<text_log>( "could not connect to sat map " + shmimN, logPrio::LOG_WARNING );
    2875            0 :             return 0;
    2876              :         }
    2877              : 
    2878            0 :         if( imageStream.md->size[0] != m_dmWidth )
    2879              :         {
    2880            0 :             ImageStreamIO_closeIm( &imageStream );
    2881            0 :             derivedT::template log<text_log>( "width mismatch between " + shmimN + " and configured DM",
    2882              :                                               logPrio::LOG_ERROR );
    2883            0 :             derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
    2884            0 :             return -1;
    2885              :         }
    2886              : 
    2887            0 :         if( imageStream.md->size[1] != m_dmHeight )
    2888              :         {
    2889            0 :             ImageStreamIO_closeIm( &imageStream );
    2890            0 :             derivedT::template log<text_log>( "height mismatch between " + shmimN + " and configured DM",
    2891              :                                               logPrio::LOG_ERROR );
    2892            0 :             derived().updateSwitchIfChanged( m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE );
    2893            0 :             return -1;
    2894              :         }
    2895              : 
    2896            0 :         imageStream.md->write = 1;
    2897            0 :         memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * ImageStreamIO_typesize( imageStream.md->datatype ) );
    2898              : 
    2899            0 :         clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
    2900              : 
    2901              :         // Set the image acquisition timestamp
    2902            0 :         imageStream.md->atime = imageStream.md->writetime;
    2903              : 
    2904            0 :         imageStream.md->cnt0++;
    2905            0 :         imageStream.md->write = 0;
    2906            0 :         ImageStreamIO_sempost( &imageStream, -1 );
    2907              : 
    2908            0 :         ImageStreamIO_closeIm( &imageStream );
    2909              :     }
    2910              : 
    2911            0 :     m_accumSatMap.setZero();
    2912            0 :     m_instSatMap.setZero();
    2913              : 
    2914            0 :     return 0;
    2915            0 : }
    2916              : 
    2917              : template <class derivedT, typename realT>
    2918            0 : void dm<derivedT, realT>::satThreadStart( dm *d )
    2919              : {
    2920            0 :     d->satThreadExec();
    2921            0 : }
    2922              : 
    2923              : template <class derivedT, typename realT>
    2924            0 : void dm<derivedT, realT>::satThreadExec()
    2925              : {
    2926              :     // Get the thread PID immediately so the caller can return.
    2927            0 :     m_satThreadID = syscall( SYS_gettid );
    2928              : 
    2929              :     // Wait for the thread starter to finish initializing this thread.
    2930            0 :     while( m_satThreadInit == true && derived().shutdown() == 0 )
    2931              :     {
    2932            0 :         sleep( 1 );
    2933              :     }
    2934              : 
    2935            0 :     if( derived().shutdown() )
    2936              :     {
    2937            0 :         return;
    2938              :     }
    2939              : 
    2940            0 :     uint32_t imsize[3] = { 0, 0, 0 };
    2941              : 
    2942              :     // Check for allocation to have happened.
    2943            0 :     while( ( m_shmimSat == "" || m_accumSatMap.rows() == 0 || m_accumSatMap.cols() == 0 ) && !derived().shutdown() )
    2944              :     {
    2945            0 :         sleep( 1 );
    2946              :     }
    2947              : 
    2948            0 :     if( derived().shutdown() )
    2949              :     {
    2950            0 :         return;
    2951              :     }
    2952              : 
    2953            0 :     imsize[0] = m_dmWidth;
    2954            0 :     imsize[1] = m_dmHeight;
    2955            0 :     imsize[2] = 1;
    2956              : 
    2957            0 :     ImageStreamIO_createIm_gpu( &m_satImageStream,
    2958              :                                 m_shmimSat.c_str(),
    2959              :                                 3,
    2960              :                                 imsize,
    2961              :                                 IMAGESTRUCT_UINT8,
    2962              :                                 -1,
    2963              :                                 1,
    2964              :                                 IMAGE_NB_SEMAPHORE,
    2965              :                                 0,
    2966              :                                 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
    2967              :                                 0 );
    2968            0 :     ImageStreamIO_createIm_gpu( &m_satPercImageStream,
    2969              :                                 m_shmimSatPerc.c_str(),
    2970              :                                 3,
    2971              :                                 imsize,
    2972              :                                 IMAGESTRUCT_FLOAT,
    2973              :                                 -1,
    2974              :                                 1,
    2975              :                                 IMAGE_NB_SEMAPHORE,
    2976              :                                 0,
    2977              :                                 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
    2978              :                                 0 );
    2979              : 
    2980            0 :     bool opened = true;
    2981              : 
    2982            0 :     m_satImageStream.md->cnt1     = 0;
    2983            0 :     m_satPercImageStream.md->cnt1 = 0;
    2984              : 
    2985              :     // This is the working memory for making the 1/0 mask out of m_accumSatMap
    2986            0 :     mx::improc::eigenImage<uint8_t> satmap( m_dmWidth, m_dmHeight );
    2987              : 
    2988            0 :     int    naccum    = 0;
    2989            0 :     double t_accumst = mx::sys::get_curr_time();
    2990              : 
    2991              :     // This is the main image grabbing loop.
    2992            0 :     while( !derived().shutdown() )
    2993              :     {
    2994              :         // Get timespec for sem_timedwait
    2995              :         timespec ts;
    2996            0 :         if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
    2997              :         {
    2998            0 :             derivedT::template log<software_critical>( { errno, 0, "clock_gettime" } );
    2999            0 :             return;
    3000              :         }
    3001            0 :         ts.tv_sec += 1;
    3002              : 
    3003              :         // Wait on semaphore
    3004            0 :         if( sem_timedwait( &m_satSemaphore, &ts ) == 0 )
    3005              :         {
    3006              :             // not a timeout -->accumulate
    3007            0 :             for( int rr = 0; rr < m_instSatMap.rows(); ++rr )
    3008              :             {
    3009            0 :                 for( int cc = 0; cc < m_instSatMap.cols(); ++cc )
    3010              :                 {
    3011            0 :                     m_accumSatMap( rr, cc ) += m_instSatMap( rr, cc );
    3012              :                 }
    3013              :             }
    3014            0 :             ++naccum;
    3015              : 
    3016              :             // If less than avg int --> go back and wait again
    3017            0 :             if( mx::sys::get_curr_time( ts ) - t_accumst < m_satAvgInt / 1000.0 )
    3018              :             {
    3019            0 :                 continue;
    3020              :             }
    3021              : 
    3022              :             // If greater than avg int --> calc stats, write to streams.
    3023            0 :             m_overSatAct = 0;
    3024            0 :             for( int rr = 0; rr < m_instSatMap.rows(); ++rr )
    3025              :             {
    3026            0 :                 for( int cc = 0; cc < m_instSatMap.cols(); ++cc )
    3027              :                 {
    3028            0 :                     m_satPercMap( rr, cc ) = m_accumSatMap( rr, cc ) / naccum;
    3029            0 :                     if( m_satPercMap( rr, cc ) >= m_percThreshold )
    3030              :                     {
    3031            0 :                         ++m_overSatAct;
    3032              :                     }
    3033            0 :                     satmap( rr, cc ) = ( m_accumSatMap( rr, cc ) > 0 ); // it's  1/0 map
    3034              :                 }
    3035              :             }
    3036              : 
    3037              :             // Check of the number of actuators saturated above the percent threshold is greater than the number
    3038              :             // threshold if it is, increment the counter
    3039            0 :             if( m_overSatAct / ( m_satPercMap.rows() * m_satPercMap.cols() * 0.75 ) > m_intervalSatThreshold )
    3040              :             {
    3041            0 :                 ++m_intervalSatExceeds;
    3042              :             }
    3043              :             else
    3044              :             {
    3045            0 :                 m_intervalSatExceeds = 0;
    3046              :             }
    3047              : 
    3048              :             // If enough consecutive intervals exceed the count threshold, we trigger
    3049            0 :             if( m_intervalSatExceeds >= m_intervalSatCountThreshold )
    3050              :             {
    3051            0 :                 m_intervalSatTrip = true;
    3052              :             }
    3053              : 
    3054            0 :             m_satImageStream.md->write     = 1;
    3055            0 :             m_satPercImageStream.md->write = 1;
    3056              : 
    3057            0 :             memcpy( m_satImageStream.array.raw, satmap.data(), m_dmWidth * m_dmHeight * sizeof( uint8_t ) );
    3058            0 :             memcpy( m_satPercImageStream.array.raw, m_satPercMap.data(), m_dmWidth * m_dmHeight * sizeof( float ) );
    3059              : 
    3060              :             // Set the time of last write
    3061            0 :             clock_gettime( CLOCK_REALTIME, &m_satImageStream.md->writetime );
    3062            0 :             m_satPercImageStream.md->writetime = m_satImageStream.md->writetime;
    3063              : 
    3064              :             // Set the image acquisition timestamp
    3065            0 :             m_satImageStream.md->atime     = m_satImageStream.md->writetime;
    3066            0 :             m_satPercImageStream.md->atime = m_satPercImageStream.md->writetime;
    3067              : 
    3068              :             // Update cnt1
    3069            0 :             m_satImageStream.md->cnt1     = 0;
    3070            0 :             m_satPercImageStream.md->cnt1 = 0;
    3071              : 
    3072              :             // Update cnt0
    3073            0 :             m_satImageStream.md->cnt0++;
    3074            0 :             m_satPercImageStream.md->cnt0++;
    3075              : 
    3076            0 :             m_satImageStream.writetimearray[0] = m_satImageStream.md->writetime;
    3077            0 :             m_satImageStream.atimearray[0]     = m_satImageStream.md->atime;
    3078            0 :             m_satImageStream.cntarray[0]       = m_satImageStream.md->cnt0;
    3079              : 
    3080            0 :             m_satPercImageStream.writetimearray[0] = m_satPercImageStream.md->writetime;
    3081            0 :             m_satPercImageStream.atimearray[0]     = m_satPercImageStream.md->atime;
    3082            0 :             m_satPercImageStream.cntarray[0]       = m_satPercImageStream.md->cnt0;
    3083              : 
    3084              :             // And post
    3085            0 :             m_satImageStream.md->write = 0;
    3086            0 :             ImageStreamIO_sempost( &m_satImageStream, -1 );
    3087              : 
    3088            0 :             m_satPercImageStream.md->write = 0;
    3089            0 :             ImageStreamIO_sempost( &m_satPercImageStream, -1 );
    3090              : 
    3091            0 :             m_accumSatMap.setZero();
    3092            0 :             naccum    = 0;
    3093            0 :             t_accumst = mx::sys::get_curr_time( ts );
    3094              :         }
    3095              :         else
    3096              :         {
    3097              :             // Check for why we timed out
    3098            0 :             if( errno == EINTR )
    3099              :             {
    3100            0 :                 break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit normally if
    3101              :                        // flags set.
    3102              :             }
    3103              : 
    3104              :             // ETIMEDOUT just means we should wait more.
    3105              :             // Otherwise, report an error.
    3106            0 :             if( errno != ETIMEDOUT )
    3107              :             {
    3108            0 :                 derivedT::template log<software_error>( { errno, "sem_timedwait" } );
    3109            0 :                 break;
    3110              :             }
    3111              :         }
    3112              :     }
    3113              : 
    3114            0 :     if( opened )
    3115              :     {
    3116            0 :         ImageStreamIO_destroyIm( &m_satImageStream );
    3117              : 
    3118            0 :         ImageStreamIO_destroyIm( &m_satPercImageStream );
    3119              :     }
    3120            0 : }
    3121              : 
    3122              : template <class derivedT, typename realT>
    3123            0 : void dm<derivedT, realT>::intervalSatTrip()
    3124              : {
    3125            0 :     if( m_satTriggerDevice.size() > 0 && m_satTriggerProperty.size() == m_satTriggerDevice.size() )
    3126              :     {
    3127            0 :         for( size_t n = 0; n < m_satTriggerDevice.size(); ++n )
    3128              :         {
    3129              :             // We just silently fail
    3130              :             try
    3131              :             {
    3132            0 :                 pcf::IndiProperty ipFreq( pcf::IndiProperty::Switch );
    3133              : 
    3134            0 :                 ipFreq.setDevice( m_satTriggerDevice[n] );
    3135            0 :                 ipFreq.setName( m_satTriggerProperty[n] );
    3136            0 :                 ipFreq.add( pcf::IndiElement( "toggle" ) );
    3137            0 :                 ipFreq["toggle"] = pcf::IndiElement::Off;
    3138            0 :                 derived().sendNewProperty( ipFreq );
    3139              : 
    3140            0 :                 derivedT::template log<text_log>( "DM saturation threshold exceeded.  Loop opened.",
    3141              :                                                   logPrio::LOG_WARNING );
    3142            0 :             }
    3143            0 :             catch( ... )
    3144              :             {
    3145              :             }
    3146              :         }
    3147              :     }
    3148            0 : }
    3149              : 
    3150              : template <class derivedT, typename realT>
    3151            0 : int dm<derivedT, realT>::updateINDI()
    3152              : {
    3153            0 :     if( !derived().m_indiDriver )
    3154              :     {
    3155            0 :         return 0;
    3156              :     }
    3157              : 
    3158            0 :     return 0;
    3159              : }
    3160              : 
    3161              : template <class derivedT, typename realT>
    3162            0 : int dm<derivedT, realT>::st_newCallBack_init( void *app, const pcf::IndiProperty &ipRecv )
    3163              : {
    3164            0 :     return static_cast<derivedT *>( app )->newCallBack_init( ipRecv );
    3165              : }
    3166              : 
    3167              : template <class derivedT, typename realT>
    3168            0 : int dm<derivedT, realT>::newCallBack_init( const pcf::IndiProperty &ipRecv )
    3169              : {
    3170            0 :     if( ipRecv.createUniqueKey() != m_indiP_init.createUniqueKey() )
    3171              :     {
    3172            0 :         return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
    3173              :     }
    3174              : 
    3175            0 :     if( !ipRecv.find( "request" ) )
    3176              :     {
    3177            0 :         return 0;
    3178              :     }
    3179              : 
    3180            0 :     if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
    3181              :     {
    3182            0 :         int rv = baseInitDM();
    3183            0 :         if( rv < 0 )
    3184              :         {
    3185            0 :             return derivedT::template log<software_error, -1>( { "error from initDM in INDI callback" } );
    3186              :         }
    3187              :     }
    3188              : 
    3189            0 :     return 0;
    3190              : }
    3191              : 
    3192              : template <class derivedT, typename realT>
    3193            0 : int dm<derivedT, realT>::st_newCallBack_zero( void *app, const pcf::IndiProperty &ipRecv )
    3194              : {
    3195            0 :     return static_cast<derivedT *>( app )->newCallBack_zero( ipRecv );
    3196              : }
    3197              : 
    3198              : template <class derivedT, typename realT>
    3199            0 : int dm<derivedT, realT>::newCallBack_zero( const pcf::IndiProperty &ipRecv )
    3200              : {
    3201            0 :     if( ipRecv.createUniqueKey() != m_indiP_zero.createUniqueKey() )
    3202              :     {
    3203            0 :         return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
    3204              :     }
    3205              : 
    3206            0 :     if( !ipRecv.find( "request" ) )
    3207            0 :         return 0;
    3208              : 
    3209            0 :     if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
    3210              :     {
    3211            0 :         return derived().zeroDM();
    3212              :     }
    3213            0 :     return 0;
    3214              : }
    3215              : 
    3216              : template <class derivedT, typename realT>
    3217            0 : int dm<derivedT, realT>::st_newCallBack_release( void *app, const pcf::IndiProperty &ipRecv )
    3218              : {
    3219            0 :     return static_cast<derivedT *>( app )->newCallBack_release( ipRecv );
    3220              : }
    3221              : 
    3222              : template <class derivedT, typename realT>
    3223            0 : int dm<derivedT, realT>::newCallBack_release( const pcf::IndiProperty &ipRecv )
    3224              : {
    3225            0 :     if( ipRecv.createUniqueKey() != m_indiP_release.createUniqueKey() )
    3226              :     {
    3227            0 :         return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
    3228              :     }
    3229              : 
    3230            0 :     if( !ipRecv.find( "request" ) )
    3231            0 :         return 0;
    3232              : 
    3233            0 :     if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
    3234              :     {
    3235            0 :         return baseReleaseDM();
    3236              :     }
    3237            0 :     return 0;
    3238              : }
    3239              : 
    3240              : template <class derivedT, typename realT>
    3241            0 : int dm<derivedT, realT>::st_newCallBack_flats( void *app, const pcf::IndiProperty &ipRecv )
    3242              : {
    3243            0 :     return static_cast<derivedT *>( app )->newCallBack_flats( ipRecv );
    3244              : }
    3245              : 
    3246              : template <class derivedT, typename realT>
    3247            0 : int dm<derivedT, realT>::newCallBack_flats( const pcf::IndiProperty &ipRecv )
    3248              : {
    3249            0 :     if( ipRecv.createUniqueKey() != m_indiP_flats.createUniqueKey() )
    3250              :     {
    3251            0 :         derivedT::template log<software_error>( { "invalid indi property received" } );
    3252            0 :         return -1;
    3253              :     }
    3254              : 
    3255            0 :     std::string newFlat;
    3256              : 
    3257            0 :     if( ipRecv.find( "default" ) )
    3258              :     {
    3259            0 :         if( ipRecv["default"].getSwitchState() == pcf::IndiElement::On )
    3260              :         {
    3261            0 :             newFlat = "default";
    3262              :         }
    3263              :     }
    3264              : 
    3265              :     // always do this to check for error:
    3266            0 :     for( auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
    3267              :     {
    3268            0 :         if( !ipRecv.find( i->first ) )
    3269            0 :             continue;
    3270              : 
    3271            0 :         if( ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
    3272              :         {
    3273            0 :             if( newFlat != "" )
    3274              :             {
    3275            0 :                 derivedT::template log<text_log>( "More than one flat selected", logPrio::LOG_ERROR );
    3276            0 :                 return -1;
    3277              :             }
    3278              : 
    3279            0 :             newFlat = i->first;
    3280              :         }
    3281              :     }
    3282              : 
    3283            0 :     if( newFlat == "" )
    3284              :     {
    3285            0 :         return 0;
    3286              :     }
    3287              : 
    3288            0 :     return loadFlat( newFlat );
    3289            0 : }
    3290              : 
    3291              : template <class derivedT, typename realT>
    3292            0 : int dm<derivedT, realT>::st_newCallBack_setFlat( void *app, const pcf::IndiProperty &ipRecv )
    3293              : {
    3294            0 :     return static_cast<derivedT *>( app )->newCallBack_setFlat( ipRecv );
    3295              : }
    3296              : 
    3297              : template <class derivedT, typename realT>
    3298            0 : int dm<derivedT, realT>::newCallBack_setFlat( const pcf::IndiProperty &ipRecv )
    3299              : {
    3300            0 :     if( ipRecv.createUniqueKey() != m_indiP_setFlat.createUniqueKey() )
    3301              :     {
    3302            0 :         return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
    3303              :     }
    3304              : 
    3305            0 :     if( !ipRecv.find( "toggle" ) )
    3306            0 :         return 0;
    3307              : 
    3308            0 :     if( ipRecv["toggle"] == pcf::IndiElement::On )
    3309              :     {
    3310            0 :         return setFlat();
    3311              :     }
    3312              :     else
    3313              :     {
    3314            0 :         return zeroFlat();
    3315              :     }
    3316              : }
    3317              : 
    3318              : template <class derivedT, typename realT>
    3319            0 : int dm<derivedT, realT>::st_newCallBack_tests( void *app, const pcf::IndiProperty &ipRecv )
    3320              : {
    3321            0 :     return static_cast<derivedT *>( app )->newCallBack_tests( ipRecv );
    3322              : }
    3323              : 
    3324              : template <class derivedT, typename realT>
    3325            0 : int dm<derivedT, realT>::newCallBack_tests( const pcf::IndiProperty &ipRecv )
    3326              : {
    3327            0 :     if( ipRecv.createUniqueKey() != m_indiP_tests.createUniqueKey() )
    3328              :     {
    3329            0 :         derivedT::template log<software_error>( { "invalid indi property received" } );
    3330            0 :         return -1;
    3331              :     }
    3332              : 
    3333            0 :     std::string newTest;
    3334              : 
    3335            0 :     if( ipRecv.find( "default" ) )
    3336              :     {
    3337            0 :         if( ipRecv["default"].getSwitchState() == pcf::IndiElement::On )
    3338              :         {
    3339            0 :             newTest = "default";
    3340              :         }
    3341              :     }
    3342              : 
    3343              :     // always do this to check for error:
    3344            0 :     for( auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
    3345              :     {
    3346            0 :         if( !ipRecv.find( i->first ) )
    3347            0 :             continue;
    3348              : 
    3349            0 :         if( ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
    3350              :         {
    3351            0 :             if( newTest != "" )
    3352              :             {
    3353            0 :                 derivedT::template log<text_log>( "More than one test selected", logPrio::LOG_ERROR );
    3354            0 :                 return -1;
    3355              :             }
    3356              : 
    3357            0 :             newTest = i->first;
    3358              :         }
    3359              :     }
    3360              : 
    3361            0 :     if( newTest == "" )
    3362              :     {
    3363            0 :         return 0;
    3364              :     }
    3365              : 
    3366            0 :     return loadTest( newTest );
    3367            0 : }
    3368              : 
    3369              : template <class derivedT, typename realT>
    3370            0 : int dm<derivedT, realT>::st_newCallBack_setTest( void *app, const pcf::IndiProperty &ipRecv )
    3371              : {
    3372            0 :     return static_cast<derivedT *>( app )->newCallBack_setTest( ipRecv );
    3373              : }
    3374              : 
    3375              : template <class derivedT, typename realT>
    3376            0 : int dm<derivedT, realT>::newCallBack_setTest( const pcf::IndiProperty &ipRecv )
    3377              : {
    3378            0 :     if( ipRecv.createUniqueKey() != m_indiP_setTest.createUniqueKey() )
    3379              :     {
    3380            0 :         return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
    3381              :     }
    3382              : 
    3383            0 :     if( !ipRecv.find( "toggle" ) )
    3384            0 :         return 0;
    3385              : 
    3386            0 :     if( ipRecv["toggle"] == pcf::IndiElement::On )
    3387              :     {
    3388            0 :         return setTest();
    3389              :     }
    3390              :     else
    3391              :     {
    3392            0 :         return zeroTest();
    3393              :     }
    3394              : }
    3395              : 
    3396              : template <class derivedT, typename realT>
    3397            0 : int dm<derivedT, realT>::st_newCallBack_zeroAll( void *app, const pcf::IndiProperty &ipRecv )
    3398              : {
    3399            0 :     return static_cast<derivedT *>( app )->newCallBack_zeroAll( ipRecv );
    3400              : }
    3401              : 
    3402              : template <class derivedT, typename realT>
    3403            0 : int dm<derivedT, realT>::newCallBack_zeroAll( const pcf::IndiProperty &ipRecv )
    3404              : {
    3405            0 :     if( ipRecv.createUniqueKey() != m_indiP_zeroAll.createUniqueKey() )
    3406              :     {
    3407            0 :         return derivedT::template log<software_error, -1>( { "wrong INDI-P in callback" } );
    3408              :     }
    3409              : 
    3410            0 :     if( !ipRecv.find( "request" ) )
    3411            0 :         return 0;
    3412              : 
    3413            0 :     if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
    3414              :     {
    3415            0 :         indi::updateSwitchIfChanged(
    3416            0 :             m_indiP_zeroAll, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY );
    3417              : 
    3418            0 :         std::lock_guard<std::mutex> guard( derived().m_indiMutex );
    3419            0 :         return zeroAll();
    3420            0 :     }
    3421            0 :     return 0;
    3422              : }
    3423              : 
    3424              : /// Call dmT::setupConfig with error checking for dm
    3425              : /**
    3426              :  * \param cfig the application configurator
    3427              :  */
    3428              : #define DM_SETUP_CONFIG( cfig )                                                                                        \
    3429              :     if( dmT::setupConfig( cfig ) < 0 )                                                                                 \
    3430              :     {                                                                                                                  \
    3431              :         log<software_error>( { "Error from dmT::setupConfig" } );                                                      \
    3432              :         m_shutdown = true;                                                                                             \
    3433              :         return;                                                                                                        \
    3434              :     }
    3435              : 
    3436              : /// Call dmT::loadConfig with error checking for dm
    3437              : /** This must be inside a function that returns int, e.g. the standard loadConfigImpl.
    3438              :  * \param cfig the application configurator
    3439              :  */
    3440              : #define DM_LOAD_CONFIG( cfig )                                                                                         \
    3441              :     if( dmT::loadConfig( cfig ) < 0 )                                                                                  \
    3442              :     {                                                                                                                  \
    3443              :         return log<software_error, -1>( { "Error from dmT::loadConfig" } );                                            \
    3444              :     }
    3445              : 
    3446              : /// Call shmimMonitorT::appStartup with error checking for dm
    3447              : #define DM_APP_STARTUP                                                                                                 \
    3448              :     if( dmT::appStartup() < 0 )                                                                                        \
    3449              :     {                                                                                                                  \
    3450              :         return log<software_error, -1>( { "Error from dmT::appStartup" } );                                            \
    3451              :     }
    3452              : 
    3453              : /// Call dmT::appLogic with error checking for dm
    3454              : #define DM_APP_LOGIC                                                                                                   \
    3455              :     if( dmT::appLogic() < 0 )                                                                                          \
    3456              :     {                                                                                                                  \
    3457              :         return log<software_error, -1>( { "Error from dmT::appLogic" } );                                              \
    3458              :     }
    3459              : 
    3460              : /// Call dmT::updateINDI with error checking for dm
    3461              : #define DM_UPDATE_INDI                                                                                                 \
    3462              :     if( dmT::updateINDI() < 0 )                                                                                        \
    3463              :     {                                                                                                                  \
    3464              :         return log<software_error, -1>( { "Error from dmT::updateINDI" } );                                            \
    3465              :     }
    3466              : 
    3467              : /// Call dmT::appShutdown with error checking for dm
    3468              : #define DM_APP_SHUTDOWN                                                                                                \
    3469              :     if( dmT::appShutdown() < 0 )                                                                                       \
    3470              :     {                                                                                                                  \
    3471              :         return log<software_error, -1>( { "Error from dmT::appShutdown" } );                                           \
    3472              :     }
    3473              : 
    3474              : } // namespace dev
    3475              : } // namespace app
    3476              : } // namespace MagAOX
    3477              : #endif
        

Generated by: LCOV version 2.0-1