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
|