LCOV - code coverage report
Current view: top level - libMagAOX/app/dev - edtCamera.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 81.0 % 142 115
Test Date: 2026-04-15 19:34:29 Functions: 100.0 % 16 16

            Line data    Source code
       1              : /** \file edtCamera.hpp
       2              :  * \brief EDT framegrabber interface
       3              :  *
       4              :  * \author Jared R. Males (jaredmales@gmail.com)
       5              :  *
       6              :  * \ingroup app_files
       7              :  */
       8              : 
       9              : #ifndef edtCamera_hpp
      10              : #define edtCamera_hpp
      11              : 
      12              : #ifndef MAGAOX_NOEDT
      13              : 
      14              :     #include <edtinc.h>
      15              : 
      16              :     #include "../../common/paths.hpp"
      17              : 
      18              :     #include "ioDevice.hpp"
      19              : 
      20              :     #include "stdCamera.hpp"
      21              : 
      22              : namespace MagAOX
      23              : {
      24              : namespace app
      25              : {
      26              : namespace dev
      27              : {
      28              : 
      29              : /// MagAO-X EDT framegrabber interface
      30              : /** Implements an interface to the EDT PDV SDK
      31              :  *
      32              :  * The derived class `derivedT` must be a MagAOXApp\<true\>, and must declare this class a friend like so:
      33              :  * \code
      34              :  * friend class dev::dssShutter<derivedT>;
      35              :  * \endcode
      36              :  *
      37              :  * A static configuration variable must be defined in derivedT as
      38              :  * \code
      39              :  * static constexpr bool c_edtCamera_relativeConfigPath = true; //or: false
      40              :  * \endcode
      41              :  * which determines whether or not the EDT config path is relative to the MagAO-X config path (true) or absolute
      42              :  * (false).
      43              :  *
      44              :  * In addition, `derivedT` should be `dev::frameGrabber` or equivalent, with an `m_reconfig` member, and calls
      45              :  * to this class's `pdvStartAcquisition`, `pdvAcquire`, and `pdvReconfig` in the relevant `dev::frameGrabber`
      46              :  * implementation functions.
      47              :  *
      48              :  * Calls to this class's `setupConfig`, `loadConfig`, `appStartup`, `appLogic`, `appShutdown`
      49              :  * `onPowerOff`, and `whilePowerOff`,  must be placed in the derived class's functions of the same name.
      50              :  *
      51              :  * \ingroup appdev
      52              :  */
      53              : template <class derivedT>
      54              : class edtCamera : public ioDevice
      55              : {
      56              : 
      57              :   public:
      58              :   protected:
      59              :     /** \name Configurable Parameters
      60              :      * @{
      61              :      */
      62              :     // Framegrabber:
      63              :     int m_unit{ 0 };     ///< EDT PDV board unit number
      64              :     int m_channel{ 0 };  ///< EDT PDV board channel number
      65              :     int m_numBuffs{ 4 }; ///< EDT PDV DMA buffer size, indicating number of images.
      66              : 
      67              :     // cameraConfigMap derived().m_cameraModes; ///< Map holding the possible camera mode configurations
      68              : 
      69              :     // std::string derived().m_startupMode; ///< The camera mode to load during first init after a power-on.
      70              : 
      71              :     ///@}
      72              : 
      73              :     PdvDev *m_pdv{ nullptr }; ///< The EDT PDV device handle
      74              : 
      75              :     u_char *m_image_p{ nullptr }; ///< The image data grabbed
      76              : 
      77              :     // std::string m_modeName; ///< The current mode name
      78              : 
      79              :     // std::string derived().m_nextMode; ///< The mode to be set by the next reconfiguration
      80              : 
      81              :     int         m_raw_height{ 0 }; ///< The height of the frame, according to the framegrabber
      82              :     int         m_raw_width{ 0 };  ///< The width of the frame, according to the framegrabber
      83              :     int         m_raw_depth{ 0 };  ///< The bit-depth of the frame, according to the framegrabber
      84              :     std::string m_cameraType;      ///< The camera type according to the framegrabber
      85              : 
      86              :   public:
      87              :     /// C'tor, sets up stdCamera
      88              :     edtCamera();
      89              : 
      90              :     /// Destructor, destroys the PdvDev structure
      91              :     ~edtCamera() noexcept;
      92              : 
      93              :     /// Send a serial command over cameralink and retrieve the response
      94              :     int pdvSerialWriteRead( std::string       &response,        ///< [out] the response to the command from the device
      95              :                             const std::string &command,         /**< [in] the command to send to the device */
      96              :                             bool               logErrors = true /**< [in] log serial transport failures when true */
      97              :     );
      98              : 
      99              :     /// Configure the EDT framegrabber
     100              :     int pdvConfig( std::string &cfgname /**< [in] The configuration name for the mode to set */ );
     101              : 
     102              :     /// Setup the configuration system
     103              :     /**
     104              :       * This should be called in `derivedT::setupConfig` as
     105              :       * \code
     106              :         edtCamera<derivedT>::setupConfig(config);
     107              :         \endcode
     108              :       * with appropriate error checking.
     109              :       */
     110              :     void setupConfig( mx::app::appConfigurator &config /**< [out] the derived classes configurator*/ );
     111              : 
     112              :     /// load the configuration system results
     113              :     /**
     114              :       * This should be called in `derivedT::loadConfig` as
     115              :       * \code
     116              :         edtCamera<derivedT>::loadConfig(config);
     117              :         \endcode
     118              :       * with appropriate error checking.
     119              :       */
     120              :     void loadConfig( mx::app::appConfigurator &config /**< [in] the derived classes configurator*/ );
     121              : 
     122              :     /// Startup function
     123              :     /**
     124              :       * This should be called in `derivedT::appStartup` as
     125              :       * \code
     126              :         edtCamera<derivedT>::appStartup();
     127              :         \endcode
     128              :       * with appropriate error checking.
     129              :       *
     130              :       * \returns 0 on success
     131              :       * \returns -1 on error, which is logged.
     132              :       */
     133              :     int appStartup();
     134              : 
     135              :     /// Application logic
     136              :     /** Checks the edtCamera thread
     137              :       *
     138              :       * This should be called from the derived's appLogic() as in
     139              :       * \code
     140              :         edtCamera<derivedT>::appLogic();
     141              :         \endcode
     142              :       * with appropriate error checking.
     143              :       *
     144              :       * \returns 0 on success
     145              :       * \returns -1 on error, which is logged.
     146              :       */
     147              :     int appLogic();
     148              : 
     149              :     /// Actions on power off
     150              :     /**
     151              :       * This should be called from the derived's onPowerOff() as in
     152              :       * \code
     153              :         edtCamera<derivedT>::onPowerOff();
     154              :         \endcode
     155              :       * with appropriate error checking.
     156              :       *
     157              :       * \returns 0 on success
     158              :       * \returns -1 on error, which is logged.
     159              :       */
     160              :     int onPowerOff();
     161              : 
     162              :     /// Actions while powered off
     163              :     /**
     164              :       * This should be called from the derived's whilePowerOff() as in
     165              :       * \code
     166              :         edtCamera<derivedT>::whilePowerOff();
     167              :         \endcode
     168              :       * with appropriate error checking.
     169              :       *
     170              :       * \returns 0 on success
     171              :       * \returns -1 on error, which is logged.
     172              :       */
     173              :     int whilePowerOff();
     174              : 
     175              :     /// Application the shutdown
     176              :     /** Shuts down the edtCamera thread
     177              :       *
     178              :       * \code
     179              :         edtCamera<derivedT>::appShutdown();
     180              :         \endcode
     181              :       * with appropriate error checking.
     182              :       *
     183              :       * \returns 0 on success
     184              :       * \returns -1 on error, which is logged.
     185              :       */
     186              :     int appShutdown();
     187              : 
     188              :     int pdvStartAcquisition();
     189              : 
     190              :     int pdvAcquire( timespec &currImageTimestamp );
     191              : 
     192              :     int pdvReconfig();
     193              : 
     194              :   protected:
     195              :     /** \name INDI
     196              :      *
     197              :      *@{
     198              :      */
     199              :   protected:
     200              :     // declare our properties
     201              : 
     202              :   public:
     203              :     /// The static callback function to be registered for the channel properties.
     204              :     /**
     205              :      * \returns 0 on success.
     206              :      * \returns -1 on error.
     207              :      */
     208              :     // static int st_newCallBack_mode( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
     209              :     //                                   const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the
     210              :     //                                   new property request.
     211              :     //                               );
     212              : 
     213              :     /// The callback called by the static version, to actually process the new request.
     214              :     /**
     215              :      * \returns 0 on success.
     216              :      * \returns -1 on error.
     217              :      */
     218              :     // int newCallBack_mode( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property
     219              :     // request.*/);
     220              : 
     221              :     /// Update the INDI properties for this device controller
     222              :     /** You should call this once per main loop.
     223              :      * It is not called automatically.
     224              :      *
     225              :      * \returns 0 on success.
     226              :      * \returns -1 on error.
     227              :      */
     228              :     int updateINDI();
     229              : 
     230              :     ///@}
     231              : 
     232              :   private:
     233           64 :     derivedT &derived()
     234              :     {
     235           64 :         return *static_cast<derivedT *>( this );
     236              :     }
     237              : };
     238              : 
     239              : template <class derivedT>
     240          228 : edtCamera<derivedT>::edtCamera()
     241              : {
     242          228 : }
     243              : 
     244              : template <class derivedT>
     245          227 : edtCamera<derivedT>::~edtCamera() noexcept
     246              : {
     247          227 :     if( m_pdv )
     248            5 :         pdv_close( m_pdv );
     249              : 
     250          227 :     return;
     251          227 : }
     252              : 
     253              :     #define MAGAOX_PDV_SERBUFSIZE 512
     254              : 
     255              : template <class derivedT>
     256          323 : int edtCamera<derivedT>::pdvSerialWriteRead( std::string &response, const std::string &command, bool logErrors )
     257              : {
     258              :     char buf[MAGAOX_PDV_SERBUFSIZE + 1];
     259              : 
     260              :     // Flush the channel first.
     261              :     // This does not indicate errors, so no checks possible.
     262          323 :     pdv_serial_read( m_pdv, buf, MAGAOX_PDV_SERBUFSIZE );
     263              : 
     264          323 :     if( pdv_serial_command( m_pdv, command.c_str() ) < 0 )
     265              :     {
     266           51 :         if( logErrors )
     267              :         {
     268           94 :             derivedT::template log<software_error>( { __FILE__, __LINE__, "PDV: error sending serial command" } );
     269              :         }
     270           51 :         return -1;
     271              :     }
     272              : 
     273              :     int ret;
     274              : 
     275          272 :     ret = pdv_serial_wait( m_pdv, m_readTimeout, 1 );
     276              : 
     277          272 :     if( ret == 0 )
     278              :     {
     279            7 :         if( derived().powerState() != 1 || derived().powerStateTarget() != 1 )
     280            1 :             return -1;
     281              : 
     282            6 :         if( logErrors )
     283              :         {
     284            4 :             derivedT::template log<software_error>( { __FILE__, __LINE__, "PDV: timeout, no serial response" } );
     285              :         }
     286            6 :         return -1;
     287              :     }
     288              : 
     289          265 :     u_char lastbyte = 0;
     290          265 :     u_char waitc    = 0;
     291              : 
     292          265 :     response.clear();
     293              : 
     294              :     do
     295              :     {
     296          265 :         ret = pdv_serial_read( m_pdv, buf, MAGAOX_PDV_SERBUFSIZE );
     297              : 
     298          265 :         if( ret > 0 )
     299              :         {
     300          265 :             response.append( buf, ret );
     301          265 :             lastbyte = static_cast<u_char>( buf[ret - 1] );
     302              :         }
     303              :         else
     304              :         {
     305            0 :             lastbyte = 0;
     306              :         }
     307              : 
     308          265 :         if( pdv_get_waitchar( m_pdv, &waitc ) && ( lastbyte == waitc ) )
     309              :         {
     310          265 :             break;
     311              :         }
     312              :         else
     313              :         {
     314            0 :             ret = pdv_serial_wait( m_pdv, m_readTimeout / 2, 1 );
     315              :         }
     316            0 :     } while( ret > 0 );
     317              : 
     318          265 :     if( ret == 0 && response.empty() && pdv_get_waitchar( m_pdv, &waitc ) )
     319              :     {
     320            0 :         if( logErrors )
     321              :         {
     322            0 :             derivedT::template log<software_error>( { __FILE__, __LINE__, "PDV: timeout in serial response" } );
     323              :         }
     324            0 :         return -1;
     325              :     }
     326              : 
     327              :     // Drain any immediate trailing bytes so CR/LF endings or prompt suffixes
     328              :     // do not become the next command's apparent response.
     329          265 :     ret = pdv_serial_wait( m_pdv, 10, 1 );
     330          530 :     while( ret > 0 )
     331              :     {
     332            0 :         ret = pdv_serial_read( m_pdv, buf, MAGAOX_PDV_SERBUFSIZE );
     333              : 
     334            0 :         if( ret > 0 )
     335              :         {
     336            0 :             ret = pdv_serial_wait( m_pdv, 10, 1 );
     337              :         }
     338              :     }
     339              : 
     340          265 :     return 0;
     341              : }
     342              : 
     343              : template <class derivedT>
     344           10 : int edtCamera<derivedT>::pdvConfig( std::string &modeName )
     345              : {
     346              :     Dependent *dd_p;
     347           10 :     EdtDev    *edt_p = NULL;
     348              :     Edtinfo    edtinfo;
     349              : 
     350              :     // Preliminaries
     351           10 :     if( m_pdv )
     352              :     {
     353            0 :         pdv_close( m_pdv );
     354            0 :         m_pdv = nullptr;
     355              :     }
     356              : 
     357           10 :     derived().m_modeName = modeName;
     358              : 
     359           10 :     if( modeName == "" )
     360              :     {
     361            1 :         return derivedT::template log<text_log, -1>( "Empty modeName passed to pdvConfig", logPrio::LOG_ERROR );
     362              :     }
     363              : 
     364            9 :     if( derived().m_cameraModes.count( modeName ) != 1 )
     365              :     {
     366            0 :         return derivedT::template log<text_log, -1>( "No mode named " + modeName + " found.", logPrio::LOG_ERROR );
     367              :     }
     368              : 
     369              :     // Construct config file, adding relative path if configured that way
     370            9 :     std::string configFile;
     371              : 
     372              :     if( derivedT::c_edtCamera_relativeConfigPath )
     373              :     {
     374            3 :         configFile = derived().configDir() + "/";
     375              :     }
     376              : 
     377            9 :     configFile += derived().m_cameraModes[modeName].m_configFile;
     378              : 
     379            9 :     derivedT::template log<text_log>( "Loading EDT PDV config file: " + configFile );
     380              : 
     381            9 :     if( ( dd_p = pdv_alloc_dependent() ) == NULL )
     382              :     {
     383            0 :         return derivedT::template log<software_error, -1>( { __FILE__, __LINE__, "EDT PDV alloc_dependent FAILED" } );
     384              :     }
     385              : 
     386            9 :     if( pdv_readcfg( configFile.c_str(), dd_p, &edtinfo ) != 0 )
     387              :     {
     388            4 :         free( dd_p );
     389            8 :         return derivedT::template log<software_error, -1>( { __FILE__, __LINE__, "EDT PDV readcfg FAILED" } );
     390              :     }
     391              : 
     392              :     char edt_devname[128];
     393            5 :     strncpy( edt_devname, EDT_INTERFACE, sizeof( edt_devname ) );
     394              : 
     395            5 :     if( ( edt_p = edt_open_channel( edt_devname, m_unit, m_channel ) ) == NULL )
     396              :     {
     397              :         char errstr[256];
     398            0 :         edt_perror( errstr );
     399            0 :         free( dd_p );
     400            0 :         return derivedT::template log<software_error, -1>(
     401            0 :             { __FILE__, __LINE__, std::string( "EDT PDV edt_open_channel FAILED: " ) + errstr } );
     402              :     }
     403              : 
     404              :     char bitdir[1];
     405            5 :     bitdir[0] = '\0';
     406              : 
     407            5 :     int pdv_debug = 0;
     408              : 
     409            5 :     if( derived().m_log.logLevel() > logPrio::LOG_INFO )
     410            0 :         pdv_debug = 2;
     411              : 
     412            5 :     if( pdv_initcam( edt_p, dd_p, m_unit, &edtinfo, configFile.c_str(), bitdir, pdv_debug ) != 0 )
     413              :     {
     414            0 :         edt_close( edt_p );
     415            0 :         free( dd_p );
     416            0 :         return derivedT::template log<software_error, -1>(
     417            0 :             { __FILE__, __LINE__, "initcam failed. Run with '--logLevel=DBG' to see complete debugging output." } );
     418              :     }
     419              : 
     420            5 :     edt_close( edt_p );
     421            5 :     free( dd_p );
     422              : 
     423              :     // Now open the PDV device handle for talking to the camera via the EDT board.
     424            5 :     if( ( m_pdv = pdv_open_channel( edt_devname, m_unit, m_channel ) ) == NULL )
     425              :     {
     426            0 :         std::string errstr = std::string( "pdv_open_channel(" ) + edt_devname + std::to_string( m_unit ) + "_" +
     427              :                              std::to_string( m_channel ) + ")";
     428              : 
     429            0 :         derivedT::template log<software_error>( { __FILE__, __LINE__, errstr } );
     430            0 :         derivedT::template log<software_error>( { __FILE__, __LINE__, errno } );
     431              : 
     432            0 :         return -1;
     433            0 :     }
     434              : 
     435            5 :     pdv_flush_fifo( m_pdv );
     436              : 
     437            5 :     pdv_serial_read_enable( m_pdv ); // This is undocumented, don't know if it's really needed.
     438              : 
     439            5 :     m_raw_width  = pdv_get_width( m_pdv );
     440            5 :     m_raw_height = pdv_get_height( m_pdv );
     441            5 :     m_raw_depth  = pdv_get_depth( m_pdv );
     442            5 :     m_cameraType = pdv_get_cameratype( m_pdv );
     443              : 
     444            5 :     derivedT::template log<text_log>( "Initialized framegrabber: " + m_cameraType );
     445            5 :     derivedT::template log<text_log>( "WxHxD: " + std::to_string( m_raw_width ) + " X " +
     446              :                                       std::to_string( m_raw_height ) + " X " + std::to_string( m_raw_depth ) );
     447              : 
     448              :     /*
     449              :      * allocate four buffers for optimal pdv ring buffer pipeline (reduce if
     450              :      * memory is at a premium)
     451              :      */
     452            5 :     pdv_multibuf( m_pdv, m_numBuffs );
     453            5 :     derivedT::template log<text_log>( "allocated " + std::to_string( m_numBuffs ) + " buffers" );
     454              : 
     455            5 :     return 0;
     456            9 : }
     457              : 
     458              : template <class derivedT>
     459           16 : void edtCamera<derivedT>::setupConfig( mx::app::appConfigurator &config )
     460              : {
     461          224 :     config.add( "framegrabber.pdv_unit",
     462              :                 "",
     463              :                 "framegrabber.pdv_unit",
     464              :                 argType::Required,
     465              :                 "framegrabber",
     466              :                 "pdv_unit",
     467              :                 false,
     468              :                 "int",
     469              :                 "The EDT PDV framegrabber unit number.  Default is 0." );
     470          224 :     config.add( "framegrabber.pdv_channel",
     471              :                 "",
     472              :                 "framegrabber.pdv_channel",
     473              :                 argType::Required,
     474              :                 "framegrabber",
     475              :                 "pdv_channel",
     476              :                 false,
     477              :                 "int",
     478              :                 "The EDT PDV framegrabber channel number.  Default is 0." );
     479          208 :     config.add( "framegrabber.numBuffs",
     480              :                 "",
     481              :                 "framegrabber.numBuffs",
     482              :                 argType::Required,
     483              :                 "framegrabber",
     484              :                 "numBuffs",
     485              :                 false,
     486              :                 "int",
     487              :                 "The EDT PDV framegrabber DMA buffer size [images].  Default is 4." );
     488              : 
     489           16 :     dev::ioDevice::setupConfig( config );
     490           16 : }
     491              : 
     492              : template <class derivedT>
     493           15 : void edtCamera<derivedT>::loadConfig( mx::app::appConfigurator &config )
     494              : {
     495           30 :     config( m_unit, "framegrabber.pdv_unit" );
     496           30 :     config( m_channel, "framegrabber.pdv_channel" );
     497           15 :     config( m_numBuffs, "framegrabber.numBuffs" );
     498              : 
     499           15 :     m_readTimeout  = 1000;
     500           15 :     m_writeTimeout = 1000;
     501           15 :     dev::ioDevice::loadConfig( config );
     502           15 : }
     503              : 
     504              : template <class derivedT>
     505            5 : int edtCamera<derivedT>::appStartup()
     506              : {
     507            5 :     if( pdvConfig( derived().m_startupMode ) < 0 )
     508              :     {
     509            3 :         derivedT::template log<software_error>( { __FILE__, __LINE__ } );
     510            3 :         return -1;
     511              :     }
     512              : 
     513            2 :     return 0;
     514              : }
     515              : 
     516              : template <class derivedT>
     517           43 : int edtCamera<derivedT>::appLogic()
     518              : {
     519           43 :     return 0;
     520              : }
     521              : 
     522              : template <class derivedT>
     523            2 : int edtCamera<derivedT>::onPowerOff()
     524              : {
     525            2 :     return 0;
     526              : }
     527              : 
     528              : template <class derivedT>
     529            2 : int edtCamera<derivedT>::whilePowerOff()
     530              : {
     531            2 :     return 0;
     532              : }
     533              : 
     534              : template <class derivedT>
     535            4 : int edtCamera<derivedT>::appShutdown()
     536              : {
     537            4 :     return 0;
     538              : }
     539              : 
     540              : template <class derivedT>
     541            2 : int edtCamera<derivedT>::pdvStartAcquisition()
     542              : {
     543            2 :     pdv_start_images( m_pdv, m_numBuffs );
     544              : 
     545            2 :     return 0;
     546              : }
     547              : 
     548              : template <class derivedT>
     549            5 : int edtCamera<derivedT>::pdvAcquire( timespec &currImageTimestamp )
     550              : {
     551              : 
     552              :     uint dmaTimeStamp[2];
     553            5 :     m_image_p = pdv_wait_last_image_timed( m_pdv, dmaTimeStamp );
     554              :     // m_image_p = pdv_wait_image_timed(m_pdv, dmaTimeStamp);
     555            5 :     pdv_start_image( m_pdv );
     556              : 
     557            5 :     currImageTimestamp.tv_sec  = dmaTimeStamp[0];
     558            5 :     currImageTimestamp.tv_nsec = dmaTimeStamp[1];
     559              : 
     560            5 :     return 0;
     561              : }
     562              : 
     563              : template <class derivedT>
     564            5 : int edtCamera<derivedT>::pdvReconfig()
     565              : {
     566              : 
     567            5 :     if( pdvConfig( derived().m_nextMode ) < 0 )
     568              :     {
     569            2 :         derivedT::template log<text_log>( "error trying to re-configure with " + derived().m_nextMode,
     570              :                                           logPrio::LOG_ERROR );
     571            2 :         sleep( 1 );
     572              :     }
     573              :     else
     574              :     {
     575            3 :         derived().m_nextMode = "";
     576              :     }
     577              : 
     578            5 :     return 0;
     579              : }
     580              : 
     581              : template <class derivedT>
     582            5 : int edtCamera<derivedT>::updateINDI()
     583              : {
     584            5 :     return 0;
     585              : }
     586              : 
     587              : } // namespace dev
     588              : } // namespace app
     589              : } // namespace MagAOX
     590              : 
     591              : #endif // MAGAOX_NOEDT
     592              : #endif // edtCamera_hpp
        

Generated by: LCOV version 2.0-1