LCOV - code coverage report
Current view: top level - apps/dmMode - dmMode.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 0.0 % 158 0
Test Date: 2026-01-03 21:03:39 Functions: 0.0 % 16 0

            Line data    Source code
       1              : /** \file dmMode.hpp
       2              :   * \brief The MagAO-X DM mode command header file
       3              :   *
       4              :   * \ingroup dmMode_files
       5              :   */
       6              : 
       7              : #ifndef dmMode_hpp
       8              : #define dmMode_hpp
       9              : 
      10              : #include <mx/improc/eigenCube.hpp>
      11              : #include <mx/ioutils/fits/fitsFile.hpp>
      12              : #include <mx/improc/eigenImage.hpp>
      13              : #include <mx/ioutils/stringUtils.hpp>
      14              : #include <mx/sys/timeUtils.hpp>
      15              : 
      16              : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
      17              : #include "../../magaox_git_version.h"
      18              : 
      19              : /** \defgroup dmMode
      20              :   * \brief The DM mode command app, places modes on a DM channel
      21              :   * \todo update md doc
      22              :   * \todo the current_amps/target_amps thing is dumb.  Should consider mode00.target, mode00.current maybe.
      23              :   *
      24              :   * <a href="../handbook/operating/software/apps/dmMode.html">Application Documentation</a>
      25              :   *
      26              :   * \ingroup apps
      27              :   *
      28              :   */
      29              : 
      30              : /** \defgroup dmMode_files
      31              :   * \ingroup dmMode
      32              :   */
      33              : 
      34              : 
      35              : namespace MagAOX
      36              : {
      37              : namespace app
      38              : {
      39              : 
      40              : /// The MagAO-X DM mode commander
      41              : /**
      42              :   * \ingroup dmMode
      43              :   */
      44              : class dmMode : public MagAOXApp<true>, public dev::telemeter<dmMode>
      45              : {
      46              : 
      47              :    typedef float realT;
      48              : 
      49              :    typedef dev::telemeter<dmMode> telemeterT;
      50              : 
      51              :    friend class dev::telemeter<dmMode>;
      52              :    friend class dmMode_test;
      53              : 
      54              : protected:
      55              : 
      56              :    /** \name Configurable Parameters
      57              :      *@{
      58              :      */
      59              : 
      60              :    std::string m_modeCube;
      61              : 
      62              :    int m_maxModes {50};
      63              : 
      64              :    std::string m_dmName;
      65              : 
      66              :    std::string m_dmChannelName;
      67              : 
      68              :    ///@}
      69              : 
      70              :    mx::improc::eigenCube<realT> m_modes;
      71              : 
      72              :    std::vector<realT> m_amps;
      73              : 
      74              :    mx::improc::eigenImage<realT> m_shape;
      75              : 
      76              :    IMAGE m_imageStream;
      77              :    uint32_t m_width {0}; ///< The width of the image
      78              :    uint32_t m_height {0}; ///< The height of the image.
      79              : 
      80              :    uint8_t m_dataType{0}; ///< The ImageStreamIO type code.
      81              :    size_t m_typeSize {0}; ///< The size of the type, in bytes.
      82              : 
      83              :    bool m_opened {true};
      84              :    bool m_restart {false};
      85              : 
      86              : public:
      87              :    /// Default c'tor.
      88              :    dmMode();
      89              : 
      90              :    /// D'tor, declared and defined for noexcept.
      91            0 :    ~dmMode() noexcept
      92            0 :    {}
      93              : 
      94              :    virtual void setupConfig();
      95              : 
      96              :    /// Implementation of loadConfig logic, separated for testing.
      97              :    /** This is called by loadConfig().
      98              :      */
      99              :    int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
     100              : 
     101              :    virtual void loadConfig();
     102              : 
     103              :    /// Startup function
     104              :    /**
     105              :      *
     106              :      */
     107              :    virtual int appStartup();
     108              : 
     109              :    /// Implementation of the FSM for dmMode.
     110              :    /**
     111              :      * \returns 0 on no critical error
     112              :      * \returns -1 on an error requiring shutdown
     113              :      */
     114              :    virtual int appLogic();
     115              : 
     116              :    /// Shutdown the app.
     117              :    /**
     118              :      *
     119              :      */
     120              :    virtual int appShutdown();
     121              : 
     122              : 
     123              :    int sendCommand();
     124              : 
     125              :    //INDI:
     126              : protected:
     127              :    //declare our properties
     128              :    pcf::IndiProperty m_indiP_dm;
     129              :    pcf::IndiProperty m_indiP_currAmps;
     130              :    pcf::IndiProperty m_indiP_tgtAmps;
     131              : 
     132              :    std::vector<std::string> m_elNames;
     133              : public:
     134            0 :    INDI_NEWCALLBACK_DECL(dmMode, m_indiP_currAmps);
     135            0 :    INDI_NEWCALLBACK_DECL(dmMode, m_indiP_tgtAmps);
     136              : 
     137              :    /** \name Telemeter Interface
     138              :      *
     139              :      * @{
     140              :      */
     141              :    int checkRecordTimes();
     142              : 
     143              :    int recordTelem( const telem_dmmodes * );
     144              : 
     145              :    int recordDmModes( bool force = false );
     146              :    ///@}
     147              : 
     148              : 
     149              : };
     150              : 
     151            0 : dmMode::dmMode() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
     152              : {
     153              : 
     154            0 :    return;
     155            0 : }
     156              : 
     157            0 : void dmMode::setupConfig()
     158              : {
     159            0 :    config.add("dm.modeCube", "", "dm.modeCube", argType::Required, "dm", "modeCube", false, "string", "Full path to the FITS file containing the modes for this DM.");
     160            0 :    config.add("dm.maxModes", "", "dm.maxModes", argType::Required, "dm", "maxModes", false, "int", "The maximum number of modes to use (truncates the cube).  If <=0 all modes in cube are used.");
     161            0 :    config.add("dm.name", "", "dm.name", argType::Required, "dm", "name", false, "string", "The descriptive name of this dm. Default is the channel name.");
     162            0 :    config.add("dm.channelName", "", "dm.channelName", argType::Required, "dm", "channelName", false, "string", "The name of the DM channel to write to.");
     163            0 :    config.add("dm.maxModes", "", "dm.maxModes", argType::Required, "dm", "maxModes", false, "int", "The maximum number of modes to use (truncates the cube).");
     164              : 
     165            0 :    telemeterT::setupConfig(config);
     166            0 : }
     167              : 
     168            0 : int dmMode::loadConfigImpl( mx::app::appConfigurator & _config )
     169              : {
     170              : 
     171            0 :    _config(m_modeCube, "dm.modeCube");
     172            0 :    _config(m_maxModes, "dm.maxModes");
     173            0 :    _config(m_dmChannelName, "dm.channelName");
     174              : 
     175            0 :    m_dmName = m_dmChannelName;
     176            0 :    _config(m_dmName, "dm.name");
     177              : 
     178            0 :    if(telemeterT::loadConfig(_config) < 0)
     179              :    {
     180            0 :       log<text_log>("Error during telemeter config", logPrio::LOG_CRITICAL);
     181            0 :       m_shutdown = true;
     182              :    }
     183              : 
     184            0 :    return 0;
     185              : }
     186              : 
     187            0 : void dmMode::loadConfig()
     188              : {
     189            0 :    loadConfigImpl(config);
     190            0 : }
     191              : 
     192            0 : int dmMode::appStartup()
     193              : {
     194            0 :    mx::fits::fitsFile<realT> ff;
     195              : 
     196            0 :    mx::error_t errc = ff.read(m_modes, m_modeCube);
     197            0 :    if(errc != mx::error_t::noerror)
     198              :    {
     199            0 :       return log<text_log,-1>(std::format("Could not open mode cube file: {} "
     200            0 :          "({})", mx::errorMessage(errc), mx::errorName(errc)), logPrio::LOG_ERROR);
     201              :    }
     202              : 
     203            0 :    if(m_maxModes > 0 && m_maxModes < m_modes.planes())
     204              :    {
     205            0 :       mx::improc::eigenCube<realT> modes;
     206              :       //This probably just works as a realloc in eigenCube but I haven't looked.
     207            0 :       modes.resize(m_modes.rows(), m_modes.cols(), m_maxModes);
     208            0 :       for(int p =0; p < modes.planes(); ++p) modes.image(p) = m_modes.image(p);
     209            0 :       m_modes.resize(m_modes.rows(), m_modes.cols(), m_maxModes);
     210            0 :       for(int p =0; p < modes.planes(); ++p) m_modes.image(p) = modes.image(p);
     211            0 :    }
     212              : 
     213              : 
     214              : 
     215            0 :    m_amps.resize(m_modes.planes(), 0);
     216            0 :    m_shape.resize(m_modes.rows(), m_modes.cols());
     217              : 
     218            0 :    REG_INDI_NEWPROP_NOCB(m_indiP_dm, "dm", pcf::IndiProperty::Text);
     219            0 :    m_indiP_dm.add(pcf::IndiElement("name"));
     220            0 :    m_indiP_dm["name"] = m_dmName;
     221            0 :    m_indiP_dm.add(pcf::IndiElement("channel"));
     222            0 :    m_indiP_dm["channel"] = m_dmChannelName;
     223              : 
     224            0 :    REG_INDI_NEWPROP(m_indiP_currAmps, "current_amps", pcf::IndiProperty::Number);
     225            0 :    REG_INDI_NEWPROP(m_indiP_tgtAmps, "target_amps", pcf::IndiProperty::Number);
     226              : 
     227            0 :    m_elNames.resize(m_amps.size());
     228              : 
     229            0 :    for(size_t n=0; n < m_amps.size(); ++n)
     230              :    {
     231              :       //std::string el = std::to_string(n);
     232            0 :       m_elNames[n] = mx::ioutils::convertToString<size_t, 4, '0'>(n);
     233              : 
     234            0 :       m_indiP_currAmps.add( pcf::IndiElement(m_elNames[n]) );
     235            0 :       m_indiP_currAmps[m_elNames[n]].set(0);
     236              : 
     237            0 :       m_indiP_tgtAmps.add( pcf::IndiElement(m_elNames[n]) );
     238              :    }
     239              : 
     240            0 :    if(telemeterT::appStartup() < 0)
     241              :    {
     242            0 :       return log<software_error,-1>({__FILE__,__LINE__});
     243              :    }
     244              : 
     245            0 :    state(stateCodes::NOTCONNECTED);
     246              : 
     247              : 
     248              : 
     249            0 :    return 0;
     250            0 : }
     251              : 
     252            0 : int dmMode::appLogic()
     253              : {
     254            0 :    if(state() == stateCodes::NOTCONNECTED)
     255              :    {
     256            0 :       m_opened = false;
     257            0 :       m_restart = false; //Set this up front, since we're about to restart.
     258              : 
     259            0 :       if( ImageStreamIO_openIm(&m_imageStream, m_dmChannelName.c_str()) == 0)
     260              :       {
     261            0 :          if(m_imageStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
     262              :          {
     263            0 :             ImageStreamIO_closeIm(&m_imageStream);
     264              :          }
     265              :          else
     266              :          {
     267            0 :             m_opened = true;
     268              :          }
     269              :       }
     270              : 
     271            0 :       if(m_opened)
     272              :       {
     273            0 :          state(stateCodes::CONNECTED);
     274              :       }
     275              :    }
     276              : 
     277            0 :    if(state() == stateCodes::CONNECTED)
     278              :    {
     279            0 :       m_dataType = m_imageStream.md[0].datatype;
     280            0 :       m_typeSize = ImageStreamIO_typesize(m_dataType);
     281            0 :       m_width = m_imageStream.md[0].size[0];
     282            0 :       m_height = m_imageStream.md[0].size[1];
     283              : 
     284              : 
     285            0 :       if(m_dataType != _DATATYPE_FLOAT )
     286              :       {
     287            0 :          return log<text_log,-1>("Data type of DM channel is not float.", logPrio::LOG_CRITICAL);
     288              :       }
     289              : 
     290            0 :       if(m_typeSize != sizeof(realT))
     291              :       {
     292            0 :          return log<text_log,-1>("Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL);
     293              :       }
     294              : 
     295            0 :       if(m_width != m_modes.rows())
     296              :       {
     297            0 :          return log<text_log,-1>("Size mismatch between DM and modes (rows)", logPrio::LOG_CRITICAL);
     298              :       }
     299              : 
     300            0 :       if(m_height != m_modes.cols())
     301              :       {
     302            0 :          return log<text_log,-1>("Size mismatch between DM and modes (cols)", logPrio::LOG_CRITICAL);
     303              :       }
     304              : 
     305            0 :       for(size_t n=0; n < m_amps.size(); ++n) m_amps[n] = 0;
     306            0 :       sendCommand();
     307              : 
     308            0 :       state(stateCodes::READY);
     309              :    }
     310              : 
     311            0 :    if(state() == stateCodes::READY)
     312              :    {
     313            0 :       if(telemeterT::appLogic() < 0)
     314              :       {
     315            0 :          log<software_error>({__FILE__, __LINE__});
     316            0 :          return 0;
     317              :       }
     318              :    }
     319              : 
     320            0 :    return 0;
     321              : }
     322              : 
     323            0 : int dmMode::appShutdown()
     324              : {
     325            0 :    telemeterT::appShutdown();
     326              : 
     327            0 :    return 0;
     328              : }
     329              : 
     330            0 : int dmMode::sendCommand()
     331              : {
     332            0 :    if(!m_opened)
     333              :    {
     334            0 :       log<text_log>("not connected to DM channel.", logPrio::LOG_WARNING);
     335            0 :       return 0;
     336              :    }
     337              : 
     338            0 :    m_shape = m_amps[0]*m_modes.image(0);
     339              : 
     340            0 :    for(size_t n = 1; n<m_amps.size(); ++n)
     341              :    {
     342            0 :       m_shape += m_amps[n]*m_modes.image(n);
     343              :    }
     344              : 
     345            0 :    if(m_imageStream.md[0].write)
     346              :    {
     347            0 :       while(m_imageStream.md[0].write) mx::sys::microSleep(10);
     348              :    }
     349              : 
     350            0 :    recordDmModes(true);
     351            0 :    m_imageStream.md[0].write = 1;
     352              : 
     353              :    uint32_t curr_image;
     354            0 :    if(m_imageStream.md[0].size[2] > 0) ///\todo change to naxis?
     355              :    {
     356            0 :       curr_image = m_imageStream.md[0].cnt1;
     357              :    }
     358            0 :    else curr_image = 0;
     359              : 
     360            0 :    char* next_dest = (char *) m_imageStream.array.raw + curr_image*m_width*m_height*m_typeSize;
     361              : 
     362            0 :    memcpy(next_dest, m_shape.data(), m_width*m_height*m_typeSize);
     363              : 
     364            0 :    m_imageStream.md[0].cnt0++;
     365              : 
     366            0 :    m_imageStream.md->write=0;
     367            0 :    ImageStreamIO_sempost(&m_imageStream,-1);
     368              : 
     369            0 :    recordDmModes(true);
     370              : 
     371            0 :    for(size_t n = 0; n<m_amps.size(); ++n)
     372              :    {
     373            0 :       m_indiP_currAmps[m_elNames[n]] = m_amps[n];
     374              :    }
     375            0 :    m_indiP_currAmps.setState (pcf::IndiProperty::Ok);
     376            0 :    m_indiDriver->sendSetProperty (m_indiP_currAmps);
     377              : 
     378            0 :    return 0;
     379              : 
     380              : }
     381              : 
     382            0 : INDI_NEWCALLBACK_DEFN(dmMode, m_indiP_currAmps)(const pcf::IndiProperty &ipRecv)
     383              : {
     384            0 :    if (ipRecv.getName() == m_indiP_currAmps.getName())
     385              :    {
     386            0 :       size_t found = 0;
     387            0 :       for(size_t n=0; n < m_amps.size(); ++n)
     388              :       {
     389            0 :          if(ipRecv.find(m_elNames[n]))
     390              :          {
     391            0 :             realT amp = ipRecv[m_elNames[n]].get<realT>();
     392              : 
     393              :             ///\todo add bounds checks here
     394              : 
     395            0 :             m_amps[n] = amp;
     396            0 :             ++found;
     397              :          }
     398              :       }
     399              : 
     400            0 :       if(found)
     401              :       {
     402            0 :          return sendCommand();
     403              :       }
     404              : 
     405            0 :       return 0;
     406              : 
     407              :    }
     408              : 
     409            0 :    return log<software_error,-1>({__FILE__,__LINE__, "invalid indi property name"});
     410              : }
     411              : 
     412            0 : INDI_NEWCALLBACK_DEFN(dmMode, m_indiP_tgtAmps)(const pcf::IndiProperty &ipRecv)
     413              : {
     414            0 :    if (ipRecv.getName() == m_indiP_tgtAmps.getName())
     415              :    {
     416            0 :       size_t found = 0;
     417            0 :       for(size_t n=0; n < m_amps.size(); ++n)
     418              :       {
     419            0 :          if(ipRecv.find(m_elNames[n]))
     420              :          {
     421            0 :             realT amp = ipRecv[m_elNames[n]].get<realT>();
     422              : 
     423              :             ///\todo add bounds checks here
     424              : 
     425            0 :             m_amps[n] = amp;
     426            0 :             ++found;
     427              :          }
     428              :       }
     429              : 
     430            0 :       if(found)
     431              :       {
     432            0 :          return sendCommand();
     433              :       }
     434              : 
     435            0 :       return 0;
     436              : 
     437              :    }
     438              : 
     439            0 :    return log<software_error,-1>({__FILE__,__LINE__, "invalid indi property name"});
     440              : }
     441              : 
     442            0 : int dmMode::checkRecordTimes()
     443              : {
     444            0 :    return telemeterT::checkRecordTimes(telem_dmmodes());
     445              : }
     446              : 
     447            0 : int dmMode::recordTelem( const telem_dmmodes * )
     448              : {
     449            0 :    return recordDmModes(true);
     450              : }
     451              : 
     452            0 : int dmMode::recordDmModes( bool force )
     453              : {
     454            0 :    static std::vector<float> lastamps(m_amps.size(), std::numeric_limits<float>::max());
     455              : 
     456            0 :    bool changed = false;
     457            0 :    for(size_t p=0; p < m_amps.size(); ++p)
     458              :    {
     459            0 :       if(m_amps[p] != lastamps[p]) changed = true;
     460              :    }
     461              : 
     462            0 :    if( changed || force )
     463              :    {
     464            0 :       for(size_t p=0; p < m_amps.size(); ++p)
     465              :       {
     466            0 :          lastamps[p] = m_amps[p];
     467              :       }
     468              : 
     469            0 :       telem<telem_dmmodes>(telem_dmmodes::messageT(lastamps));
     470              :    }
     471              : 
     472            0 :    return 0;
     473              : }
     474              : 
     475              : } //namespace app
     476              : } //namespace MagAOX
     477              : 
     478              : #endif //dmMode_hpp
        

Generated by: LCOV version 2.0-1