API
 
Loading...
Searching...
No Matches
cred2Ctrl_test.cpp
Go to the documentation of this file.
1/** \file cred2Ctrl_test.cpp
2 * \brief Catch2 tests for the cred2Ctrl app.
3 * \author OpenAI Codex
4 *
5 * \ingroup cred2Ctrl_files
6 */
7
8/** \defgroup cred2Ctrl_unit_test cred2Ctrl Unit Tests
9 * \brief Unit tests for the cred2Ctrl application.
10 *
11 * \ingroup application_unit_test
12 */
13
14#include "../../../tests/testXWC.hpp"
15
16#include <atomic>
17#include <array>
18#include <chrono>
19#include <cstdio>
20#include <cstdlib>
21#include <deque>
22#include <fstream>
23#include <string>
24#include <thread>
25#include <unistd.h>
26#include <vector>
27
28#define protected public
29#include "../cred2Ctrl.hpp"
30#undef protected
31
32using namespace MagAOX::app;
33
34namespace
35{
36
37using cred2MagAOXAppT = MagAOX::app::MagAOXApp<>;
38
39/// Scripted serial response returned by the EDT serial-command stubs.
40struct serialResponse
41{
42 std::string response; ///< Response returned by the next serial read.
43 int commandResult{ 0 }; ///< Return code from `pdv_serial_command`.
44 int initialWaitResult{ 1 }; ///< Return code from the first `pdv_serial_wait`.
45};
46
47/// Shared EDT stub state used to verify acquisition and serial calls from `cred2Ctrl`.
48struct edtStubState
49{
50 /// Reset the stub to a known default state before each test section.
51 void reset()
52 {
53 startImagesCalls = 0;
54 lastStartNumBuffs = -1;
55 startImageCalls = 0;
56 waitTimeSec = 0;
57 waitTimeNsec = 0;
58 waitImage.fill( 0 );
59 readcfgReturn = 0;
60 readcfgCalls = 0;
61 multibufCalls = 0;
62 serialResponses.clear();
63 serialCommands.clear();
64 activeSerialResponse.clear();
65 activeSerialReadPending = false;
66 activeSerialTransaction = false;
67 activeSerialWaitResult = 0;
68 activeSerialWaitServed = false;
69 baudSetResult = 0;
70 baudCurrent = 115200;
71 baudSetCalls = 0;
72 baudGetCalls = 0;
73 lastRequestedBaud = -1;
74 }
75
76 int startImagesCalls{ 0 }; ///< Number of `pdv_start_images` calls observed.
77 int lastStartNumBuffs{ -1 }; ///< Most recent requested EDT buffer count.
78 int startImageCalls{ 0 }; ///< Number of `pdv_start_image` calls observed.
79 uint waitTimeSec{ 0 }; ///< Seconds returned by `pdv_wait_last_image_timed`.
80 uint waitTimeNsec{ 0 }; ///< Nanoseconds returned by `pdv_wait_last_image_timed`.
81 std::array<u_char, 64> waitImage{}; ///< Raw EDT frame buffer returned to the app.
82 int readcfgReturn{ 0 }; ///< Return code forced from `pdv_readcfg`.
83 int readcfgCalls{ 0 }; ///< Number of `pdv_readcfg` calls observed.
84 int multibufCalls{ 0 }; ///< Number of `pdv_multibuf` calls observed.
85 std::deque<serialResponse> serialResponses; ///< Scripted serial responses queued for future commands.
86 std::vector<std::string> serialCommands; ///< Serial commands issued by the app under test.
87 std::string activeSerialResponse; ///< Active serial response returned by the current command.
88 bool activeSerialReadPending{ false }; ///< Indicates that the next `pdv_serial_read` should return data.
89 bool activeSerialTransaction{ false }; ///< Indicates that a serial command is mid-transaction.
90 int activeSerialWaitResult{ 0 }; ///< Return code for the first wait after a command.
91 bool activeSerialWaitServed{ false }; ///< Tracks whether the first serial wait has been consumed.
92 int baudSetResult{ 0 }; ///< Return code forced from `pdv_serial_set_baud`.
93 int baudCurrent{ 115200 }; ///< Baud value returned by `pdv_serial_get_baud`.
94 int baudSetCalls{ 0 }; ///< Number of `pdv_serial_set_baud` calls observed.
95 int baudGetCalls{ 0 }; ///< Number of `pdv_serial_get_baud` calls observed.
96 int lastRequestedBaud{ -1 }; ///< Most recent baud requested by the app under test.
97};
98
99/// Global EDT stub state for the `extern "C"` wrappers below.
100edtStubState g_edtStubState;
101
102/// Reset all external-library stub state before a test.
103[[maybe_unused]] void resetStubState()
104{
105 g_edtStubState.reset();
106}
107
108/// Queue one scripted serial response for the next `pdvSerialWriteRead` invocation.
109[[maybe_unused]] void
110queueSerialResponse( const std::string &response, int commandResult = 0, int initialWaitResult = 1 )
111{
112 g_edtStubState.serialResponses.push_back( { response, commandResult, initialWaitResult } );
113}
114
115} // namespace
116
117extern "C"
118{
119
121 {
122 return reinterpret_cast<Dependent *>( malloc( sizeof( Dependent ) ) );
123 }
124
125 int pdv_readcfg( const char *configFile, Dependent *dd_p, Edtinfo *edtinfo )
126 {
127 static_cast<void>( configFile );
128 static_cast<void>( dd_p );
129 static_cast<void>( edtinfo );
130 ++g_edtStubState.readcfgCalls;
131 return g_edtStubState.readcfgReturn;
132 }
133
134 EdtDev *edt_open_channel( const char *deviceName, int unit, int channel )
135 {
136 static EdtDev device;
137 static_cast<void>( deviceName );
138 static_cast<void>( unit );
139 static_cast<void>( channel );
140 return &device;
141 }
142
143 void edt_perror( char *errstr )
144 {
145 if( errstr != nullptr )
146 {
147 errstr[0] = '\0';
148 }
149 }
150
151 int pdv_initcam( EdtDev *edt_p,
152 Dependent *dd_p,
153 int unit,
154 Edtinfo *edtinfo,
155 const char *configFile,
156 char *bitdir,
157 int pdv_debug )
158 {
159 static_cast<void>( edt_p );
160 static_cast<void>( dd_p );
161 static_cast<void>( unit );
162 static_cast<void>( edtinfo );
163 static_cast<void>( configFile );
164 static_cast<void>( bitdir );
165 static_cast<void>( pdv_debug );
166 return 0;
167 }
168
169 void edt_close( EdtDev *edt_p )
170 {
171 static_cast<void>( edt_p );
172 }
173
174 PdvDev *pdv_open_channel( const char *deviceName, int unit, int channel )
175 {
176 static PdvDev device;
177 static_cast<void>( deviceName );
178 static_cast<void>( unit );
179 static_cast<void>( channel );
180 return &device;
181 }
182
183 void pdv_close( PdvDev *pdv_p )
184 {
185 static_cast<void>( pdv_p );
186 }
187
188 void pdv_flush_fifo( PdvDev *pdv_p )
189 {
190 static_cast<void>( pdv_p );
191 }
192
194 {
195 static_cast<void>( pdv_p );
196 }
197
198 int pdv_get_width( PdvDev *pdv_p )
199 {
200 static_cast<void>( pdv_p );
201 return 640;
202 }
203
204 int pdv_get_height( PdvDev *pdv_p )
205 {
206 static_cast<void>( pdv_p );
207 return 512;
208 }
209
210 int pdv_get_depth( PdvDev *pdv_p )
211 {
212 static_cast<void>( pdv_p );
213 return 16;
214 }
215
217 {
218 static char cameraType[] = "stub_pdv";
219 static_cast<void>( pdv_p );
220 return cameraType;
221 }
222
223 void pdv_multibuf( PdvDev *pdv_p, int numBuffs )
224 {
225 static_cast<void>( pdv_p );
226 static_cast<void>( numBuffs );
227 ++g_edtStubState.multibufCalls;
228 }
229
230 void pdv_start_images( PdvDev *pdv_p, int numBuffs )
231 {
232 static_cast<void>( pdv_p );
233 ++g_edtStubState.startImagesCalls;
234 g_edtStubState.lastStartNumBuffs = numBuffs;
235 }
236
237 u_char *pdv_wait_last_image_timed( PdvDev *pdv_p, uint dmaTimeStamp[2] )
238 {
239 static_cast<void>( pdv_p );
240 dmaTimeStamp[0] = g_edtStubState.waitTimeSec;
241 dmaTimeStamp[1] = g_edtStubState.waitTimeNsec;
242 return g_edtStubState.waitImage.data();
243 }
244
245 void pdv_start_image( PdvDev *pdv_p )
246 {
247 static_cast<void>( pdv_p );
248 ++g_edtStubState.startImageCalls;
249 }
250
251 int pdv_serial_read( PdvDev *pdv_p, char *buf, int size )
252 {
253 static_cast<void>( pdv_p );
254 if( buf != nullptr && size > 0 )
255 {
256 buf[0] = '\0';
257 }
258
259 if( !g_edtStubState.activeSerialReadPending || buf == nullptr || size <= 0 )
260 {
261 return 0;
262 }
263
264 size_t copyCount = g_edtStubState.activeSerialResponse.size();
265 if( copyCount > static_cast<size_t>( size - 1 ) )
266 {
267 copyCount = static_cast<size_t>( size - 1 );
268 }
269
270 std::copy_n( g_edtStubState.activeSerialResponse.data(), copyCount, buf );
271 buf[copyCount] = '\0';
272
273 g_edtStubState.activeSerialReadPending = false;
274
275 return static_cast<int>( copyCount );
276 }
277
278 int pdv_serial_command( PdvDev *pdv_p, const char *command )
279 {
280 static_cast<void>( pdv_p );
281 g_edtStubState.serialCommands.emplace_back( command == nullptr ? "" : command );
282
283 g_edtStubState.activeSerialResponse.clear();
284 g_edtStubState.activeSerialReadPending = false;
285 g_edtStubState.activeSerialTransaction = false;
286 g_edtStubState.activeSerialWaitResult = 0;
287 g_edtStubState.activeSerialWaitServed = false;
288
289 if( g_edtStubState.serialResponses.empty() )
290 {
291 return 0;
292 }
293
294 serialResponse response = g_edtStubState.serialResponses.front();
295 g_edtStubState.serialResponses.pop_front();
296
297 if( response.commandResult < 0 )
298 {
299 return response.commandResult;
300 }
301
302 g_edtStubState.activeSerialResponse = response.response;
303 g_edtStubState.activeSerialReadPending = true;
304 g_edtStubState.activeSerialTransaction = true;
305 g_edtStubState.activeSerialWaitResult = response.initialWaitResult;
306
307 return response.commandResult;
308 }
309
310 int pdv_serial_wait( PdvDev *pdv_p, int timeout, int count )
311 {
312 static_cast<void>( pdv_p );
313 static_cast<void>( timeout );
314 static_cast<void>( count );
315
316 if( !g_edtStubState.activeSerialTransaction )
317 {
318 return 0;
319 }
320
321 if( !g_edtStubState.activeSerialWaitServed )
322 {
323 g_edtStubState.activeSerialWaitServed = true;
324 return g_edtStubState.activeSerialWaitResult;
325 }
326
327 return 0;
328 }
329
330 int pdv_get_waitchar( PdvDev *pdv_p, u_char *waitc )
331 {
332 static_cast<void>( pdv_p );
333 if( waitc != nullptr )
334 {
335 *waitc = '\n';
336 }
337 return 1;
338 }
339
340 int pdv_serial_set_baud( PdvDev *pdv_p, int baud )
341 {
342 static_cast<void>( pdv_p );
343 ++g_edtStubState.baudSetCalls;
344 g_edtStubState.lastRequestedBaud = baud;
345 if( g_edtStubState.baudSetResult == 0 )
346 {
347 g_edtStubState.baudCurrent = baud;
348 }
349
350 return g_edtStubState.baudSetResult;
351 }
352
354 {
355 static_cast<void>( pdv_p );
356 ++g_edtStubState.baudGetCalls;
357 return g_edtStubState.baudCurrent;
358 }
359
360} // extern "C"
361
362namespace libXWCTest
363{
364
365/// Namespace for `cred2Ctrl` unit tests.
366/** \ingroup cred2Ctrl_unit_test
367 */
368namespace cred2CtrlTest
369{
370
371namespace
372{
373
374/// Build a unique shmim name for one temporary test stream.
375std::string uniqueShmimName( const std::string &suffix )
376{
377 static unsigned counter = 0;
378
379 ++counter;
380
381 return "cred2Ctrl_test_" + suffix + "_" + std::to_string( ::getpid() ) + "_" + std::to_string( counter );
382}
383
384/// Build a unique temporary config-file path for one test.
385std::string uniqueConfigPath( const std::string &suffix )
386{
387 return "/tmp/cred2Ctrl_test_" + suffix + "_" + std::to_string( ::getpid() ) + ".conf";
388}
389
390/// Remove one temporary file and ignore missing-file errors.
391void removeIfPresent( const std::string &path )
392{
393 if( !path.empty() )
394 {
395 static_cast<void>( ::unlink( path.c_str() ) );
396 }
397}
398
399/// Test harness exposing protected `cred2Ctrl` helpers.
400class cred2Ctrl_test : public MagAOX::app::cred2Ctrl
401{
402 public:
403 /// Construct a testable controller instance with unique runtime identifiers.
404 cred2Ctrl_test()
405 {
406 m_configName = uniqueShmimName( "config" );
407 m_shmimName = uniqueShmimName( "main" );
408 }
409
410 /// Remove the generated EDT config file after each test.
411 ~cred2Ctrl_test() noexcept
412 {
413 removeIfPresent( m_configFile );
414 }
415};
416
417/// Put the app into the nominal powered-on state used by the serial helpers.
418[[maybe_unused]] void setPoweredOn( cred2Ctrl_test &app )
419{
420 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 1;
421 app.m_powerTargetState = 1;
422}
423
424/// Load the standard app configuration with optional overrides for one test.
425[[maybe_unused]] void loadAppConfig( cred2Ctrl_test &app,
426 const std::string &suffix,
427 const std::vector<std::string> &sections,
428 const std::vector<std::string> &keywords,
429 const std::vector<std::string> &values )
430{
431 const std::string configPath = uniqueConfigPath( suffix );
432
433 app.setupConfig();
434 mx::app::writeConfigFile( configPath, sections, keywords, values );
435 app.config.readConfig( configPath );
436 app.loadConfig();
437 removeIfPresent( configPath );
438
439 app.m_tel.logPath( "/tmp" );
440 app.m_tel.logName( app.m_configName );
441 app.m_tel.logExt( "bintel" );
442}
443
444/// Load the minimum configuration required for `cred2Ctrl` startup and runtime tests.
445[[maybe_unused]] void loadDefaultConfig( cred2Ctrl_test &app, const std::string &suffix )
446{
447 loadAppConfig( app, suffix, { "framegrabber" }, { "shmimName" }, { uniqueShmimName( "stream" ) } );
448}
449
450/// Start a short-lived framegrabber thread so `frameGrabber::appLogic()` sees a running worker.
451[[maybe_unused]] void startFgThread( cred2Ctrl_test &app, int sleepMs = 200 )
452{
453 app.m_fgThread =
454 std::thread( [sleepMs]() { std::this_thread::sleep_for( std::chrono::milliseconds( sleepMs ) ); } );
455}
456
457/// Join the temporary framegrabber thread if a test started one.
458[[maybe_unused]] void joinFgThread( cred2Ctrl_test &app )
459{
460 if( app.m_fgThread.joinable() )
461 {
462 app.m_fgThread.join();
463 }
464}
465
466/// Keep a temporary framegrabber thread joined even when a test fails mid-scope.
467struct fgThreadScope
468{
469 cred2Ctrl_test &m_app; ///< App whose temporary framegrabber thread is managed by this scope.
470
471 /// Start a temporary framegrabber thread for the lifetime of this scope.
472 explicit fgThreadScope( cred2Ctrl_test &app, int sleepMs = 200 ) : m_app( app )
473 {
474 startFgThread( m_app, sleepMs );
475 }
476
477 /// Join the temporary framegrabber thread on scope exit.
478 ~fgThreadScope()
479 {
480 joinFgThread( m_app );
481 }
482};
483
484/// Ensure startup-driven telemetry resources are shut down when a test exits early.
485struct startupScope
486{
487 cred2Ctrl_test &m_app; ///< App whose startup resources are managed by this scope.
488 bool m_started; ///< Tracks whether `appStartup()` completed successfully in the test.
489
490 /// Construct a startup guard for one test app instance.
491 explicit startupScope( cred2Ctrl_test &app ) : m_app( app ), m_started( false )
492 {
493 }
494
495 /// Record whether startup completed so shutdown only runs when needed.
496 void markStarted( bool started )
497 {
498 m_started = started;
499 }
500
501 /// Shut down telemetry logging and app resources on scope exit after a successful startup.
502 ~startupScope()
503 {
504 if( m_started )
505 {
506 m_app.m_shutdown = 1;
507 m_app.m_tel.logShutdown( true );
508 static_cast<void>( m_app.appShutdown() );
509 }
510 }
511};
512
513/// Report whether a telemetry timestamp has been written at least once.
514[[maybe_unused]] bool hasRecordedTime( const timespec &ts )
515{
516 return ts.tv_sec != 0 || ts.tv_nsec != 0;
517}
518
519} // namespace
520
521#ifndef CRED2CTRL_TEST_SUPPORT_ONLY
522
523/// Verify configuration loading writes the runtime EDT config and applies overrides.
524/**
525 * \ingroup cred2Ctrl_unit_test
526 */
527TEST_CASE( "cred2Ctrl configuration loading writes a runtime EDT config", "[cred2Ctrl]" )
528{
529 resetStubState();
530
531 // clang-format off
532 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
536 #endif
537 // clang-format on
538
539 SECTION( "loadConfig uses the default serial baud and seeds the runtime camera mode" )
540 {
541 cred2Ctrl_test app;
542
543 loadAppConfig( app, "defaults", { "unused" }, { "value" }, { "0" } );
544
545 REQUIRE( app.m_serialBaud == 115200 );
546 REQUIRE( app.m_startupMode == "runtime" );
547 REQUIRE( app.m_cameraModes.count( "runtime" ) == 1 );
548 REQUIRE( app.m_cameraModes["runtime"].m_configFile == app.m_configFile );
549
550 std::ifstream configFile( app.m_configFile );
551 REQUIRE( configFile.good() );
552
553 const std::string contents( ( std::istreambuf_iterator<char>( configFile ) ),
554 std::istreambuf_iterator<char>() );
555 REQUIRE( contents.find( "camera_model: \"C-RED 2\"" ) != std::string::npos );
556 REQUIRE( contents.find( "width: 640" ) != std::string::npos );
557 REQUIRE( contents.find( "height: 512" ) != std::string::npos );
558 REQUIRE( contents.find( "serial_baud: 115200" ) != std::string::npos );
559 }
560
561 SECTION( "loadConfig applies configured serial-baud overrides to the runtime config file" )
562 {
563 cred2Ctrl_test app;
564
565 loadAppConfig( app,
566 "serial_baud",
567 { "camera", "framegrabber" },
568 { "serialBaud", "shmimName" },
569 { "57600", uniqueShmimName( "override" ) } );
570
571 REQUIRE( app.m_serialBaud == 57600 );
572 REQUIRE( app.m_configFile.find( "cred2_" ) != std::string::npos );
573 REQUIRE( app.m_cameraModes["runtime"].m_configFile == app.m_configFile );
574
575 std::ifstream configFile( app.m_configFile );
576 REQUIRE( configFile.good() );
577
578 const std::string contents( ( std::istreambuf_iterator<char>( configFile ) ),
579 std::istreambuf_iterator<char>() );
580 REQUIRE( contents.find( "serial_baud: 57600" ) != std::string::npos );
581 }
582
583 SECTION( "loadConfig marks the app for shutdown when the runtime EDT config cannot be written" )
584 {
585 cred2Ctrl_test app;
586 const std::string configPath = uniqueConfigPath( "load_failure" );
587
588 app.m_configName = "missing/cred2_write_failure";
589 app.setupConfig();
590 mx::app::writeConfigFile(
591 configPath, { "framegrabber" }, { "shmimName" }, { uniqueShmimName( "load_failure_stream" ) } );
592 app.config.readConfig( configPath );
593 app.loadConfig();
594 removeIfPresent( configPath );
595
596 REQUIRE( app.m_shutdown == true );
597 }
598
599 SECTION( "heap deletion and direct EDT stub calls cover the remaining test harness wrappers" )
600 {
601 char shortError[2] = { 'x', '\0' };
602 char shortRead[4] = { '\0', '\0', '\0', '\0' };
603
604 edt_perror( shortError );
605 REQUIRE( shortError[0] == '\0' );
606
607 queueSerialResponse( "abcdef\n" );
608 REQUIRE( pdv_serial_command( nullptr, "stub command" ) == 0 );
609 REQUIRE( pdv_serial_wait( nullptr, 0, 0 ) == 1 );
610 REQUIRE( pdv_serial_read( nullptr, shortRead, sizeof( shortRead ) ) == 3 );
611 REQUIRE( std::string( shortRead ) == "abc" );
612
613 cred2Ctrl_test *heapApp = new cred2Ctrl_test;
614 REQUIRE( heapApp != nullptr );
615 delete heapApp;
616
617 MagAOX::app::cred2Ctrl *baseHeapApp = new cred2Ctrl_test;
618 REQUIRE( baseHeapApp != nullptr );
619 delete baseHeapApp;
620
622 REQUIRE( plainHeapApp != nullptr );
623 delete plainHeapApp;
624 }
625}
626
627/// Verify local response helpers cover the C-RED 2-specific preset mappings.
628/**
629 * \ingroup cred2Ctrl_unit_test
630 */
631TEST_CASE( "cred2Ctrl helper mappings normalize responses and preset names", "[cred2Ctrl]" )
632{
633 bool enabled = false;
634 float parsedValue = 0;
635 float centerX = 0;
636 float centerY = 0;
637 int fanRangeFirst = 0;
638 int fanRangeSecond = 0;
639 std::string gainName;
640 std::string commandGain;
641 int fanPercent = -1;
642 int startColumn = 0;
643 int endColumn = 0;
644 int startRow = 0;
645 int endRow = 0;
646 int width = 0;
647 int height = 0;
648 cred2Roi roi;
649 std::vector<float> parsedValues;
650
651 REQUIRE( cred2LowerResponse( " Medium\r\nfli-cli>" ) == "medium" );
652
653 REQUIRE( cred2FanPresetName( 0.0f ) == "off" );
654 REQUIRE( cred2FanPresetName( 25.0f ) == "p25" );
655 REQUIRE( cred2FanPresetName( 50.0f ) == "p50" );
656 REQUIRE( cred2FanPresetName( 75.0f ) == "p75" );
657 REQUIRE( cred2FanPresetName( 100.0f ) == "p100" );
658
659 REQUIRE( cred2FanPresetPercent( fanPercent, "off" ) == 0 );
660 REQUIRE( fanPercent == 0 );
661 REQUIRE( cred2FanPresetPercent( fanPercent, "p25" ) == 0 );
662 REQUIRE( fanPercent == 25 );
663 REQUIRE( cred2FanPresetPercent( fanPercent, "p50" ) == 0 );
664 REQUIRE( fanPercent == 50 );
665 REQUIRE( cred2FanPresetPercent( fanPercent, "p75" ) == 0 );
666 REQUIRE( fanPercent == 75 );
667 REQUIRE( cred2FanPresetPercent( fanPercent, "p100" ) == 0 );
668 REQUIRE( fanPercent == 100 );
669 REQUIRE( cred2FanPresetPercent( fanPercent, "mystery" ) == -1 );
670
671 REQUIRE( cred2AnalogGainName( gainName, "medium" ) == 0 );
672 REQUIRE( gainName == "med" );
673 REQUIRE( cred2AnalogGainName( gainName, "med" ) == 0 );
674 REQUIRE( gainName == "med" );
675 REQUIRE( cred2AnalogGainName( gainName, "high" ) == 0 );
676 REQUIRE( gainName == "high" );
677 REQUIRE( cred2AnalogGainName( gainName, "low" ) == 0 );
678 REQUIRE( gainName == "low" );
679 REQUIRE( cred2AnalogGainName( gainName, "unknown" ) == -1 );
680
681 REQUIRE( cred2AnalogGainCommand( commandGain, "low" ) == 0 );
682 REQUIRE( commandGain == "low" );
683 REQUIRE( cred2AnalogGainCommand( commandGain, "med" ) == 0 );
684 REQUIRE( commandGain == "medium" );
685 REQUIRE( cred2AnalogGainCommand( commandGain, "high" ) == 0 );
686 REQUIRE( commandGain == "high" );
687 REQUIRE( cred2AnalogGainCommand( commandGain, "invalid" ) == -1 );
688
689 REQUIRE( cred2ParseFloat( parsedValue, "12.5" ) == 0 );
690 REQUIRE( parsedValue == Approx( 12.5f ) );
691 REQUIRE( cred2ParseFloat( parsedValue, "" ) == -1 );
692 REQUIRE( cred2ParseFloat( parsedValue, "12.5 junk" ) == -1 );
693
694 REQUIRE( cred2ParseFloatVector( parsedValues, "1:2:3", 3 ) == 0 );
695 REQUIRE( parsedValues == std::vector<float>{ 1.0f, 2.0f, 3.0f } );
696 REQUIRE( cred2ParseFloatVector( parsedValues, "", 0 ) == -1 );
697 REQUIRE( cred2ParseFloatVector( parsedValues, "1:bad:3", 3 ) == -1 );
698 REQUIRE( parsedValues.empty() );
699
700 REQUIRE( cred2ParseCropState( enabled, startColumn, endColumn, startRow, endRow, "off" ) == 0 );
701 REQUIRE( enabled == false );
702 REQUIRE( startColumn == 0 );
703 REQUIRE( endColumn == 0 );
704 REQUIRE( startRow == 0 );
705 REQUIRE( endRow == 0 );
706 REQUIRE( cred2ParseCropState( enabled, startColumn, endColumn, startRow, endRow, "" ) == -1 );
707 REQUIRE( cred2ParseCropState( enabled, startColumn, endColumn, startRow, endRow, "maybe" ) == -1 );
708 REQUIRE( cred2ParseCropState( enabled, startColumn, endColumn, startRow, endRow, "on:1-2" ) == -1 );
709 REQUIRE( cred2ParseCropState( enabled, startColumn, endColumn, startRow, endRow, "on:bad:1-2" ) == -1 );
710
711 REQUIRE( cred2ParseRange( fanRangeFirst, fanRangeSecond, "1-4" ) == 0 );
712 REQUIRE( fanRangeFirst == 1 );
713 REQUIRE( fanRangeSecond == 4 );
714 REQUIRE( cred2ParseRange( fanRangeFirst, fanRangeSecond, "1" ) == -1 );
715
716 REQUIRE( cred2RoiFromCenter( roi, 319.5f, 255.5f, 640, 512, 640, 512 ) == 0 );
717 REQUIRE( roi.fullFrame == true );
718 REQUIRE( cred2RoiFromCenter( roi, 319.5f, 255.5f, 0, 512, 640, 512 ) == -1 );
719 REQUIRE( cred2RoiToCenter( centerX, centerY, width, height, roi, 640, 512 ) == 0 );
720 REQUIRE( centerX == Approx( 319.5f ) );
721 REQUIRE( centerY == Approx( 255.5f ) );
722 REQUIRE( width == 640 );
723 REQUIRE( height == 512 );
724
725 roi.startColumn = 10;
726 roi.endColumn = 5;
727 REQUIRE( cred2RoiToCenter( centerX, centerY, width, height, roi, 640, 512 ) == -1 );
728}
729
730/// Verify serial helper wrappers clean responses and report transport failures consistently.
731/**
732 * \ingroup cred2Ctrl_unit_test
733 */
734TEST_CASE( "cred2Ctrl command helpers clean responses and validate acknowledgements", "[cred2Ctrl]" )
735{
736 cred2Ctrl_test app;
737 std::string response;
738
739 resetStubState();
740
741 // clang-format off
742 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
743 XWCTEST_DOXYGEN_REF( cred2Ctrl::sendCommand( response, "fps raw", true ) );
744 XWCTEST_DOXYGEN_REF( cred2Ctrl::issueCommand( "set fps 100", false ) );
745 #endif
746 // clang-format on
747
748 SECTION( "sendCommand strips prompts and returns the cleaned response" )
749 {
750 setPoweredOn( app );
751 queueSerialResponse( "400\r\nfli-cli>\n" );
752
753 REQUIRE( app.sendCommand( response, "fps raw" ) == 0 );
754 REQUIRE( response == "400" );
755 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "fps raw" } );
756 }
757
758 SECTION( "sendCommand returns -1 without logging when the camera is powered off" )
759 {
760 queueSerialResponse( "", -1 );
761
762 REQUIRE( app.sendCommand( response, "fps raw" ) == -1 );
763 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "fps raw" } );
764 }
765
766 SECTION( "issueCommand accepts missing responses when allowNoResponse is true" )
767 {
768 setPoweredOn( app );
769 queueSerialResponse( "", -1 );
770
771 REQUIRE( app.issueCommand( "set fps 100", true ) == 0 );
772 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set fps 100" } );
773 }
774
775 SECTION( "issueCommand rejects explicit error responses" )
776 {
777 setPoweredOn( app );
778 queueSerialResponse( "Error: busy\n" );
779
780 REQUIRE( app.issueCommand( "set fps 100", false ) == -1 );
781 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set fps 100" } );
782 }
783
784 SECTION( "issueCommand accepts normal acknowledgement responses" )
785 {
786 setPoweredOn( app );
787 queueSerialResponse( "OK\n" );
788
789 REQUIRE( app.issueCommand( "set fps 100", false ) == 0 );
790 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set fps 100" } );
791 }
792}
793
794/// Verify ROI synchronization handles full-frame, cropped, and reconfiguring camera states.
795/**
796 * \ingroup cred2Ctrl_unit_test
797 */
798TEST_CASE( "cred2Ctrl syncROIFromCamera tracks camera cropping state", "[cred2Ctrl]" )
799{
800 resetStubState();
801
802 // clang-format off
803 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
805 #endif
806 // clang-format on
807
808 SECTION( "full-frame cropping disables camera-side crop tracking" )
809 {
810 cred2Ctrl_test app;
811
812 setPoweredOn( app );
813 app.m_raw_width = app.m_full_w;
814 app.m_raw_height = app.m_full_h;
815 queueSerialResponse( "off\n" );
816
817 REQUIRE( app.syncROIFromCamera() == 0 );
818 REQUIRE( app.m_cameraCropEnabled == false );
819 REQUIRE( app.m_currentROI.x == Approx( app.m_full_x ) );
820 REQUIRE( app.m_currentROI.y == Approx( app.m_full_y ) );
821 REQUIRE( app.m_currentROI.w == app.m_full_w );
822 REQUIRE( app.m_currentROI.h == app.m_full_h );
823 }
824
825 SECTION( "explicit crop coordinates update the cached ROI" )
826 {
827 cred2Ctrl_test app;
828
829 setPoweredOn( app );
830 app.m_raw_width = 256;
831 app.m_raw_height = 256;
832 queueSerialResponse( "on:192-447:128-383\n" );
833
834 REQUIRE( app.syncROIFromCamera() == 0 );
835 REQUIRE( app.m_cameraCropEnabled == true );
836 REQUIRE( app.m_currentROI.x == Approx( 319.5f ) );
837 REQUIRE( app.m_currentROI.y == Approx( 255.5f ) );
838 REQUIRE( app.m_currentROI.w == 256 );
839 REQUIRE( app.m_currentROI.h == 256 );
840 REQUIRE( app.m_width == 256 );
841 REQUIRE( app.m_height == 256 );
842 }
843
844 SECTION( "querying separate row and column limits handles cameras that omit them from the summary response" )
845 {
846 cred2Ctrl_test app;
847
848 setPoweredOn( app );
849 app.m_raw_width = 256;
850 app.m_raw_height = 256;
851 queueSerialResponse( "on\n" );
852 queueSerialResponse( "192-447\n" );
853 queueSerialResponse( "128-383\n" );
854
855 REQUIRE( app.syncROIFromCamera() == 0 );
856 REQUIRE( app.m_cameraCropEnabled == true );
857 REQUIRE( app.m_currentROI.x == Approx( 319.5f ) );
858 REQUIRE( app.m_currentROI.y == Approx( 255.5f ) );
859 REQUIRE( app.m_currentROI.w == 256 );
860 REQUIRE( app.m_currentROI.h == 256 );
861 REQUIRE( g_edtStubState.serialCommands ==
862 std::vector<std::string>{ "cropping raw", "cropping columns raw", "cropping rows raw" } );
863 }
864
865 SECTION( "invalid crop responses return an error" )
866 {
867 cred2Ctrl_test app;
868
869 setPoweredOn( app );
870 queueSerialResponse( "garbage\n" );
871
872 REQUIRE( app.syncROIFromCamera() == -1 );
873 }
874
875 SECTION( "mismatched EDT dimensions trigger a reconfiguration and config rewrite" )
876 {
877 cred2Ctrl_test app;
878
879 loadDefaultConfig( app, "roi_reconfig" );
880 setPoweredOn( app );
881 app.m_modeName = "runtime";
882 app.m_raw_width = 320;
883 app.m_raw_height = 256;
884 queueSerialResponse( "off\n" );
885
886 REQUIRE( app.syncROIFromCamera() == 0 );
887 REQUIRE( g_edtStubState.readcfgCalls > 0 );
888 REQUIRE( app.m_modeName == "runtime" );
889 }
890
891 SECTION( "missing crop-column details are reported as an error" )
892 {
893 cred2Ctrl_test app;
894
895 setPoweredOn( app );
896 app.m_raw_width = 256;
897 app.m_raw_height = 256;
898 queueSerialResponse( "on\n" );
899 queueSerialResponse( "bad\n" );
900
901 REQUIRE( app.syncROIFromCamera() == -1 );
902 }
903
904 SECTION( "missing crop-row details are reported as an error" )
905 {
906 cred2Ctrl_test app;
907
908 setPoweredOn( app );
909 app.m_raw_width = 256;
910 app.m_raw_height = 256;
911 queueSerialResponse( "on\n" );
912 queueSerialResponse( "192-447\n" );
913 queueSerialResponse( "bad\n" );
914
915 REQUIRE( app.syncROIFromCamera() == -1 );
916 }
917
918 SECTION( "camera-reported crop corners that cannot map to a valid ROI are rejected" )
919 {
920 cred2Ctrl_test app;
921
922 setPoweredOn( app );
923 app.m_raw_width = 256;
924 app.m_raw_height = 256;
925 queueSerialResponse( "on:447-192:128-383\n" );
926
927 REQUIRE( app.syncROIFromCamera() == -1 );
928 }
929
930 SECTION( "reconfiguration failures bubble out when the EDT runtime reload fails" )
931 {
932 cred2Ctrl_test app;
933
934 loadDefaultConfig( app, "roi_reconfig_fail" );
935 setPoweredOn( app );
936 app.m_modeName = "runtime";
937 app.m_raw_width = 320;
938 app.m_raw_height = 256;
939 g_edtStubState.readcfgReturn = -1;
940 queueSerialResponse( "off\n" );
941
942 REQUIRE( app.syncROIFromCamera() == -1 );
943 }
944
945 SECTION( "runtime config rewrite failures bubble out while syncing a resized ROI" )
946 {
947 cred2Ctrl_test app;
948
949 loadDefaultConfig( app, "roi_write_fail" );
950 setPoweredOn( app );
951 app.m_modeName = "runtime";
952 app.m_configFile = "/tmp/cred2Ctrl_missing_dir/roi_runtime.cfg";
953 app.m_raw_width = 320;
954 app.m_raw_height = 256;
955 queueSerialResponse( "off\n" );
956
957 REQUIRE( app.syncROIFromCamera() == -1 );
958 }
959}
960
961/// Verify the primary getter helpers update cached state and tolerate malformed responses.
962/**
963 * \ingroup cred2Ctrl_unit_test
964 */
965TEST_CASE( "cred2Ctrl getter helpers parse temperatures fps and limits", "[cred2Ctrl]" )
966{
967 resetStubState();
968
969 // clang-format off
970 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
974 #endif
975 // clang-format on
976
977 SECTION( "getTemps caches valid bundled temperatures and setpoint state" )
978 {
979 cred2Ctrl_test app;
980
981 setPoweredOn( app );
982 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
983 queueSerialResponse( "-15.00\n" );
984
985 REQUIRE( app.getTemps() == 0 );
986 REQUIRE( app.m_temps.motherboard == Approx( 40.50f ) );
987 REQUIRE( app.m_temps.frontend == Approx( 37.00f ) );
988 REQUIRE( app.m_temps.powerboard == Approx( 40.25f ) );
989 REQUIRE( app.m_temps.snake == Approx( -14.92f ) );
990 REQUIRE( app.m_temps.setpoint == Approx( -15.0f ) );
991 REQUIRE( app.m_temps.peltier == Approx( 2.29f ) );
992 REQUIRE( app.m_temps.heatsink == Approx( 27.50f ) );
993 REQUIRE( app.m_tempControlStatus == true );
994 REQUIRE( app.m_tempControlStatusStr == "ON TARGET" );
995 REQUIRE( app.m_tempControlOnTarget == true );
996 }
997
998 SECTION( "getTemps keeps the previous cache on malformed bundled temperatures" )
999 {
1000 cred2Ctrl_test app;
1001
1002 setPoweredOn( app );
1003 app.m_temps.motherboard = 1;
1004 app.m_temps.frontend = 2;
1005 app.m_temps.powerboard = 3;
1006 app.m_temps.snake = 4;
1007 app.m_temps.setpoint = 5;
1008 app.m_temps.peltier = 6;
1009 app.m_temps.heatsink = 7;
1010 app.m_ccdTemp = 4;
1011 app.m_ccdTempSetpt = 5;
1012
1013 queueSerialResponse( "40.50:37.00\n" );
1014
1015 REQUIRE( app.getTemps() == 0 );
1016 REQUIRE( app.m_temps.motherboard == Approx( 1.0f ) );
1017 REQUIRE( app.m_ccdTemp == Approx( 4.0f ) );
1018 REQUIRE( app.m_ccdTempSetpt == Approx( 5.0f ) );
1019 }
1020
1021 SECTION( "getTemps keeps the previous cache when the snake-setpoint query transport fails" )
1022 {
1023 cred2Ctrl_test app;
1024
1025 setPoweredOn( app );
1026 app.m_ccdTempSetpt = -5.0f;
1027 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
1028 queueSerialResponse( "", -1 );
1029
1030 REQUIRE( app.getTemps() == 0 );
1031 REQUIRE( app.m_ccdTempSetpt == Approx( -5.0f ) );
1032 }
1033
1034 SECTION( "getTemps keeps the previous cache when the snake-setpoint response is malformed" )
1035 {
1036 cred2Ctrl_test app;
1037
1038 setPoweredOn( app );
1039 app.m_ccdTempSetpt = -6.0f;
1040 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
1041 queueSerialResponse( "bad-setpoint\n" );
1042
1043 REQUIRE( app.getTemps() == 0 );
1044 REQUIRE( app.m_ccdTempSetpt == Approx( -6.0f ) );
1045 }
1046
1047 SECTION( "getTemps reports off-target cooling when the detector is still far from the setpoint" )
1048 {
1049 cred2Ctrl_test app;
1050
1051 setPoweredOn( app );
1052 queueSerialResponse( "40.50:37.00:40.25:-10.00:2.29:27.50\n" );
1053 queueSerialResponse( "-15.00\n" );
1054
1055 REQUIRE( app.getTemps() == 0 );
1056 REQUIRE( app.m_tempControlStatus == true );
1057 REQUIRE( app.m_tempControlStatusStr == "OFF TARGET" );
1058 REQUIRE( app.m_tempControlOnTarget == false );
1059 }
1060
1061 SECTION( "getTemps reports TEMP OFF once the warm setpoint is reached" )
1062 {
1063 cred2Ctrl_test app;
1064
1065 setPoweredOn( app );
1066 queueSerialResponse( "40.50:37.00:40.25:20.00:2.29:27.50\n" );
1067 queueSerialResponse( "20.00\n" );
1068
1069 REQUIRE( app.getTemps() == 0 );
1070 REQUIRE( app.m_tempControlStatus == false );
1071 REQUIRE( app.m_tempControlStatusStr == "TEMP OFF" );
1072 }
1073
1074 SECTION( "getTemps reports WARMING while returning to the warm setpoint" )
1075 {
1076 cred2Ctrl_test app;
1077
1078 setPoweredOn( app );
1079 queueSerialResponse( "40.50:37.00:40.25:10.00:2.29:27.50\n" );
1080 queueSerialResponse( "20.00\n" );
1081
1082 REQUIRE( app.getTemps() == 0 );
1083 REQUIRE( app.m_tempControlStatus == false );
1084 REQUIRE( app.m_tempControlStatusStr == "WARMING" );
1085 }
1086
1087 SECTION( "getFPS updates the cached frame rate on valid responses" )
1088 {
1089 cred2Ctrl_test app;
1090
1091 setPoweredOn( app );
1092 queueSerialResponse( "150.5\n" );
1093
1094 REQUIRE( app.getFPS() == 0 );
1095 REQUIRE( app.m_fps == Approx( 150.5f ) );
1096 }
1097
1098 SECTION( "getFPS rejects malformed responses" )
1099 {
1100 cred2Ctrl_test app;
1101
1102 setPoweredOn( app );
1103 queueSerialResponse( "Result: OK\n" );
1104
1105 REQUIRE( app.getFPS() == -1 );
1106 }
1107
1108 SECTION( "updateFPSLimits updates bounds and clamps the requested fps" )
1109 {
1110 cred2Ctrl_test app;
1111
1112 setPoweredOn( app );
1113 app.m_fpsSet = 1000.0f;
1114 queueSerialResponse( "10.0\n" );
1115 queueSerialResponse( "500.0\n" );
1116
1117 REQUIRE( app.updateFPSLimits() == 0 );
1118 REQUIRE( app.m_minFPS == Approx( 10.0f ) );
1119 REQUIRE( app.m_maxFPS == Approx( 500.0f ) );
1120 REQUIRE( app.m_fpsSet == Approx( 500.0f ) );
1121 }
1122
1123 SECTION( "updateFPSLimits reports malformed minimum-fps responses" )
1124 {
1125 cred2Ctrl_test app;
1126
1127 setPoweredOn( app );
1128 queueSerialResponse( "not-a-number\n" );
1129
1130 REQUIRE( app.updateFPSLimits() == -1 );
1131 }
1132
1133 SECTION( "updateFPSLimits reports malformed maximum-fps responses" )
1134 {
1135 cred2Ctrl_test app;
1136
1137 setPoweredOn( app );
1138 queueSerialResponse( "10.0\n" );
1139 queueSerialResponse( "not-a-number\n" );
1140
1141 REQUIRE( app.updateFPSLimits() == -1 );
1142 }
1143}
1144
1145/// Verify the remaining getter helpers parse discrete fan gain and LED state.
1146/**
1147 * \ingroup cred2Ctrl_unit_test
1148 */
1149TEST_CASE( "cred2Ctrl discrete getter helpers cover fallback parsing paths", "[cred2Ctrl]" )
1150{
1151 resetStubState();
1152
1153 // clang-format off
1154 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
1158 #endif
1159 // clang-format on
1160
1161 SECTION( "getFanSpeed accepts automatic mode without querying the manual percentage" )
1162 {
1163 cred2Ctrl_test app;
1164
1165 setPoweredOn( app );
1166 queueSerialResponse( "automatic\n" );
1167
1168 REQUIRE( app.getFanSpeed() == 0 );
1169 REQUIRE( app.m_fanSpeedName == "auto" );
1170 REQUIRE( app.m_fanSpeedNameSet == "auto" );
1171 REQUIRE( app.m_fanSpeedValid == true );
1172 }
1173
1174 SECTION( "getFanSpeed falls back to the non-raw fan-speed response when needed" )
1175 {
1176 cred2Ctrl_test app;
1177
1178 setPoweredOn( app );
1179 queueSerialResponse( "manual\n" );
1180 queueSerialResponse( "speed\n" );
1181 queueSerialResponse( "75\n" );
1182
1183 REQUIRE( app.getFanSpeed() == 0 );
1184 REQUIRE( app.m_fanSpeedName == "p75" );
1185 REQUIRE( g_edtStubState.serialCommands ==
1186 std::vector<std::string>{ "fan mode raw", "fan speed raw", "fan speed" } );
1187 }
1188
1189 SECTION( "getFanSpeed falls back to the non-raw mode command when the raw response is not recognizable" )
1190 {
1191 cred2Ctrl_test app;
1192
1193 setPoweredOn( app );
1194 queueSerialResponse( "mystery\n" );
1195 queueSerialResponse( "manual\n" );
1196 queueSerialResponse( "50\n" );
1197
1198 REQUIRE( app.getFanSpeed() == 0 );
1199 REQUIRE( app.m_fanSpeedName == "p50" );
1200 REQUIRE( g_edtStubState.serialCommands ==
1201 std::vector<std::string>{ "fan mode raw", "fan mode", "fan speed raw" } );
1202 }
1203
1204 SECTION( "getFanSpeed rejects mode responses that never indicate auto or manual" )
1205 {
1206 cred2Ctrl_test app;
1207
1208 setPoweredOn( app );
1209 queueSerialResponse( "mystery\n" );
1210 queueSerialResponse( "still mystery\n" );
1211
1212 REQUIRE( app.getFanSpeed() == -1 );
1213 }
1214
1215 SECTION( "getFanSpeed returns -1 when the raw mode query transport fails" )
1216 {
1217 cred2Ctrl_test app;
1218
1219 setPoweredOn( app );
1220 queueSerialResponse( "", -1 );
1221
1222 REQUIRE( app.getFanSpeed() == -1 );
1223 }
1224
1225 SECTION( "getFanSpeed returns -1 when the fallback mode query transport fails" )
1226 {
1227 cred2Ctrl_test app;
1228
1229 setPoweredOn( app );
1230 queueSerialResponse( "mystery\n" );
1231 queueSerialResponse( "", -1 );
1232
1233 REQUIRE( app.getFanSpeed() == -1 );
1234 }
1235
1236 SECTION( "getFanSpeed rejects manual fan-speed responses that remain unparseable" )
1237 {
1238 cred2Ctrl_test app;
1239
1240 setPoweredOn( app );
1241 queueSerialResponse( "manual\n" );
1242 queueSerialResponse( "speed\n" );
1243 queueSerialResponse( "still bad\n" );
1244
1245 REQUIRE( app.getFanSpeed() == -1 );
1246 }
1247
1248 SECTION( "getFanSpeed returns -1 when the manual fan-speed transport fails" )
1249 {
1250 cred2Ctrl_test app;
1251
1252 setPoweredOn( app );
1253 queueSerialResponse( "manual\n" );
1254 queueSerialResponse( "", -1 );
1255
1256 REQUIRE( app.getFanSpeed() == -1 );
1257 }
1258
1259 SECTION( "getAnalogGain maps the reported sensibility name" )
1260 {
1261 cred2Ctrl_test app;
1262
1263 setPoweredOn( app );
1264 queueSerialResponse( "medium\n" );
1265
1266 REQUIRE( app.getAnalogGain() == 0 );
1267 REQUIRE( app.m_analogGainName == "med" );
1268 REQUIRE( app.m_analogGainNameSet == "med" );
1269 REQUIRE( app.m_analogGainValid == true );
1270 }
1271
1272 SECTION( "getAnalogGain rejects unsupported sensibility responses" )
1273 {
1274 cred2Ctrl_test app;
1275
1276 setPoweredOn( app );
1277 queueSerialResponse( "mystery\n" );
1278
1279 REQUIRE( app.getAnalogGain() == -1 );
1280 }
1281
1282 SECTION( "getAnalogGain returns -1 on transport failure" )
1283 {
1284 cred2Ctrl_test app;
1285
1286 setPoweredOn( app );
1287 queueSerialResponse( "", -1 );
1288
1289 REQUIRE( app.getAnalogGain() == -1 );
1290 }
1291
1292 SECTION( "getLEDState falls back from the raw command to the human-readable response" )
1293 {
1294 cred2Ctrl_test app;
1295
1296 setPoweredOn( app );
1297 queueSerialResponse( "", -1 );
1298 queueSerialResponse( "on\n" );
1299
1300 REQUIRE( app.getLEDState() == 0 );
1301 REQUIRE( app.m_ledState == true );
1302 REQUIRE( app.m_ledStateSet == true );
1303 REQUIRE( app.m_ledStateValid == true );
1304 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "led raw", "led" } );
1305 }
1306
1307 SECTION( "getLEDState rejects responses that do not indicate on or off" )
1308 {
1309 cred2Ctrl_test app;
1310
1311 setPoweredOn( app );
1312 queueSerialResponse( "unknown\n" );
1313 queueSerialResponse( "still unknown\n" );
1314
1315 REQUIRE( app.getLEDState() == -1 );
1316 }
1317
1318 SECTION( "getLEDState returns -1 when both raw and fallback LED queries fail" )
1319 {
1320 cred2Ctrl_test app;
1321
1322 setPoweredOn( app );
1323 queueSerialResponse( "", -1 );
1324 queueSerialResponse( "", -1 );
1325
1326 REQUIRE( app.getLEDState() == -1 );
1327 }
1328
1329 SECTION( "getLEDState recognizes descriptive raw responses that still contain off" )
1330 {
1331 cred2Ctrl_test app;
1332
1333 setPoweredOn( app );
1334 queueSerialResponse( "led is off\n" );
1335
1336 REQUIRE( app.getLEDState() == 0 );
1337 REQUIRE( app.m_ledState == false );
1338 }
1339
1340 SECTION( "getLEDState recognizes descriptive raw responses that still contain on" )
1341 {
1342 cred2Ctrl_test app;
1343
1344 setPoweredOn( app );
1345 queueSerialResponse( "led is on\n" );
1346
1347 REQUIRE( app.getLEDState() == 0 );
1348 REQUIRE( app.m_ledState == true );
1349 }
1350
1351 SECTION( "getLEDState falls back to an off state on the human-readable retry" )
1352 {
1353 cred2Ctrl_test app;
1354
1355 setPoweredOn( app );
1356 queueSerialResponse( "unknown\n" );
1357 queueSerialResponse( "off\n" );
1358
1359 REQUIRE( app.getLEDState() == 0 );
1360 REQUIRE( app.m_ledState == false );
1361 }
1362
1363 SECTION( "getLEDState returns -1 when the retry transport fails after an ambiguous raw response" )
1364 {
1365 cred2Ctrl_test app;
1366
1367 setPoweredOn( app );
1368 queueSerialResponse( "unknown\n" );
1369 queueSerialResponse( "", -1 );
1370
1371 REQUIRE( app.getLEDState() == -1 );
1372 }
1373
1374 SECTION( "getLEDState falls back to an on state on the human-readable retry" )
1375 {
1376 cred2Ctrl_test app;
1377
1378 setPoweredOn( app );
1379 queueSerialResponse( "unknown\n" );
1380 queueSerialResponse( "on\n" );
1381
1382 REQUIRE( app.getLEDState() == 0 );
1383 REQUIRE( app.m_ledState == true );
1384 }
1385}
1386
1387/// Verify the setter helpers validate bounds and send the expected serial commands.
1388/**
1389 * \ingroup cred2Ctrl_unit_test
1390 */
1391TEST_CASE( "cred2Ctrl setter helpers validate bounds and update state", "[cred2Ctrl]" )
1392{
1393 resetStubState();
1394
1395 // clang-format off
1396 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
1407 #endif
1408 // clang-format on
1409
1410 SECTION( "powerOnDefaults restores controller-side defaults" )
1411 {
1412 cred2Ctrl_test app;
1413
1414 app.m_tempControlStatusSet = true;
1415 app.m_tempControlStatus = true;
1416 app.m_tempControlStatusStr = "ON TARGET";
1417 app.m_cameraCropEnabled = true;
1418 app.m_fanSpeedValid = true;
1419 app.m_analogGainValid = true;
1420 app.m_ledStateValid = true;
1421
1422 REQUIRE( app.powerOnDefaults() == 0 );
1423 REQUIRE( app.m_tempControlStatusSet == false );
1424 REQUIRE( app.m_tempControlStatus == false );
1425 REQUIRE( app.m_tempControlStatusStr == "TEMP OFF" );
1426 REQUIRE( app.m_cameraCropEnabled == false );
1427 REQUIRE( app.m_fanSpeedNameSet == "auto" );
1428 REQUIRE( app.m_analogGainNameSet == "med" );
1429 REQUIRE( app.m_ledStateSet == true );
1430 }
1431
1432 SECTION( "setTempControl uses the configured setpoint when cooling is enabled" )
1433 {
1434 cred2Ctrl_test app;
1435
1436 setPoweredOn( app );
1437 app.m_ccdTempSetpt = -15.5f;
1438 app.m_tempControlStatusSet = true;
1439 queueSerialResponse( "OK\n" );
1440
1441 REQUIRE( app.setTempControl() == 0 );
1442 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set temperatures snake -15.5" } );
1443 REQUIRE( app.m_tempControlStatus == true );
1444 REQUIRE( app.m_tempControlStatusStr == "OFF TARGET" );
1445 }
1446
1447 SECTION( "setTempControl switches to the warm-up setpoint when cooling is disabled" )
1448 {
1449 cred2Ctrl_test app;
1450
1451 setPoweredOn( app );
1452 app.m_tempControlStatusSet = false;
1453 queueSerialResponse( "OK\n" );
1454
1455 REQUIRE( app.setTempControl() == 0 );
1456 REQUIRE( app.m_ccdTempSetpt == Approx( 20.0f ) );
1457 REQUIRE( app.m_tempControlStatus == false );
1458 REQUIRE( app.m_tempControlStatusStr == "WARMING" );
1459 }
1460
1461 SECTION( "setTempControl logs a notice when cooling is requested with a warm setpoint" )
1462 {
1463 cred2Ctrl_test app;
1464
1465 app.m_ccdTempSetpt = 20.0f;
1466 app.m_tempControlStatusSet = true;
1467
1468 REQUIRE( app.setTempControl() == 0 );
1469 REQUIRE( g_edtStubState.serialCommands.empty() );
1470 }
1471
1472 SECTION( "setTempSetPt rejects out-of-range values" )
1473 {
1474 cred2Ctrl_test app;
1475
1476 app.m_ccdTempSetpt = 50.0f;
1477 REQUIRE( app.setTempSetPt() == -1 );
1478 REQUIRE( g_edtStubState.serialCommands.empty() );
1479 }
1480
1481 SECTION( "setFPS validates bounds before sending the command and refreshes the cached fps" )
1482 {
1483 cred2Ctrl_test app;
1484
1485 setPoweredOn( app );
1486 app.m_minFPS = 10;
1487 app.m_maxFPS = 500;
1488 app.m_fpsSet = 125.5f;
1489 queueSerialResponse( "OK\n" );
1490 queueSerialResponse( "125.5\n" );
1491
1492 REQUIRE( app.setFPS() == 0 );
1493 REQUIRE( app.m_fps == Approx( 125.5f ) );
1494 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set fps 125.5", "fps raw" } );
1495 }
1496
1497 SECTION( "setFPS rejects requests outside the current min-max bounds" )
1498 {
1499 cred2Ctrl_test app;
1500
1501 app.m_minFPS = 10;
1502 app.m_maxFPS = 500;
1503 app.m_fpsSet = 600.0f;
1504
1505 REQUIRE( app.setFPS() == -1 );
1506 REQUIRE( g_edtStubState.serialCommands.empty() );
1507 }
1508
1509 SECTION( "setFPS returns an error when the camera rejects the command" )
1510 {
1511 cred2Ctrl_test app;
1512
1513 setPoweredOn( app );
1514 app.m_minFPS = 10;
1515 app.m_maxFPS = 500;
1516 app.m_fpsSet = 125.5f;
1517 queueSerialResponse( "Error\n" );
1518
1519 REQUIRE( app.setFPS() == -1 );
1520 }
1521
1522 SECTION( "setFanSpeed handles automatic and manual presets" )
1523 {
1524 cred2Ctrl_test app;
1525
1526 setPoweredOn( app );
1527 app.m_fanSpeedNameSet = "p75";
1528 queueSerialResponse( "OK\n" );
1529 queueSerialResponse( "OK\n" );
1530 queueSerialResponse( "manual\n" );
1531 queueSerialResponse( "75\n" );
1532
1533 REQUIRE( app.setFanSpeed() == 0 );
1534 REQUIRE( app.m_fanSpeedName == "p75" );
1535 REQUIRE(
1536 g_edtStubState.serialCommands ==
1537 std::vector<std::string>{ "set fan mode manual", "set fan speed 75", "fan mode raw", "fan speed raw" } );
1538 }
1539
1540 SECTION( "setFanSpeed handles the automatic preset" )
1541 {
1542 cred2Ctrl_test app;
1543
1544 setPoweredOn( app );
1545 app.m_fanSpeedNameSet = "auto";
1546 queueSerialResponse( "OK\n" );
1547 queueSerialResponse( "automatic\n" );
1548
1549 REQUIRE( app.setFanSpeed() == 0 );
1550 REQUIRE( app.m_fanSpeedName == "auto" );
1551 }
1552
1553 SECTION( "setFanSpeed rejects unknown presets before sending commands" )
1554 {
1555 cred2Ctrl_test app;
1556
1557 app.m_fanSpeedNameSet = "mystery";
1558
1559 REQUIRE( app.setFanSpeed() == -1 );
1560 REQUIRE( g_edtStubState.serialCommands.empty() );
1561 }
1562
1563 SECTION( "setFanSpeed returns an error when automatic mode cannot be enabled" )
1564 {
1565 cred2Ctrl_test app;
1566
1567 setPoweredOn( app );
1568 app.m_fanSpeedNameSet = "auto";
1569 queueSerialResponse( "Error\n" );
1570
1571 REQUIRE( app.setFanSpeed() == -1 );
1572 }
1573
1574 SECTION( "setFanSpeed returns an error when the manual speed update is rejected" )
1575 {
1576 cred2Ctrl_test app;
1577
1578 setPoweredOn( app );
1579 app.m_fanSpeedNameSet = "p25";
1580 queueSerialResponse( "OK\n" );
1581 queueSerialResponse( "Error\n" );
1582
1583 REQUIRE( app.setFanSpeed() == -1 );
1584 }
1585
1586 SECTION( "setFanSpeed returns an error when manual mode cannot be enabled" )
1587 {
1588 cred2Ctrl_test app;
1589
1590 setPoweredOn( app );
1591 app.m_fanSpeedNameSet = "p25";
1592 queueSerialResponse( "Error\n" );
1593
1594 REQUIRE( app.setFanSpeed() == -1 );
1595 }
1596
1597 SECTION( "setAnalogGain rejects unknown presets and accepts valid ones" )
1598 {
1599 cred2Ctrl_test app;
1600
1601 setPoweredOn( app );
1602 app.m_analogGainNameSet = "bad";
1603 REQUIRE( app.setAnalogGain() == -1 );
1604
1605 resetStubState();
1606 setPoweredOn( app );
1607 app.m_analogGainNameSet = "high";
1608 queueSerialResponse( "OK\n" );
1609 queueSerialResponse( "high\n" );
1610
1611 REQUIRE( app.setAnalogGain() == 0 );
1612 REQUIRE( app.m_analogGainName == "high" );
1613 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set sensibility high", "sensibility" } );
1614 }
1615
1616 SECTION( "setAnalogGain returns an error when the camera rejects the update" )
1617 {
1618 cred2Ctrl_test app;
1619
1620 setPoweredOn( app );
1621 app.m_analogGainNameSet = "med";
1622 queueSerialResponse( "Error\n" );
1623
1624 REQUIRE( app.setAnalogGain() == -1 );
1625 }
1626
1627 SECTION( "setLED sends the expected on and off commands" )
1628 {
1629 cred2Ctrl_test app;
1630
1631 setPoweredOn( app );
1632 app.m_ledStateSet = false;
1633 queueSerialResponse( "OK\n" );
1634 queueSerialResponse( "off\n" );
1635
1636 REQUIRE( app.setLED() == 0 );
1637 REQUIRE( app.m_ledState == false );
1638 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set led off", "led raw" } );
1639 }
1640
1641 SECTION( "setLED sends the on command when the LED is enabled" )
1642 {
1643 cred2Ctrl_test app;
1644
1645 setPoweredOn( app );
1646 app.m_ledStateSet = true;
1647 queueSerialResponse( "OK\n" );
1648 queueSerialResponse( "on\n" );
1649
1650 REQUIRE( app.setLED() == 0 );
1651 REQUIRE( app.m_ledState == true );
1652 }
1653
1654 SECTION( "setLED returns an error when the requested LED update is rejected" )
1655 {
1656 cred2Ctrl_test app;
1657
1658 setPoweredOn( app );
1659 app.m_ledStateSet = true;
1660 queueSerialResponse( "Error\n" );
1661
1662 REQUIRE( app.setLED() == -1 );
1663 }
1664
1665 SECTION( "setLED returns an error when disabling the LED is rejected" )
1666 {
1667 cred2Ctrl_test app;
1668
1669 setPoweredOn( app );
1670 app.m_ledStateSet = false;
1671 queueSerialResponse( "Error\n" );
1672
1673 REQUIRE( app.setLED() == -1 );
1674 }
1675
1676 SECTION( "setExpTime remains a no-op" )
1677 {
1678 cred2Ctrl_test app;
1679
1680 REQUIRE( app.setExpTime() == 0 );
1681 }
1682
1683 SECTION( "checkNextROI rounds sizes and centers to the hardware step sizes" )
1684 {
1685 cred2Ctrl_test app;
1686 cred2Roi roi;
1687
1688 app.m_nextROI.x = 140.0f;
1689 app.m_nextROI.y = 65.0f;
1690 app.m_nextROI.w = 250;
1691 app.m_nextROI.h = 130;
1692
1693 REQUIRE( app.checkNextROI() == 0 );
1694 REQUIRE( app.m_nextROI.w == 256 );
1695 REQUIRE( app.m_nextROI.h == 132 );
1696 REQUIRE(
1698 roi, app.m_nextROI.x, app.m_nextROI.y, app.m_nextROI.w, app.m_nextROI.h, app.m_full_w, app.m_full_h ) ==
1699 0 );
1700 REQUIRE( roi.startColumn % 32 == 0 );
1701 REQUIRE( roi.startRow % 4 == 0 );
1702 }
1703
1704 SECTION( "setNextROI requests a reconfiguration of the current mode" )
1705 {
1706 cred2Ctrl_test app;
1707
1708 app.m_modeName = "runtime";
1709 app.m_nextROI.x = 127.5f;
1710 app.m_nextROI.y = 63.5f;
1711 app.m_nextROI.w = 256;
1712 app.m_nextROI.h = 128;
1713 app.m_nextROI.bin_x = 1;
1714 app.m_nextROI.bin_y = 1;
1715
1716 REQUIRE( app.setNextROI() == 0 );
1717 REQUIRE( app.state() == stateCodes::CONFIGURING );
1718 REQUIRE( app.m_reconfig == true );
1719 REQUIRE( app.m_nextMode == "runtime" );
1720 }
1721}
1722
1723/// Verify framegrabber-facing helpers cover acquisition, copying, and reconfiguration logic.
1724/**
1725 * \ingroup cred2Ctrl_unit_test
1726 */
1727TEST_CASE( "cred2Ctrl framegrabber helpers manage acquisition and ROI configuration", "[cred2Ctrl]" )
1728{
1729 resetStubState();
1730
1731 // clang-format off
1732 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
1739 #endif
1740 // clang-format on
1741
1742 SECTION( "configureAcquisition handles full-frame and cropped ROI requests" )
1743 {
1744 cred2Ctrl_test app;
1745
1746 setPoweredOn( app );
1747 app.m_cameraCropEnabled = true;
1748 app.m_fpsSet = 125.0f;
1749 app.m_nextROI.x = app.m_full_x;
1750 app.m_nextROI.y = app.m_full_y;
1751 app.m_nextROI.w = app.m_full_w;
1752 app.m_nextROI.h = app.m_full_h;
1753 app.m_nextROI.bin_x = 1;
1754 app.m_nextROI.bin_y = 1;
1755
1756 REQUIRE( app.configureAcquisition() == 0 );
1757 REQUIRE( app.m_cameraCropEnabled == false );
1758 REQUIRE( app.m_width == static_cast<uint32_t>( app.m_full_w ) );
1759 REQUIRE( app.m_height == static_cast<uint32_t>( app.m_full_h ) );
1760 REQUIRE( app.m_fps == Approx( 125.0f ) );
1761 REQUIRE( app.m_roiSettleCounter == 5 );
1762 REQUIRE( app.state() == stateCodes::READY );
1763 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set cropping off" } );
1764
1765 resetStubState();
1766 setPoweredOn( app );
1767 app.m_nextROI.x = 319.5f;
1768 app.m_nextROI.y = 255.5f;
1769 app.m_nextROI.w = 256;
1770 app.m_nextROI.h = 256;
1771 app.m_nextROI.bin_x = 1;
1772 app.m_nextROI.bin_y = 1;
1773
1774 REQUIRE( app.configureAcquisition() == 0 );
1775 REQUIRE( app.m_cameraCropEnabled == true );
1776 REQUIRE( app.m_width == 256 );
1777 REQUIRE( app.m_height == 256 );
1778 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{ "set cropping columns 192-447",
1779 "set cropping rows 128-383",
1780 "set cropping on" } );
1781 }
1782
1783 SECTION( "configureAcquisition rejects invalid ROIs" )
1784 {
1785 cred2Ctrl_test app;
1786
1787 setPoweredOn( app );
1788 app.m_nextROI.x = -10;
1789 app.m_nextROI.y = -10;
1790 app.m_nextROI.w = 256;
1791 app.m_nextROI.h = 256;
1792
1793 REQUIRE( app.configureAcquisition() == -1 );
1794 REQUIRE( app.state() == stateCodes::ERROR );
1795 }
1796
1797 SECTION( "fps and acquisition helpers report the current rate and timestamps" )
1798 {
1799 cred2Ctrl_test app;
1800
1801 app.m_fps = 250.25f;
1802 app.m_numBuffs = 6;
1803
1804 REQUIRE( app.fps() == Approx( 250.25f ) );
1805 REQUIRE( app.startAcquisition() == 0 );
1806 REQUIRE( app.state() == stateCodes::OPERATING );
1807 REQUIRE( g_edtStubState.startImagesCalls == 1 );
1808 REQUIRE( g_edtStubState.lastStartNumBuffs == 6 );
1809
1810 g_edtStubState.waitTimeSec = 12;
1811 g_edtStubState.waitTimeNsec = 345;
1812
1813 REQUIRE( app.acquireAndCheckValid() == 0 );
1814 REQUIRE( app.m_currImageTimestamp.tv_sec == 12 );
1815 REQUIRE( app.m_currImageTimestamp.tv_nsec == 345 );
1816 REQUIRE( g_edtStubState.startImageCalls == 1 );
1817 }
1818
1819 SECTION( "loadImageIntoStream copies the raw EDT frame into the destination buffer" )
1820 {
1821 cred2Ctrl_test app;
1822 std::array<uint16_t, 4> source{ 1, 2, 3, 4 };
1823 std::array<uint16_t, 4> dest{ 0, 0, 0, 0 };
1824
1825 app.m_image_p = reinterpret_cast<u_char *>( source.data() );
1826 app.m_width = 2;
1827 app.m_height = 2;
1828 app.m_typeSize = sizeof( uint16_t );
1829
1830 REQUIRE( app.loadImageIntoStream( dest.data() ) == 0 );
1831 REQUIRE( dest == source );
1832 }
1833
1834 SECTION( "reconfig reloads the runtime EDT configuration and restores READY state" )
1835 {
1836 cred2Ctrl_test app;
1837
1838 loadDefaultConfig( app, "reconfig" );
1839 app.m_modeName = "runtime";
1840 app.m_nextMode = "runtime";
1841
1842 REQUIRE( app.reconfig() == 0 );
1843 REQUIRE( g_edtStubState.readcfgCalls > 0 );
1844 REQUIRE( app.state() == stateCodes::READY );
1845 REQUIRE( app.m_nextMode == "runtime" );
1846 }
1847
1848 SECTION( "writeConfig reports file-open failures" )
1849 {
1850 cred2Ctrl_test app;
1851
1852 app.m_configFile = "/tmp/cred2Ctrl_missing_dir/runtime.cfg";
1853
1854 REQUIRE( app.writeConfig() == -1 );
1855 }
1856
1857 SECTION( "configureAcquisition reports failures while disabling full-frame cropping" )
1858 {
1859 cred2Ctrl_test app;
1860
1861 setPoweredOn( app );
1862 app.m_cameraCropEnabled = true;
1863 app.m_nextROI.x = app.m_full_x;
1864 app.m_nextROI.y = app.m_full_y;
1865 app.m_nextROI.w = app.m_full_w;
1866 app.m_nextROI.h = app.m_full_h;
1867 app.m_nextROI.bin_x = 1;
1868 app.m_nextROI.bin_y = 1;
1869 queueSerialResponse( "Error\n" );
1870
1871 REQUIRE( app.configureAcquisition() == -1 );
1872 REQUIRE( app.state() == stateCodes::ERROR );
1873 }
1874
1875 SECTION( "configureAcquisition reports failures while programming cropped ROI commands" )
1876 {
1877 cred2Ctrl_test app;
1878
1879 setPoweredOn( app );
1880 app.m_nextROI.x = 319.5f;
1881 app.m_nextROI.y = 255.5f;
1882 app.m_nextROI.w = 256;
1883 app.m_nextROI.h = 256;
1884 app.m_nextROI.bin_x = 1;
1885 app.m_nextROI.bin_y = 1;
1886 queueSerialResponse( "OK\n" );
1887 queueSerialResponse( "Error\n" );
1888
1889 REQUIRE( app.configureAcquisition() == -1 );
1890 REQUIRE( app.state() == stateCodes::ERROR );
1891 }
1892
1893 SECTION( "loadImageIntoStream reports a failure when the configured flip mode is invalid" )
1894 {
1895 cred2Ctrl_test app;
1896 std::array<uint16_t, 4> dest{ 0, 0, 0, 0 };
1897
1898 app.m_image_p = reinterpret_cast<u_char *>( g_edtStubState.waitImage.data() );
1899 app.m_width = 2;
1900 app.m_height = 2;
1901 app.m_typeSize = sizeof( uint16_t );
1902 app.m_defaultFlip = 999;
1903
1904 REQUIRE( app.loadImageIntoStream( dest.data() ) == -1 );
1905 }
1906
1907 SECTION( "reconfig reports file-write failures before touching the EDT driver" )
1908 {
1909 cred2Ctrl_test app;
1910
1911 app.m_modeName = "runtime";
1912 app.m_nextMode = "runtime";
1913 app.m_configFile = "/tmp/cred2Ctrl_missing_dir/reconfig.cfg";
1914
1915 REQUIRE( app.reconfig() == -1 );
1916 }
1917
1918 SECTION( "reconfig returns the EDT reload error when pdvReconfig fails" )
1919 {
1920 cred2Ctrl_test app;
1921
1922 loadDefaultConfig( app, "reconfig_driver_fail" );
1923 app.m_modeName = "runtime";
1924 app.m_nextMode = "runtime";
1925 g_edtStubState.readcfgReturn = -1;
1926
1927 REQUIRE( app.reconfig() == -1 );
1928 }
1929}
1930
1931/// Verify telemetry wrappers emit their records and helper lifecycle methods reset cached state.
1932/**
1933 * \ingroup cred2Ctrl_unit_test
1934 */
1935TEST_CASE( "cred2Ctrl telemetry wrappers and power-off helpers update cached state", "[cred2Ctrl]" )
1936{
1937 cred2Ctrl_test app;
1938
1939 resetStubState();
1940
1941 // clang-format off
1942 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
1944 XWCTEST_DOXYGEN_REF( cred2Ctrl::recordTelem( static_cast<const MagAOX::logger::cred2_temps *>( nullptr ) ) );
1945 XWCTEST_DOXYGEN_REF( cred2Ctrl::recordTelem( static_cast<const MagAOX::logger::telem_stdcam *>( nullptr ) ) );
1951 #endif
1952 // clang-format on
1953
1954 app.m_temps.motherboard = 40.5f;
1955 app.m_temps.frontend = 37.0f;
1956 app.m_temps.powerboard = 40.25f;
1957 app.m_temps.snake = -14.92f;
1958 app.m_temps.setpoint = -15.0f;
1959 app.m_temps.peltier = 2.29f;
1960 app.m_temps.heatsink = 27.5f;
1961 app.m_modeName = "runtime";
1962 app.m_currentROI.x = 319.5f;
1963 app.m_currentROI.y = 255.5f;
1964 app.m_currentROI.w = 640;
1965 app.m_currentROI.h = 512;
1966 app.m_currentROI.bin_x = 1;
1967 app.m_currentROI.bin_y = 1;
1968 app.m_fps = 150.0f;
1969 app.m_ccdTemp = -14.92f;
1970 app.m_ccdTempSetpt = -15.0f;
1971 app.m_tempControlStatus = true;
1972 app.m_tempControlOnTarget = true;
1973 app.m_tempControlStatusStr = "ON TARGET";
1974 app.m_mna = 1.0;
1975 app.m_vara = 0.01;
1976 app.m_mnw = 2.0;
1977 app.m_varw = 0.04;
1978 app.m_mnwa = 3.0;
1979 app.m_varwa = 0.09;
1980
1981 SECTION( "recordTelem overloads forward to the typed telemetry recorders" )
1982 {
1986
1987 REQUIRE( app.recordTelem( static_cast<const MagAOX::logger::cred2_temps *>( nullptr ) ) == 0 );
1988 REQUIRE( hasRecordedTime( MagAOX::logger::cred2_temps::lastRecord ) );
1989
1990 REQUIRE( app.recordTelem( static_cast<const MagAOX::logger::telem_stdcam *>( nullptr ) ) == 0 );
1991 REQUIRE( hasRecordedTime( MagAOX::logger::telem_stdcam::lastRecord ) );
1992
1993 REQUIRE( app.recordTelem( static_cast<const MagAOX::logger::telem_fgtimings *>( nullptr ) ) == 0 );
1994 REQUIRE( hasRecordedTime( MagAOX::logger::telem_fgtimings::lastRecord ) );
1995 }
1996
1997 SECTION( "checkRecordTimes emits stale telemetry once intervals have elapsed" )
1998 {
2002
2003 app.m_maxInterval = 10.0;
2004 static_cast<cred2MagAOXAppT &>( app ).m_loopPause = 0;
2005
2006 REQUIRE( app.checkRecordTimes() == 0 );
2007 REQUIRE( hasRecordedTime( MagAOX::logger::cred2_temps::lastRecord ) );
2008 REQUIRE( hasRecordedTime( MagAOX::logger::telem_stdcam::lastRecord ) );
2009 REQUIRE( hasRecordedTime( MagAOX::logger::telem_fgtimings::lastRecord ) );
2010 }
2011
2012 SECTION( "recordTemps honors cached values and the force flag" )
2013 {
2015 app.m_temps.motherboard = 41.5f;
2016 app.m_temps.frontend = 38.0f;
2017 app.m_temps.powerboard = 41.25f;
2018 app.m_temps.snake = -13.92f;
2019 app.m_temps.setpoint = -14.0f;
2020 app.m_temps.peltier = 3.29f;
2021 app.m_temps.heatsink = 28.5f;
2022
2023 REQUIRE( app.recordTemps( false ) == 0 );
2024 REQUIRE( hasRecordedTime( MagAOX::logger::cred2_temps::lastRecord ) );
2025
2026 const timespec firstRecord = MagAOX::logger::cred2_temps::lastRecord;
2027
2028 REQUIRE( app.recordTemps( false ) == 0 );
2029 REQUIRE( MagAOX::logger::cred2_temps::lastRecord.tv_sec == firstRecord.tv_sec );
2030 REQUIRE( MagAOX::logger::cred2_temps::lastRecord.tv_nsec == firstRecord.tv_nsec );
2031
2032 REQUIRE( app.recordTemps( true ) == 0 );
2033 REQUIRE( hasRecordedTime( MagAOX::logger::cred2_temps::lastRecord ) );
2034 }
2035
2036 SECTION( "power-off helpers invalidate cached temperatures and leave shutdown clean" )
2037 {
2038 app.m_powerOnCounter = -1;
2039 REQUIRE( app.onPowerOff() == 0 );
2040 REQUIRE( app.m_powerOnCounter == 0 );
2041 REQUIRE( app.m_temps.snake == Approx( -999.0f ) );
2042 REQUIRE( app.m_ccdTemp == Approx( -999.0f ) );
2043 REQUIRE( app.m_tempControlStatus == false );
2044 REQUIRE( app.m_tempControlStatusStr == "UNKNOWN" );
2045
2046 REQUIRE( app.whilePowerOff() == 0 );
2047 REQUIRE( app.appShutdown() == 0 );
2048 }
2049}
2050
2051/// Verify appLogic covers connection transitions, steady-state refresh, and hardware-loss handling.
2052/**
2053 * \ingroup cred2Ctrl_unit_test
2054 */
2055TEST_CASE( "cred2Ctrl appLogic handles connection and housekeeping flow", "[cred2Ctrl]" )
2056{
2057 resetStubState();
2058
2059 // clang-format off
2060 #ifdef CRED2CTRL_TEST_DOXYGEN_REF
2062 #endif
2063 // clang-format on
2064
2065 SECTION( "POWERON returns immediately while the controller waits for the hardware" )
2066 {
2067 cred2Ctrl_test app;
2068
2069 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 1;
2070 app.m_powerTargetState = 1;
2071 app.state( stateCodes::POWERON );
2072 app.m_powerOnCounter = 0;
2073 static_cast<cred2MagAOXAppT &>( app ).m_loopPause = 0;
2074 fgThreadScope fgThread( app );
2075
2076 REQUIRE( app.appLogic() == 0 );
2077 REQUIRE( app.state() == stateCodes::POWERON );
2078 }
2079
2080 SECTION( "NOTCONNECTED returns immediately once the hardware power is already off" )
2081 {
2082 cred2Ctrl_test app;
2083
2084 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2085 app.m_powerTargetState = 0;
2086 app.state( stateCodes::NOTCONNECTED );
2087 fgThreadScope fgThread( app );
2088
2089 REQUIRE( app.appLogic() == 0 );
2090 REQUIRE( app.state() == stateCodes::NOTCONNECTED );
2091 }
2092
2093 SECTION( "a valid connection refresh transitions from NOTCONNECTED to READY" )
2094 {
2095 cred2Ctrl_test app;
2096
2097 setPoweredOn( app );
2098 app.state( stateCodes::NOTCONNECTED );
2099 app.m_modeName = "runtime";
2100 app.m_startupMode = "runtime";
2101 app.m_raw_width = 640;
2102 app.m_raw_height = 512;
2103 app.m_ccdTempSetpt = -999;
2104 fgThreadScope fgThread( app );
2105
2106 queueSerialResponse( "150.5\n" );
2107 queueSerialResponse( "off\n" );
2108 queueSerialResponse( "10.0\n" );
2109 queueSerialResponse( "500.0\n" );
2110 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2111 queueSerialResponse( "-15.0\n" );
2112 queueSerialResponse( "150.5\n" );
2113 queueSerialResponse( "automatic\n" );
2114 queueSerialResponse( "medium\n" );
2115 queueSerialResponse( "on\n" );
2116
2117 REQUIRE( app.appLogic() == 0 );
2118 REQUIRE( app.state() == stateCodes::READY );
2119 REQUIRE( app.m_fps == Approx( 150.5f ) );
2120 REQUIRE( app.m_fanSpeedName == "auto" );
2121 REQUIRE( app.m_analogGainName == "med" );
2122 REQUIRE( app.m_ledState == true );
2123 }
2124
2125 SECTION( "a malformed initial fps response leaves the controller in NODEVICE" )
2126 {
2127 cred2Ctrl_test app;
2128
2129 setPoweredOn( app );
2130 app.state( stateCodes::NOTCONNECTED );
2131 fgThreadScope fgThread( app );
2132 queueSerialResponse( "bad fps\n" );
2133
2134 REQUIRE( app.appLogic() == 0 );
2135 REQUIRE( app.state() == stateCodes::NODEVICE );
2136 }
2137
2138 SECTION( "a transport failure during the initial fps probe also leaves the controller in NODEVICE" )
2139 {
2140 cred2Ctrl_test app;
2141
2142 setPoweredOn( app );
2143 app.state( stateCodes::NOTCONNECTED );
2144 fgThreadScope fgThread( app );
2145 queueSerialResponse( "", -1 );
2146
2147 REQUIRE( app.appLogic() == 0 );
2148 REQUIRE( app.state() == stateCodes::NODEVICE );
2149 }
2150
2151 SECTION( "connected-state sync failures are ignored after power has already been lost" )
2152 {
2153 cred2Ctrl_test app;
2154
2155 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2156 app.m_powerTargetState = 0;
2157 app.state( stateCodes::CONNECTED );
2158 fgThreadScope fgThread( app );
2159 queueSerialResponse( "garbage\n" );
2160
2161 REQUIRE( app.appLogic() == 0 );
2162 REQUIRE( app.state() == stateCodes::CONNECTED );
2163 }
2164
2165 SECTION( "connected-state sync failures promote the controller to ERROR while power is still on" )
2166 {
2167 cred2Ctrl_test app;
2168
2169 setPoweredOn( app );
2170 app.state( stateCodes::CONNECTED );
2171 fgThreadScope fgThread( app );
2172 queueSerialResponse( "garbage\n" );
2173
2174 REQUIRE( app.appLogic() == 0 );
2175 REQUIRE( app.state() == stateCodes::ERROR );
2176 }
2177
2178 SECTION( "connected-state refresh failures promote the controller to ERROR while still powered" )
2179 {
2180 cred2Ctrl_test app;
2181
2182 setPoweredOn( app );
2183 app.state( stateCodes::CONNECTED );
2184 app.m_raw_width = app.m_full_w;
2185 app.m_raw_height = app.m_full_h;
2186 fgThreadScope fgThread( app );
2187 queueSerialResponse( "off\n" );
2188 queueSerialResponse( "bad-fps-limit\n" );
2189
2190 REQUIRE( app.appLogic() == 0 );
2191 REQUIRE( app.state() == stateCodes::ERROR );
2192 }
2193
2194 SECTION( "connected-state refresh failures are ignored after power has already been lost" )
2195 {
2196 cred2Ctrl_test app;
2197
2198 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2199 app.m_powerTargetState = 0;
2200 app.state( stateCodes::CONNECTED );
2201 app.m_raw_width = app.m_full_w;
2202 app.m_raw_height = app.m_full_h;
2203 fgThreadScope fgThread( app );
2204 queueSerialResponse( "off\n" );
2205 queueSerialResponse( "bad-fps-limit\n" );
2206
2207 REQUIRE( app.appLogic() == 0 );
2208 REQUIRE( app.state() == stateCodes::CONNECTED );
2209 }
2210
2211 SECTION( "connected-state setpoint update failures are logged while the camera is still powered" )
2212 {
2213 cred2Ctrl_test app;
2214
2215 setPoweredOn( app );
2216 app.state( stateCodes::CONNECTED );
2217 app.m_raw_width = app.m_full_w;
2218 app.m_raw_height = app.m_full_h;
2219 fgThreadScope fgThread( app );
2220
2221 queueSerialResponse( "off\n" );
2222 queueSerialResponse( "10.0\n" );
2223 queueSerialResponse( "500.0\n" );
2224 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2225 queueSerialResponse( "-15.0\n" );
2226 queueSerialResponse( "150.5\n" );
2227 queueSerialResponse( "automatic\n" );
2228 queueSerialResponse( "medium\n" );
2229 queueSerialResponse( "on\n" );
2230 queueSerialResponse( "Error\n" );
2231
2232 REQUIRE( app.appLogic() == 0 );
2233 REQUIRE( app.state() == stateCodes::READY );
2234 }
2235
2236 SECTION( "connected-state setpoint update failures are ignored after power has already been lost" )
2237 {
2238 cred2Ctrl_test app;
2239
2240 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2241 app.m_powerTargetState = 0;
2242 app.state( stateCodes::CONNECTED );
2243 app.m_raw_width = app.m_full_w;
2244 app.m_raw_height = app.m_full_h;
2245 fgThreadScope fgThread( app );
2246
2247 queueSerialResponse( "off\n" );
2248 queueSerialResponse( "10.0\n" );
2249 queueSerialResponse( "500.0\n" );
2250 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2251 queueSerialResponse( "-15.0\n" );
2252 queueSerialResponse( "150.5\n" );
2253 queueSerialResponse( "automatic\n" );
2254 queueSerialResponse( "medium\n" );
2255 queueSerialResponse( "on\n" );
2256 queueSerialResponse( "", -1 );
2257
2258 REQUIRE( app.appLogic() == 0 );
2259 REQUIRE( app.state() == stateCodes::READY );
2260 }
2261
2262 SECTION( "ready-state housekeeping failures promote the controller to ERROR while powered on" )
2263 {
2264 cred2Ctrl_test app;
2265
2266 setPoweredOn( app );
2267 app.state( stateCodes::READY );
2268 app.m_roiSettleCounter = 0;
2269 fgThreadScope fgThread( app );
2270
2271 queueSerialResponse( "10.0\n" );
2272 queueSerialResponse( "500.0\n" );
2273 queueSerialResponse( "", -1 );
2274
2275 REQUIRE( app.appLogic() == 0 );
2276 REQUIRE( app.state() == stateCodes::ERROR );
2277 }
2278
2279 SECTION( "ready-state housekeeping failures are ignored once power has already been lost" )
2280 {
2281 cred2Ctrl_test app;
2282
2283 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2284 app.m_powerTargetState = 0;
2285 app.state( stateCodes::READY );
2286 app.m_roiSettleCounter = 0;
2287 fgThreadScope fgThread( app );
2288
2289 queueSerialResponse( "10.0\n" );
2290 queueSerialResponse( "500.0\n" );
2291 queueSerialResponse( "", -1 );
2292
2293 REQUIRE( app.appLogic() == 0 );
2294 REQUIRE( app.state() == stateCodes::READY );
2295 }
2296
2297 SECTION( "ready-state returns immediately when the INDI mutex is already held elsewhere" )
2298 {
2299 cred2Ctrl_test app;
2300 std::atomic<bool> mutexHeld{ false };
2301
2302 setPoweredOn( app );
2303 app.state( stateCodes::READY );
2304 app.m_roiSettleCounter = 0;
2305 fgThreadScope fgThread( app );
2306
2307 std::thread mutexHolder(
2308 [&app, &mutexHeld]()
2309 {
2310 std::lock_guard<std::mutex> lock( app.m_indiMutex );
2311 mutexHeld.store( true );
2312 std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
2313 } );
2314
2315 while( !mutexHeld.load() )
2316 {
2317 std::this_thread::yield();
2318 }
2319
2320 REQUIRE( app.appLogic() == 0 );
2321 REQUIRE( app.state() == stateCodes::READY );
2322 REQUIRE( g_edtStubState.serialCommands.empty() );
2323
2324 mutexHolder.join();
2325 }
2326
2327 SECTION( "ready-state update-limit failures are ignored once power has already been lost" )
2328 {
2329 cred2Ctrl_test app;
2330
2331 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2332 app.m_powerTargetState = 0;
2333 app.state( stateCodes::READY );
2334 app.m_roiSettleCounter = 0;
2335 fgThreadScope fgThread( app );
2336
2337 queueSerialResponse( "bad-fps-limit\n" );
2338
2339 REQUIRE( app.appLogic() == 0 );
2340 REQUIRE( app.state() == stateCodes::READY );
2341 }
2342
2343 SECTION( "ready-state update-limit failures promote the controller to ERROR while powered on" )
2344 {
2345 cred2Ctrl_test app;
2346
2347 setPoweredOn( app );
2348 app.state( stateCodes::READY );
2349 app.m_roiSettleCounter = 0;
2350 fgThreadScope fgThread( app );
2351
2352 queueSerialResponse( "bad-fps-limit\n" );
2353
2354 REQUIRE( app.appLogic() == 0 );
2355 REQUIRE( app.state() == stateCodes::ERROR );
2356 }
2357
2358 SECTION( "ready-state fan-query failures promote the controller to ERROR while powered on" )
2359 {
2360 cred2Ctrl_test app;
2361
2362 setPoweredOn( app );
2363 app.state( stateCodes::READY );
2364 app.m_roiSettleCounter = 0;
2365 fgThreadScope fgThread( app );
2366
2367 queueSerialResponse( "10.0\n" );
2368 queueSerialResponse( "500.0\n" );
2369 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2370 queueSerialResponse( "-15.0\n" );
2371 queueSerialResponse( "150.5\n" );
2372 queueSerialResponse( "", -1 );
2373
2374 REQUIRE( app.appLogic() == 0 );
2375 REQUIRE( app.state() == stateCodes::ERROR );
2376 }
2377
2378 SECTION( "ready-state fan-query failures are ignored once power has already been lost" )
2379 {
2380 cred2Ctrl_test app;
2381
2382 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2383 app.m_powerTargetState = 0;
2384 app.state( stateCodes::READY );
2385 app.m_roiSettleCounter = 0;
2386 fgThreadScope fgThread( app );
2387
2388 queueSerialResponse( "10.0\n" );
2389 queueSerialResponse( "500.0\n" );
2390 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2391 queueSerialResponse( "-15.0\n" );
2392 queueSerialResponse( "150.5\n" );
2393 queueSerialResponse( "", -1 );
2394
2395 REQUIRE( app.appLogic() == 0 );
2396 REQUIRE( app.state() == stateCodes::READY );
2397 }
2398
2399 SECTION( "ready-state analog-gain failures are ignored once power has already been lost" )
2400 {
2401 cred2Ctrl_test app;
2402
2403 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2404 app.m_powerTargetState = 0;
2405 app.state( stateCodes::READY );
2406 app.m_roiSettleCounter = 0;
2407 fgThreadScope fgThread( app );
2408
2409 queueSerialResponse( "10.0\n" );
2410 queueSerialResponse( "500.0\n" );
2411 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2412 queueSerialResponse( "-15.0\n" );
2413 queueSerialResponse( "150.5\n" );
2414 queueSerialResponse( "automatic\n" );
2415 queueSerialResponse( "", -1 );
2416
2417 REQUIRE( app.appLogic() == 0 );
2418 REQUIRE( app.state() == stateCodes::READY );
2419 }
2420
2421 SECTION( "ready-state analog-gain failures promote the controller to ERROR while powered on" )
2422 {
2423 cred2Ctrl_test app;
2424
2425 setPoweredOn( app );
2426 app.state( stateCodes::READY );
2427 app.m_roiSettleCounter = 0;
2428 fgThreadScope fgThread( app );
2429
2430 queueSerialResponse( "10.0\n" );
2431 queueSerialResponse( "500.0\n" );
2432 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2433 queueSerialResponse( "-15.0\n" );
2434 queueSerialResponse( "150.5\n" );
2435 queueSerialResponse( "automatic\n" );
2436 queueSerialResponse( "", -1 );
2437
2438 REQUIRE( app.appLogic() == 0 );
2439 REQUIRE( app.state() == stateCodes::ERROR );
2440 }
2441
2442 SECTION( "ready-state LED failures promote the controller to ERROR while powered on" )
2443 {
2444 cred2Ctrl_test app;
2445
2446 setPoweredOn( app );
2447 app.state( stateCodes::READY );
2448 app.m_roiSettleCounter = 0;
2449 fgThreadScope fgThread( app );
2450
2451 queueSerialResponse( "10.0\n" );
2452 queueSerialResponse( "500.0\n" );
2453 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2454 queueSerialResponse( "-15.0\n" );
2455 queueSerialResponse( "150.5\n" );
2456 queueSerialResponse( "automatic\n" );
2457 queueSerialResponse( "medium\n" );
2458 queueSerialResponse( "unknown\n" );
2459 queueSerialResponse( "still unknown\n" );
2460
2461 REQUIRE( app.appLogic() == 0 );
2462 REQUIRE( app.state() == stateCodes::ERROR );
2463 }
2464
2465 SECTION( "ready-state LED failures are ignored once power has already been lost" )
2466 {
2467 cred2Ctrl_test app;
2468
2469 static_cast<cred2MagAOXAppT &>( app ).m_powerState = 0;
2470 app.m_powerTargetState = 0;
2471 app.state( stateCodes::READY );
2472 app.m_roiSettleCounter = 0;
2473 fgThreadScope fgThread( app );
2474
2475 queueSerialResponse( "10.0\n" );
2476 queueSerialResponse( "500.0\n" );
2477 queueSerialResponse( "40.50:37.00:40.25:-14.92:2.29:27.50\n" );
2478 queueSerialResponse( "-15.0\n" );
2479 queueSerialResponse( "150.5\n" );
2480 queueSerialResponse( "automatic\n" );
2481 queueSerialResponse( "medium\n" );
2482 queueSerialResponse( "unknown\n" );
2483 queueSerialResponse( "still unknown\n" );
2484
2485 REQUIRE( app.appLogic() == 0 );
2486 REQUIRE( app.state() == stateCodes::READY );
2487 }
2488
2489 SECTION( "ready-state ROI settling skips one housekeeping cycle" )
2490 {
2491 cred2Ctrl_test app;
2492
2493 setPoweredOn( app );
2494 app.state( stateCodes::READY );
2495 app.m_roiSettleCounter = 2;
2496 fgThreadScope fgThread( app );
2497
2498 REQUIRE( app.appLogic() == 0 );
2499 REQUIRE( app.m_roiSettleCounter == 1 );
2500 REQUIRE( g_edtStubState.serialCommands.empty() );
2501 }
2502}
2503
2504#endif // CRED2CTRL_TEST_SUPPORT_ONLY
2505
2506} // namespace cred2CtrlTest
2507} // namespace libXWCTest
The base-class for XWCTk applications.
MagAO-X application to control the C-RED 2 camera.
Definition cred2Ctrl.hpp:51
int checkNextROI()
Validate and normalize the requested ROI.
int getFanSpeed()
Query and update the current fan-control state.
float fps()
Return the currently measured frame rate.
int setTempControl()
Implement the C-RED 2 temperature-controller toggle semantics.
int setTempSetPt()
Send the current target detector temperature setpoint to the camera.
int checkRecordTimes()
Check the telemetry record timers.
virtual int onPowerOff()
Actions required when the camera power turns off.
int loadImageIntoStream(void *dest)
Copy the current EDT image into the output stream.
int reconfig()
Reconfigure the EDT board for the pending ROI.
int syncROIFromCamera()
Query the camera for its current ROI and synchronize local state.
int configureAcquisition()
Configure camera-side ROI settings before acquisition starts.
int getAnalogGain()
Query and update the current analog-gain state.
virtual void loadConfig()
Load the configuration system results.
int recordTemps(bool force=false)
Record the detailed C-RED 2 temperature telemetry when values change.
int setLED()
Send the requested LED state to the camera.
int powerOnDefaults()
Set defaults for a power-on state.
virtual void setupConfig()
Setup the configuration system.
int updateFPSLimits()
Query and update the current camera FPS limits.
int getLEDState()
Query and update the current LED state.
int setAnalogGain()
Send the requested analog-gain mode to the camera.
virtual int appShutdown()
Shutdown function.
int setFPS()
Send the requested frame rate to the camera.
int setExpTime()
Required by stdCamera, but unused for C-RED 2.
int acquireAndCheckValid()
Wait for and validate the next acquired image.
int setNextROI()
Request that the next valid ROI be applied through reconfiguration.
virtual int appLogic()
Main FSM logic.
int startAcquisition()
Start frame acquisition on the EDT board.
int getTemps()
Query and update the camera temperature channels.
int writeConfig()
Write the temporary EDT configuration file for the pending ROI.
int getFPS()
Query and update the current camera frame rate.
int recordTelem(const cred2_temps *)
Record the detailed C-RED 2 temperature telemetry.
int issueCommand(const std::string &command, bool allowNoResponse=false)
Send a command that should return a success acknowledgement.
int sendCommand(std::string &response, const std::string &command, bool logFailure=true)
Send a command over Camera Link serial and clean the response.
int setFanSpeed()
Send the requested fan-control mode to the camera.
virtual int whilePowerOff()
Actions required while the camera remains powered off.
void pdv_flush_fifo(PdvDev *pdv_p)
Flush the stub PDV FIFO.
int pdv_readcfg(const char *configFile, Dependent *dd_p, Edtinfo *edtinfo)
Read a stub EDT configuration file.
void pdv_start_images(PdvDev *pdv_p, int numBuffs)
Start stub PDV acquisition for multiple images.
void edt_perror(char *errstr)
Report the last stub EDT error message.
void pdv_start_image(PdvDev *pdv_p)
Start acquisition of the next stub PDV image.
EdtDev * edt_open_channel(const char *deviceName, int unit, int channel)
Open a stub EDT device channel.
int pdv_get_waitchar(PdvDev *pdv_p, u_char *waitc)
Return the configured stub PDV serial terminator.
void pdv_serial_read_enable(PdvDev *pdv_p)
Enable stub PDV serial reads.
int pdv_serial_command(PdvDev *pdv_p, const char *command)
Send a command over the stub PDV serial channel.
void pdv_close(PdvDev *pdv_p)
Close a stub PDV device handle.
void edt_close(EdtDev *edt_p)
Close a stub EDT device channel.
int pdv_initcam(EdtDev *edt_p, Dependent *dd_p, int unit, Edtinfo *edtinfo, const char *configFile, char *bitdir, int pdv_debug)
Initialize a stub EDT camera instance.
int pdv_get_width(PdvDev *pdv_p)
Return the stub PDV frame width.
int pdv_get_height(PdvDev *pdv_p)
Return the stub PDV frame height.
int pdv_serial_set_baud(PdvDev *pdv_p, int baud)
PdvDev * pdv_open_channel(const char *deviceName, int unit, int channel)
Open a stub PDV device handle.
int pdv_get_depth(PdvDev *pdv_p)
Return the stub PDV frame bit depth.
int pdv_serial_wait(PdvDev *pdv_p, int timeout, int count)
Wait for serial data on the stub PDV channel.
char * pdv_get_cameratype(PdvDev *pdv_p)
Return the stub PDV camera type string.
u_char * pdv_wait_last_image_timed(PdvDev *pdv_p, uint dmaTimeStamp[2])
Wait for the last stub PDV image and fill the DMA timestamp.
Dependent * pdv_alloc_dependent()
Allocate a stub EDT dependent configuration object.
void pdv_multibuf(PdvDev *pdv_p, int numBuffs)
Configure the stub PDV ring-buffer depth.
int pdv_serial_get_baud(PdvDev *pdv_p)
int pdv_serial_read(PdvDev *pdv_p, char *buf, int size)
Read bytes from the stub PDV serial channel.
unsigned int uint
Definition edtinc.h:9
unsigned char u_char
Definition edtinc.h:10
Stub EDT dependent-configuration type used by unit tests.
Definition edtinc.h:26
Stub EDT device handle type used by unit tests.
Definition edtinc.h:14
Stub EDT configuration info structure used by unit tests.
Definition edtinc.h:32
Stub EDT camera handle type used by unit tests.
Definition edtinc.h:20
TEST_CASE("cred2Ctrl lifecycle entrypoints handle startup failures and success", "[cred2Ctrl]")
Verify lifecycle entrypoints cover startup failure handling and successful startup cleanup.
bool fullFrame
True when the ROI spans the full detector.
int startRow
First included row.
int endColumn
Last included column.
int startColumn
First included column.
C-RED 2 ROI expressed as 0-based inclusive column and row limits.
@ OPERATING
The device is operating, other than homing.
@ NODEVICE
No device exists for the application to control.
@ CONFIGURING
The application is configuring the device.
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
@ READY
The device is ready for operation, but is not operating.
@ CONNECTED
The application has connected to the device or service.
@ NOTCONNECTED
The application is not connected to the device or service.
@ POWERON
The device power is on.
#define XWCTEST_DOXYGEN_REF(fxn)
This inserts an unused call to a function signature to make doxygen make the link.
Definition testXWC.hpp:18
int cred2ParseRange(int &firstValue, int &secondValue, const std::string &response)
Parse a raw range response such as 0-639.
int cred2RoiToCenter(float &centerX, float &centerY, int &width, int &height, const cred2Roi &roi, int fullWidth, int fullHeight)
Convert C-RED 2 ROI corners into a MagAO-X ROI center/size description.
int cred2ParseCropState(bool &enabled, int &startColumn, int &endColumn, int &startRow, int &endRow, const std::string &response)
Parse a raw cropping status response such as on or on:192-447:128-383.
int cred2RoiFromCenter(cred2Roi &roi, float centerX, float centerY, int width, int height, int fullWidth, int fullHeight)
Convert a MagAO-X ROI center/size description into C-RED 2 corners.
int cred2ParseFloatVector(std::vector< float > &values, const std::string &response, size_t expectedValues)
Parse a delimited list of raw numeric responses into a float vector.
int cred2ParseFloat(float &value, const std::string &response)
Parse a raw numeric response into a float.
std::unique_lock< std::mutex > lock(m_indiMutex)
Namespace for all libXWC tests.
Log entry recording the C-RED 2 detailed temperature channels.
static timespec lastRecord
The timestamp of the last time this log was recorded. Used by telemetry.
Log entry recording framegrabber timings.
static timespec lastRecord
The timestamp of the last time this log was recorded. Used by the telemetry system.
Log entry recording stdcam stage specific status.
static timespec lastRecord
The timestamp of the last time this log was recorded. Used by the telemetry system.