Line data Source code
1 : /** \file ocam2KCtrl.hpp
2 : * \brief The MagAO-X OCAM2K EMCCD camera controller.
3 : *
4 : * \ingroup ocam2KCtrl_files
5 : */
6 :
7 : #ifndef ocam2KCtrl_hpp
8 : #define ocam2KCtrl_hpp
9 :
10 : #include <fcntl.h>
11 : #include <edtinc.h>
12 : #include <unistd.h>
13 :
14 : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
15 : #include "../../magaox_git_version.h"
16 :
17 : typedef MagAOX::app::MagAOXApp<true> MagAOXAppT; // This needs to be before pdvUtils.hpp for logging to work.
18 :
19 : #include "fli/ocam2_sdk.h"
20 : #include "ocamUtils.hpp"
21 :
22 : namespace MagAOX
23 : {
24 : namespace app
25 : {
26 :
27 : /** \defgroup ocam2KCtrl OCAM2K EMCCD Camera
28 : * \brief Control of the OCAM2K EMCCD Camera.
29 : *
30 : * <a href="../handbook/operating/software/apps/ocam2KCtrl.html">Application Documentation</a>
31 : *
32 : * \ingroup apps
33 : *
34 : */
35 :
36 : /** \defgroup ocam2KCtrl_files OCAM2K EMCCD Camera Files
37 : * \ingroup ocam2KCtrl
38 : */
39 :
40 : /** MagAO-X application to control the OCAM 2K EMCCD
41 : *
42 : * \ingroup ocam2KCtrl
43 : *
44 : */
45 : class ocam2KCtrl : public MagAOXApp<>,
46 : public dev::stdCamera<ocam2KCtrl>,
47 : public dev::edtCamera<ocam2KCtrl>,
48 : public dev::frameGrabber<ocam2KCtrl>,
49 : public dev::dssShutter<ocam2KCtrl>,
50 : public dev::telemeter<ocam2KCtrl>
51 : {
52 : friend class dev::stdCamera<ocam2KCtrl>;
53 : friend class dev::edtCamera<ocam2KCtrl>;
54 : friend class dev::frameGrabber<ocam2KCtrl>;
55 : friend class dev::dssShutter<ocam2KCtrl>;
56 : friend class dev::telemeter<ocam2KCtrl>;
57 :
58 : typedef MagAOXApp<> MagAOXAppT;
59 :
60 : public:
61 : /** \name app::dev Configurations
62 : *@{
63 : */
64 : static constexpr bool c_stdCamera_tempControl =
65 : true; ///< app::dev config to tell stdCamera to expose temperature controls
66 :
67 : static constexpr bool c_stdCamera_temp =
68 : true; ///< app::dev config to tell stdCamera to expose temperature (ignored since tempControl==true)
69 :
70 : static constexpr bool c_stdCamera_readoutSpeed =
71 : false; ///< app::dev config to tell stdCamera not to expose readout speed controls
72 :
73 : static constexpr bool c_stdCamera_vShiftSpeed =
74 : false; ///< app:dev config to tell stdCamera not to expose vertical shift speed control
75 : static constexpr bool c_stdCamera_fanSpeed =
76 : false; ///< app::dev config to tell stdCamera not to expose fan-speed control
77 :
78 : static constexpr bool c_stdCamera_emGain = true; ///< app::dev config to tell stdCamera to expose EM gain controls
79 :
80 : static constexpr bool c_stdCamera_exptimeCtrl =
81 : false; ///< app::dev config to tell stdCamera not to expose exposure time controls
82 :
83 : static constexpr bool c_stdCamera_fpsCtrl = true; ///< app::dev config to tell stdCamera to expose FPS controls
84 :
85 : static constexpr bool c_stdCamera_fps =
86 : true; ///< app::dev config to tell stdCamera not to expose FPS status (ignored since fpsCtrl==true)
87 :
88 : static constexpr bool c_stdCamera_synchro =
89 : true; ///< app::dev config to tell stdCamera to expose synchro mode controls
90 :
91 : static constexpr bool c_stdCamera_usesModes =
92 : true; ///< app:dev config to tell stdCamera not to expose mode controls
93 :
94 : static constexpr bool c_stdCamera_usesROI = false; ///< app:dev config to tell stdCamera to expose ROI controls
95 :
96 : static constexpr bool c_stdCamera_cropMode =
97 : false; ///< app:dev config to tell stdCamera to expose Crop Mode controls
98 :
99 : static constexpr bool c_stdCamera_hasShutter =
100 : true; ///< app:dev config to tell stdCamera to expose shutter controls
101 :
102 : static constexpr bool c_stdCamera_usesStateString =
103 : true; ///< app::dev confg to tell stdCamera to expose the state string property
104 :
105 : static constexpr bool c_edtCamera_relativeConfigPath =
106 : true; ///< app::dev config to tell edtCamera to use relative path to camera config file
107 :
108 : static constexpr bool c_frameGrabber_flippable =
109 : false; ///< app:dev config to tell framegrabber these images can not be flipped
110 :
111 : ///@}
112 : protected:
113 : /** \name configurable parameters
114 : *@{
115 : */
116 :
117 : std::string m_ocamDescrambleFile; ///< Path to the OCAM 2K pixel descrambling file, relative to the MagAO-X config
118 : ///< directory.
119 :
120 : std::string m_syncShmimName; ///< Name of the sync-only ImageStreamIO output. Defaults to `framegrabber.shmimName +
121 : ///< "_sync"`.
122 :
123 : ///@}
124 :
125 : static constexpr uint32_t c_syncStreamWidth{ 1 }; ///< Width of the sync-only ImageStreamIO stream.
126 : static constexpr uint32_t c_syncStreamHeight{ 1 }; ///< Height of the sync-only ImageStreamIO stream.
127 : static constexpr uint32_t c_syncStreamDepth{ 1 }; ///< Depth of the sync-only ImageStreamIO stream.
128 : static constexpr uint8_t c_syncStreamDataType{ _DATATYPE_UINT8 }; ///< Data type of the sync-only stream.
129 :
130 : ocam2_id m_ocam2_id{ 0 }; ///< OCAM SDK id.
131 :
132 : long m_currImageNumber{ -1 }; ///< The current image number, retrieved from the raw image itself.
133 :
134 : long m_lastImageNumber{ -1 }; ///< The last image number saved from the previous acquisition loop.
135 :
136 : bool m_protectionReset{ false }; ///< Flag indicating that protection has been reset at least once.
137 :
138 : unsigned m_protectionResetConfirmed{ 0 }; ///< Counter indicating the number of times that the protection reset has
139 : ///< been requested within 10 seconds, for confirmation.
140 :
141 : double m_protectionResetReqTime{
142 : 0 }; ///< The time at which protection reset was requested. You have 10 seconds to confirm.
143 :
144 : bool m_poweredOn{
145 : false }; ///< Tracks that power-on defaults still need to restore the configured temperature setpoint.
146 :
147 : ocamTemps m_temps; ///< Structure holding the last temperature measurement.
148 :
149 : unsigned m_digitalBinX{ 1 }; ///< Digital x-binning factor applied after descrambling.
150 :
151 : unsigned m_digitalBinY{ 1 }; ///< Digital y-binning factor applied after descrambling.
152 :
153 : bool m_digitalBin{ false }; ///< Indicates whether post-descramble digital binning is active for the current mode.
154 :
155 : mx::improc::eigenImage<int16_t>
156 : m_digitalBinWork; ///< Scratch image holding the full descrambled frame before digital binning.
157 :
158 : std::string m_syncDevice{
159 : "fxngensync" }; ///< INDI device providing the external sync frequency when synchro mode is enabled.
160 : std::string m_syncFreqProp{ "C1freq" }; ///< INDI property name reporting the external sync frequency.
161 :
162 : float m_syncFreq{ 0 }; ///< Current externally supplied sync frequency in Hz.
163 :
164 : IMAGE *m_syncImageStream{ nullptr }; ///< Secondary 1x1 uint8 ImageStreamIO stream used only for frame metadata
165 : ///< and semaphores. Its lifetime is sequenced by the framegrabber thread and
166 : ///< app shutdown so per-frame publication can remain lock-free.
167 :
168 : std::recursive_mutex
169 : m_cameraMutex; ///< Protects EDT PDV access and OCAM SDK lifetime across reconfigure, grab, and control paths.
170 :
171 : public:
172 : /// Default c'tor
173 : ocam2KCtrl();
174 :
175 : /// Destructor
176 : ~ocam2KCtrl() noexcept;
177 :
178 : /// Setup the configuration system (called by MagAOXApp::setup())
179 : virtual void setupConfig();
180 :
181 : /// load the configuration system results (called by MagAOXApp::setup())
182 : virtual void loadConfig();
183 :
184 : /// Startup functions
185 : /** Sets up the INDI vars, and the f.g. thread.
186 : *
187 : */
188 : virtual int appStartup();
189 :
190 : /// Implementation of the FSM for the OCAM 2K.
191 : virtual int appLogic();
192 :
193 : /// Implementation of the on-power-off FSM logic
194 : virtual int onPowerOff();
195 :
196 : /// Implementation of the while-powered-off FSM
197 : virtual int whilePowerOff();
198 :
199 : /// Do any needed shutdown tasks.
200 : virtual int appShutdown();
201 :
202 : /// Get the current device temperatures
203 : /**
204 : * \returns 0 on success
205 : * \returns -1 on error
206 : */
207 : int getTemps();
208 :
209 : /// Get the current frame rate.
210 : /**
211 : * \returns 0 on success
212 : * \returns -1 on error
213 : */
214 : int getFPS();
215 :
216 : /** \name stdCamera Interface
217 : *
218 : * @{
219 : */
220 :
221 : /// Set defaults for a power on state.
222 : /**
223 : * \returns 0 on success
224 : * \returns -1 on error
225 : */
226 : int powerOnDefaults();
227 :
228 : /// Turn temperature control on or off.
229 : /** Sets temperature control on or off based on the current value of m_tempControlStatus
230 : * \returns 0 on success
231 : * \returns -1 on error
232 : */
233 : int setTempControl();
234 :
235 : /// Set the CCD temperature setpoint [stdCamera interface].
236 : /** Sets the temperature to m_ccdTempSetpt.
237 : * \returns 0 on success
238 : * \returns -1 on error
239 : */
240 : int setTempSetPt();
241 :
242 : /// Set the frame rate. [stdCamera interface]
243 : /** Sets the frame rate to m_fpsSet.
244 : *
245 : * \returns 0 on success
246 : * \returns -1 on error
247 : */
248 : int setFPS();
249 :
250 : /// Set the synchro state. [stdCamera interface]
251 : /** Sets the synchro state to m_synchroSet.
252 : *
253 : * \returns 0 on success
254 : * \returns -1 on error
255 : */
256 : int setSynchro();
257 :
258 : /// Required by stdCamera, but this does not do anything for this camera [stdCamera interface]
259 : /**
260 : * \returns 0 always
261 : */
262 : int setExpTime();
263 :
264 : /// Required by stdCamera, but this does not do anything for this camera [stdCamera interface]
265 : /**
266 : * \returns 0 always
267 : */
268 : int setNextROI();
269 :
270 : /// Sets the shutter state, via call to dssShutter::setShutterState(int) [stdCamera interface]
271 : /**
272 : * \returns 0 always
273 : */
274 : int setShutter( int sh /**< [in] requested shutter state */ );
275 :
276 : /// Return the current stdCamera state string.
277 : std::string stateString();
278 :
279 : /// Report whether the current stdCamera state string is valid for persistence and telemetry.
280 : bool stateStringValid();
281 :
282 : ///@}
283 :
284 : /// Reset the EM Protection
285 : /**
286 : * \returns 0 on success
287 : * \returns -1 on error
288 : */
289 : int resetEMProtection();
290 :
291 : /// Get the current EM Gain.
292 : /**
293 : * \returns 0 on success
294 : * \returns -1 on error
295 : */
296 : int getEMGain();
297 :
298 : /// Set the EM gain.
299 : /** Sets it to the value of stdCamera::m_emGainSet
300 : *
301 : * \returns 0 on success
302 : * \returns -1 on error
303 : */
304 : int setEMGain();
305 :
306 : /// Implementation of the framegrabber configureAcquisition interface
307 : /** Sends the mode command over serial, sets the FPS, and initializes the OCAM SDK.
308 : *
309 : * \returns 0 on success
310 : * \returns -1 on error
311 : */
312 : int configureAcquisition();
313 :
314 : /// Implementation of the frameGrabber fps interface
315 : /** Just returns the value of m_fps
316 : */
317 : float fps();
318 :
319 : /// Implementation of the framegrabber startAcquisition interface
320 : /** Initializes m_lastImageNumber, and calls edtCamera::pdvStartAcquisition
321 : *
322 : * \returns 0 on success
323 : * \returns -1 on error
324 : */
325 : int startAcquisition();
326 :
327 : /// Implementation of the framegrabber acquireAndCheckValid interface
328 : /** Calls edtCamera::pdvAcquire, then analyzes the OCAM generated framenumber for skips and corruption.
329 : *
330 : * \returns 0 on success
331 : * \returns -1 on error
332 : */
333 : int acquireAndCheckValid();
334 :
335 : /// Implementation of the framegrabber loadImageIntoStream interface
336 : /** Conducts the OCAM descramble.
337 : *
338 : * \returns 0 on success
339 : * \returns -1 on error
340 : */
341 : int loadImageIntoStream( void *dest /**< [in] destination image buffer */ );
342 :
343 : /// Implementation of the framegrabber reconfig interface
344 : /** Locks the INDI mutex and calls edtCamera::pdvReconfig.
345 : * \returns 0 on success
346 : * \returns -1 on error
347 : */
348 : int reconfig();
349 :
350 : /// Publish the sync-only stream immediately after the main framegrabber publication.
351 : /** This hot path relies on framegrabber sequencing: configureAcquisition() and this hook both run on the
352 : * framegrabber thread, while appShutdown() destroys the sync stream only after frameGrabber::appShutdown()
353 : * joins that thread.
354 : */
355 : int frameGrabberPostPublish( IMAGE *imageStream /**< [in] main framegrabber stream that was just posted */ );
356 :
357 : protected:
358 : /// Ensure the sync-only ImageStreamIO stream exists with the expected 1x1 uint8 layout.
359 : int ensureSyncStream();
360 :
361 : /// Destroy the sync-only ImageStreamIO stream owned by this app.
362 : int destroySyncStream();
363 :
364 : // INDI:
365 : protected:
366 : // declare our properties
367 : pcf::IndiProperty m_indiP_temps; ///< INDI property publishing the latest camera temperatures.
368 : pcf::IndiProperty m_indiP_emProt; ///< INDI property publishing the EM protection state.
369 : pcf::IndiProperty m_indiP_emProtReset; ///< INDI property handling operator requests to reset EM protection.
370 :
371 : pcf::IndiProperty m_indiP_syncFreq; ///< INDI subscription to the external sync-frequency source.
372 :
373 : public:
374 1 : INDI_NEWCALLBACK_DECL( ocam2KCtrl, m_indiP_emProtReset );
375 :
376 1 : INDI_SETCALLBACK_DECL( ocam2KCtrl, m_indiP_syncFreq );
377 :
378 : /** \name Telemeter Interface
379 : *
380 : * @{
381 : */
382 : int checkRecordTimes();
383 :
384 : int recordTelem( const ocam_temps * );
385 :
386 : int recordTelem( const telem_stdcam * );
387 :
388 : int recordTelem( const telem_fgtimings * );
389 :
390 : int
391 : recordTemps( bool force = false /**< [in] whether to force a telemetry record even if values have not changed */ );
392 :
393 : ///@}
394 : };
395 :
396 777 : inline ocam2KCtrl::ocam2KCtrl() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
397 : {
398 : //--- MagAOXApp Power Mgt. ---
399 111 : m_powerMgtEnabled = true;
400 111 : m_powerOnWait = 10;
401 :
402 : //--- stdCamera ---
403 111 : m_startupTemp = 20;
404 :
405 111 : m_maxEMGain = 600;
406 :
407 111 : return;
408 0 : }
409 :
410 110 : inline ocam2KCtrl::~ocam2KCtrl() noexcept
411 : {
412 110 : return;
413 110 : }
414 :
415 4 : inline void ocam2KCtrl::setupConfig()
416 : {
417 4 : dev::stdCamera<ocam2KCtrl>::setupConfig( config );
418 :
419 4 : dev::edtCamera<ocam2KCtrl>::setupConfig( config );
420 :
421 56 : config.add( "camera.ocamDescrambleFile",
422 : "",
423 : "camera.ocamDescrambleFile",
424 : argType::Required,
425 : "camera",
426 : "ocamDescrambleFile",
427 : false,
428 : "string",
429 : "The path of the OCAM descramble file, relative to MagAOX/config." );
430 :
431 4 : dev::frameGrabber<ocam2KCtrl>::setupConfig( config );
432 :
433 56 : config.add( "framegrabber.syncShmimName",
434 : "",
435 : "framegrabber.syncShmimName",
436 : argType::Required,
437 : "framegrabber",
438 : "syncShmimName",
439 : false,
440 : "string",
441 : "The name of the 1x1 uint8 ImageStreamIO sync stream used only for frame metadata and semaphores. "
442 : "Defaults to framegrabber.shmimName + \"_sync\"." );
443 :
444 4 : dev::dssShutter<ocam2KCtrl>::setupConfig( config );
445 :
446 4 : dev::telemeter<ocam2KCtrl>::setupConfig( config );
447 4 : }
448 :
449 4 : inline void ocam2KCtrl::loadConfig()
450 : {
451 4 : dev::stdCamera<ocam2KCtrl>::loadConfig( config );
452 4 : dev::edtCamera<ocam2KCtrl>::loadConfig( config );
453 :
454 8 : config( m_ocamDescrambleFile, "camera.ocamDescrambleFile" );
455 :
456 4 : if( m_maxEMGain < 1 )
457 : {
458 1 : m_maxEMGain = 1;
459 1 : log<text_log>( "maxEMGain set to 1" );
460 : }
461 :
462 4 : if( m_maxEMGain > 600 )
463 : {
464 1 : m_maxEMGain = 600;
465 1 : log<text_log>( "maxEMGain set to 600" );
466 : }
467 :
468 4 : dev::frameGrabber<ocam2KCtrl>::loadConfig( config );
469 8 : config( m_syncShmimName, "framegrabber.syncShmimName" );
470 4 : if( m_syncShmimName == "" )
471 : {
472 1 : m_syncShmimName = m_shmimName + "_sync";
473 : }
474 4 : dev::dssShutter<ocam2KCtrl>::loadConfig( config );
475 4 : dev::telemeter<ocam2KCtrl>::loadConfig( config );
476 4 : }
477 :
478 14 : inline int ocam2KCtrl::ensureSyncStream()
479 : {
480 14 : if( m_syncImageStream != nullptr )
481 : {
482 2 : uint32_t syncWidth = m_syncImageStream->md[0].size[0];
483 2 : uint32_t syncHeight = 1;
484 2 : uint32_t syncDepth = 1;
485 :
486 2 : if( m_syncImageStream->md[0].naxis > 1 )
487 : {
488 2 : syncHeight = m_syncImageStream->md[0].size[1];
489 : }
490 :
491 2 : if( m_syncImageStream->md[0].naxis > 2 )
492 : {
493 2 : syncDepth = m_syncImageStream->md[0].size[2];
494 : }
495 :
496 2 : if( m_syncImageStream->md[0].datatype == c_syncStreamDataType && syncWidth == c_syncStreamWidth &&
497 1 : syncHeight == c_syncStreamHeight && syncDepth == c_syncStreamDepth )
498 : {
499 1 : return 0;
500 : }
501 :
502 1 : ImageStreamIO_destroyIm( m_syncImageStream );
503 1 : free( m_syncImageStream );
504 1 : m_syncImageStream = nullptr;
505 : }
506 :
507 : char syncFileName[1024];
508 13 : ImageStreamIO_filename( syncFileName, sizeof( syncFileName ), m_syncShmimName.c_str() );
509 :
510 13 : int syncFd = ::open( syncFileName, O_RDWR );
511 13 : if( syncFd != -1 )
512 : {
513 2 : ::close( syncFd );
514 :
515 : IMAGE staleSyncStream;
516 2 : if( ImageStreamIO_openIm( &staleSyncStream, m_syncShmimName.c_str() ) != IMAGESTREAMIO_SUCCESS )
517 : {
518 3 : return log<software_error, -1>(
519 1 : { __FILE__, __LINE__, "error opening existing sync ImageStreamIO stream" } );
520 : }
521 :
522 1 : if( ImageStreamIO_destroyIm( &staleSyncStream ) != IMAGESTREAMIO_SUCCESS )
523 : {
524 0 : return log<software_error, -1>(
525 0 : { __FILE__, __LINE__, "error destroying existing sync ImageStreamIO stream" } );
526 : }
527 : }
528 :
529 12 : m_syncImageStream = reinterpret_cast<IMAGE *>( malloc( sizeof( IMAGE ) ) );
530 12 : if( m_syncImageStream == nullptr )
531 : {
532 0 : return log<software_error, -1>( { __FILE__, __LINE__, "error allocating sync ImageStreamIO stream" } );
533 : }
534 :
535 12 : uint32_t imsize[3] = { c_syncStreamWidth, c_syncStreamHeight, c_syncStreamDepth };
536 :
537 12 : if( ImageStreamIO_createIm_gpu( m_syncImageStream,
538 : m_syncShmimName.c_str(),
539 : 3,
540 : imsize,
541 : c_syncStreamDataType,
542 : -1,
543 : 1,
544 : IMAGE_NB_SEMAPHORE,
545 : 0,
546 : CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
547 12 : 0 ) != IMAGESTREAMIO_SUCCESS )
548 : {
549 0 : free( m_syncImageStream );
550 0 : m_syncImageStream = nullptr;
551 0 : return log<software_error, -1>( { __FILE__, __LINE__, "error creating sync ImageStreamIO stream" } );
552 : }
553 :
554 12 : m_syncImageStream->array.UI8[0] = 0;
555 12 : m_syncImageStream->md[0].cnt0 = 0;
556 12 : m_syncImageStream->md[0].cnt1 = 0;
557 12 : m_syncImageStream->md[0].write = 0;
558 12 : m_syncImageStream->writetimearray[0] = m_syncImageStream->md[0].writetime;
559 12 : m_syncImageStream->atimearray[0] = m_syncImageStream->md[0].atime;
560 12 : m_syncImageStream->cntarray[0] = 0;
561 :
562 12 : return 0;
563 : }
564 :
565 112 : inline int ocam2KCtrl::destroySyncStream()
566 : {
567 112 : if( m_syncImageStream == nullptr )
568 : {
569 100 : return 0;
570 : }
571 :
572 12 : int rv = 0;
573 :
574 12 : if( ImageStreamIO_destroyIm( m_syncImageStream ) != IMAGESTREAMIO_SUCCESS )
575 : {
576 0 : rv = log<software_error, -1>( { __FILE__, __LINE__, "error destroying sync ImageStreamIO stream" } );
577 : }
578 :
579 12 : free( m_syncImageStream );
580 12 : m_syncImageStream = nullptr;
581 :
582 12 : return rv;
583 : }
584 :
585 3 : inline int ocam2KCtrl::appStartup()
586 : {
587 9 : REG_INDI_NEWPROP_NOCB( m_indiP_temps, "temps", pcf::IndiProperty::Number );
588 6 : m_indiP_temps.add( pcf::IndiElement( "cpu" ) );
589 6 : m_indiP_temps["cpu"].set( 0 );
590 6 : m_indiP_temps.add( pcf::IndiElement( "power" ) );
591 6 : m_indiP_temps["power"].set( 0 );
592 6 : m_indiP_temps.add( pcf::IndiElement( "bias" ) );
593 6 : m_indiP_temps["bias"].set( 0 );
594 6 : m_indiP_temps.add( pcf::IndiElement( "water" ) );
595 6 : m_indiP_temps["water"].set( 0 );
596 6 : m_indiP_temps.add( pcf::IndiElement( "left" ) );
597 6 : m_indiP_temps["left"].set( 0 );
598 6 : m_indiP_temps.add( pcf::IndiElement( "right" ) );
599 6 : m_indiP_temps["right"].set( 0 );
600 6 : m_indiP_temps.add( pcf::IndiElement( "cooling" ) );
601 6 : m_indiP_temps["cooling"].set( 0 );
602 :
603 9 : REG_INDI_NEWPROP_NOCB( m_indiP_emProt, "emProtection", pcf::IndiProperty::Text );
604 6 : m_indiP_emProt.add( pcf::IndiElement( "status" ) );
605 6 : m_indiP_emProt["status"].set( "UNKNOWN" );
606 3 : m_indiP_emProt.setState( INDI_IDLE );
607 :
608 18 : createStandardIndiRequestSw( m_indiP_emProtReset, "emProtectionReset", "Reset", "EM Protection" );
609 3 : registerIndiPropertyNew( m_indiP_emProtReset, INDI_NEWCALLBACK( m_indiP_emProtReset ) );
610 :
611 3 : REG_INDI_SETPROP( m_indiP_syncFreq, m_syncDevice, m_syncFreqProp );
612 :
613 3 : if( dev::stdCamera<ocam2KCtrl>::appStartup() < 0 )
614 : {
615 1 : return log<software_critical, -1>( { "" } );
616 : }
617 :
618 2 : if( dev::edtCamera<ocam2KCtrl>::appStartup() < 0 )
619 : {
620 1 : return log<software_critical, -1>( { "" } );
621 : }
622 :
623 1 : if( dev::frameGrabber<ocam2KCtrl>::appStartup() < 0 )
624 : {
625 0 : return log<software_critical, -1>( { "" } );
626 : }
627 :
628 1 : if( dev::dssShutter<ocam2KCtrl>::appStartup() < 0 )
629 : {
630 0 : return log<software_critical, -1>( { "" } );
631 : }
632 :
633 1 : m_temps.setInvalid();
634 1 : if( dev::telemeter<ocam2KCtrl>::appStartup() < 0 )
635 : {
636 0 : return log<software_error, -1>( { "" } );
637 : }
638 :
639 1 : return 0;
640 : }
641 :
642 20 : inline int ocam2KCtrl::appLogic()
643 : {
644 :
645 : // and run stdCamera's appLogic
646 20 : if( dev::stdCamera<ocam2KCtrl>::appLogic() < 0 )
647 : {
648 0 : return log<software_error, -1>( { "" } );
649 : }
650 :
651 : // and run edtCamera's appLogic
652 20 : if( dev::edtCamera<ocam2KCtrl>::appLogic() < 0 )
653 : {
654 0 : return log<software_error, -1>( { "" } );
655 : }
656 :
657 : // first run frameGrabber's appLogic to see if the f.g. thread has exited.
658 20 : if( dev::frameGrabber<ocam2KCtrl>::appLogic() < 0 )
659 : {
660 1 : return log<software_error, -1>( { "" } );
661 : }
662 :
663 : // and run dssShutter's appLogic
664 19 : if( dev::dssShutter<ocam2KCtrl>::appLogic() < 0 )
665 : {
666 0 : return log<software_error, -1>( { "" } );
667 : }
668 :
669 19 : if( state() == stateCodes::POWERON )
670 1 : return 0;
671 :
672 18 : if( state() == stateCodes::NOTCONNECTED || state() == stateCodes::ERROR )
673 : {
674 3 : m_temps.setInvalid();
675 :
676 3 : std::string response;
677 :
678 : // Might have gotten here because of a power off.
679 3 : if( MagAOXAppT::m_powerState == 0 )
680 1 : return 0;
681 :
682 2 : int ret = 0;
683 : { // mutex scope
684 2 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
685 4 : ret = pdvSerialWriteRead( response, "fps" ); // m_pdv, "fps", m_readTimeout);
686 2 : }
687 2 : if( ret == 0 )
688 : {
689 1 : state( stateCodes::CONNECTED );
690 : }
691 : else
692 : {
693 1 : sleep( 1 );
694 1 : return 0;
695 : }
696 3 : }
697 :
698 16 : if( state() == stateCodes::CONNECTED )
699 : {
700 : // Get a lock
701 7 : std::unique_lock<std::mutex> lock( m_indiMutex );
702 :
703 7 : if( getFPS() == 0 )
704 : {
705 5 : if( m_fpsSet == 0 )
706 4 : state( stateCodes::READY );
707 : else
708 1 : state( stateCodes::OPERATING );
709 :
710 5 : if( m_poweredOn && m_ccdTempSetpt > -999 )
711 : {
712 3 : m_poweredOn = false;
713 3 : if( setTempSetPt() < 0 )
714 : {
715 2 : if( powerState() != 1 || powerStateTarget() != 1 )
716 : {
717 1 : return 0;
718 : }
719 1 : return log<software_error, 0>( { "" } );
720 : }
721 : }
722 :
723 : // We always have to set synchro on connecting, b/c otherwise we don't know the state.
724 3 : m_synchroSet = false;
725 3 : if( setSynchro() != 0 )
726 : {
727 1 : log<software_error>( { "error from setSynchro on CONNECT" } );
728 : }
729 : }
730 : else
731 : {
732 2 : if( powerState() != 1 || powerStateTarget() != 1 )
733 1 : return 0;
734 1 : state( stateCodes::ERROR );
735 1 : return log<software_error, 0>( { "" } );
736 : }
737 7 : }
738 :
739 12 : if( state() == stateCodes::READY || state() == stateCodes::OPERATING )
740 : {
741 : // Get a lock if we can
742 11 : std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
743 :
744 : // but don't wait for it, just go back around.
745 11 : if( !lock.owns_lock() )
746 1 : return 0;
747 :
748 10 : if( getTemps() < 0 )
749 : {
750 2 : if( powerState() != 1 || powerStateTarget() != 1 )
751 1 : return 0;
752 1 : m_temps.setInvalid();
753 1 : state( stateCodes::ERROR );
754 1 : return 0;
755 : }
756 :
757 8 : if( getFPS() < 0 )
758 : {
759 2 : if( powerState() != 1 || powerStateTarget() != 1 )
760 1 : return 0;
761 :
762 1 : state( stateCodes::ERROR );
763 1 : return 0;
764 : }
765 :
766 6 : if( m_protectionResetConfirmed > 0 )
767 : {
768 2 : if( mx::sys::get_curr_time() - m_protectionResetReqTime > 10.0 )
769 : {
770 2 : m_protectionResetConfirmed = 0;
771 8 : updateIfChanged( m_indiP_emProt, "status", std::string( "UNCONFIRMED" ) );
772 2 : log<text_log>( "protection reset request not confirmed", logPrio::LOG_NOTICE );
773 : }
774 : }
775 :
776 6 : if( getEMGain() < 0 )
777 : {
778 2 : if( MagAOXAppT::m_powerState == 0 )
779 1 : return 0;
780 :
781 1 : state( stateCodes::ERROR );
782 1 : return 0;
783 : }
784 :
785 4 : if( frameGrabber<ocam2KCtrl>::updateINDI() < 0 )
786 : {
787 0 : log<software_error>( { "" } );
788 0 : state( stateCodes::ERROR );
789 0 : return 0;
790 : }
791 :
792 4 : if( stdCamera<ocam2KCtrl>::updateINDI() < 0 )
793 : {
794 0 : log<software_error>( { "" } );
795 0 : state( stateCodes::ERROR );
796 0 : return 0;
797 : }
798 :
799 4 : if( edtCamera<ocam2KCtrl>::updateINDI() < 0 )
800 : {
801 0 : log<software_error>( { "" } );
802 0 : state( stateCodes::ERROR );
803 0 : return 0;
804 : }
805 :
806 4 : if( telemeter<ocam2KCtrl>::appLogic() < 0 )
807 : {
808 4 : log<software_error>( { "" } );
809 4 : return 0;
810 : }
811 11 : }
812 :
813 : ///\todo Fall through check?
814 :
815 1 : return 0;
816 : }
817 :
818 1 : inline int ocam2KCtrl::onPowerOff()
819 : {
820 1 : m_powerOnCounter = 0;
821 :
822 1 : std::lock_guard<std::mutex> lock( m_indiMutex );
823 :
824 4 : updateIfChanged( m_indiP_emProt, "status", std::string( "UNKNOWN" ), INDI_IDLE );
825 :
826 1 : m_temps.setInvalid();
827 :
828 2 : updateIfChanged( m_indiP_temps, "cpu", m_temps.CPU );
829 2 : updateIfChanged( m_indiP_temps, "power", m_temps.POWER );
830 2 : updateIfChanged( m_indiP_temps, "bias", m_temps.BIAS );
831 2 : updateIfChanged( m_indiP_temps, "water", m_temps.WATER );
832 2 : updateIfChanged( m_indiP_temps, "left", m_temps.LEFT );
833 2 : updateIfChanged( m_indiP_temps, "right", m_temps.RIGHT );
834 2 : updateIfChanged( m_indiP_temps, "cooling", m_temps.COOLING_POWER );
835 :
836 1 : if( stdCamera<ocam2KCtrl>::onPowerOff() < 0 )
837 : {
838 0 : log<software_error>( { "" } );
839 : }
840 :
841 1 : if( edtCamera<ocam2KCtrl>::onPowerOff() < 0 )
842 : {
843 0 : log<software_error>( { "" } );
844 : }
845 :
846 1 : if( frameGrabber<ocam2KCtrl>::onPowerOff() < 0 )
847 : {
848 0 : log<software_error>( { "" } );
849 : }
850 :
851 1 : if( dssShutter<ocam2KCtrl>::onPowerOff() < 0 )
852 : {
853 0 : log<software_error>( { "" } );
854 : }
855 :
856 : // Setting m_poweredOn
857 1 : m_poweredOn = true;
858 :
859 1 : return 0;
860 1 : }
861 :
862 1 : inline int ocam2KCtrl::whilePowerOff()
863 : {
864 1 : std::lock_guard<std::mutex> lock( m_indiMutex );
865 :
866 1 : if( stdCamera<ocam2KCtrl>::whilePowerOff() < 0 )
867 : {
868 0 : log<software_error>( { "" } );
869 : }
870 :
871 1 : if( edtCamera<ocam2KCtrl>::whilePowerOff() < 0 )
872 : {
873 0 : log<software_error>( { "" } );
874 : }
875 :
876 1 : if( dssShutter<ocam2KCtrl>::whilePowerOff() < 0 )
877 : {
878 0 : log<software_error>( { "" } );
879 : }
880 :
881 1 : return 0;
882 1 : }
883 :
884 2 : inline int ocam2KCtrl::appShutdown()
885 : {
886 : ///\todo error check these base class fxns.
887 :
888 2 : dev::stdCamera<ocam2KCtrl>::appShutdown();
889 :
890 2 : dev::edtCamera<ocam2KCtrl>::appShutdown();
891 :
892 2 : dev::frameGrabber<ocam2KCtrl>::appShutdown();
893 :
894 2 : destroySyncStream();
895 :
896 2 : dev::dssShutter<ocam2KCtrl>::appShutdown();
897 :
898 2 : dev::telemeter<ocam2KCtrl>::appShutdown();
899 :
900 2 : return 0;
901 : }
902 :
903 16 : inline int ocam2KCtrl::getTemps()
904 : {
905 16 : std::string response;
906 :
907 : { // mutex scope
908 16 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
909 48 : if( pdvSerialWriteRead( response, "temp" ) != 0 )
910 : {
911 4 : if( powerState() != 1 || powerStateTarget() != 1 )
912 2 : return -1;
913 2 : return log<software_error, -1>( { "" } );
914 : }
915 16 : }
916 :
917 : {
918 12 : ocamTemps temps;
919 :
920 12 : if( parseTemps( temps, response ) < 0 )
921 : {
922 2 : if( powerState() != 1 || powerStateTarget() != 1 )
923 1 : return -1;
924 :
925 1 : m_temps.setInvalid();
926 1 : m_ccdTemp = m_temps.CCD;
927 1 : m_ccdTempSetpt = m_temps.SET;
928 1 : m_tempControlStatus = false;
929 1 : m_tempControlStatusStr = "UNKNOWN";
930 :
931 1 : recordTemps();
932 1 : recordCamera();
933 :
934 1 : std::cerr << "Temp. parse error. Response:\n" << response << std::endl;
935 :
936 : // We don't trust the temps, but don't reconfig just for this.
937 1 : return log<software_error, 0>( { "Temp. parse error" } );
938 : }
939 :
940 10 : m_temps = temps;
941 :
942 : // stdCamera temp control:
943 10 : m_ccdTemp = m_temps.CCD;
944 10 : m_ccdTempSetpt = m_temps.SET;
945 :
946 : // Detect that temperature control is off
947 10 : if( m_temps.COOLING_POWER < 5 )
948 : {
949 1 : if( m_temps.CCD - m_temps.SET > 2.99 )
950 : {
951 1 : m_tempControlStatus = false;
952 : }
953 : }
954 : else
955 9 : m_tempControlStatus = true;
956 :
957 10 : if( m_tempControlStatus == true )
958 : {
959 9 : if( fabs( m_temps.CCD - m_temps.SET ) < 1.0 )
960 : {
961 8 : m_tempControlStatusStr = "ON TARGET";
962 8 : m_tempControlOnTarget = true;
963 : }
964 : else
965 : {
966 1 : m_tempControlStatusStr = "OFF TARGET";
967 1 : m_tempControlOnTarget = false;
968 : }
969 : }
970 : else
971 : {
972 1 : m_tempControlStatusStr = "TEMP OFF";
973 1 : m_tempControlOnTarget = false;
974 : }
975 :
976 : // Telemeter:
977 10 : recordTemps();
978 10 : recordCamera();
979 :
980 20 : updateIfChanged( m_indiP_temps, "cpu", m_temps.CPU );
981 20 : updateIfChanged( m_indiP_temps, "power", m_temps.POWER );
982 20 : updateIfChanged( m_indiP_temps, "bias", m_temps.BIAS );
983 20 : updateIfChanged( m_indiP_temps, "water", m_temps.WATER );
984 20 : updateIfChanged( m_indiP_temps, "left", m_temps.LEFT );
985 20 : updateIfChanged( m_indiP_temps, "right", m_temps.RIGHT );
986 20 : updateIfChanged( m_indiP_temps, "cooling", m_temps.COOLING_POWER );
987 10 : return 0;
988 : }
989 16 : }
990 :
991 2 : inline int ocam2KCtrl::powerOnDefaults()
992 : {
993 : // Camera boots up with this true in most cases.
994 2 : m_tempControlStatusSet = false;
995 2 : m_tempControlStatus = false;
996 :
997 2 : return 0;
998 : }
999 :
1000 5 : inline int ocam2KCtrl::setTempControl()
1001 : {
1002 5 : std::string response;
1003 :
1004 5 : std::string command;
1005 :
1006 5 : std::string comStr = "temp ";
1007 5 : if( m_tempControlStatusSet )
1008 : {
1009 3 : command = "on";
1010 3 : m_tempControlStatusSet = true;
1011 3 : m_tempControlStatus = true;
1012 : }
1013 : else
1014 : {
1015 2 : if( m_ccdTemp > 19 ) // 19 is 20 with a 1 C slop
1016 : {
1017 1 : command = "off";
1018 1 : m_tempControlStatusSet = false;
1019 1 : m_tempControlStatus = false;
1020 : }
1021 : else
1022 : {
1023 1 : return log<text_log, -1>( "Can not turn temp control off when not at 20 C or higher", logPrio::LOG_ERROR );
1024 : }
1025 : }
1026 :
1027 4 : comStr += command;
1028 :
1029 : { // mutex scope
1030 4 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1031 4 : if( pdvSerialWriteRead( response, comStr ) != 0 )
1032 : {
1033 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1034 1 : return -1;
1035 1 : return log<software_error, -1>( { "" } );
1036 : }
1037 4 : }
1038 :
1039 : {
1040 2 : std::cerr << "response: " << response << "\n";
1041 : ///\todo check response
1042 2 : log<text_log, 0>( { "Set temperature control to " + command } );
1043 : }
1044 :
1045 2 : if( m_tempControlStatusSet && m_ccdTempSetpt > -999 )
1046 : {
1047 1 : return setTempSetPt();
1048 : }
1049 :
1050 1 : recordCamera();
1051 :
1052 1 : return 0;
1053 5 : }
1054 :
1055 9 : inline int ocam2KCtrl::setTempSetPt()
1056 : {
1057 9 : std::string response;
1058 :
1059 9 : std::string tempStr = std::to_string( m_ccdTempSetpt );
1060 :
1061 : ///\todo make more configurable
1062 9 : if( m_ccdTempSetpt >= 30 || m_ccdTempSetpt < -50 )
1063 : {
1064 4 : return log<text_log, -1>( { "attempt to set temperature outside valid range: " + tempStr },
1065 2 : logPrio::LOG_ERROR );
1066 : }
1067 :
1068 : { // mutex scope
1069 7 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1070 7 : if( pdvSerialWriteRead( response, "temp " + tempStr ) != 0 )
1071 : {
1072 4 : if( powerState() != 1 || powerStateTarget() != 1 )
1073 2 : return -1;
1074 2 : return log<software_error, -1>( { "" } );
1075 : }
1076 7 : }
1077 :
1078 : {
1079 3 : std::cerr << "response: " << response << "\n";
1080 :
1081 3 : recordCamera();
1082 :
1083 : ///\todo check response
1084 3 : std::cerr << "temp " << tempStr << " response: " << response << "\n";
1085 :
1086 3 : return log<text_log, 0>( { "set temperature: " + tempStr } );
1087 : }
1088 9 : }
1089 :
1090 20 : inline int ocam2KCtrl::getFPS()
1091 : {
1092 20 : if( !m_synchro )
1093 : {
1094 19 : std::string response;
1095 :
1096 : { // mutex scope
1097 19 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1098 57 : if( pdvSerialWriteRead( response, "fps" ) != 0 )
1099 5 : return log<software_error, -1>( { "" } ); // m_pdv, "fps", m_readTimeout) == 0)
1100 19 : }
1101 :
1102 : {
1103 : float fps;
1104 14 : if( parseFPS( fps, response ) < 0 )
1105 : {
1106 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1107 1 : return -1;
1108 :
1109 1 : std::cerr << "fps parse error. Response:\n" << response << "\n";
1110 :
1111 1 : return log<software_error, 0>( { "fps parse error" } );
1112 : }
1113 12 : m_fps = fps;
1114 :
1115 12 : recordCamera();
1116 :
1117 12 : return 0;
1118 : }
1119 19 : }
1120 : else
1121 : {
1122 1 : m_fps = m_syncFreq;
1123 1 : recordCamera();
1124 :
1125 1 : return 0;
1126 : }
1127 : }
1128 :
1129 17 : inline int ocam2KCtrl::setFPS()
1130 : {
1131 17 : std::string fpsStr = std::to_string( m_fpsSet );
1132 :
1133 17 : if( !m_synchro )
1134 : {
1135 15 : std::string response;
1136 :
1137 : { // mutex scope
1138 15 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1139 15 : if( pdvSerialWriteRead( response, "fps " + fpsStr ) != 0 )
1140 : {
1141 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1142 1 : return -1;
1143 1 : return log<software_error, -1>( { "" } );
1144 : }
1145 15 : }
1146 :
1147 : {
1148 : ///\todo check response
1149 13 : std::cerr << "fps " << fpsStr << " response: " << response << "\n";
1150 13 : log<text_log>( { "set fps: " + fpsStr } );
1151 :
1152 : // We always want to reset the latency circular buffers
1153 : ///\todo verify that this works!!
1154 13 : m_nextMode = m_modeName;
1155 13 : m_reconfig = true;
1156 : }
1157 15 : }
1158 : else
1159 : {
1160 2 : std::cerr << "setting: " << fpsStr << "\n";
1161 :
1162 2 : pcf::IndiProperty ipFreq( pcf::IndiProperty::Number );
1163 :
1164 2 : ipFreq.setDevice( m_syncDevice );
1165 2 : ipFreq.setName( m_syncFreqProp );
1166 4 : ipFreq.add( pcf::IndiElement( "target" ) );
1167 :
1168 2 : ipFreq["target"] = fpsStr;
1169 :
1170 2 : sendNewProperty( ipFreq );
1171 2 : }
1172 :
1173 15 : return 0;
1174 17 : }
1175 :
1176 18 : inline int ocam2KCtrl::setSynchro()
1177 : {
1178 18 : std::string response;
1179 :
1180 : { // mutex scope
1181 18 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1182 :
1183 : // First set the actual FPS to 0 to get to max
1184 18 : std::string fpsStr = std::to_string( 0 );
1185 18 : if( pdvSerialWriteRead( response, "fps " + fpsStr ) == 0 )
1186 : {
1187 : ///\todo check response
1188 16 : std::cerr << "fps " << fpsStr << " response: " << response << "\n";
1189 16 : log<text_log>( { "set fps: " + fpsStr } );
1190 : }
1191 : else
1192 : {
1193 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1194 1 : return -1;
1195 1 : return log<software_error, -1>( { "" } );
1196 : }
1197 :
1198 : // Now actually turn synchro on
1199 16 : std::string sStr;
1200 16 : if( m_synchroSet )
1201 3 : sStr = "on";
1202 : else
1203 13 : sStr = "off";
1204 16 : if( pdvSerialWriteRead( response, "synchro " + sStr ) == 0 )
1205 : {
1206 : ///\todo check response
1207 12 : std::cerr << "synchro " << sStr << " resonse: " << response << "\n";
1208 12 : log<text_log>( { "set synchro: " + sStr } );
1209 :
1210 12 : m_synchro = m_synchroSet;
1211 :
1212 12 : if( m_synchro == false )
1213 : {
1214 33 : updateSwitchIfChanged( m_indiP_synchro, "toggle", pcf::IndiElement::Off, INDI_IDLE );
1215 : }
1216 : else
1217 : {
1218 3 : updateSwitchIfChanged( m_indiP_synchro, "toggle", pcf::IndiElement::On, INDI_OK );
1219 : }
1220 : }
1221 : else
1222 : {
1223 4 : if( powerState() != 1 || powerStateTarget() != 1 )
1224 1 : return -1;
1225 3 : return log<software_error, -1>( { "" } );
1226 : }
1227 28 : }
1228 :
1229 : // Finally we set the FPS of the synchro device
1230 12 : return setFPS();
1231 18 : }
1232 :
1233 1 : inline int ocam2KCtrl::setExpTime()
1234 : {
1235 1 : return 0;
1236 : }
1237 :
1238 1 : inline int ocam2KCtrl::setNextROI()
1239 : {
1240 1 : return 0;
1241 : }
1242 :
1243 1 : inline int ocam2KCtrl::setShutter( int sh )
1244 : {
1245 1 : return dssShutter<ocam2KCtrl>::setShutterState( sh );
1246 : }
1247 :
1248 1 : inline std::string ocam2KCtrl::stateString()
1249 : {
1250 1 : std::string ss;
1251 :
1252 1 : ss += m_modeName + "_";
1253 1 : ss += std::to_string( m_fps ) + "_";
1254 1 : ss += std::to_string( m_emGain ) + "_";
1255 1 : ss += std::to_string( m_ccdTempSetpt );
1256 :
1257 1 : return ss;
1258 0 : }
1259 :
1260 3 : inline bool ocam2KCtrl::stateStringValid()
1261 : {
1262 3 : if( state() == stateCodes::OPERATING && m_tempControlOnTarget )
1263 : {
1264 1 : return true;
1265 : }
1266 : else
1267 2 : return false;
1268 : }
1269 :
1270 4 : inline int ocam2KCtrl::resetEMProtection()
1271 : {
1272 4 : std::string response;
1273 :
1274 : { // mutex scope
1275 4 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1276 12 : if( pdvSerialWriteRead( response, "protection reset" ) != 0 )
1277 : {
1278 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1279 1 : return -1;
1280 1 : return log<software_error, -1>( { "" } );
1281 : }
1282 4 : }
1283 :
1284 : {
1285 2 : std::cerr << "\n******************************************\n";
1286 2 : std::cerr << "protection reset:\n";
1287 2 : std::cerr << response << "\n";
1288 2 : std::cerr << "\n******************************************\n";
1289 : ///\todo check response.
1290 :
1291 8 : updateIfChanged( m_indiP_emProt, "status", std::string( "RESET" ), INDI_OK );
1292 :
1293 2 : log<text_log>( "overillumination protection has been reset", logPrio::LOG_NOTICE );
1294 :
1295 2 : m_protectionResetConfirmed = 0;
1296 :
1297 2 : m_protectionReset = true;
1298 :
1299 2 : return 0;
1300 : }
1301 4 : }
1302 :
1303 10 : inline int ocam2KCtrl::getEMGain()
1304 : {
1305 10 : std::string response;
1306 :
1307 : { // mutex scope
1308 10 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1309 30 : if( pdvSerialWriteRead( response, "gain" ) != 0 )
1310 : {
1311 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1312 1 : return -1;
1313 1 : return log<software_error, -1>( { "" } );
1314 : }
1315 10 : }
1316 :
1317 : {
1318 : unsigned emGain;
1319 8 : if( parseEMGain( emGain, response ) < 0 )
1320 : {
1321 3 : if( powerState() != 1 || powerStateTarget() != 1 )
1322 1 : return -1;
1323 :
1324 2 : if( response.find( "HV" ) != std::string::npos )
1325 : {
1326 1 : m_emGain = 1;
1327 5 : updateIfChanged( m_indiP_emProt, "status", std::string( "TRIPPED" ), INDI_ALERT );
1328 1 : return log<software_warning, -1>( { "EM Gain tripped!" } );
1329 : }
1330 :
1331 1 : std::cerr << "EM Gain parse error, response:\n" << response << "\n";
1332 :
1333 1 : return log<software_error, -1>( { "EM Gain parse error" } );
1334 : }
1335 :
1336 5 : m_emGain = emGain;
1337 :
1338 5 : return 0;
1339 : }
1340 10 : }
1341 :
1342 5 : inline int ocam2KCtrl::setEMGain()
1343 : {
1344 5 : std::string response;
1345 :
1346 5 : if( m_protectionReset == false )
1347 : {
1348 1 : log<text_log>( "Attempt to set EM gain before protection reset", logPrio::LOG_NOTICE );
1349 :
1350 1 : if( m_emGainSet > 1 ) // we allow setting to 1 for safety
1351 : {
1352 1 : return 0;
1353 : }
1354 : }
1355 :
1356 4 : unsigned emg = m_emGainSet; // a float
1357 :
1358 4 : if( emg < 1 || emg > m_maxEMGain )
1359 : {
1360 1 : log<text_log>( "Attempt to set EM gain to " + std::to_string( emg ) + " outside limits refused",
1361 : logPrio::LOG_WARNING );
1362 1 : return 0;
1363 : }
1364 :
1365 3 : std::string emgStr = std::to_string( emg );
1366 : { // mutex scope
1367 3 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1368 3 : if( pdvSerialWriteRead( response, "gain " + emgStr ) != 0 ) // m_pdv, "gain " + emgStr, m_readTimeout) == 0)
1369 : {
1370 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1371 1 : return -1;
1372 1 : return log<software_error, -1>( { "" } );
1373 : }
1374 3 : }
1375 :
1376 : {
1377 : ///\todo check response
1378 1 : std::cerr << "gain " << emgStr << " response: " << emgStr << "\n";
1379 :
1380 1 : log<text_log>( { "set EM Gain: " + emgStr } );
1381 :
1382 1 : return 0;
1383 : }
1384 5 : }
1385 :
1386 11 : inline int ocam2KCtrl::configureAcquisition()
1387 : {
1388 : // lock mutex
1389 11 : std::unique_lock<std::mutex> lock( m_indiMutex );
1390 11 : std::lock_guard<std::recursive_mutex> cameraGuard( m_cameraMutex );
1391 :
1392 : // Send command to camera to place it in the correct mode
1393 11 : std::string response;
1394 11 : if( pdvSerialWriteRead( response, m_cameraModes[m_modeName].m_serialCommand ) != 0 )
1395 : {
1396 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1397 1 : return -1;
1398 :
1399 1 : log<software_error>( { "Error sending command to set mode" } );
1400 1 : sleep( 1 );
1401 1 : return -1;
1402 : }
1403 :
1404 9 : m_currentROI.x = 119.5;
1405 9 : m_currentROI.y = 119.5;
1406 9 : m_currentROI.w = 240;
1407 9 : m_currentROI.h = 240;
1408 9 : m_currentROI.bin_x = m_cameraModes[m_modeName].m_binningX;
1409 9 : m_currentROI.bin_y = m_cameraModes[m_modeName].m_binningY;
1410 :
1411 9 : m_digitalBinX = m_cameraModes[m_modeName].m_digitalBinX;
1412 9 : m_digitalBinY = m_cameraModes[m_modeName].m_digitalBinY;
1413 :
1414 9 : if( m_digitalBinX > 1 )
1415 : {
1416 2 : m_digitalBin = true;
1417 2 : std::cerr << "digital binning!\n";
1418 : }
1419 : else
1420 : {
1421 7 : m_digitalBin = false;
1422 : }
1423 :
1424 9 : recordCamera();
1425 :
1426 : ///\todo check response of pdvSerialWriteRead
1427 9 : log<text_log>( "camera configured with: " + m_cameraModes[m_modeName].m_serialCommand );
1428 :
1429 9 : if( m_fpsSet > 0 )
1430 : {
1431 1 : setFPS();
1432 : }
1433 :
1434 9 : log<text_log>( "Send command to set mode: " + m_cameraModes[m_modeName].m_serialCommand );
1435 9 : log<text_log>( "Response was: " + response );
1436 :
1437 9 : if( setSynchro() < 0 )
1438 : {
1439 1 : log<software_error>( { "Error setting synchro during configureAcquisition" } );
1440 : }
1441 :
1442 : /* Initialize the OCAM2 SDK
1443 : */
1444 :
1445 9 : if( m_ocam2_id > 0 )
1446 : {
1447 1 : ocam2_exit( m_ocam2_id );
1448 1 : m_ocam2_id = 0;
1449 : }
1450 : ocam2_rc rc;
1451 : ocam2_mode mode;
1452 :
1453 : int OCAM_SZw;
1454 : int OCAM_SZh;
1455 9 : if( m_raw_height == 121 )
1456 : {
1457 5 : mode = OCAM2_NORMAL;
1458 5 : OCAM_SZw = 240;
1459 5 : OCAM_SZh = 240;
1460 : }
1461 4 : else if( m_raw_height == 62 )
1462 : {
1463 1 : mode = OCAM2_BINNING;
1464 1 : OCAM_SZw = 120;
1465 1 : OCAM_SZh = 120;
1466 : }
1467 3 : else if( m_raw_height == 41 )
1468 : {
1469 1 : mode = OCAM2_BINNING1x3;
1470 1 : OCAM_SZw = 240;
1471 1 : OCAM_SZh = 80;
1472 : }
1473 2 : else if( m_raw_height == 31 )
1474 : {
1475 1 : mode = OCAM2_BINNING1x4;
1476 1 : OCAM_SZw = 240;
1477 1 : OCAM_SZh = 60;
1478 : }
1479 : else
1480 : {
1481 1 : log<text_log>( "Unrecognized OCAM2 mode.", logPrio::LOG_ERROR );
1482 1 : return -1;
1483 : }
1484 :
1485 8 : std::string ocamDescrambleFile = m_configDir + "/" + m_ocamDescrambleFile;
1486 :
1487 8 : std::cerr << "ocamDescrambleFile: " << ocamDescrambleFile << std::endl;
1488 :
1489 8 : ocam2_id nextOcam2Id = 0;
1490 :
1491 8 : rc = ocam2_init( mode, ocamDescrambleFile.c_str(), &nextOcam2Id );
1492 :
1493 8 : if( rc != OCAM2_OK )
1494 : {
1495 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1496 1 : return -1;
1497 1 : log<text_log>( "ocam2_init error. Failed to initialize OCAM SDK with descramble file: " + ocamDescrambleFile,
1498 : logPrio::LOG_ERROR );
1499 1 : return -1;
1500 : }
1501 :
1502 6 : m_ocam2_id = nextOcam2Id;
1503 :
1504 6 : log<text_log>( "OCAM2K initialized. id: " + std::to_string( m_ocam2_id ) );
1505 :
1506 12 : log<text_log>( std::string( "OCAM2K mode is:" ) + ocam2_modeStr( ocam2_getMode( m_ocam2_id ) ) );
1507 :
1508 6 : if( m_digitalBin )
1509 : {
1510 1 : std::cerr << "and digital binning!\n";
1511 1 : m_digitalBinWork.resize( OCAM_SZw, OCAM_SZh );
1512 :
1513 1 : m_width = OCAM_SZw / m_digitalBinX;
1514 1 : m_height = OCAM_SZh / m_digitalBinY;
1515 : }
1516 : else
1517 : {
1518 5 : m_width = OCAM_SZw;
1519 5 : m_height = OCAM_SZh;
1520 : }
1521 :
1522 6 : m_dataType = _DATATYPE_UINT16;
1523 :
1524 6 : if( ensureSyncStream() < 0 )
1525 : {
1526 0 : return log<software_error, -1>( { "Error preparing sync ImageStreamIO stream" } );
1527 : }
1528 :
1529 6 : state( stateCodes::OPERATING );
1530 :
1531 6 : return 0;
1532 11 : }
1533 :
1534 2 : inline float ocam2KCtrl::fps()
1535 : {
1536 2 : return m_fps;
1537 : }
1538 :
1539 1 : inline int ocam2KCtrl::startAcquisition()
1540 : {
1541 1 : m_lastImageNumber = -1;
1542 1 : return edtCamera<ocam2KCtrl>::pdvStartAcquisition();
1543 : }
1544 :
1545 4 : inline int ocam2KCtrl::acquireAndCheckValid()
1546 : {
1547 4 : edtCamera<ocam2KCtrl>::pdvAcquire( m_currImageTimestamp );
1548 :
1549 : /* Removed all pdv timeout and overrun checking, since we can rely on frame number from the camera
1550 : to detect missed and corrupted frames.
1551 :
1552 : See ef0dd24 for last version with full checks in it.
1553 : */
1554 :
1555 : // Get the image number to see if this is valid.
1556 : // This is how it is in the ocam2_sdk:
1557 4 : unsigned currImageNumber = ( reinterpret_cast<int *>( m_image_p ) )[OCAM2_IMAGE_NB_OFFSET / 4]; /* int offset */
1558 4 : m_currImageNumber = currImageNumber;
1559 :
1560 : // For the first loop after a restart
1561 4 : if( m_lastImageNumber == -1 )
1562 : {
1563 1 : m_lastImageNumber = m_currImageNumber - 1;
1564 : }
1565 :
1566 4 : if( m_currImageNumber - m_lastImageNumber != 1 )
1567 : {
1568 : // Detect exact condition of a wraparound on the unsigned int.
1569 : // Yes, this can only happen once every 13.72 days at 3622 fps
1570 : // But just in case . . .
1571 3 : if( m_lastImageNumber != std::numeric_limits<unsigned int>::max() && m_currImageNumber != 0 )
1572 : {
1573 : // The far more likely case is a problem...
1574 :
1575 : // If a reasonably small number of frames skipped, then we trust the image number
1576 3 : if( m_currImageNumber - m_lastImageNumber > 1 && m_currImageNumber - m_lastImageNumber < 100 )
1577 : {
1578 : // This we handle as a non-timeout -- report how many frames were skipped
1579 1 : long framesSkipped = m_currImageNumber - m_lastImageNumber - 1;
1580 :
1581 1 : log<text_log>( "frames skipped: " + std::to_string( framesSkipped ), logPrio::LOG_ERROR );
1582 :
1583 1 : m_lastImageNumber = -1;
1584 1 : m_nextMode = m_modeName;
1585 1 : m_reconfig = 1;
1586 1 : return 1;
1587 : }
1588 : else // but if it's any bigger or < 0, it's probably garbage
1589 : {
1590 2 : if( powerState() != 1 || powerStateTarget() != 1 )
1591 1 : return -1;
1592 :
1593 : ///\todo need frame corrupt log type
1594 1 : log<text_log>( "frame number possibly corrupt: " + std::to_string( m_currImageNumber ) + " - " +
1595 2 : std::to_string( m_lastImageNumber ),
1596 : logPrio::LOG_ERROR );
1597 :
1598 1 : m_nextMode = m_modeName;
1599 1 : m_reconfig = 1;
1600 :
1601 : // Reset the counters.
1602 1 : m_lastImageNumber = -1;
1603 1 : return 1;
1604 : }
1605 : }
1606 : }
1607 1 : m_lastImageNumber = m_currImageNumber;
1608 1 : return 0;
1609 : }
1610 :
1611 2 : inline int ocam2KCtrl::loadImageIntoStream( void *dest )
1612 : {
1613 2 : unsigned currImageNumber = 0;
1614 :
1615 2 : if( !m_digitalBin )
1616 : {
1617 1 : ocam2_descramble( m_ocam2_id,
1618 : &currImageNumber,
1619 : reinterpret_cast<int16_t *>( dest ),
1620 1 : reinterpret_cast<int16_t *>( m_image_p ) );
1621 : // memcpy(dest, m_image_p, 120*120*2); //This is about 10 usec faster -- but we have to descramble.
1622 : }
1623 : else
1624 : {
1625 1 : ocam2_descramble(
1626 1 : m_ocam2_id, &currImageNumber, m_digitalBinWork.data(), reinterpret_cast<int16_t *>( m_image_p ) );
1627 :
1628 1 : mx::improc::eigenMap<int16_t> destMap( reinterpret_cast<int16_t *>( dest ), m_width, m_height );
1629 :
1630 1 : if( m_digitalBinX > 1 )
1631 : {
1632 3 : for( int cc = 0; cc < destMap.cols(); ++cc )
1633 : {
1634 6 : for( int rr = 0; rr < destMap.rows(); ++rr )
1635 : {
1636 4 : destMap( rr, cc ) = m_digitalBinWork( rr * m_digitalBinX + 0, cc );
1637 :
1638 8 : for( unsigned b = 1; b < m_digitalBinX; ++b )
1639 : {
1640 4 : destMap( rr, cc ) = m_digitalBinWork( rr * m_digitalBinX + b, cc );
1641 : }
1642 : }
1643 : }
1644 : }
1645 : }
1646 :
1647 2 : return 0;
1648 : }
1649 :
1650 1 : inline int ocam2KCtrl::reconfig()
1651 : {
1652 1 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1653 :
1654 1 : int rv = edtCamera<ocam2KCtrl>::pdvReconfig();
1655 :
1656 1 : if( rv < 0 )
1657 : {
1658 0 : return rv;
1659 : }
1660 :
1661 1 : state( stateCodes::READY );
1662 1 : return 0;
1663 1 : }
1664 :
1665 3 : inline int ocam2KCtrl::frameGrabberPostPublish( IMAGE *imageStream )
1666 : {
1667 3 : if( imageStream == nullptr )
1668 : {
1669 1 : return 0;
1670 : }
1671 :
1672 2 : if( m_syncImageStream == nullptr )
1673 : {
1674 1 : return log<software_error, -1>( { "Sync ImageStreamIO stream unavailable during publication" } );
1675 : }
1676 :
1677 1 : m_syncImageStream->md[0].write = 1;
1678 1 : m_syncImageStream->array.UI8[0] = 0;
1679 1 : m_syncImageStream->md[0].writetime = imageStream->md[0].writetime;
1680 1 : m_syncImageStream->md[0].atime = imageStream->md[0].atime;
1681 1 : m_syncImageStream->md[0].cnt0 = imageStream->md[0].cnt0;
1682 1 : m_syncImageStream->md[0].cnt1 = 0;
1683 1 : m_syncImageStream->writetimearray[0] = imageStream->md[0].writetime;
1684 1 : m_syncImageStream->atimearray[0] = imageStream->md[0].atime;
1685 1 : m_syncImageStream->cntarray[0] = imageStream->md[0].cnt0;
1686 1 : m_syncImageStream->md[0].write = 0;
1687 :
1688 1 : ImageStreamIO_sempost( m_syncImageStream, -1 );
1689 :
1690 1 : return 0;
1691 : }
1692 :
1693 7 : INDI_NEWCALLBACK_DEFN( ocam2KCtrl, m_indiP_emProtReset )( const pcf::IndiProperty &ipRecv )
1694 : {
1695 7 : if( MagAOXAppT::m_powerState == 0 )
1696 1 : return 0;
1697 :
1698 6 : if( ipRecv.getName() != m_indiP_emProtReset.getName() )
1699 : {
1700 1 : log<software_error>( { "wrong INDI property received." } );
1701 1 : return -1;
1702 : }
1703 :
1704 10 : if( !ipRecv.find( "request" ) )
1705 : {
1706 1 : return 0;
1707 : }
1708 :
1709 8 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::Off )
1710 : {
1711 1 : return 0;
1712 : }
1713 :
1714 3 : std::unique_lock<std::mutex> lock( m_indiMutex );
1715 :
1716 3 : if( m_protectionResetConfirmed == 0 )
1717 : {
1718 8 : updateIfChanged( m_indiP_emProt, "status", std::string( "CONFIRM" ), INDI_BUSY );
1719 :
1720 2 : m_protectionResetConfirmed = 1;
1721 :
1722 2 : m_protectionResetReqTime = mx::sys::get_curr_time();
1723 :
1724 2 : log<text_log>( "protection reset requested", logPrio::LOG_NOTICE );
1725 :
1726 2 : return 0;
1727 : }
1728 :
1729 : // If here, this is a confirmation.
1730 1 : return resetEMProtection();
1731 3 : }
1732 :
1733 3 : INDI_SETCALLBACK_DEFN( ocam2KCtrl, m_indiP_syncFreq )( const pcf::IndiProperty &ipRecv )
1734 : {
1735 3 : INDI_VALIDATE_CALLBACK_PROPS( ipRecv, m_indiP_syncFreq )
1736 :
1737 6 : if( !ipRecv.find( "current" ) )
1738 : {
1739 1 : return -1;
1740 : }
1741 :
1742 2 : m_syncFreq = ipRecv["current"].get<double>();
1743 :
1744 2 : if( m_synchro && m_syncFreq != m_fps )
1745 : {
1746 2 : recordCamera( true );
1747 2 : m_fps = m_syncFreq;
1748 :
1749 : // We always want to reset the latency circular buffers
1750 2 : m_nextMode = m_modeName;
1751 2 : m_reconfig = true;
1752 : }
1753 :
1754 2 : return 0;
1755 : }
1756 :
1757 1 : inline int ocam2KCtrl::checkRecordTimes()
1758 : {
1759 1 : return telemeter<ocam2KCtrl>::checkRecordTimes( ocam_temps(), telem_stdcam(), telem_fgtimings() );
1760 : }
1761 :
1762 2 : inline int ocam2KCtrl::recordTelem( const ocam_temps * )
1763 : {
1764 2 : return recordTemps( true );
1765 : }
1766 :
1767 2 : inline int ocam2KCtrl::recordTelem( const telem_stdcam * )
1768 : {
1769 2 : return recordCamera( true );
1770 : }
1771 :
1772 2 : inline int ocam2KCtrl::recordTelem( const telem_fgtimings * )
1773 : {
1774 2 : return recordFGTimings( true );
1775 : }
1776 :
1777 13 : inline int ocam2KCtrl::recordTemps( bool force )
1778 : {
1779 : static ocamTemps lastTemps;
1780 :
1781 13 : if( !( lastTemps == m_temps ) || force )
1782 : {
1783 7 : telem<ocam_temps>( { m_temps.CCD,
1784 7 : m_temps.CPU,
1785 7 : m_temps.POWER,
1786 7 : m_temps.BIAS,
1787 7 : m_temps.WATER,
1788 7 : m_temps.LEFT,
1789 7 : m_temps.RIGHT,
1790 7 : m_temps.COOLING_POWER } );
1791 7 : lastTemps = m_temps;
1792 : }
1793 :
1794 13 : return 0;
1795 : }
1796 :
1797 : } // namespace app
1798 : } // namespace MagAOX
1799 :
1800 : #endif
|