Line data Source code
1 : /** \file cred2Ctrl.hpp
2 : * \brief The MagAO-X C-RED 2 camera controller.
3 : *
4 : * \author Jared R. Males (jaredmales@gmail.com)
5 : *
6 : * \ingroup cred2Ctrl_files
7 : */
8 :
9 : #ifndef cred2Ctrl_hpp
10 : #define cred2Ctrl_hpp
11 :
12 : #include <algorithm>
13 : #include <cmath>
14 : #include <dlfcn.h>
15 : #include <fstream>
16 : #include <mutex>
17 : #include <sstream>
18 : #include <string>
19 :
20 : #include "../../libMagAOX/libMagAOX.hpp" // Note this is included on command line to trigger pch
21 : #include "../../magaox_git_version.h"
22 :
23 : #include "cred2Utils.hpp"
24 :
25 : namespace MagAOX
26 : {
27 : namespace app
28 : {
29 :
30 : /** \defgroup cred2Ctrl C-RED 2 Camera
31 : * \brief Control of the First Light Imaging C-RED 2 camera.
32 : *
33 : * <a href="../handbook/operating/software/apps/cred2Ctrl.html">Application Documentation</a>
34 : *
35 : * \ingroup apps
36 : */
37 :
38 : /** \defgroup cred2Ctrl_files C-RED 2 Camera Files
39 : * \ingroup cred2Ctrl
40 : */
41 :
42 : /// MagAO-X application to control the C-RED 2 camera.
43 : /**
44 : * \ingroup cred2Ctrl
45 : */
46 : class cred2Ctrl : public MagAOXApp<>,
47 : public dev::stdCamera<cred2Ctrl>,
48 : public dev::edtCamera<cred2Ctrl>,
49 : public dev::frameGrabber<cred2Ctrl>,
50 : public dev::telemeter<cred2Ctrl>
51 : {
52 : friend class dev::stdCamera<cred2Ctrl>;
53 : friend class dev::edtCamera<cred2Ctrl>;
54 : friend class dev::frameGrabber<cred2Ctrl>;
55 : friend class dev::telemeter<cred2Ctrl>;
56 :
57 : typedef MagAOXApp<> MagAOXAppT;
58 : typedef dev::stdCamera<cred2Ctrl> stdCameraT;
59 : typedef dev::frameGrabber<cred2Ctrl> frameGrabberT;
60 : typedef dev::telemeter<cred2Ctrl> telemeterT;
61 :
62 : public:
63 : /** \name app::dev Configurations
64 : * @{
65 : */
66 : static constexpr bool c_stdCamera_tempControl = true; ///< Expose temperature setpoint control.
67 : static constexpr bool c_stdCamera_temp = true; ///< Expose detector temperature status.
68 : static constexpr bool c_stdCamera_readoutSpeed = false; ///< Do not expose readout-speed controls.
69 : static constexpr bool c_stdCamera_vShiftSpeed = false; ///< Do not expose vertical-shift controls.
70 : static constexpr bool c_stdCamera_emGain = false; ///< Do not expose EM-gain controls.
71 : static constexpr bool c_stdCamera_exptimeCtrl = false; ///< Do not expose exposure-time controls.
72 : static constexpr bool c_stdCamera_fpsCtrl = true; ///< Expose FPS controls.
73 : static constexpr bool c_stdCamera_fps = true; ///< Expose FPS status.
74 : static constexpr bool c_stdCamera_fan = true; ///< Expose fan-speed controls.
75 : static constexpr bool c_stdCamera_analogGain = true; ///< Expose discrete analog-gain controls.
76 : static constexpr bool c_stdCamera_led = true; ///< Expose status LED controls.
77 : static constexpr bool c_stdCamera_synchro = false; ///< Do not expose synchro controls in the first pass.
78 : static constexpr bool c_stdCamera_usesModes = false; ///< Use one synthetic runtime mode rather than INDI modes.
79 : static constexpr bool c_stdCamera_usesROI = true; ///< Expose ROI controls.
80 : static constexpr bool c_stdCamera_cropMode = false; ///< Do not expose crop-mode controls separately.
81 : static constexpr bool c_stdCamera_hasShutter = false; ///< Do not expose shutter controls.
82 : static constexpr bool c_stdCamera_usesStateString = false; ///< Do not expose a dark-management state string.
83 : static constexpr bool c_edtCamera_relativeConfigPath = false; ///< Use an absolute temporary EDT config path.
84 : static constexpr bool c_frameGrabber_flippable = true; ///< Expose image flip controls through the framegrabber.
85 :
86 : ///@}
87 :
88 : protected:
89 : /** \name Configurable Parameters - Data
90 : * @{
91 : */
92 : std::string m_configFile; ///< Absolute path to the temporary EDT configuration file.
93 :
94 : int m_serialBaud{ 115200 }; ///< Camera Link serial baud rate used for C-RED 2 CLI access.
95 : ///@}
96 :
97 : /** \name C-RED 2 State - Data
98 : * @{
99 : */
100 : cred2Temps m_temps; ///< Cached camera temperature values used for INDI and telemetry updates.
101 :
102 : bool m_cameraCropEnabled{ false }; ///< Tracks whether this controller has enabled camera-side cropping.
103 : int m_roiSettleCounter{ 0 }; ///< Number of main-loop cycles to skip serial status polling after ROI changes.
104 :
105 : std::recursive_mutex m_cameraMutex; ///< Protects serial command traffic and EDT reconfiguration.
106 : ///@}
107 :
108 : /** \name INDI - Data
109 : * @{
110 : */
111 : pcf::IndiProperty m_indiP_temps; ///< Property reporting the detailed C-RED 2 temperature channels.
112 : pcf::IndiProperty m_indiP_fpsLimits; ///< Property reporting the current C-RED 2 minimum and maximum FPS.
113 :
114 : ///@}
115 :
116 : public:
117 : /// Default c'tor.
118 : cred2Ctrl();
119 :
120 : /// D'tor, declared and defined for noexcept.
121 : ~cred2Ctrl() noexcept;
122 :
123 : /// Setup the configuration system.
124 : virtual void setupConfig();
125 :
126 : /// Load the configuration system results.
127 : virtual void loadConfig();
128 :
129 : /// Implementation of loadConfig logic with standard helper-macro error handling.
130 : int loadConfigImpl( mx::app::appConfigurator &config /**< [in] application configurator with loaded values */ );
131 :
132 : /// Startup function.
133 : virtual int appStartup();
134 :
135 : /// Main FSM logic.
136 : virtual int appLogic();
137 :
138 : /// Actions required when the camera power turns off.
139 : virtual int onPowerOff();
140 :
141 : /// Actions required while the camera remains powered off.
142 : virtual int whilePowerOff();
143 :
144 : /// Shutdown function.
145 : virtual int appShutdown();
146 :
147 : /// Query and update the camera temperature channels.
148 : int getTemps();
149 :
150 : /// Query and update the current camera frame rate.
151 : int getFPS();
152 :
153 : /// Query and update the current camera FPS limits.
154 : int updateFPSLimits();
155 :
156 : /// Query and update the current fan-control state.
157 : int getFanSpeed();
158 :
159 : /// Query and update the current analog-gain state.
160 : int getAnalogGain();
161 :
162 : /// Query and update the current LED state.
163 : int getLEDState();
164 :
165 : /// Query the camera for its current ROI and synchronize local state.
166 : int syncROIFromCamera();
167 :
168 : /** \name stdCamera Interface
169 : * @{
170 : */
171 :
172 : /// Set defaults for a power-on state.
173 : int powerOnDefaults();
174 :
175 : /// Implement the C-RED 2 temperature-controller toggle semantics.
176 : int setTempControl();
177 :
178 : /// Send the current target detector temperature setpoint to the camera.
179 : int setTempSetPt();
180 :
181 : /// Send the requested frame rate to the camera.
182 : int setFPS();
183 :
184 : /// Send the requested fan-control mode to the camera.
185 : int setFanSpeed();
186 :
187 : /// Send the requested analog-gain mode to the camera.
188 : int setAnalogGain();
189 :
190 : /// Send the requested LED state to the camera.
191 : int setLED();
192 :
193 : /// Required by `stdCamera`, but unused for C-RED 2.
194 : int setExpTime();
195 :
196 : /// Validate and normalize the requested ROI.
197 : int checkNextROI();
198 :
199 : /// Request that the next valid ROI be applied through reconfiguration.
200 : int setNextROI();
201 :
202 : ///@}
203 :
204 : /** \name Framegrabber Interface
205 : * @{
206 : */
207 :
208 : /// Write the temporary EDT configuration file for the pending ROI.
209 : int writeConfig();
210 :
211 : /// Configure camera-side ROI settings before acquisition starts.
212 : int configureAcquisition();
213 :
214 : /// Return the currently measured frame rate.
215 : float fps();
216 :
217 : /// Start frame acquisition on the EDT board.
218 : int startAcquisition();
219 :
220 : /// Wait for and validate the next acquired image.
221 : int acquireAndCheckValid();
222 :
223 : /// Copy the current EDT image into the output stream.
224 : int loadImageIntoStream( void *dest /**< [in] destination frame buffer */ );
225 :
226 : /// Reconfigure the EDT board for the pending ROI.
227 : int reconfig();
228 :
229 : ///@}
230 :
231 : /** \name Telemeter Interface
232 : * @{
233 : */
234 :
235 : /// Check the telemetry record timers.
236 : int checkRecordTimes();
237 :
238 : /// Record the detailed C-RED 2 temperature telemetry.
239 : int recordTelem( const cred2_temps * /**< [in] type-dispatch tag */ );
240 :
241 : /// Record standard camera telemetry.
242 : int recordTelem( const telem_stdcam * /**< [in] type-dispatch tag */ );
243 :
244 : /// Record framegrabber timing telemetry.
245 : int recordTelem( const telem_fgtimings * /**< [in] type-dispatch tag */ );
246 :
247 : /// Record the detailed C-RED 2 temperature telemetry when values change.
248 : int recordTemps( bool force = false /**< [in] force a telemetry record even if the cached values match */ );
249 :
250 : ///@}
251 :
252 : protected:
253 : /// Apply and verify the configured Camera Link serial baud rate.
254 : int setSerialBaud();
255 :
256 : /// Send a command over Camera Link serial and clean the response.
257 : int sendCommand( std::string &response, ///< [out] cleaned command response
258 : const std::string &command, /**< [in] CLI command to send */
259 : bool logFailure = true /**< [in] log transport failures when true */
260 : );
261 :
262 : /// Send a command that should return a success acknowledgement.
263 : int issueCommand( const std::string &command, /**< [in] CLI command to send */
264 : bool allowNoResponse = false /**< [in] treat a missing response as acceptable */
265 : );
266 : };
267 :
268 : namespace
269 : {
270 :
271 : /// Normalize a C-RED 2 text response for tolerant string parsing.
272 45 : inline std::string cred2LowerResponse( const std::string &response /**< [in] raw or cleaned CLI response */ )
273 : {
274 45 : std::string clean = cred2CleanResponse( response );
275 45 : std::transform( clean.begin(),
276 : clean.end(),
277 : clean.begin(),
278 326 : []( unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
279 :
280 45 : return clean;
281 : }
282 :
283 : /// Convert a C-RED 2 fan percentage into the nearest exposed preset name.
284 8 : inline std::string cred2FanPresetName( float fanPercent /**< [in] current or requested fan percentage */ )
285 : {
286 8 : if( fanPercent <= 0.5f )
287 : {
288 2 : return "off";
289 : }
290 :
291 7 : if( fanPercent < 37.5f )
292 : {
293 2 : return "p25";
294 : }
295 :
296 6 : if( fanPercent < 62.5f )
297 : {
298 4 : return "p50";
299 : }
300 :
301 4 : if( fanPercent < 87.5f )
302 : {
303 6 : return "p75";
304 : }
305 :
306 2 : return "p100";
307 : }
308 :
309 : /// Convert an exposed fan preset name into the corresponding manual fan percentage.
310 10 : inline int cred2FanPresetPercent( int &fanPercent, ///< [out] mapped manual fan percentage
311 : const std::string &fanPreset /**< [in] exposed fan preset name */
312 : )
313 : {
314 10 : if( fanPreset == "off" )
315 : {
316 1 : fanPercent = 0;
317 1 : return 0;
318 : }
319 :
320 9 : if( fanPreset == "p25" )
321 : {
322 3 : fanPercent = 25;
323 3 : return 0;
324 : }
325 :
326 6 : if( fanPreset == "p50" )
327 : {
328 1 : fanPercent = 50;
329 1 : return 0;
330 : }
331 :
332 5 : if( fanPreset == "p75" )
333 : {
334 2 : fanPercent = 75;
335 2 : return 0;
336 : }
337 :
338 3 : if( fanPreset == "p100" )
339 : {
340 1 : fanPercent = 100;
341 1 : return 0;
342 : }
343 :
344 2 : return -1;
345 : }
346 :
347 : /// Parse a C-RED 2 sensibility response into the exposed analog-gain preset name.
348 13 : inline int cred2AnalogGainName( std::string &gainName, ///< [out] exposed analog-gain preset name
349 : const std::string &response /**< [in] raw or cleaned sensibility response */
350 : )
351 : {
352 13 : std::string clean = cred2LowerResponse( response );
353 :
354 13 : if( clean.find( "medium" ) != std::string::npos || clean == "med" )
355 : {
356 8 : gainName = "med";
357 8 : return 0;
358 : }
359 :
360 5 : if( clean.find( "high" ) != std::string::npos )
361 : {
362 2 : gainName = "high";
363 2 : return 0;
364 : }
365 :
366 3 : if( clean.find( "low" ) != std::string::npos )
367 : {
368 1 : gainName = "low";
369 1 : return 0;
370 : }
371 :
372 2 : return -1;
373 13 : }
374 :
375 : /// Convert an exposed analog-gain preset name into the C-RED 2 command argument.
376 7 : inline int cred2AnalogGainCommand( std::string &commandGain, ///< [out] C-RED 2 sensibility command argument
377 : const std::string &gainName /**< [in] exposed analog-gain preset name */
378 : )
379 : {
380 7 : if( gainName == "low" )
381 : {
382 1 : commandGain = "low";
383 1 : return 0;
384 : }
385 :
386 6 : if( gainName == "med" )
387 : {
388 2 : commandGain = "medium";
389 2 : return 0;
390 : }
391 :
392 4 : if( gainName == "high" )
393 : {
394 2 : commandGain = "high";
395 2 : return 0;
396 : }
397 :
398 2 : return -1;
399 : }
400 :
401 : } // namespace
402 :
403 351 : inline cred2Ctrl::cred2Ctrl() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
404 : {
405 117 : m_powerMgtEnabled = true;
406 :
407 117 : m_startupTemp = 20;
408 117 : m_minTemp = -40;
409 117 : m_maxTemp = 20;
410 117 : m_stepTemp = 1;
411 :
412 117 : m_stepFPS = 0.001;
413 :
414 117 : m_full_x = 319.5;
415 117 : m_full_y = 255.5;
416 117 : m_full_w = 640;
417 117 : m_full_h = 512;
418 117 : m_full_bin_x = 1;
419 117 : m_full_bin_y = 1;
420 :
421 117 : m_default_x = m_full_x;
422 117 : m_default_y = m_full_y;
423 117 : m_default_w = m_full_w;
424 117 : m_default_h = m_full_h;
425 117 : m_default_bin_x = m_full_bin_x;
426 117 : m_default_bin_y = m_full_bin_y;
427 :
428 117 : m_full_currbin_x = m_full_x;
429 117 : m_full_currbin_y = m_full_y;
430 117 : m_full_currbin_w = m_full_w;
431 117 : m_full_currbin_h = m_full_h;
432 :
433 117 : m_minROIx = 0;
434 117 : m_maxROIx = 639;
435 117 : m_stepROIx = 0.5;
436 :
437 117 : m_minROIy = 0;
438 117 : m_maxROIy = 511;
439 117 : m_stepROIy = 0.5;
440 :
441 117 : m_minROIWidth = 32;
442 117 : m_maxROIWidth = 640;
443 117 : m_stepROIWidth = 32;
444 :
445 117 : m_minROIHeight = 4;
446 117 : m_maxROIHeight = 512;
447 117 : m_stepROIHeight = 4;
448 :
449 117 : m_minROIBinning_x = 1;
450 117 : m_maxROIBinning_x = 1;
451 117 : m_stepROIBinning_x = 1;
452 :
453 117 : m_minROIBinning_y = 1;
454 117 : m_maxROIBinning_y = 1;
455 117 : m_stepROIBinning_y = 1;
456 :
457 819 : m_fanSpeedNames = { "off", "p25", "p50", "p75", "p100", "auto" };
458 819 : m_fanSpeedNameLabels = { "Off", "25", "50", "75", "100", "Auto" };
459 117 : m_fanSpeedNameSet = "auto";
460 :
461 468 : m_analogGainNames = { "low", "med", "high" };
462 468 : m_analogGainNameLabels = { "Low", "Med", "High" };
463 117 : m_analogGainNameSet = "med";
464 :
465 117 : m_ledStateSet = true;
466 :
467 117 : m_temps.setInvalid();
468 1170 : }
469 :
470 118 : inline cred2Ctrl::~cred2Ctrl() noexcept
471 : {
472 118 : }
473 :
474 12 : inline void cred2Ctrl::setupConfig()
475 : {
476 12 : STDCAMERA_SETUP_CONFIG( config );
477 :
478 12 : dev::edtCamera<cred2Ctrl>::setupConfig( config );
479 :
480 12 : FRAMEGRABBER_SETUP_CONFIG( config );
481 :
482 12 : TELEMETER_SETUP_CONFIG( config );
483 :
484 180 : config.add( "camera.serialBaud",
485 : "",
486 : "camera.serialBaud",
487 : argType::Required,
488 : "camera",
489 : "serialBaud",
490 : false,
491 : "int",
492 : "The Camera Link serial baud rate for C-RED 2 CLI commands. Default is 115200." );
493 : }
494 :
495 12 : inline int cred2Ctrl::loadConfigImpl( mx::app::appConfigurator &config )
496 : {
497 12 : STDCAMERA_LOAD_CONFIG( config );
498 :
499 12 : config( m_serialBaud, "camera.serialBaud" );
500 :
501 12 : m_configFile = "/tmp/cred2_" + configName() + ".cfg";
502 :
503 60 : m_cameraModes["runtime"] = dev::cameraConfig( { m_configFile,
504 : "",
505 12 : static_cast<unsigned>( m_nextROI.x ),
506 12 : static_cast<unsigned>( m_nextROI.y ),
507 12 : static_cast<unsigned>( m_nextROI.w ),
508 12 : static_cast<unsigned>( m_nextROI.h ),
509 12 : static_cast<unsigned>( m_nextROI.bin_x ),
510 12 : static_cast<unsigned>( m_nextROI.bin_y ),
511 : 1,
512 : 1,
513 12 : 0 } );
514 12 : m_startupMode = "runtime";
515 :
516 12 : if( writeConfig() < 0 )
517 : {
518 2 : return log<software_critical, -1>( { __FILE__, __LINE__, "could not write initial C-RED 2 EDT config" } );
519 : }
520 :
521 11 : dev::edtCamera<cred2Ctrl>::loadConfig( config );
522 :
523 11 : FRAMEGRABBER_LOAD_CONFIG( config );
524 :
525 11 : TELEMETER_LOAD_CONFIG( config );
526 :
527 11 : return 0;
528 : }
529 :
530 12 : inline void cred2Ctrl::loadConfig()
531 : {
532 12 : if( loadConfigImpl( config ) < 0 )
533 : {
534 1 : log<software_critical>( { __FILE__, __LINE__, "error loading config" } );
535 1 : m_shutdown = true;
536 : }
537 12 : }
538 :
539 4 : inline int cred2Ctrl::appStartup()
540 : {
541 12 : REG_INDI_NEWPROP_NOCB( m_indiP_temps, "temps", pcf::IndiProperty::Number );
542 8 : m_indiP_temps.add( pcf::IndiElement( "motherboard" ) );
543 8 : m_indiP_temps["motherboard"].set( 0 );
544 8 : m_indiP_temps.add( pcf::IndiElement( "frontend" ) );
545 8 : m_indiP_temps["frontend"].set( 0 );
546 8 : m_indiP_temps.add( pcf::IndiElement( "powerboard" ) );
547 8 : m_indiP_temps["powerboard"].set( 0 );
548 8 : m_indiP_temps.add( pcf::IndiElement( "snake" ) );
549 8 : m_indiP_temps["snake"].set( 0 );
550 8 : m_indiP_temps.add( pcf::IndiElement( "setpoint" ) );
551 8 : m_indiP_temps["setpoint"].set( 0 );
552 8 : m_indiP_temps.add( pcf::IndiElement( "peltier" ) );
553 8 : m_indiP_temps["peltier"].set( 0 );
554 8 : m_indiP_temps.add( pcf::IndiElement( "heatsink" ) );
555 8 : m_indiP_temps["heatsink"].set( 0 );
556 :
557 24 : createROIndiNumber( m_indiP_fpsLimits, "fps_limits" );
558 8 : m_indiP_fpsLimits.add( pcf::IndiElement( "min" ) );
559 8 : m_indiP_fpsLimits["min"].set( m_minFPS );
560 16 : m_indiP_fpsLimits["min"].setFormat( "%0.6f" );
561 8 : m_indiP_fpsLimits.add( pcf::IndiElement( "max" ) );
562 8 : m_indiP_fpsLimits["max"].set( m_maxFPS );
563 16 : m_indiP_fpsLimits["max"].setFormat( "%0.6f" );
564 :
565 4 : if( registerIndiPropertyReadOnly( m_indiP_fpsLimits ) < 0 )
566 : {
567 1 : return log<software_critical, -1>( { __FILE__, __LINE__ } );
568 : }
569 :
570 3 : STDCAMERA_APP_STARTUP;
571 :
572 3 : if( dev::edtCamera<cred2Ctrl>::appStartup() < 0 )
573 : {
574 2 : return log<software_critical, -1>( { __FILE__, __LINE__ } );
575 : }
576 :
577 1 : if( setSerialBaud() < 0 )
578 : {
579 0 : return log<software_critical, -1>( { __FILE__, __LINE__ } );
580 : }
581 :
582 1 : FRAMEGRABBER_APP_STARTUP;
583 :
584 1 : TELEMETER_APP_STARTUP;
585 :
586 1 : return 0;
587 : }
588 :
589 23 : inline int cred2Ctrl::appLogic()
590 : {
591 23 : STDCAMERA_APP_LOGIC;
592 :
593 23 : if( dev::edtCamera<cred2Ctrl>::appLogic() < 0 )
594 : {
595 0 : return log<software_error, -1>( { __FILE__, __LINE__ } );
596 : }
597 :
598 23 : FRAMEGRABBER_APP_LOGIC;
599 :
600 23 : if( state() == stateCodes::POWERON )
601 : {
602 1 : return 0;
603 : }
604 :
605 22 : if( state() == stateCodes::NOTCONNECTED || state() == stateCodes::NODEVICE || state() == stateCodes::ERROR )
606 : {
607 4 : if( powerState() == 0 )
608 : {
609 3 : return 0;
610 : }
611 :
612 3 : std::string response;
613 6 : if( sendCommand( response, "fps raw" ) == 0 )
614 : {
615 2 : float fpsValue = 0;
616 2 : if( cred2ParseFloat( fpsValue, response ) == 0 )
617 : {
618 1 : state( stateCodes::CONNECTED );
619 : }
620 : else
621 : {
622 1 : state( stateCodes::NODEVICE );
623 1 : sleep( 1 );
624 1 : return 0;
625 : }
626 : }
627 : else
628 : {
629 1 : state( stateCodes::NODEVICE );
630 1 : sleep( 1 );
631 1 : return 0;
632 : }
633 3 : }
634 :
635 19 : if( state() == stateCodes::CONNECTED )
636 : {
637 7 : std::unique_lock<std::mutex> lock( m_indiMutex );
638 :
639 7 : if( syncROIFromCamera() < 0 )
640 : {
641 2 : if( powerState() != 1 || powerStateTarget() != 1 )
642 : {
643 1 : return 0;
644 : }
645 :
646 1 : state( stateCodes::ERROR );
647 1 : return 0;
648 : }
649 :
650 8 : if( updateFPSLimits() < 0 || getTemps() < 0 || getFPS() < 0 || getFanSpeed() < 0 || getAnalogGain() < 0 ||
651 3 : getLEDState() < 0 )
652 : {
653 2 : if( powerState() != 1 || powerStateTarget() != 1 )
654 : {
655 1 : return 0;
656 : }
657 :
658 1 : state( stateCodes::ERROR );
659 1 : return 0;
660 : }
661 :
662 3 : state( stateCodes::READY );
663 :
664 3 : if( m_ccdTempSetpt > -999 )
665 : {
666 3 : if( setTempSetPt() < 0 )
667 : {
668 3 : if( powerState() != 1 || powerStateTarget() != 1 )
669 : {
670 1 : return 0;
671 : }
672 :
673 2 : return log<software_error, 0>( { __FILE__, __LINE__ } );
674 : }
675 : }
676 7 : }
677 :
678 12 : if( state() == stateCodes::READY || state() == stateCodes::OPERATING )
679 : {
680 12 : std::unique_lock<std::mutex> lock( m_indiMutex, std::try_to_lock );
681 12 : if( !lock.owns_lock() )
682 : {
683 1 : return 0;
684 : }
685 :
686 11 : if( m_roiSettleCounter > 0 )
687 : {
688 1 : --m_roiSettleCounter;
689 : }
690 : else
691 : {
692 10 : if( updateFPSLimits() < 0 )
693 : {
694 2 : if( powerState() != 1 || powerStateTarget() != 1 )
695 : {
696 1 : return 0;
697 : }
698 :
699 1 : state( stateCodes::ERROR );
700 1 : return 0;
701 : }
702 :
703 8 : if( getTemps() < 0 )
704 : {
705 0 : if( powerState() != 1 || powerStateTarget() != 1 )
706 : {
707 0 : return 0;
708 : }
709 :
710 0 : state( stateCodes::ERROR );
711 0 : return 0;
712 : }
713 :
714 8 : if( getFPS() < 0 )
715 : {
716 2 : if( powerState() != 1 || powerStateTarget() != 1 )
717 : {
718 1 : return 0;
719 : }
720 :
721 1 : state( stateCodes::ERROR );
722 1 : return 0;
723 : }
724 :
725 6 : if( getFanSpeed() < 0 )
726 : {
727 2 : if( powerState() != 1 || powerStateTarget() != 1 )
728 : {
729 1 : return 0;
730 : }
731 :
732 1 : state( stateCodes::ERROR );
733 1 : return 0;
734 : }
735 :
736 4 : if( getAnalogGain() < 0 )
737 : {
738 2 : if( powerState() != 1 || powerStateTarget() != 1 )
739 : {
740 1 : return 0;
741 : }
742 :
743 1 : state( stateCodes::ERROR );
744 1 : return 0;
745 : }
746 :
747 2 : if( getLEDState() < 0 )
748 : {
749 2 : if( powerState() != 1 || powerStateTarget() != 1 )
750 : {
751 1 : return 0;
752 : }
753 :
754 1 : state( stateCodes::ERROR );
755 1 : return 0;
756 : }
757 : }
758 :
759 1 : if( frameGrabber<cred2Ctrl>::updateINDI() < 0 )
760 : {
761 0 : log<software_error>( { __FILE__, __LINE__ } );
762 0 : state( stateCodes::ERROR );
763 0 : return 0;
764 : }
765 :
766 1 : if( stdCamera<cred2Ctrl>::updateINDI() < 0 )
767 : {
768 0 : log<software_error>( { __FILE__, __LINE__ } );
769 0 : state( stateCodes::ERROR );
770 0 : return 0;
771 : }
772 :
773 1 : if( edtCamera<cred2Ctrl>::updateINDI() < 0 )
774 : {
775 0 : log<software_error>( { __FILE__, __LINE__ } );
776 0 : state( stateCodes::ERROR );
777 0 : return 0;
778 : }
779 :
780 1 : if( telemeter<cred2Ctrl>::appLogic() < 0 )
781 : {
782 1 : log<software_error>( { __FILE__, __LINE__ } );
783 1 : return 0;
784 : }
785 12 : }
786 :
787 0 : return 0;
788 : }
789 :
790 1 : inline int cred2Ctrl::onPowerOff()
791 : {
792 1 : m_powerOnCounter = 0;
793 :
794 1 : std::lock_guard<std::mutex> lock( m_indiMutex );
795 :
796 1 : m_temps.setInvalid();
797 1 : m_ccdTemp = -999;
798 1 : m_tempControlStatus = false;
799 1 : m_tempControlOnTarget = false;
800 1 : m_tempControlStatusStr = "UNKNOWN";
801 :
802 2 : updateIfChanged( m_indiP_temps, "motherboard", m_temps.motherboard );
803 2 : updateIfChanged( m_indiP_temps, "frontend", m_temps.frontend );
804 2 : updateIfChanged( m_indiP_temps, "powerboard", m_temps.powerboard );
805 2 : updateIfChanged( m_indiP_temps, "snake", m_temps.snake );
806 2 : updateIfChanged( m_indiP_temps, "setpoint", m_temps.setpoint );
807 2 : updateIfChanged( m_indiP_temps, "peltier", m_temps.peltier );
808 2 : updateIfChanged( m_indiP_temps, "heatsink", m_temps.heatsink );
809 :
810 1 : if( stdCamera<cred2Ctrl>::onPowerOff() < 0 )
811 : {
812 0 : log<software_error>( { __FILE__, __LINE__ } );
813 : }
814 :
815 1 : if( edtCamera<cred2Ctrl>::onPowerOff() < 0 )
816 : {
817 0 : log<software_error>( { __FILE__, __LINE__ } );
818 : }
819 :
820 1 : if( frameGrabber<cred2Ctrl>::onPowerOff() < 0 )
821 : {
822 0 : log<software_error>( { __FILE__, __LINE__ } );
823 : }
824 :
825 1 : return 0;
826 1 : }
827 :
828 1 : inline int cred2Ctrl::whilePowerOff()
829 : {
830 1 : std::lock_guard<std::mutex> lock( m_indiMutex );
831 :
832 1 : if( stdCamera<cred2Ctrl>::whilePowerOff() < 0 )
833 : {
834 0 : log<software_error>( { __FILE__, __LINE__ } );
835 : }
836 :
837 1 : if( edtCamera<cred2Ctrl>::whilePowerOff() < 0 )
838 : {
839 0 : log<software_error>( { __FILE__, __LINE__ } );
840 : }
841 :
842 1 : return 0;
843 1 : }
844 :
845 2 : inline int cred2Ctrl::appShutdown()
846 : {
847 2 : STDCAMERA_APP_SHUTDOWN;
848 :
849 2 : dev::edtCamera<cred2Ctrl>::appShutdown();
850 :
851 2 : FRAMEGRABBER_APP_SHUTDOWN;
852 :
853 2 : TELEMETER_APP_SHUTDOWN;
854 :
855 2 : return 0;
856 : }
857 :
858 198 : inline int cred2Ctrl::sendCommand( std::string &response, const std::string &command, bool logFailure )
859 : {
860 198 : std::string rawResponse;
861 :
862 198 : response.clear();
863 :
864 : { // mutex scope
865 198 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
866 198 : if( pdvSerialWriteRead( rawResponse, command, logFailure ) != 0 )
867 : {
868 26 : if( powerState() != 1 || powerStateTarget() != 1 )
869 : {
870 6 : return -1;
871 : }
872 :
873 20 : if( !logFailure )
874 : {
875 7 : return -1;
876 : }
877 :
878 13 : return log<software_error, -1>( { __FILE__, __LINE__, "error sending C-RED 2 command: " + command } );
879 : }
880 198 : }
881 :
882 172 : response = cred2CleanResponse( rawResponse );
883 :
884 172 : return 0;
885 198 : }
886 :
887 30 : inline int cred2Ctrl::issueCommand( const std::string &command, bool allowNoResponse )
888 : {
889 30 : std::string response;
890 30 : if( sendCommand( response, command, !allowNoResponse ) < 0 )
891 : {
892 7 : if( allowNoResponse )
893 : {
894 5 : return 0;
895 : }
896 :
897 2 : return -1;
898 : }
899 :
900 23 : if( !cred2ResponseOK( response ) )
901 : {
902 22 : return log<text_log, -1>( "C-RED 2 rejected command '" + command + "' with response: " + response,
903 11 : logPrio::LOG_ERROR );
904 : }
905 :
906 12 : return 0;
907 30 : }
908 :
909 17 : inline int cred2Ctrl::syncROIFromCamera()
910 : {
911 17 : std::string response;
912 17 : bool cropEnabled = false;
913 17 : int startColumn = 0;
914 17 : int endColumn = 0;
915 17 : int startRow = 0;
916 17 : int endRow = 0;
917 :
918 68 : if( sendCommand( response, "cropping raw", false ) < 0 ||
919 17 : cred2ParseCropState( cropEnabled, startColumn, endColumn, startRow, endRow, response ) < 0 )
920 : {
921 3 : return log<software_error, -1>( { __FILE__, __LINE__, "failed to query current cropping mode: " + response } );
922 : }
923 :
924 14 : if( !cropEnabled )
925 : {
926 9 : m_cameraCropEnabled = false;
927 9 : m_currentROI.x = m_full_x;
928 9 : m_currentROI.y = m_full_y;
929 9 : m_currentROI.w = m_full_w;
930 9 : m_currentROI.h = m_full_h;
931 9 : m_currentROI.bin_x = m_full_bin_x;
932 9 : m_currentROI.bin_y = m_full_bin_y;
933 : }
934 : else
935 : {
936 5 : cred2Roi cameraROI;
937 :
938 5 : if( startColumn == 0 && endColumn == 0 && startRow == 0 && endRow == 0 )
939 : {
940 12 : if( sendCommand( response, "cropping columns raw", false ) < 0 ||
941 3 : cred2ParseRange( cameraROI.startColumn, cameraROI.endColumn, response ) < 0 )
942 : {
943 2 : return log<software_error, -1>(
944 3 : { __FILE__, __LINE__, "failed to query current cropping columns: " + response } );
945 : }
946 :
947 8 : if( sendCommand( response, "cropping rows raw", false ) < 0 ||
948 2 : cred2ParseRange( cameraROI.startRow, cameraROI.endRow, response ) < 0 )
949 : {
950 2 : return log<software_error, -1>(
951 3 : { __FILE__, __LINE__, "failed to query current cropping rows: " + response } );
952 : }
953 : }
954 : else
955 : {
956 2 : cameraROI.startColumn = startColumn;
957 2 : cameraROI.endColumn = endColumn;
958 2 : cameraROI.startRow = startRow;
959 2 : cameraROI.endRow = endRow;
960 : }
961 :
962 3 : cameraROI.fullFrame = false;
963 :
964 6 : if( cred2RoiToCenter(
965 3 : m_currentROI.x, m_currentROI.y, m_currentROI.w, m_currentROI.h, cameraROI, m_full_w, m_full_h ) < 0 )
966 : {
967 2 : return log<software_error, -1>( { __FILE__, __LINE__, "camera reported an invalid ROI" } );
968 : }
969 :
970 2 : m_currentROI.bin_x = 1;
971 2 : m_currentROI.bin_y = 1;
972 2 : m_cameraCropEnabled = true;
973 : }
974 :
975 11 : m_nextROI = m_currentROI;
976 11 : m_width = m_currentROI.w;
977 11 : m_height = m_currentROI.h;
978 11 : m_dataType = _DATATYPE_INT16;
979 :
980 22 : updateIfChanged( m_indiP_roi_x, "current", m_currentROI.x, INDI_OK );
981 22 : updateIfChanged( m_indiP_roi_y, "current", m_currentROI.y, INDI_OK );
982 22 : updateIfChanged( m_indiP_roi_w, "current", m_currentROI.w, INDI_OK );
983 22 : updateIfChanged( m_indiP_roi_h, "current", m_currentROI.h, INDI_OK );
984 22 : updateIfChanged( m_indiP_roi_bin_x, "current", m_currentROI.bin_x, INDI_OK );
985 22 : updateIfChanged( m_indiP_roi_bin_y, "current", m_currentROI.bin_y, INDI_OK );
986 :
987 22 : updateIfChanged( m_indiP_roi_x, "target", m_nextROI.x, INDI_OK );
988 22 : updateIfChanged( m_indiP_roi_y, "target", m_nextROI.y, INDI_OK );
989 22 : updateIfChanged( m_indiP_roi_w, "target", m_nextROI.w, INDI_OK );
990 22 : updateIfChanged( m_indiP_roi_h, "target", m_nextROI.h, INDI_OK );
991 22 : updateIfChanged( m_indiP_roi_bin_x, "target", m_nextROI.bin_x, INDI_OK );
992 22 : updateIfChanged( m_indiP_roi_bin_y, "target", m_nextROI.bin_y, INDI_OK );
993 :
994 11 : if( m_currentROI.w != m_raw_width || m_currentROI.h != m_raw_height )
995 : {
996 3 : if( writeConfig() < 0 )
997 : {
998 1 : return log<software_error, -1>( { __FILE__, __LINE__ } );
999 : }
1000 :
1001 2 : m_nextMode = m_modeName.empty() ? m_startupMode : m_modeName;
1002 :
1003 2 : if( dev::edtCamera<cred2Ctrl>::pdvReconfig() < 0 )
1004 : {
1005 0 : return log<software_error, -1>( { __FILE__, __LINE__ } );
1006 : }
1007 :
1008 2 : if( setSerialBaud() < 0 )
1009 : {
1010 1 : return log<software_error, -1>( { __FILE__, __LINE__ } );
1011 : }
1012 : }
1013 :
1014 9 : return 0;
1015 17 : }
1016 :
1017 5 : inline int cred2Ctrl::setSerialBaud()
1018 : {
1019 : typedef int ( *setBaudFnT )( PdvDev *, int );
1020 : typedef int ( *getBaudFnT )( PdvDev * );
1021 :
1022 5 : if( m_pdv == nullptr )
1023 : {
1024 4 : return log<software_error, -1>( { __FILE__, __LINE__, "cannot set serial baud with null PDV handle" } );
1025 : }
1026 :
1027 3 : static setBaudFnT setBaudFn = reinterpret_cast<setBaudFnT>( dlsym( RTLD_DEFAULT, "pdv_serial_set_baud" ) );
1028 3 : static getBaudFnT getBaudFn = reinterpret_cast<getBaudFnT>( dlsym( RTLD_DEFAULT, "pdv_serial_get_baud" ) );
1029 :
1030 3 : if( setBaudFn == nullptr || getBaudFn == nullptr )
1031 : {
1032 3 : return 0;
1033 : }
1034 :
1035 0 : if( setBaudFn( m_pdv, m_serialBaud ) < 0 )
1036 : {
1037 0 : return log<software_error, -1>(
1038 0 : { __FILE__, __LINE__, "failed to set C-RED 2 serial baud to " + std::to_string( m_serialBaud ) } );
1039 : }
1040 :
1041 0 : int actualBaud = getBaudFn( m_pdv );
1042 0 : if( actualBaud != m_serialBaud )
1043 : {
1044 0 : return log<software_error, -1>( { __FILE__,
1045 : __LINE__,
1046 0 : "EDT serial baud verification failed: expected " +
1047 0 : std::to_string( m_serialBaud ) + ", got " +
1048 0 : std::to_string( actualBaud ) } );
1049 : }
1050 :
1051 0 : return 0;
1052 : }
1053 :
1054 18 : inline int cred2Ctrl::getTemps()
1055 : {
1056 18 : cred2Temps temps;
1057 18 : std::string response;
1058 18 : std::vector<float> bundledTemps;
1059 18 : const double diffLimit = 1.0;
1060 36 : const std::string bundledCommand( "temperatures raw" );
1061 18 : const std::string setpointCommand( "temperatures snake setpoint raw" );
1062 5 : const auto keepLastTemps = [this]( const std::string &detail )
1063 : {
1064 10 : return log<text_log, 0>( "transient C-RED 2 temperature refresh failure; keeping previous cached values: " +
1065 : detail,
1066 10 : logPrio::LOG_WARNING );
1067 18 : };
1068 :
1069 18 : if( sendCommand( response, bundledCommand, false ) < 0 )
1070 : {
1071 2 : return keepLastTemps( bundledCommand );
1072 : }
1073 :
1074 16 : if( cred2ParseFloatVector( bundledTemps, response, 6 ) < 0 )
1075 : {
1076 1 : return keepLastTemps( bundledCommand + " -> " + response );
1077 : }
1078 :
1079 15 : temps.motherboard = bundledTemps[0];
1080 15 : temps.frontend = bundledTemps[1];
1081 15 : temps.powerboard = bundledTemps[2];
1082 15 : temps.snake = bundledTemps[3];
1083 15 : temps.peltier = bundledTemps[4];
1084 15 : temps.heatsink = bundledTemps[5];
1085 :
1086 15 : if( sendCommand( response, setpointCommand, false ) < 0 )
1087 : {
1088 1 : return keepLastTemps( setpointCommand );
1089 : }
1090 :
1091 14 : if( cred2ParseFloat( temps.setpoint, response ) < 0 )
1092 : {
1093 1 : return keepLastTemps( setpointCommand + " -> " + response );
1094 : }
1095 :
1096 13 : m_temps = temps;
1097 13 : m_ccdTemp = temps.snake;
1098 13 : m_ccdTempSetpt = temps.setpoint;
1099 :
1100 13 : if( m_ccdTempSetpt < 19.5 )
1101 : {
1102 11 : m_tempControlStatus = true;
1103 11 : if( std::fabs( m_ccdTemp - m_ccdTempSetpt ) < diffLimit )
1104 : {
1105 10 : m_tempControlStatusStr = "ON TARGET";
1106 10 : m_tempControlOnTarget = true;
1107 : }
1108 : else
1109 : {
1110 1 : m_tempControlStatusStr = "OFF TARGET";
1111 1 : m_tempControlOnTarget = false;
1112 : }
1113 : }
1114 : else
1115 : {
1116 2 : m_tempControlStatus = false;
1117 2 : m_tempControlOnTarget = false;
1118 2 : if( std::fabs( m_ccdTemp - m_ccdTempSetpt ) < diffLimit )
1119 : {
1120 1 : m_tempControlStatusStr = "TEMP OFF";
1121 : }
1122 : else
1123 : {
1124 1 : m_tempControlStatusStr = "WARMING";
1125 : }
1126 : }
1127 :
1128 26 : updateIfChanged( m_indiP_temps, "motherboard", m_temps.motherboard );
1129 26 : updateIfChanged( m_indiP_temps, "frontend", m_temps.frontend );
1130 26 : updateIfChanged( m_indiP_temps, "powerboard", m_temps.powerboard );
1131 26 : updateIfChanged( m_indiP_temps, "snake", m_temps.snake );
1132 26 : updateIfChanged( m_indiP_temps, "setpoint", m_temps.setpoint );
1133 26 : updateIfChanged( m_indiP_temps, "peltier", m_temps.peltier );
1134 26 : updateIfChanged( m_indiP_temps, "heatsink", m_temps.heatsink );
1135 :
1136 13 : recordTemps();
1137 13 : recordCamera();
1138 :
1139 13 : return 0;
1140 18 : }
1141 :
1142 14 : inline int cred2Ctrl::getFPS()
1143 : {
1144 14 : std::string response;
1145 14 : float fpsValue = 0;
1146 :
1147 28 : if( sendCommand( response, "fps raw" ) < 0 )
1148 : {
1149 2 : return -1;
1150 : }
1151 :
1152 12 : if( cred2ParseFloat( fpsValue, response ) < 0 )
1153 : {
1154 1 : return log<software_error, -1>( { __FILE__, __LINE__, "failed to parse fps response: " + response } );
1155 : }
1156 :
1157 11 : m_fps = fpsValue;
1158 11 : recordCamera();
1159 :
1160 11 : return 0;
1161 14 : }
1162 :
1163 19 : inline int cred2Ctrl::getFanSpeed()
1164 : {
1165 19 : std::string response;
1166 19 : std::string fanMode;
1167 19 : float fanPercent = 0;
1168 :
1169 38 : if( sendCommand( response, "fan mode raw" ) < 0 )
1170 : {
1171 3 : return -1;
1172 : }
1173 :
1174 16 : fanMode = cred2LowerResponse( response );
1175 16 : if( fanMode.find( "auto" ) == std::string::npos && fanMode.find( "manual" ) == std::string::npos )
1176 : {
1177 6 : if( sendCommand( response, "fan mode" ) < 0 )
1178 : {
1179 1 : return -1;
1180 : }
1181 :
1182 2 : fanMode = cred2LowerResponse( response );
1183 : }
1184 :
1185 15 : if( fanMode.find( "auto" ) != std::string::npos )
1186 : {
1187 9 : m_fanSpeedName = "auto";
1188 9 : m_fanSpeedNameSet = m_fanSpeedName;
1189 9 : m_fanSpeedValid = true;
1190 9 : return 0;
1191 : }
1192 :
1193 6 : if( fanMode.find( "manual" ) == std::string::npos )
1194 : {
1195 1 : return log<software_error, -1>( { __FILE__, __LINE__, "failed to parse fan mode response: " + response } );
1196 : }
1197 :
1198 10 : if( sendCommand( response, "fan speed raw" ) < 0 )
1199 : {
1200 1 : return -1;
1201 : }
1202 :
1203 4 : if( cred2ParseFloat( fanPercent, response ) < 0 )
1204 : {
1205 6 : if( sendCommand( response, "fan speed" ) < 0 || cred2ParseFloat( fanPercent, response ) < 0 )
1206 : {
1207 1 : return log<software_error, -1>( { __FILE__, __LINE__, "failed to parse fan speed response: " + response } );
1208 : }
1209 : }
1210 :
1211 3 : m_fanSpeedName = cred2FanPresetName( fanPercent );
1212 3 : m_fanSpeedNameSet = m_fanSpeedName;
1213 3 : m_fanSpeedValid = true;
1214 3 : recordCamera();
1215 :
1216 3 : return 0;
1217 19 : }
1218 :
1219 11 : inline int cred2Ctrl::getAnalogGain()
1220 : {
1221 11 : std::string response;
1222 11 : std::string analogGain;
1223 :
1224 22 : if( sendCommand( response, "sensibility" ) < 0 )
1225 : {
1226 3 : return -1;
1227 : }
1228 :
1229 8 : if( cred2AnalogGainName( analogGain, response ) < 0 )
1230 : {
1231 1 : return log<software_error, -1>( { __FILE__, __LINE__, "failed to parse sensibility response: " + response } );
1232 : }
1233 :
1234 7 : m_analogGainName = analogGain;
1235 7 : m_analogGainNameSet = m_analogGainName;
1236 7 : m_analogGainValid = true;
1237 7 : recordCamera();
1238 :
1239 7 : return 0;
1240 11 : }
1241 :
1242 15 : inline int cred2Ctrl::getLEDState()
1243 : {
1244 15 : std::string response;
1245 15 : bool ledState = false;
1246 :
1247 30 : if( sendCommand( response, "led raw" ) < 0 )
1248 : {
1249 4 : if( sendCommand( response, "led" ) < 0 )
1250 : {
1251 1 : return -1;
1252 : }
1253 : }
1254 :
1255 14 : if( cred2ParseBool( ledState, response ) < 0 )
1256 : {
1257 8 : std::string clean = cred2LowerResponse( response );
1258 :
1259 8 : if( clean.find( "off" ) != std::string::npos )
1260 : {
1261 1 : ledState = false;
1262 : }
1263 7 : else if( clean.find( "on" ) != std::string::npos )
1264 : {
1265 1 : ledState = true;
1266 : }
1267 : else
1268 : {
1269 12 : if( sendCommand( response, "led" ) < 0 )
1270 : {
1271 1 : return -1;
1272 : }
1273 :
1274 5 : clean = cred2LowerResponse( response );
1275 5 : if( clean.find( "off" ) != std::string::npos )
1276 : {
1277 1 : ledState = false;
1278 : }
1279 4 : else if( clean.find( "on" ) != std::string::npos )
1280 : {
1281 1 : ledState = true;
1282 : }
1283 : else
1284 : {
1285 3 : return log<software_error, -1>( { __FILE__, __LINE__, "failed to parse led response: " + response } );
1286 : }
1287 : }
1288 8 : }
1289 :
1290 10 : m_ledState = ledState;
1291 10 : m_ledStateSet = ledState;
1292 10 : m_ledStateValid = true;
1293 10 : recordCamera();
1294 :
1295 10 : return 0;
1296 15 : }
1297 :
1298 18 : inline int cred2Ctrl::updateFPSLimits()
1299 : {
1300 18 : std::string response;
1301 18 : float minFPS = 0;
1302 18 : float maxFPS = 0;
1303 :
1304 54 : if( sendCommand( response, "minfps raw" ) < 0 || cred2ParseFloat( minFPS, response ) < 0 )
1305 : {
1306 5 : return log<software_error, -1>( { __FILE__, __LINE__, "failed to parse minfps response: " + response } );
1307 : }
1308 :
1309 39 : if( sendCommand( response, "maxfps raw" ) < 0 || cred2ParseFloat( maxFPS, response ) < 0 )
1310 : {
1311 1 : return log<software_error, -1>( { __FILE__, __LINE__, "failed to parse maxfps response: " + response } );
1312 : }
1313 :
1314 12 : m_minFPS = minFPS;
1315 12 : m_maxFPS = maxFPS;
1316 12 : m_fpsSet = std::clamp( m_fpsSet, m_minFPS, m_maxFPS );
1317 :
1318 24 : updateIfChanged( m_indiP_fpsLimits, "min", m_minFPS, INDI_IDLE );
1319 24 : updateIfChanged( m_indiP_fpsLimits, "max", m_maxFPS, INDI_IDLE );
1320 :
1321 12 : recordCamera();
1322 :
1323 12 : return 0;
1324 18 : }
1325 :
1326 1 : inline int cred2Ctrl::powerOnDefaults()
1327 : {
1328 1 : m_tempControlStatus = false;
1329 1 : m_tempControlStatusSet = false;
1330 1 : m_tempControlStatusStr = "TEMP OFF";
1331 1 : m_tempControlOnTarget = false;
1332 1 : m_cameraCropEnabled = false;
1333 1 : m_currentROI.x = m_default_x;
1334 1 : m_currentROI.y = m_default_y;
1335 1 : m_currentROI.w = m_default_w;
1336 1 : m_currentROI.h = m_default_h;
1337 1 : m_currentROI.bin_x = m_default_bin_x;
1338 1 : m_currentROI.bin_y = m_default_bin_y;
1339 :
1340 1 : m_fanSpeedValid = false;
1341 1 : m_analogGainValid = false;
1342 1 : m_ledStateValid = false;
1343 1 : m_fanSpeedNameSet = "auto";
1344 1 : m_analogGainNameSet = "med";
1345 1 : m_ledStateSet = true;
1346 :
1347 1 : m_nextROI = m_currentROI;
1348 :
1349 1 : return 0;
1350 : }
1351 :
1352 3 : inline int cred2Ctrl::setTempControl()
1353 : {
1354 3 : if( m_tempControlStatusSet )
1355 : {
1356 2 : if( m_ccdTempSetpt >= 19.5 )
1357 : {
1358 2 : return log<text_log, 0>(
1359 : "temperature control is setpoint-driven for C-RED 2; choose a target below 20 C to cool",
1360 1 : logPrio::LOG_NOTICE );
1361 : }
1362 :
1363 1 : return setTempSetPt();
1364 : }
1365 :
1366 1 : m_ccdTempSetpt = 20;
1367 1 : return setTempSetPt();
1368 : }
1369 :
1370 6 : inline int cred2Ctrl::setTempSetPt()
1371 : {
1372 6 : if( m_ccdTempSetpt < m_minTemp || m_ccdTempSetpt > m_maxTemp )
1373 : {
1374 2 : return log<text_log, -1>( "attempt to set temperature outside valid range: " + std::to_string( m_ccdTempSetpt ),
1375 1 : logPrio::LOG_ERROR );
1376 : }
1377 :
1378 5 : std::ostringstream command;
1379 5 : command << "set temperatures snake " << m_ccdTempSetpt;
1380 :
1381 5 : if( issueCommand( command.str() ) < 0 )
1382 : {
1383 3 : return -1;
1384 : }
1385 :
1386 2 : m_tempControlStatusSet = ( m_ccdTempSetpt < 19.5 );
1387 2 : m_tempControlStatus = m_tempControlStatusSet;
1388 2 : m_tempControlOnTarget = false;
1389 2 : m_tempControlStatusStr = m_tempControlStatusSet ? "OFF TARGET" : "WARMING";
1390 :
1391 2 : recordCamera();
1392 :
1393 2 : return 0;
1394 5 : }
1395 :
1396 3 : inline int cred2Ctrl::setFPS()
1397 : {
1398 3 : if( m_fpsSet < m_minFPS || m_fpsSet > m_maxFPS )
1399 : {
1400 2 : return log<text_log, -1>( "attempt to set fps outside valid range: " + std::to_string( m_fpsSet ),
1401 1 : logPrio::LOG_ERROR );
1402 : }
1403 :
1404 2 : std::ostringstream command;
1405 2 : command << "set fps " << m_fpsSet;
1406 :
1407 2 : if( issueCommand( command.str() ) < 0 )
1408 : {
1409 1 : return -1;
1410 : }
1411 :
1412 1 : log<text_log>( "set fps: " + std::to_string( m_fpsSet ) );
1413 :
1414 1 : return getFPS();
1415 2 : }
1416 :
1417 6 : inline int cred2Ctrl::setFanSpeed()
1418 : {
1419 6 : if( m_fanSpeedNameSet == "auto" )
1420 : {
1421 4 : if( issueCommand( "set fan mode automatic" ) < 0 )
1422 : {
1423 1 : return -1;
1424 : }
1425 : }
1426 : else
1427 : {
1428 4 : int fanPercent = 0;
1429 4 : if( cred2FanPresetPercent( fanPercent, m_fanSpeedNameSet ) < 0 )
1430 : {
1431 1 : return log<software_error, -1>( { __FILE__, __LINE__, "unknown fan speed preset: " + m_fanSpeedNameSet } );
1432 : }
1433 :
1434 6 : if( issueCommand( "set fan mode manual" ) < 0 )
1435 : {
1436 1 : return -1;
1437 : }
1438 :
1439 2 : if( issueCommand( "set fan speed " + std::to_string( fanPercent ) ) < 0 )
1440 : {
1441 1 : return -1;
1442 : }
1443 : }
1444 :
1445 2 : return getFanSpeed();
1446 : }
1447 :
1448 3 : inline int cred2Ctrl::setAnalogGain()
1449 : {
1450 3 : std::string commandGain;
1451 :
1452 3 : if( cred2AnalogGainCommand( commandGain, m_analogGainNameSet ) < 0 )
1453 : {
1454 1 : return log<software_error, -1>( { __FILE__, __LINE__, "unknown analog gain preset: " + m_analogGainNameSet } );
1455 : }
1456 :
1457 2 : if( issueCommand( "set sensibility " + commandGain ) < 0 )
1458 : {
1459 1 : return -1;
1460 : }
1461 :
1462 1 : return getAnalogGain();
1463 3 : }
1464 :
1465 4 : inline int cred2Ctrl::setLED()
1466 : {
1467 4 : if( m_ledStateSet )
1468 : {
1469 4 : if( issueCommand( "set led on" ) < 0 )
1470 : {
1471 1 : return -1;
1472 : }
1473 : }
1474 : else
1475 : {
1476 4 : if( issueCommand( "set led off" ) < 0 )
1477 : {
1478 1 : return -1;
1479 : }
1480 : }
1481 :
1482 2 : return getLEDState();
1483 : }
1484 :
1485 1 : inline int cred2Ctrl::setExpTime()
1486 : {
1487 1 : return 0;
1488 : }
1489 :
1490 2 : inline int cred2Ctrl::checkNextROI()
1491 : {
1492 8 : auto roundToStep = []( int value, int step )
1493 8 : { return static_cast<int>( std::lround( static_cast<double>( value ) / static_cast<double>( step ) ) ) * step; };
1494 :
1495 2 : m_nextROI.bin_x = 1;
1496 2 : m_nextROI.bin_y = 1;
1497 :
1498 2 : int width = roundToStep( m_nextROI.w, 32 );
1499 2 : width = std::clamp( width, 32, m_full_w );
1500 :
1501 2 : int height = roundToStep( m_nextROI.h, 4 );
1502 2 : height = std::clamp( height, 4, m_full_h );
1503 :
1504 2 : int startColumn = static_cast<int>( std::lround( m_nextROI.x - 0.5f * ( static_cast<float>( width ) - 1.0f ) ) );
1505 2 : int startRow = static_cast<int>( std::lround( m_nextROI.y - 0.5f * ( static_cast<float>( height ) - 1.0f ) ) );
1506 :
1507 2 : startColumn = roundToStep( startColumn, 32 );
1508 2 : startRow = roundToStep( startRow, 4 );
1509 :
1510 2 : startColumn = std::clamp( startColumn, 0, m_full_w - width );
1511 2 : startRow = std::clamp( startRow, 0, m_full_h - height );
1512 :
1513 2 : m_nextROI.w = width;
1514 2 : m_nextROI.h = height;
1515 2 : m_nextROI.x = startColumn + 0.5f * ( static_cast<float>( width ) - 1.0f );
1516 2 : m_nextROI.y = startRow + 0.5f * ( static_cast<float>( height ) - 1.0f );
1517 :
1518 4 : updateIfChanged( m_indiP_roi_x, "target", m_nextROI.x, INDI_OK );
1519 4 : updateIfChanged( m_indiP_roi_y, "target", m_nextROI.y, INDI_OK );
1520 4 : updateIfChanged( m_indiP_roi_w, "target", m_nextROI.w, INDI_OK );
1521 4 : updateIfChanged( m_indiP_roi_h, "target", m_nextROI.h, INDI_OK );
1522 4 : updateIfChanged( m_indiP_roi_bin_x, "target", m_nextROI.bin_x, INDI_OK );
1523 4 : updateIfChanged( m_indiP_roi_bin_y, "target", m_nextROI.bin_y, INDI_OK );
1524 :
1525 2 : return 0;
1526 : }
1527 :
1528 1 : inline int cred2Ctrl::setNextROI()
1529 : {
1530 1 : if( checkNextROI() < 0 )
1531 : {
1532 0 : return -1;
1533 : }
1534 :
1535 1 : recordCamera( true );
1536 1 : state( stateCodes::CONFIGURING );
1537 :
1538 1 : m_nextMode = m_modeName.empty() ? m_startupMode : m_modeName;
1539 1 : m_reconfig = true;
1540 :
1541 2 : updateSwitchIfChanged( m_indiP_roi_set, "request", pcf::IndiElement::Off, INDI_IDLE );
1542 2 : updateSwitchIfChanged( m_indiP_roi_full, "request", pcf::IndiElement::Off, INDI_IDLE );
1543 2 : updateSwitchIfChanged( m_indiP_roi_last, "request", pcf::IndiElement::Off, INDI_IDLE );
1544 2 : updateSwitchIfChanged( m_indiP_roi_default, "request", pcf::IndiElement::Off, INDI_IDLE );
1545 :
1546 1 : return 0;
1547 : }
1548 :
1549 19 : inline int cred2Ctrl::writeConfig()
1550 : {
1551 19 : std::ofstream fout( m_configFile );
1552 19 : if( fout.fail() )
1553 : {
1554 8 : return log<software_error, -1>( { __FILE__, __LINE__, "error opening C-RED 2 config file for writing" } );
1555 : }
1556 :
1557 15 : const int width = m_nextROI.w / m_nextROI.bin_x;
1558 15 : const int height = m_nextROI.h / m_nextROI.bin_y;
1559 :
1560 15 : fout << "camera_class: \"FirstLightImaging\"\n";
1561 15 : fout << "camera_model: \"C-RED 2\"\n";
1562 15 : fout << "camera_info: \"" << width << "x" << height << " (4-tap, freerun)\"\n";
1563 15 : fout << "width: " << width << "\n";
1564 15 : fout << "height: " << height << "\n";
1565 15 : fout << "depth: 16\n";
1566 15 : fout << "extdepth: 16\n";
1567 15 : fout << "rbtfile: aiagcl.bit\n";
1568 15 : fout << "CL_DATA_PATH_NORM: 3f # four tap\n";
1569 15 : fout << "CL_CFG_NORM: 02\n";
1570 15 : fout << "CL_CFG2_NORM: 40\n";
1571 15 : fout << "method_framesync: EMULATE_TIMEOUT\n";
1572 15 : fout << "htaps: 4\n";
1573 15 : fout << "serial_baud: " << m_serialBaud << "\n";
1574 15 : fout << "serial_term: <0A>\n";
1575 15 : fout << "serial_waitc: 0D\n";
1576 :
1577 15 : fout.close();
1578 :
1579 15 : return 0;
1580 19 : }
1581 :
1582 5 : inline int cred2Ctrl::configureAcquisition()
1583 : {
1584 5 : std::unique_lock<std::mutex> lock( m_indiMutex );
1585 5 : std::lock_guard<std::recursive_mutex> cameraGuard( m_cameraMutex );
1586 :
1587 5 : cred2Roi roi;
1588 5 : if( cred2RoiFromCenter( roi, m_nextROI.x, m_nextROI.y, m_nextROI.w, m_nextROI.h, m_full_w, m_full_h ) < 0 )
1589 : {
1590 1 : state( stateCodes::ERROR );
1591 2 : return log<software_error, -1>( { __FILE__, __LINE__, "invalid ROI specified for C-RED 2 configure" } );
1592 : }
1593 :
1594 4 : if( roi.fullFrame )
1595 : {
1596 6 : if( m_cameraCropEnabled && issueCommand( "set cropping off", true ) < 0 )
1597 : {
1598 1 : state( stateCodes::ERROR );
1599 1 : return -1;
1600 : }
1601 :
1602 1 : m_cameraCropEnabled = false;
1603 : }
1604 : else
1605 : {
1606 4 : if( issueCommand( "set cropping columns " + cred2ColumnsSpec( roi ), true ) < 0 ||
1607 7 : issueCommand( "set cropping rows " + cred2RowsSpec( roi ), true ) < 0 ||
1608 4 : issueCommand( "set cropping on", true ) < 0 )
1609 : {
1610 1 : state( stateCodes::ERROR );
1611 1 : return -1;
1612 : }
1613 :
1614 1 : m_cameraCropEnabled = true;
1615 : }
1616 :
1617 2 : m_currentROI = m_nextROI;
1618 :
1619 4 : updateIfChanged( m_indiP_roi_x, "current", m_currentROI.x, INDI_OK );
1620 4 : updateIfChanged( m_indiP_roi_y, "current", m_currentROI.y, INDI_OK );
1621 4 : updateIfChanged( m_indiP_roi_w, "current", m_currentROI.w, INDI_OK );
1622 4 : updateIfChanged( m_indiP_roi_h, "current", m_currentROI.h, INDI_OK );
1623 4 : updateIfChanged( m_indiP_roi_bin_x, "current", m_currentROI.bin_x, INDI_OK );
1624 4 : updateIfChanged( m_indiP_roi_bin_y, "current", m_currentROI.bin_y, INDI_OK );
1625 :
1626 2 : m_nextROI = m_currentROI;
1627 :
1628 4 : updateIfChanged( m_indiP_roi_x, "target", m_nextROI.x, INDI_OK );
1629 4 : updateIfChanged( m_indiP_roi_y, "target", m_nextROI.y, INDI_OK );
1630 4 : updateIfChanged( m_indiP_roi_w, "target", m_nextROI.w, INDI_OK );
1631 4 : updateIfChanged( m_indiP_roi_h, "target", m_nextROI.h, INDI_OK );
1632 4 : updateIfChanged( m_indiP_roi_bin_x, "target", m_nextROI.bin_x, INDI_OK );
1633 4 : updateIfChanged( m_indiP_roi_bin_y, "target", m_nextROI.bin_y, INDI_OK );
1634 :
1635 2 : m_width = m_currentROI.w;
1636 2 : m_height = m_currentROI.h;
1637 2 : m_dataType = _DATATYPE_INT16;
1638 :
1639 : // Use the current FPS target while the camera settles so the framegrabber
1640 : // does not keep reconfiguring latency buffers on a stale pre-ROI value.
1641 2 : if( m_fpsSet > 0 )
1642 : {
1643 2 : m_fps = m_fpsSet;
1644 : }
1645 :
1646 : // Give the camera a few app-logic cycles to settle after crop changes
1647 : // before resuming serial status polls such as fps, temperatures, and
1648 : // refreshed FPS limits.
1649 2 : m_roiSettleCounter = 5;
1650 :
1651 2 : recordCamera( true );
1652 2 : state( stateCodes::READY );
1653 :
1654 2 : return 0;
1655 5 : }
1656 :
1657 1 : inline float cred2Ctrl::fps()
1658 : {
1659 1 : return m_fps;
1660 : }
1661 :
1662 1 : inline int cred2Ctrl::startAcquisition()
1663 : {
1664 1 : state( stateCodes::OPERATING );
1665 1 : recordCamera();
1666 1 : return edtCamera<cred2Ctrl>::pdvStartAcquisition();
1667 : }
1668 :
1669 1 : inline int cred2Ctrl::acquireAndCheckValid()
1670 : {
1671 1 : return edtCamera<cred2Ctrl>::pdvAcquire( m_currImageTimestamp );
1672 : }
1673 :
1674 2 : inline int cred2Ctrl::loadImageIntoStream( void *dest )
1675 : {
1676 2 : if( frameGrabber<cred2Ctrl>::loadImageIntoStreamCopy( dest, m_image_p, m_width, m_height, m_typeSize ) == nullptr )
1677 : {
1678 1 : return -1;
1679 : }
1680 :
1681 1 : return 0;
1682 : }
1683 :
1684 3 : inline int cred2Ctrl::reconfig()
1685 : {
1686 3 : recordCamera( true );
1687 3 : state( stateCodes::CONFIGURING );
1688 :
1689 3 : if( writeConfig() < 0 )
1690 : {
1691 1 : return -1;
1692 : }
1693 :
1694 2 : std::lock_guard<std::recursive_mutex> guard( m_cameraMutex );
1695 2 : int rv = edtCamera<cred2Ctrl>::pdvReconfig();
1696 2 : if( rv < 0 )
1697 : {
1698 0 : return rv;
1699 : }
1700 :
1701 2 : if( setSerialBaud() < 0 )
1702 : {
1703 1 : return -1;
1704 : }
1705 :
1706 1 : state( stateCodes::READY );
1707 1 : m_nextMode = m_modeName;
1708 :
1709 1 : return 0;
1710 2 : }
1711 :
1712 1 : inline int cred2Ctrl::checkRecordTimes()
1713 : {
1714 1 : return telemeter<cred2Ctrl>::checkRecordTimes( cred2_temps(), telem_stdcam(), telem_fgtimings() );
1715 : }
1716 :
1717 2 : inline int cred2Ctrl::recordTelem( const cred2_temps * )
1718 : {
1719 2 : return recordTemps( true );
1720 : }
1721 :
1722 2 : inline int cred2Ctrl::recordTelem( const telem_stdcam * )
1723 : {
1724 2 : return recordCamera( true );
1725 : }
1726 :
1727 2 : inline int cred2Ctrl::recordTelem( const telem_fgtimings * )
1728 : {
1729 2 : return recordFGTimings( true );
1730 : }
1731 :
1732 18 : inline int cred2Ctrl::recordTemps( bool force )
1733 : {
1734 : static cred2Temps lastTemps;
1735 :
1736 18 : if( !( lastTemps == m_temps ) || force )
1737 : {
1738 9 : telem<cred2_temps>( { m_temps.motherboard,
1739 9 : m_temps.frontend,
1740 9 : m_temps.powerboard,
1741 9 : m_temps.snake,
1742 9 : m_temps.setpoint,
1743 9 : m_temps.peltier,
1744 9 : m_temps.heatsink } );
1745 9 : lastTemps = m_temps;
1746 : }
1747 :
1748 18 : return 0;
1749 : }
1750 :
1751 : } // namespace app
1752 : } // namespace MagAOX
1753 :
1754 : #endif // cred2Ctrl_hpp
|