14#include "../../../tests/testXWC.hpp"
28#define protected public
29#include "../ocam2KCtrl.hpp"
41 int commandResult{ 0 };
42 int initialWaitResult{ 1 };
52 lastStartNumBuffs = -1;
58 serialResponses.clear();
59 serialCommands.clear();
60 activeSerialResponse.clear();
61 activeSerialReadPending =
false;
62 activeSerialTransaction =
false;
63 activeSerialWaitResult = 0;
64 activeSerialWaitServed =
false;
67 int startImagesCalls{ 0 };
68 int lastStartNumBuffs{ -1 };
69 int startImageCalls{ 0 };
70 uint waitTimeSec{ 0 };
71 uint waitTimeNsec{ 0 };
72 std::array<u_char, 32> waitImage{};
73 int readcfgReturn{ 0 };
74 std::deque<serialResponse> serialResponses;
75 std::vector<std::string> serialCommands;
76 std::string activeSerialResponse;
77 bool activeSerialReadPending{
false };
78 bool activeSerialTransaction{
false };
79 int activeSerialWaitResult{ 0 };
80 bool activeSerialWaitServed{
false };
95 lastDescrambleFile.clear();
104 const short *lastRaw{
nullptr };
105 short *lastOutput{
nullptr };
106 std::vector<int16_t> outputImage;
107 unsigned int imageNumber{ 0 };
109 std::string lastDescrambleFile;
118edtStubState g_edtStubState;
121ocam2StubState g_ocam2StubState;
126 g_edtStubState.reset();
127 g_ocam2StubState.reset();
131[[maybe_unused]]
void setStubFrameNumber(
unsigned int frameNumber )
134 static_cast<int>( frameNumber );
139queueSerialResponse(
const std::string &response,
int commandResult = 0,
int initialWaitResult = 1 )
141 g_edtStubState.serialResponses.push_back( { response, commandResult, initialWaitResult } );
156 static_cast<void>( configFile );
157 static_cast<void>( dd_p );
158 static_cast<void>( edtinfo );
159 return g_edtStubState.readcfgReturn;
165 static_cast<void>( deviceName );
166 static_cast<void>( unit );
167 static_cast<void>( channel );
173 if( errstr !=
nullptr )
183 const char *configFile,
187 static_cast<void>( edt_p );
188 static_cast<void>( dd_p );
189 static_cast<void>( unit );
190 static_cast<void>( edtinfo );
191 static_cast<void>( configFile );
192 static_cast<void>( bitdir );
193 static_cast<void>( pdv_debug );
199 static_cast<void>( edt_p );
205 static_cast<void>( deviceName );
206 static_cast<void>( unit );
207 static_cast<void>( channel );
213 static_cast<void>( pdv_p );
218 static_cast<void>( pdv_p );
223 static_cast<void>( pdv_p );
228 static_cast<void>( pdv_p );
234 static_cast<void>( pdv_p );
240 static_cast<void>( pdv_p );
246 static char cameraType[] =
"stub_pdv";
247 static_cast<void>( pdv_p );
253 static_cast<void>( pdv_p );
254 static_cast<void>( numBuffs );
259 static_cast<void>( pdv_p );
260 ++g_edtStubState.startImagesCalls;
261 g_edtStubState.lastStartNumBuffs = numBuffs;
266 static_cast<void>( pdv_p );
267 dmaTimeStamp[0] = g_edtStubState.waitTimeSec;
268 dmaTimeStamp[1] = g_edtStubState.waitTimeNsec;
269 return g_edtStubState.waitImage.data();
274 static_cast<void>( pdv_p );
275 ++g_edtStubState.startImageCalls;
280 static_cast<void>( pdv_p );
281 if( buf !=
nullptr && size > 0 )
286 if( !g_edtStubState.activeSerialReadPending || buf ==
nullptr || size <= 0 )
291 size_t copyCount = g_edtStubState.activeSerialResponse.size();
292 if( copyCount >
static_cast<size_t>( size - 1 ) )
294 copyCount =
static_cast<size_t>( size - 1 );
297 std::copy_n( g_edtStubState.activeSerialResponse.data(), copyCount, buf );
298 buf[copyCount] =
'\0';
300 g_edtStubState.activeSerialReadPending =
false;
302 return static_cast<int>( copyCount );
307 static_cast<void>( pdv_p );
308 g_edtStubState.serialCommands.emplace_back( command ==
nullptr ?
"" : command );
310 g_edtStubState.activeSerialResponse.clear();
311 g_edtStubState.activeSerialReadPending =
false;
312 g_edtStubState.activeSerialTransaction =
false;
313 g_edtStubState.activeSerialWaitResult = 0;
314 g_edtStubState.activeSerialWaitServed =
false;
316 if( g_edtStubState.serialResponses.empty() )
321 serialResponse response = g_edtStubState.serialResponses.front();
322 g_edtStubState.serialResponses.pop_front();
324 if( response.commandResult < 0 )
326 return response.commandResult;
329 g_edtStubState.activeSerialResponse = response.response;
330 g_edtStubState.activeSerialReadPending =
true;
331 g_edtStubState.activeSerialTransaction =
true;
332 g_edtStubState.activeSerialWaitResult = response.initialWaitResult;
334 return response.commandResult;
339 static_cast<void>( pdv_p );
340 static_cast<void>( timeout );
341 static_cast<void>( count );
343 if( !g_edtStubState.activeSerialTransaction )
348 if( !g_edtStubState.activeSerialWaitServed )
350 g_edtStubState.activeSerialWaitServed =
true;
351 return g_edtStubState.activeSerialWaitResult;
359 static_cast<void>( pdv_p );
360 if( waitc !=
nullptr )
379 g_ocam2StubState.lastInitMode = mode;
380 g_ocam2StubState.lastDescrambleFile = descrbFile ==
nullptr ?
"" : descrbFile;
383 *
id = g_ocam2StubState.nextInitId;
385 g_ocam2StubState.reportedMode = mode;
386 return g_ocam2StubState.initReturn;
391 g_ocam2StubState.lastId = id;
392 g_ocam2StubState.lastRaw = imageRaw;
393 g_ocam2StubState.lastOutput = image;
394 if( number !=
nullptr )
396 *number = g_ocam2StubState.imageNumber;
398 if( image !=
nullptr )
400 for(
size_t nn = 0; nn < g_ocam2StubState.outputImage.size(); ++nn )
402 image[nn] = g_ocam2StubState.outputImage[nn];
409 ++g_ocam2StubState.exitCalls;
410 g_ocam2StubState.lastExitId = id;
416 static_cast<void>( id );
417 return g_ocam2StubState.reportedMode;
422 static_cast<void>( mode );
434namespace ocam2KCtrlTest
441std::string uniqueShmimName(
const std::string &suffix )
443 static unsigned counter = 0;
447 return "ocam2KCtrl_test_" + suffix +
"_" + std::to_string( ::getpid() ) +
"_" + std::to_string( counter );
451[[maybe_unused]] std::string uniqueConfigPath(
const std::string &suffix )
453 return "/tmp/ocam2KCtrl_test_" + suffix +
"_" + std::to_string( ::getpid() ) +
".conf";
461 explicit tempStream(
const std::string &name,
465 uint8_t dataType = _DATATYPE_UINT8 )
468 uint32_t imsize[3] = {
width, height, depth };
470 if( ImageStreamIO_createIm_gpu( &m_image,
479 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
480 0 ) != IMAGESTREAMIO_SUCCESS )
482 throw std::runtime_error(
"failed to create temporary ImageStreamIO stream" );
485 m_image.md[0].cnt1 = 0;
493 ImageStreamIO_destroyIm( &m_image );
512 bool m_owner{
true };
522 m_configName =
"ocam2KCtrl_test";
523 m_shmimName = uniqueShmimName(
"main" );
524 m_syncShmimName = uniqueShmimName(
"sync" );
528 ~ocam2KCtrl_test() noexcept
535[[maybe_unused]]
void setPoweredOn( ocam2KCtrl_test &app )
537 static_cast<MagAOXAppT &
>( app ).m_powerState = 1;
538 app.m_powerTargetState = 1;
542[[maybe_unused]]
void configureStartupMode( ocam2KCtrl_test &app,
543 const std::string &modeName =
"science",
544 const std::string &serialCommand =
"mode science",
545 const std::string &configFileName =
"stub.cfg" )
549 app.m_startupMode = modeName;
550 app.m_modeName = modeName;
551 app.m_configDir =
"/tmp";
559 app.m_tel.logPath(
"/tmp" );
560 app.m_tel.logName( app.m_configName );
561 app.m_tel.logExt(
"bintel" );
568 app.m_cameraModes[modeName] = config;
572void startFgThread( ocam2KCtrl_test &app,
int sleepMs = 200 )
575 std::thread( [sleepMs]() { std::this_thread::sleep_for( std::chrono::milliseconds( sleepMs ) ); } );
579void joinFgThread( ocam2KCtrl_test &app )
581 if( app.m_fgThread.joinable() )
583 app.m_fgThread.join();
590 ocam2KCtrl_test &m_app;
593 explicit fgThreadScope( ocam2KCtrl_test &app,
int sleepMs = 200 ) : m_app( app )
595 startFgThread( m_app, sleepMs );
601 joinFgThread( m_app );
608 ocam2KCtrl_test &m_app;
612 explicit startupScope( ocam2KCtrl_test &app ) : m_app( app ), m_started( false )
617 void markStarted(
bool started )
627 m_app.m_shutdown = 1;
628 m_app.m_tel.logShutdown(
true );
629 static_cast<void>( m_app.appShutdown() );
635[[maybe_unused]]
bool hasRecordedTime(
const timespec &ts )
637 return ts.tv_sec != 0 || ts.tv_nsec != 0;
642#ifndef OCAM2KCTRL_TEST_SUPPORT_ONLY
648TEST_CASE(
"ocam2KCtrl sync stream creation uses a 1x1 uint8 layout",
"[ocam2KCtrl]" )
651 IMAGE openedSyncStream;
656 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
661 REQUIRE( app.ensureSyncStream() == 0 );
662 REQUIRE( app.m_syncImageStream !=
nullptr );
663 REQUIRE( app.m_syncImageStream->md[0].datatype == _DATATYPE_UINT8 );
664 REQUIRE( app.m_syncImageStream->md[0].size[0] == 1 );
665 REQUIRE( app.m_syncImageStream->md[0].size[1] == 1 );
666 REQUIRE( app.m_syncImageStream->md[0].size[2] == 1 );
667 REQUIRE( app.m_syncImageStream->md[0].cnt1 == 0 );
669 REQUIRE( ImageStreamIO_openIm( &openedSyncStream, app.m_syncShmimName.c_str() ) == IMAGESTREAMIO_SUCCESS );
670 REQUIRE( openedSyncStream.md[0].datatype == _DATATYPE_UINT8 );
671 REQUIRE( openedSyncStream.md[0].size[0] == 1 );
672 REQUIRE( openedSyncStream.md[0].size[1] == 1 );
673 REQUIRE( openedSyncStream.md[0].size[2] == 1 );
674 REQUIRE( ImageStreamIO_closeIm( &openedSyncStream ) == IMAGESTREAMIO_SUCCESS );
681TEST_CASE(
"ocam2KCtrl sync stream publication mirrors metadata and posts semaphores",
"[ocam2KCtrl]" )
684 tempStream sourceStream( uniqueShmimName(
"source" ) );
691 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
696 SECTION(
"null source streams are ignored" )
698 REQUIRE( app.frameGrabberPostPublish(
nullptr ) == 0 );
701 SECTION(
"publication fails cleanly if the sync stream has not been prepared" )
703 REQUIRE( app.frameGrabberPostPublish( sourceStream.image() ) == -1 );
706 SECTION(
"publication mirrors metadata and posts one semaphore on the sync stream" )
708 REQUIRE( app.ensureSyncStream() == 0 );
710 sourceStream.image()->md[0].writetime.tv_sec = 1234;
711 sourceStream.image()->md[0].writetime.tv_nsec = 5678;
712 sourceStream.image()->md[0].atime.tv_sec = 1234;
713 sourceStream.image()->md[0].atime.tv_nsec = 4321;
714 sourceStream.image()->md[0].cnt0 = 77;
715 sourceStream.image()->md[0].cnt1 = 0;
716 sourceStream.image()->writetimearray[0] = sourceStream.image()->md[0].writetime;
717 sourceStream.image()->atimearray[0] = sourceStream.image()->md[0].atime;
718 sourceStream.image()->cntarray[0] = sourceStream.image()->md[0].cnt0;
720 semIndex = ImageStreamIO_getsemwaitindex( app.m_syncImageStream, 0 );
721 REQUIRE( semIndex >= 0 );
722 ImageStreamIO_semflush( app.m_syncImageStream, semIndex );
723 REQUIRE( sem_getvalue( app.m_syncImageStream->semptr[semIndex], &semValue ) == 0 );
724 REQUIRE( semValue == 0 );
726 app.m_syncImageStream->array.UI8[0] = 99;
728 REQUIRE( app.frameGrabberPostPublish( sourceStream.image() ) == 0 );
729 REQUIRE( sem_getvalue( app.m_syncImageStream->semptr[semIndex], &semValue ) == 0 );
730 REQUIRE( semValue == 1 );
731 REQUIRE( app.m_syncImageStream->array.UI8[0] == 0 );
732 REQUIRE( app.m_syncImageStream->md[0].writetime.tv_sec == sourceStream.image()->md[0].writetime.tv_sec );
733 REQUIRE( app.m_syncImageStream->md[0].writetime.tv_nsec == sourceStream.image()->md[0].writetime.tv_nsec );
734 REQUIRE( app.m_syncImageStream->md[0].atime.tv_sec == sourceStream.image()->md[0].atime.tv_sec );
735 REQUIRE( app.m_syncImageStream->md[0].atime.tv_nsec == sourceStream.image()->md[0].atime.tv_nsec );
736 REQUIRE( app.m_syncImageStream->md[0].cnt0 == sourceStream.image()->md[0].cnt0 );
737 REQUIRE( app.m_syncImageStream->md[0].cnt1 == 0 );
738 REQUIRE( app.m_syncImageStream->writetimearray[0].tv_sec == sourceStream.image()->md[0].writetime.tv_sec );
739 REQUIRE( app.m_syncImageStream->atimearray[0].tv_nsec == sourceStream.image()->md[0].atime.tv_nsec );
740 REQUIRE( app.m_syncImageStream->cntarray[0] == sourceStream.image()->md[0].cnt0 );
748TEST_CASE(
"ocam2KCtrl ensureSyncStream reuses and replaces existing stream state",
"[ocam2KCtrl]" )
755 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
760 SECTION(
"a valid existing sync stream is reused in place" )
762 REQUIRE( app.ensureSyncStream() == 0 );
764 IMAGE *existingStream = app.m_syncImageStream;
766 REQUIRE( app.ensureSyncStream() == 0 );
767 REQUIRE( app.m_syncImageStream == existingStream );
770 SECTION(
"a mismatched in-memory sync stream is destroyed and recreated with the expected layout" )
772 app.m_syncImageStream =
reinterpret_cast<IMAGE *
>( malloc(
sizeof( IMAGE ) ) );
773 REQUIRE( app.m_syncImageStream !=
nullptr );
776 uint32_t wrongSize[3] = { 1, 2, 1 };
778 REQUIRE( ImageStreamIO_createIm_gpu( app.m_syncImageStream,
779 app.m_syncShmimName.c_str(),
787 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
788 0 ) == IMAGESTREAMIO_SUCCESS );
791 void *wrongPtr = app.m_syncImageStream;
793 REQUIRE( app.ensureSyncStream() == 0 );
794 REQUIRE( app.m_syncImageStream !=
nullptr );
795 REQUIRE(
static_cast<void *
>( app.m_syncImageStream ) == wrongPtr );
796 REQUIRE( app.m_syncImageStream->md[0].datatype == _DATATYPE_UINT8 );
797 REQUIRE( app.m_syncImageStream->md[0].size[0] == 1 );
798 REQUIRE( app.m_syncImageStream->md[0].size[1] == 1 );
799 REQUIRE( app.m_syncImageStream->md[0].size[2] == 1 );
800 REQUIRE( app.m_syncImageStream->md[0].size[1] != 2 );
803 SECTION(
"a stale on-disk sync stream is reopened, destroyed, and replaced" )
805 tempStream staleStream( app.m_syncShmimName, 1, 1, 1, _DATATYPE_UINT8 );
806 staleStream.dismiss();
808 REQUIRE( app.m_syncImageStream ==
nullptr );
809 REQUIRE( app.ensureSyncStream() == 0 );
810 REQUIRE( app.m_syncImageStream !=
nullptr );
811 REQUIRE( app.m_syncImageStream->md[0].datatype == _DATATYPE_UINT8 );
812 REQUIRE( app.m_syncImageStream->md[0].size[0] == 1 );
813 REQUIRE( app.m_syncImageStream->md[0].size[1] == 1 );
814 REQUIRE( app.m_syncImageStream->md[0].size[2] == 1 );
817 SECTION(
"an invalid pre-existing sync-stream file returns an error" )
819 char syncFileName[1024];
820 ImageStreamIO_filename( syncFileName,
sizeof( syncFileName ), app.m_syncShmimName.c_str() );
822 std::FILE *syncFile = std::fopen( syncFileName,
"w" );
823 REQUIRE( syncFile !=
nullptr );
824 REQUIRE( std::fputs(
"not an ImageStreamIO stream\n", syncFile ) >= 0 );
825 REQUIRE( std::fclose( syncFile ) == 0 );
827 REQUIRE( app.ensureSyncStream() == -1 );
828 REQUIRE( app.m_syncImageStream ==
nullptr );
829 REQUIRE( ::unlink( syncFileName ) == 0 );
837TEST_CASE(
"ocam2KCtrl stateString reports mode fps gain and setpoint",
"[ocam2KCtrl]" )
844 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
849 app.m_modeName =
"science";
852 app.m_ccdTempSetpt = 19.5;
854 REQUIRE( app.stateString() ==
"science_150.000000_42.000000_19.500000" );
861TEST_CASE(
"ocam2KCtrl stateStringValid requires OPERATING and on-target temperature control",
"[ocam2KCtrl]" )
868 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
874 app.m_tempControlOnTarget =
true;
875 REQUIRE( app.stateStringValid() );
877 app.m_tempControlOnTarget =
false;
878 REQUIRE( !app.stateStringValid() );
881 app.m_tempControlOnTarget =
true;
882 REQUIRE( !app.stateStringValid() );
889TEST_CASE(
"ocam2KCtrl configuration loading handles defaults and supported gain limits",
"[ocam2KCtrl]" )
894 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
900 SECTION(
"loadConfig falls back to the main shmim name for the sync stream" )
906 mx::app::writeConfigFile( uniqueConfigPath(
"defaults" ), {
"unused" }, {
"value" }, {
"0" } );
907 app.config.readConfig( uniqueConfigPath(
"defaults" ) );
909 app.m_shmimName =
"ocam_main_default";
910 app.m_syncShmimName =
"";
914 REQUIRE( app.m_ocamDescrambleFile ==
"" );
915 REQUIRE( app.m_maxEMGain == Approx( 600.0f ) );
916 REQUIRE( app.m_syncShmimName == app.m_shmimName +
"_sync" );
919 SECTION(
"loadConfig applies configured overrides for the descramble and stream names" )
925 mx::app::writeConfigFile( uniqueConfigPath(
"overrides" ),
926 {
"camera",
"camera",
"framegrabber",
"framegrabber" },
927 {
"ocamDescrambleFile",
"maxEMGain",
"shmimName",
"syncShmimName" },
928 {
"descramble_stub.txt",
"321",
"ocam_main_override",
"ocam_sync_override" } );
929 app.config.readConfig( uniqueConfigPath(
"overrides" ) );
933 REQUIRE( app.m_ocamDescrambleFile ==
"descramble_stub.txt" );
934 REQUIRE( app.m_maxEMGain == Approx( 321.0f ) );
935 REQUIRE( app.m_shmimName ==
"ocam_main_override" );
936 REQUIRE( app.m_syncShmimName ==
"ocam_sync_override" );
939 SECTION(
"loadConfig clamps maxEMGain below the supported minimum" )
945 mx::app::writeConfigFile( uniqueConfigPath(
"gain_low" ), {
"camera" }, {
"maxEMGain" }, {
"0" } );
946 app.config.readConfig( uniqueConfigPath(
"gain_low" ) );
950 REQUIRE( app.m_maxEMGain == Approx( 1.0f ) );
953 SECTION(
"loadConfig clamps maxEMGain above the supported maximum" )
959 mx::app::writeConfigFile( uniqueConfigPath(
"gain_high" ), {
"camera" }, {
"maxEMGain" }, {
"700" } );
960 app.config.readConfig( uniqueConfigPath(
"gain_high" ) );
964 REQUIRE( app.m_maxEMGain == Approx( 600.0f ) );
972TEST_CASE(
"ocam2KCtrl getTemps handles valid and malformed serial responses",
"[ocam2KCtrl]" )
979 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
986 SECTION(
"valid temperature response updates cached temperatures and status" )
988 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
989 "SET[205]\nCooling Power [102]mW.\n\n" );
991 REQUIRE( app.getTemps() == 0 );
992 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
993 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp" );
994 REQUIRE( app.m_temps.CCD == Approx( 20.4f ) );
995 REQUIRE( app.m_temps.CPU == Approx( 41.0f ) );
996 REQUIRE( app.m_ccdTemp == Approx( 20.4f ) );
997 REQUIRE( app.m_ccdTempSetpt == Approx( 20.5f ) );
998 REQUIRE( app.m_tempControlStatus ==
true );
999 REQUIRE( app.m_tempControlStatusStr ==
"ON TARGET" );
1000 REQUIRE( app.m_tempControlOnTarget ==
true );
1003 SECTION(
"malformed temperature response marks cached temperatures invalid without forcing reconfig" )
1005 queueSerialResponse(
"Temperatures : CCD[20.4]\n" );
1007 app.m_temps.CCD = 5;
1009 app.m_ccdTempSetpt = 6;
1010 app.m_tempControlStatus =
true;
1011 app.m_tempControlOnTarget =
false;
1012 app.m_tempControlStatusStr =
"ON TARGET";
1014 REQUIRE( app.getTemps() == 0 );
1015 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1016 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp" );
1017 REQUIRE( app.m_temps.CCD == Approx( -999.0f ) );
1018 REQUIRE( app.m_temps.SET == Approx( -999.0f ) );
1019 REQUIRE( app.m_ccdTemp == Approx( -999.0f ) );
1020 REQUIRE( app.m_ccdTempSetpt == Approx( -999.0f ) );
1021 REQUIRE( app.m_tempControlStatus ==
false );
1022 REQUIRE( app.m_tempControlStatusStr ==
"UNKNOWN" );
1023 REQUIRE( app.m_tempControlOnTarget ==
false );
1026 SECTION(
"low cooling power with a warm detector reports temperature control off" )
1028 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
1029 "SET[165]\nCooling Power [2]mW.\n\n" );
1031 REQUIRE( app.getTemps() == 0 );
1032 REQUIRE( app.m_temps.CCD == Approx( 20.4f ) );
1033 REQUIRE( app.m_temps.SET == Approx( 16.5f ) );
1034 REQUIRE( app.m_tempControlStatus ==
false );
1035 REQUIRE( app.m_tempControlStatusStr ==
"TEMP OFF" );
1036 REQUIRE( app.m_tempControlOnTarget ==
false );
1039 SECTION(
"serial failures while powered off return -1 immediately" )
1041 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1042 app.m_powerTargetState = 0;
1044 queueSerialResponse(
"", -1 );
1046 REQUIRE( app.getTemps() == -1 );
1047 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1048 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp" );
1051 SECTION(
"serial failures while powered on return a software error" )
1053 queueSerialResponse(
"", -1 );
1055 REQUIRE( app.getTemps() == -1 );
1056 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1057 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp" );
1060 SECTION(
"malformed temperature responses while powered off return -1" )
1062 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1063 app.m_powerTargetState = 0;
1065 queueSerialResponse(
"Temperatures : CCD[20.4]\n" );
1067 REQUIRE( app.getTemps() == -1 );
1068 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1069 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp" );
1077TEST_CASE(
"ocam2KCtrl getFPS handles valid and malformed serial responses",
"[ocam2KCtrl]" )
1079 ocam2KCtrl_test app;
1084 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1089 SECTION(
"valid fps response updates the cached frame rate" )
1091 setPoweredOn( app );
1092 app.m_synchro =
false;
1095 queueSerialResponse(
"fps [150.5] Hz\n" );
1097 REQUIRE( app.getFPS() == 0 );
1098 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1099 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps" );
1100 REQUIRE( app.m_fps == Approx( 150.5f ) );
1103 SECTION(
"malformed fps response is non-fatal and leaves the cached value unchanged" )
1105 setPoweredOn( app );
1106 app.m_synchro =
false;
1109 queueSerialResponse(
"fps 150.5\n" );
1111 REQUIRE( app.getFPS() == 0 );
1112 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1113 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps" );
1114 REQUIRE( app.m_fps == Approx( 75.0f ) );
1117 SECTION(
"malformed fps responses while powered off return -1" )
1119 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1120 app.m_powerTargetState = 0;
1121 app.m_synchro =
false;
1124 queueSerialResponse(
"fps 150.5\n" );
1126 REQUIRE( app.getFPS() == -1 );
1127 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1128 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps" );
1129 REQUIRE( app.m_fps == Approx( 75.0f ) );
1132 SECTION(
"serial failures while powered on return a software error" )
1134 setPoweredOn( app );
1135 app.m_synchro =
false;
1138 queueSerialResponse(
"", -1 );
1140 REQUIRE( app.getFPS() == -1 );
1141 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1142 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps" );
1143 REQUIRE( app.m_fps == Approx( 75.0f ) );
1146 SECTION(
"synchro mode bypasses serial queries and mirrors the sync frequency" )
1148 app.m_synchro =
true;
1149 app.m_syncFreq = 205.25f;
1152 REQUIRE( app.getFPS() == 0 );
1153 REQUIRE( g_edtStubState.serialCommands.empty() );
1154 REQUIRE( app.m_fps == Approx( 205.25f ) );
1162TEST_CASE(
"ocam2KCtrl temperature control helpers handle valid and invalid requests",
"[ocam2KCtrl]" )
1164 ocam2KCtrl_test app;
1169 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1175 setPoweredOn( app );
1177 SECTION(
"setTempControl refuses to turn cooling off below the safe temperature" )
1179 app.m_tempControlStatusSet =
false;
1180 app.m_tempControlStatus =
true;
1181 app.m_ccdTemp = 18.5f;
1183 REQUIRE( app.setTempControl() == -1 );
1184 REQUIRE( g_edtStubState.serialCommands.empty() );
1185 REQUIRE( app.m_tempControlStatus ==
true );
1188 SECTION(
"setTempControl turns cooling off when the detector is warm enough" )
1190 app.m_tempControlStatusSet =
false;
1191 app.m_tempControlStatus =
true;
1192 app.m_ccdTemp = 20.0f;
1194 queueSerialResponse(
"temperature control off\n" );
1196 REQUIRE( app.setTempControl() == 0 );
1197 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1198 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp off" );
1199 REQUIRE( app.m_tempControlStatusSet ==
false );
1200 REQUIRE( app.m_tempControlStatus ==
false );
1203 SECTION(
"setTempControl turns cooling on and reapplies the configured setpoint" )
1205 app.m_tempControlStatusSet =
true;
1206 app.m_tempControlStatus =
false;
1207 app.m_ccdTempSetpt = 15.5f;
1209 queueSerialResponse(
"temperature control on\n" );
1210 queueSerialResponse(
"setpoint updated\n" );
1212 REQUIRE( app.setTempControl() == 0 );
1213 REQUIRE( g_edtStubState.serialCommands.size() == 2 );
1214 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp on" );
1215 REQUIRE( g_edtStubState.serialCommands[1] ==
"temp 15.500000" );
1216 REQUIRE( app.m_tempControlStatusSet ==
true );
1217 REQUIRE( app.m_tempControlStatus ==
true );
1220 SECTION(
"setTempControl returns -1 on serial failure once power is already off" )
1222 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1223 app.m_powerTargetState = 0;
1224 app.m_tempControlStatusSet =
true;
1225 app.m_tempControlStatus =
false;
1226 app.m_ccdTempSetpt = 15.5f;
1228 queueSerialResponse(
"", -1 );
1230 REQUIRE( app.setTempControl() == -1 );
1231 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1232 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp on" );
1235 SECTION(
"setTempControl returns a software error on serial failure while still powered" )
1237 app.m_tempControlStatusSet =
true;
1238 app.m_tempControlStatus =
false;
1239 app.m_ccdTempSetpt = 15.5f;
1241 queueSerialResponse(
"", -1 );
1243 REQUIRE( app.setTempControl() == -1 );
1244 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1245 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp on" );
1248 SECTION(
"setTempSetPt rejects out-of-range high setpoints without serial traffic" )
1250 app.m_ccdTempSetpt = 30.0f;
1252 REQUIRE( app.setTempSetPt() == -1 );
1253 REQUIRE( g_edtStubState.serialCommands.empty() );
1256 SECTION(
"setTempSetPt rejects out-of-range low setpoints without serial traffic" )
1258 app.m_ccdTempSetpt = -50.1f;
1260 REQUIRE( app.setTempSetPt() == -1 );
1261 REQUIRE( g_edtStubState.serialCommands.empty() );
1264 SECTION(
"setTempSetPt sends valid setpoints to the camera" )
1266 app.m_ccdTempSetpt = 12.25f;
1268 queueSerialResponse(
"setpoint updated\n" );
1270 REQUIRE( app.setTempSetPt() == 0 );
1271 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1272 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp 12.250000" );
1275 SECTION(
"setTempSetPt returns -1 on serial failure once power is already off" )
1277 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1278 app.m_powerTargetState = 0;
1279 app.m_ccdTempSetpt = 12.25f;
1281 queueSerialResponse(
"", -1 );
1283 REQUIRE( app.setTempSetPt() == -1 );
1284 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1285 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp 12.250000" );
1288 SECTION(
"setTempSetPt returns a software error on serial failure while still powered" )
1290 app.m_ccdTempSetpt = 12.25f;
1292 queueSerialResponse(
"", -1 );
1294 REQUIRE( app.setTempSetPt() == -1 );
1295 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1296 REQUIRE( g_edtStubState.serialCommands[0] ==
"temp 12.250000" );
1304TEST_CASE(
"ocam2KCtrl setShutter rejects invalid shutter requests",
"[ocam2KCtrl]" )
1306 ocam2KCtrl_test app;
1311 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1316 REQUIRE( app.setShutter( 2 ) == -1 );
1323TEST_CASE(
"ocam2KCtrl helper interfaces reset local state and power-off lifecycle flags",
"[ocam2KCtrl]" )
1325 ocam2KCtrl_test app;
1330 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1341 app.m_tempControlStatusSet =
true;
1342 app.m_tempControlStatus =
true;
1343 REQUIRE( app.powerOnDefaults() == 0 );
1344 REQUIRE( app.m_tempControlStatusSet ==
false );
1345 REQUIRE( app.m_tempControlStatus ==
false );
1348 REQUIRE( app.fps() == Approx( 250.5f ) );
1350 REQUIRE( app.setExpTime() == 0 );
1351 REQUIRE( app.setNextROI() == 0 );
1353 REQUIRE( app.ensureSyncStream() == 0 );
1354 REQUIRE( app.m_syncImageStream !=
nullptr );
1356 app.m_powerOnCounter = -1;
1357 app.m_poweredOn =
false;
1360 app.m_circBuffLength = 23;
1361 app.m_reconfig =
false;
1363 REQUIRE( app.onPowerOff() == 0 );
1364 REQUIRE( app.m_powerOnCounter == 0 );
1365 REQUIRE( app.m_poweredOn ==
true );
1366 REQUIRE( app.m_width == 0 );
1367 REQUIRE( app.m_height == 0 );
1368 REQUIRE( app.m_circBuffLength == 1 );
1369 REQUIRE( app.m_reconfig ==
true );
1371 REQUIRE( app.whilePowerOff() == 0 );
1373 REQUIRE( app.appShutdown() == 0 );
1374 REQUIRE( app.m_syncImageStream ==
nullptr );
1381TEST_CASE(
"ocam2KCtrl startAcquisition resets frame tracking and starts EDT buffers",
"[ocam2KCtrl]" )
1383 ocam2KCtrl_test app;
1388 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1394 app.m_lastImageNumber = 99;
1396 REQUIRE( app.startAcquisition() == 0 );
1397 REQUIRE( app.m_lastImageNumber == -1 );
1398 REQUIRE( g_edtStubState.startImagesCalls == 1 );
1399 REQUIRE( g_edtStubState.lastStartNumBuffs == 6 );
1406TEST_CASE(
"ocam2KCtrl acquireAndCheckValid handles valid, skipped, and corrupt frame numbers",
"[ocam2KCtrl]" )
1408 ocam2KCtrl_test app;
1413 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1418 static_cast<MagAOXAppT &
>( app ).m_powerState = 1;
1419 app.m_powerTargetState = 1;
1420 app.m_modeName =
"science";
1422 SECTION(
"first valid frame initializes the previous-frame tracker" )
1426 g_edtStubState.waitTimeSec = 12;
1427 g_edtStubState.waitTimeNsec = 345;
1428 setStubFrameNumber( 101 );
1430 app.m_lastImageNumber = -1;
1432 REQUIRE( app.acquireAndCheckValid() == 0 );
1433 REQUIRE( app.m_currImageTimestamp.tv_sec == 12 );
1434 REQUIRE( app.m_currImageTimestamp.tv_nsec == 345 );
1435 REQUIRE( app.m_currImageNumber == 101 );
1436 REQUIRE( app.m_lastImageNumber == 101 );
1437 REQUIRE( g_edtStubState.startImageCalls == 1 );
1440 SECTION(
"small skipped-frame gaps request reconfiguration" )
1444 setStubFrameNumber( 15 );
1446 app.m_lastImageNumber = 12;
1447 app.m_nextMode =
"";
1448 app.m_reconfig =
false;
1450 REQUIRE( app.acquireAndCheckValid() == 1 );
1451 REQUIRE( app.m_lastImageNumber == -1 );
1452 REQUIRE( app.m_nextMode ==
"science" );
1453 REQUIRE( app.m_reconfig ==
true );
1456 SECTION(
"large frame-number jumps on powered hardware are treated as corruption" )
1460 setStubFrameNumber( 500 );
1462 app.m_lastImageNumber = 12;
1463 app.m_nextMode =
"";
1464 app.m_reconfig =
false;
1466 REQUIRE( app.acquireAndCheckValid() == 1 );
1467 REQUIRE( app.m_lastImageNumber == -1 );
1468 REQUIRE( app.m_nextMode ==
"science" );
1469 REQUIRE( app.m_reconfig ==
true );
1472 SECTION(
"large frame-number jumps during power loss return an error instead of reconfiguring" )
1476 setStubFrameNumber( 500 );
1478 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1479 app.m_powerTargetState = 0;
1480 app.m_lastImageNumber = 12;
1481 app.m_nextMode =
"";
1482 app.m_reconfig =
false;
1484 REQUIRE( app.acquireAndCheckValid() == -1 );
1485 REQUIRE( app.m_nextMode ==
"" );
1486 REQUIRE( app.m_reconfig ==
false );
1494TEST_CASE(
"ocam2KCtrl loadImageIntoStream uses the OCAM descramble output",
"[ocam2KCtrl]" )
1496 ocam2KCtrl_test app;
1497 std::array<int16_t, 4> rawImage{ 1, 2, 3, 4 };
1498 std::array<int16_t, 4> destImage{ 0, 0, 0, 0 };
1503 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1508 SECTION(
"the non-digital path writes the descrambled frame directly to the destination" )
1511 app.m_digitalBin =
false;
1512 app.m_image_p =
reinterpret_cast<u_char *
>( rawImage.data() );
1514 g_ocam2StubState.imageNumber = 44;
1515 g_ocam2StubState.outputImage = { 10, 20, 30, 40 };
1517 REQUIRE( app.loadImageIntoStream( destImage.data() ) == 0 );
1518 REQUIRE( g_ocam2StubState.lastId == 7 );
1519 REQUIRE( g_ocam2StubState.lastRaw == rawImage.data() );
1520 REQUIRE( g_ocam2StubState.lastOutput == destImage.data() );
1521 REQUIRE( destImage[0] == 10 );
1522 REQUIRE( destImage[1] == 20 );
1523 REQUIRE( destImage[2] == 30 );
1524 REQUIRE( destImage[3] == 40 );
1527 SECTION(
"the digital-binning path descrambles into the work image before filling the output frame" )
1530 app.m_digitalBin =
true;
1531 app.m_digitalBinX = 2;
1532 app.m_digitalBinY = 1;
1535 app.m_image_p =
reinterpret_cast<u_char *
>( rawImage.data() );
1536 app.m_digitalBinWork.resize( 4, 2 );
1538 destImage.fill( 0 );
1539 g_ocam2StubState.imageNumber = 55;
1540 g_ocam2StubState.outputImage = { 7, 7, 7, 7, 7, 7, 7, 7 };
1542 REQUIRE( app.loadImageIntoStream( destImage.data() ) == 0 );
1543 REQUIRE( g_ocam2StubState.lastId == 8 );
1544 REQUIRE( g_ocam2StubState.lastRaw == rawImage.data() );
1545 REQUIRE( g_ocam2StubState.lastOutput == app.m_digitalBinWork.data() );
1546 REQUIRE( destImage[0] == 7 );
1547 REQUIRE( destImage[1] == 7 );
1548 REQUIRE( destImage[2] == 7 );
1549 REQUIRE( destImage[3] == 7 );
1557TEST_CASE(
"ocam2KCtrl serial gain helpers handle valid and invalid responses",
"[ocam2KCtrl]" )
1559 ocam2KCtrl_test app;
1564 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1571 setPoweredOn( app );
1573 SECTION(
"valid EM gain response updates the cached value" )
1577 queueSerialResponse(
"Gain set to 42 \n\n" );
1579 REQUIRE( app.getEMGain() == 0 );
1580 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1581 REQUIRE( g_edtStubState.serialCommands[0] ==
"gain" );
1582 REQUIRE( app.m_emGain == 42 );
1585 SECTION(
"malformed EM gain response returns an error and keeps the previous value" )
1589 queueSerialResponse(
"Gain set to \n\n" );
1591 REQUIRE( app.getEMGain() == -1 );
1592 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1593 REQUIRE( g_edtStubState.serialCommands[0] ==
"gain" );
1594 REQUIRE( app.m_emGain == 77 );
1597 SECTION(
"malformed EM gain responses while powered off return -1 immediately" )
1599 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1600 app.m_powerTargetState = 0;
1603 queueSerialResponse(
"Gain set to \n\n" );
1605 REQUIRE( app.getEMGain() == -1 );
1606 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1607 REQUIRE( g_edtStubState.serialCommands[0] ==
"gain" );
1608 REQUIRE( app.m_emGain == 77 );
1611 SECTION(
"HV trip response forces the cached EM gain back to the safe minimum" )
1615 queueSerialResponse(
"HV trip\n" );
1617 REQUIRE( app.getEMGain() == -1 );
1618 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1619 REQUIRE( g_edtStubState.serialCommands[0] ==
"gain" );
1620 REQUIRE( app.m_emGain == 1 );
1623 SECTION(
"resetEMProtection marks the protection state as reset" )
1625 app.m_protectionReset =
false;
1626 app.m_protectionResetConfirmed = 2;
1628 queueSerialResponse(
"Protection reset\n" );
1630 REQUIRE( app.resetEMProtection() == 0 );
1631 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1632 REQUIRE( g_edtStubState.serialCommands[0] ==
"protection reset" );
1633 REQUIRE( app.m_protectionReset ==
true );
1634 REQUIRE( app.m_protectionResetConfirmed == 0 );
1637 SECTION(
"resetEMProtection returns a software error while still powered" )
1639 app.m_indiP_emProt = pcf::IndiProperty( pcf::IndiProperty::Text );
1640 app.m_indiP_emProt.add( pcf::IndiElement(
"status" ) );
1641 app.m_indiP_emProt[
"status"].set(
"CONFIRM" );
1643 queueSerialResponse(
"", -1 );
1645 REQUIRE( app.resetEMProtection() == -1 );
1646 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1647 REQUIRE( g_edtStubState.serialCommands[0] ==
"protection reset" );
1650 SECTION(
"resetEMProtection returns -1 once power is already off" )
1652 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1653 app.m_powerTargetState = 0;
1655 queueSerialResponse(
"", -1 );
1657 REQUIRE( app.resetEMProtection() == -1 );
1658 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1659 REQUIRE( g_edtStubState.serialCommands[0] ==
"protection reset" );
1662 SECTION(
"setEMGain refuses unsafe requests before protection reset" )
1664 app.m_protectionReset =
false;
1665 app.m_emGainSet = 10;
1667 REQUIRE( app.setEMGain() == 0 );
1668 REQUIRE( g_edtStubState.serialCommands.empty() );
1671 SECTION(
"setEMGain refuses out-of-range requests without serial traffic" )
1673 app.m_protectionReset =
true;
1674 app.m_maxEMGain = 100;
1675 app.m_emGainSet = 200;
1677 REQUIRE( app.setEMGain() == 0 );
1678 REQUIRE( g_edtStubState.serialCommands.empty() );
1681 SECTION(
"setEMGain sends the requested gain after protection reset" )
1683 app.m_protectionReset =
true;
1684 app.m_maxEMGain = 600;
1685 app.m_emGainSet = 25;
1687 queueSerialResponse(
"Gain set to 25 \n\n" );
1689 REQUIRE( app.setEMGain() == 0 );
1690 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1691 REQUIRE( g_edtStubState.serialCommands[0] ==
"gain 25" );
1694 SECTION(
"setEMGain returns -1 on serial failure once power is already off" )
1696 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1697 app.m_powerTargetState = 0;
1698 app.m_protectionReset =
true;
1699 app.m_maxEMGain = 600;
1700 app.m_emGainSet = 25;
1702 queueSerialResponse(
"", -1 );
1704 REQUIRE( app.setEMGain() == -1 );
1705 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1706 REQUIRE( g_edtStubState.serialCommands[0] ==
"gain 25" );
1709 SECTION(
"setEMGain returns a software error on serial failure while still powered" )
1711 app.m_protectionReset =
true;
1712 app.m_maxEMGain = 600;
1713 app.m_emGainSet = 25;
1715 queueSerialResponse(
"", -1 );
1717 REQUIRE( app.setEMGain() == -1 );
1718 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1719 REQUIRE( g_edtStubState.serialCommands[0] ==
"gain 25" );
1727TEST_CASE(
"ocam2KCtrl serial setter commands send the expected sequence",
"[ocam2KCtrl]" )
1729 ocam2KCtrl_test app;
1734 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1740 setPoweredOn( app );
1742 SECTION(
"setFPS sends the requested rate and queues a reconfiguration" )
1744 app.m_synchro =
false;
1745 app.m_fpsSet = 250.5f;
1746 app.m_modeName =
"science";
1747 app.m_nextMode =
"";
1748 app.m_reconfig =
false;
1750 queueSerialResponse(
"fps updated\n" );
1752 REQUIRE( app.setFPS() == 0 );
1753 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1754 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 250.500000" );
1755 REQUIRE( app.m_nextMode ==
"science" );
1756 REQUIRE( app.m_reconfig ==
true );
1759 SECTION(
"setFPS returns a software error on serial failure while still powered" )
1761 app.m_synchro =
false;
1762 app.m_fpsSet = 250.5f;
1764 queueSerialResponse(
"", -1 );
1766 REQUIRE( app.setFPS() == -1 );
1767 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1768 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 250.500000" );
1771 SECTION(
"setFPS uses the sync-device property path when synchro is enabled" )
1773 app.m_synchro =
true;
1774 app.m_fpsSet = 88.5f;
1776 REQUIRE( app.setFPS() == 0 );
1777 REQUIRE( g_edtStubState.serialCommands.empty() );
1780 SECTION(
"setFPS returns -1 on serial failure once power is already off" )
1782 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1783 app.m_powerTargetState = 0;
1784 app.m_synchro =
false;
1785 app.m_fpsSet = 250.5f;
1787 queueSerialResponse(
"", -1 );
1789 REQUIRE( app.setFPS() == -1 );
1790 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1791 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 250.500000" );
1794 SECTION(
"setSynchro off uses the OCAM serial path for both synchro and FPS" )
1796 app.m_synchroSet =
false;
1797 app.m_fpsSet = 175.0f;
1798 app.m_indiP_synchro = pcf::IndiProperty( pcf::IndiProperty::Switch );
1799 app.m_indiP_synchro.add( pcf::IndiElement(
"toggle", pcf::IndiElement::On ) );
1801 queueSerialResponse(
"fps max\n" );
1802 queueSerialResponse(
"synchro off\n" );
1803 queueSerialResponse(
"fps restored\n" );
1805 REQUIRE( app.setSynchro() == 0 );
1806 REQUIRE( g_edtStubState.serialCommands.size() == 3 );
1807 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 0" );
1808 REQUIRE( g_edtStubState.serialCommands[1] ==
"synchro off" );
1809 REQUIRE( g_edtStubState.serialCommands[2] ==
"fps 175.000000" );
1810 REQUIRE( app.m_synchro ==
false );
1813 SECTION(
"setSynchro on updates the local synchro state and leaves FPS to the sync device" )
1815 app.m_synchroSet =
true;
1816 app.m_fpsSet = 175.0f;
1817 app.m_indiP_synchro = pcf::IndiProperty( pcf::IndiProperty::Switch );
1818 app.m_indiP_synchro.add( pcf::IndiElement(
"toggle", pcf::IndiElement::Off ) );
1820 queueSerialResponse(
"fps max\n" );
1821 queueSerialResponse(
"synchro on\n" );
1823 REQUIRE( app.setSynchro() == 0 );
1824 REQUIRE( g_edtStubState.serialCommands.size() == 2 );
1825 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 0" );
1826 REQUIRE( g_edtStubState.serialCommands[1] ==
"synchro on" );
1827 REQUIRE( app.m_synchro ==
true );
1830 SECTION(
"setSynchro returns a software error when the initial fps-max command fails on powered hardware" )
1832 app.m_synchroSet =
false;
1833 app.m_fpsSet = 175.0f;
1835 queueSerialResponse(
"", -1 );
1837 REQUIRE( app.setSynchro() == -1 );
1838 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1839 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 0" );
1842 SECTION(
"setSynchro returns a software error when the synchro command fails on powered hardware" )
1844 app.m_synchroSet =
true;
1845 app.m_fpsSet = 175.0f;
1847 queueSerialResponse(
"fps max\n" );
1848 queueSerialResponse(
"", -1 );
1850 REQUIRE( app.setSynchro() == -1 );
1851 REQUIRE( g_edtStubState.serialCommands.size() == 2 );
1852 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 0" );
1853 REQUIRE( g_edtStubState.serialCommands[1] ==
"synchro on" );
1856 SECTION(
"setSynchro returns -1 when the initial fps-max command fails while powered off" )
1858 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1859 app.m_powerTargetState = 0;
1860 app.m_synchroSet =
false;
1861 app.m_fpsSet = 175.0f;
1863 queueSerialResponse(
"", -1 );
1865 REQUIRE( app.setSynchro() == -1 );
1866 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
1867 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 0" );
1870 SECTION(
"setSynchro returns -1 when the synchro command fails while powered off" )
1872 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
1873 app.m_powerTargetState = 0;
1874 app.m_synchroSet =
true;
1875 app.m_fpsSet = 175.0f;
1877 queueSerialResponse(
"fps max\n" );
1878 queueSerialResponse(
"", -1 );
1880 REQUIRE( app.setSynchro() == -1 );
1881 REQUIRE( g_edtStubState.serialCommands.size() == 2 );
1882 REQUIRE( g_edtStubState.serialCommands[0] ==
"fps 0" );
1883 REQUIRE( g_edtStubState.serialCommands[1] ==
"synchro on" );
1891TEST_CASE(
"ocam2KCtrl configureAcquisition handles valid and invalid OCAM modes",
"[ocam2KCtrl]" )
1893 ocam2KCtrl_test app;
1899 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
1904 setPoweredOn( app );
1906 app.m_configDir =
"/tmp";
1907 app.m_ocamDescrambleFile =
"ocam_descramble_stub.txt";
1908 app.m_modeName =
"science";
1914 app.m_cameraModes[
"science"] = config;
1916 SECTION(
"configureAcquisition sets up the SDK, digital binning, and sync stream" )
1918 app.m_raw_height = 121;
1919 app.m_fpsSet = 50.0f;
1920 app.m_synchroSet =
false;
1923 queueSerialResponse(
"mode set\n" );
1924 queueSerialResponse(
"fps set\n" );
1925 queueSerialResponse(
"fps max\n" );
1926 queueSerialResponse(
"synchro off\n" );
1927 queueSerialResponse(
"fps restored\n" );
1929 REQUIRE( app.configureAcquisition() == 0 );
1930 REQUIRE( g_edtStubState.serialCommands.size() == 5 );
1931 REQUIRE( g_edtStubState.serialCommands[0] ==
"mode science" );
1932 REQUIRE( g_edtStubState.serialCommands[1] ==
"fps 50.000000" );
1933 REQUIRE( g_edtStubState.serialCommands[2] ==
"fps 0" );
1934 REQUIRE( g_edtStubState.serialCommands[3] ==
"synchro off" );
1935 REQUIRE( g_edtStubState.serialCommands[4] ==
"fps 50.000000" );
1936 REQUIRE( g_ocam2StubState.exitCalls == 1 );
1937 REQUIRE( g_ocam2StubState.lastExitId == 9 );
1938 REQUIRE( g_ocam2StubState.lastInitMode ==
OCAM2_NORMAL );
1939 REQUIRE( g_ocam2StubState.lastDescrambleFile ==
"/tmp/ocam_descramble_stub.txt" );
1940 REQUIRE( app.m_ocam2_id == 1 );
1941 REQUIRE( app.m_currentROI.x == Approx( 119.5 ) );
1942 REQUIRE( app.m_currentROI.y == Approx( 119.5 ) );
1943 REQUIRE( app.m_currentROI.w == Approx( 240.0 ) );
1944 REQUIRE( app.m_currentROI.h == Approx( 240.0 ) );
1945 REQUIRE( app.m_currentROI.bin_x == 1 );
1946 REQUIRE( app.m_currentROI.bin_y == 1 );
1947 REQUIRE( app.m_digitalBin ==
true );
1948 REQUIRE( app.m_width == 120 );
1949 REQUIRE( app.m_height == 80 );
1950 REQUIRE( app.m_dataType == _DATATYPE_UINT16 );
1951 REQUIRE( app.m_syncImageStream !=
nullptr );
1955 SECTION(
"configureAcquisition keeps the full OCAM frame size when digital binning is disabled" )
1957 app.m_raw_height = 121;
1958 app.m_fpsSet = 0.0f;
1959 app.m_synchroSet =
false;
1960 app.m_cameraModes[
"science"].m_digitalBinX = 1;
1961 app.m_cameraModes[
"science"].m_digitalBinY = 1;
1963 queueSerialResponse(
"mode set\n" );
1964 queueSerialResponse(
"fps max\n" );
1965 queueSerialResponse(
"synchro off\n" );
1966 queueSerialResponse(
"fps restored\n" );
1968 REQUIRE( app.configureAcquisition() == 0 );
1969 REQUIRE( g_ocam2StubState.lastInitMode ==
OCAM2_NORMAL );
1970 REQUIRE( app.m_digitalBin ==
false );
1971 REQUIRE( app.m_width == 240 );
1972 REQUIRE( app.m_height == 240 );
1975 SECTION(
"configureAcquisition selects OCAM binning mode for 62-row raw frames" )
1977 app.m_raw_height = 62;
1978 app.m_fpsSet = 0.0f;
1979 app.m_synchroSet =
false;
1980 app.m_cameraModes[
"science"].m_digitalBinX = 1;
1981 app.m_cameraModes[
"science"].m_digitalBinY = 1;
1983 queueSerialResponse(
"mode set\n" );
1984 queueSerialResponse(
"fps max\n" );
1985 queueSerialResponse(
"synchro off\n" );
1986 queueSerialResponse(
"fps restored\n" );
1988 REQUIRE( app.configureAcquisition() == 0 );
1990 REQUIRE( app.m_width == 120 );
1991 REQUIRE( app.m_height == 120 );
1994 SECTION(
"configureAcquisition selects OCAM 1x3 binning mode for 41-row raw frames" )
1996 app.m_raw_height = 41;
1997 app.m_fpsSet = 0.0f;
1998 app.m_synchroSet =
false;
1999 app.m_cameraModes[
"science"].m_digitalBinX = 1;
2000 app.m_cameraModes[
"science"].m_digitalBinY = 1;
2002 queueSerialResponse(
"mode set\n" );
2003 queueSerialResponse(
"fps max\n" );
2004 queueSerialResponse(
"synchro off\n" );
2005 queueSerialResponse(
"fps restored\n" );
2007 REQUIRE( app.configureAcquisition() == 0 );
2009 REQUIRE( app.m_width == 240 );
2010 REQUIRE( app.m_height == 80 );
2013 SECTION(
"configureAcquisition selects OCAM 1x4 binning mode for 31-row raw frames" )
2015 app.m_raw_height = 31;
2016 app.m_fpsSet = 0.0f;
2017 app.m_synchroSet =
false;
2018 app.m_cameraModes[
"science"].m_digitalBinX = 1;
2019 app.m_cameraModes[
"science"].m_digitalBinY = 1;
2021 queueSerialResponse(
"mode set\n" );
2022 queueSerialResponse(
"fps max\n" );
2023 queueSerialResponse(
"synchro off\n" );
2024 queueSerialResponse(
"fps restored\n" );
2026 REQUIRE( app.configureAcquisition() == 0 );
2028 REQUIRE( app.m_width == 240 );
2029 REQUIRE( app.m_height == 60 );
2032 SECTION(
"configureAcquisition reports a set-mode serial failure on powered hardware" )
2034 app.m_raw_height = 121;
2035 app.m_fpsSet = 0.0f;
2036 app.m_synchroSet =
false;
2038 queueSerialResponse(
"", -1 );
2040 REQUIRE( app.configureAcquisition() == -1 );
2041 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
2042 REQUIRE( g_edtStubState.serialCommands[0] ==
"mode science" );
2045 SECTION(
"configureAcquisition returns -1 quietly once power is already off during the set-mode command" )
2047 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2048 app.m_powerTargetState = 0;
2049 app.m_raw_height = 121;
2050 app.m_fpsSet = 0.0f;
2051 app.m_synchroSet =
false;
2053 queueSerialResponse(
"", -1 );
2055 REQUIRE( app.configureAcquisition() == -1 );
2056 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
2057 REQUIRE( g_edtStubState.serialCommands[0] ==
"mode science" );
2060 SECTION(
"configureAcquisition logs but continues when setSynchro fails" )
2062 app.m_raw_height = 121;
2063 app.m_fpsSet = 0.0f;
2064 app.m_synchroSet =
false;
2065 app.m_cameraModes[
"science"].m_digitalBinX = 1;
2066 app.m_cameraModes[
"science"].m_digitalBinY = 1;
2068 queueSerialResponse(
"mode set\n" );
2069 queueSerialResponse(
"fps max\n" );
2070 queueSerialResponse(
"", -1 );
2072 REQUIRE( app.configureAcquisition() == 0 );
2073 REQUIRE( g_edtStubState.serialCommands.size() == 3 );
2074 REQUIRE( g_edtStubState.serialCommands[0] ==
"mode science" );
2075 REQUIRE( g_edtStubState.serialCommands[1] ==
"fps 0" );
2076 REQUIRE( g_edtStubState.serialCommands[2] ==
"synchro off" );
2077 REQUIRE( g_ocam2StubState.lastInitMode ==
OCAM2_NORMAL );
2080 SECTION(
"configureAcquisition returns -1 if OCAM initialization fails after power is lost" )
2082 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2083 app.m_powerTargetState = 0;
2084 app.m_raw_height = 121;
2085 app.m_fpsSet = 0.0f;
2086 app.m_synchroSet =
false;
2087 app.m_cameraModes[
"science"].m_digitalBinX = 1;
2088 app.m_cameraModes[
"science"].m_digitalBinY = 1;
2091 queueSerialResponse(
"mode set\n" );
2092 queueSerialResponse(
"fps max\n" );
2093 queueSerialResponse(
"synchro off\n" );
2094 queueSerialResponse(
"fps restored\n" );
2096 REQUIRE( app.configureAcquisition() == -1 );
2097 REQUIRE( app.m_syncImageStream ==
nullptr );
2100 SECTION(
"configureAcquisition returns -1 if OCAM initialization fails on powered hardware" )
2102 app.m_raw_height = 121;
2103 app.m_fpsSet = 0.0f;
2104 app.m_synchroSet =
false;
2105 app.m_cameraModes[
"science"].m_digitalBinX = 1;
2106 app.m_cameraModes[
"science"].m_digitalBinY = 1;
2109 queueSerialResponse(
"mode set\n" );
2110 queueSerialResponse(
"fps max\n" );
2111 queueSerialResponse(
"synchro off\n" );
2112 queueSerialResponse(
"fps restored\n" );
2114 REQUIRE( app.configureAcquisition() == -1 );
2115 REQUIRE( app.m_syncImageStream ==
nullptr );
2118 SECTION(
"configureAcquisition rejects unsupported raw frame heights" )
2120 app.m_raw_height = 100;
2121 app.m_fpsSet = 0.0f;
2122 app.m_synchroSet =
false;
2124 queueSerialResponse(
"mode set\n" );
2125 queueSerialResponse(
"fps max\n" );
2126 queueSerialResponse(
"synchro off\n" );
2127 queueSerialResponse(
"fps restored\n" );
2129 REQUIRE( app.configureAcquisition() == -1 );
2130 REQUIRE( g_edtStubState.serialCommands.size() == 4 );
2131 REQUIRE( g_edtStubState.serialCommands[0] ==
"mode science" );
2132 REQUIRE( g_edtStubState.serialCommands[1] ==
"fps 0" );
2133 REQUIRE( g_edtStubState.serialCommands[2] ==
"synchro off" );
2134 REQUIRE( g_edtStubState.serialCommands[3] ==
"fps 0.000000" );
2135 REQUIRE( g_ocam2StubState.exitCalls == 0 );
2136 REQUIRE( g_ocam2StubState.lastDescrambleFile.empty() );
2137 REQUIRE( app.m_syncImageStream ==
nullptr );
2145TEST_CASE(
"ocam2KCtrl INDI callbacks update local state",
"[ocam2KCtrl]" )
2147 ocam2KCtrl_test app;
2152 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
2153 XWCTEST_DOXYGEN_REF( ocam2KCtrl::newCallBack_m_indiP_emProtReset( pcf::IndiProperty() ) );
2158 setPoweredOn( app );
2160 SECTION(
"EM protection reset callback requires a confirmation before issuing the serial reset" )
2162 pcf::IndiProperty
ipRecv( pcf::IndiProperty::Switch );
2164 app.m_indiP_emProtReset.setDevice(
"ocam2KCtrl" );
2165 app.m_indiP_emProtReset.setName(
"emProtReset" );
2167 ipRecv.setDevice(
"ocam2KCtrl" );
2168 ipRecv.setName(
"emProtReset" );
2169 ipRecv.add( pcf::IndiElement(
"request", pcf::IndiElement::On ) );
2171 REQUIRE( app.newCallBack_m_indiP_emProtReset(
ipRecv ) == 0 );
2172 REQUIRE( app.m_protectionResetConfirmed == 1 );
2173 REQUIRE( g_edtStubState.serialCommands.empty() );
2175 queueSerialResponse(
"Protection reset\n" );
2177 REQUIRE( app.newCallBack_m_indiP_emProtReset(
ipRecv ) == 0 );
2178 REQUIRE( g_edtStubState.serialCommands.size() == 1 );
2179 REQUIRE( g_edtStubState.serialCommands[0] ==
"protection reset" );
2180 REQUIRE( app.m_protectionReset ==
true );
2181 REQUIRE( app.m_protectionResetConfirmed == 0 );
2184 SECTION(
"EM protection reset callback ignores requests while power is off" )
2186 pcf::IndiProperty
ipRecv( pcf::IndiProperty::Switch );
2188 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2189 app.m_powerTargetState = 0;
2190 app.m_indiP_emProtReset.setDevice(
"ocam2KCtrl" );
2191 app.m_indiP_emProtReset.setName(
"emProtReset" );
2192 ipRecv.setDevice(
"ocam2KCtrl" );
2193 ipRecv.setName(
"emProtReset" );
2194 ipRecv.add( pcf::IndiElement(
"request", pcf::IndiElement::On ) );
2196 REQUIRE( app.newCallBack_m_indiP_emProtReset(
ipRecv ) == 0 );
2197 REQUIRE( app.m_protectionResetConfirmed == 0 );
2198 REQUIRE( g_edtStubState.serialCommands.empty() );
2201 SECTION(
"EM protection reset callback rejects the wrong property name" )
2203 pcf::IndiProperty
ipRecv( pcf::IndiProperty::Switch );
2205 app.m_indiP_emProtReset.setDevice(
"ocam2KCtrl" );
2206 app.m_indiP_emProtReset.setName(
"emProtReset" );
2207 ipRecv.setDevice(
"ocam2KCtrl" );
2208 ipRecv.setName(
"wrongName" );
2209 ipRecv.add( pcf::IndiElement(
"request", pcf::IndiElement::On ) );
2211 REQUIRE( app.newCallBack_m_indiP_emProtReset(
ipRecv ) == -1 );
2214 SECTION(
"EM protection reset callback ignores requests without the request element or with it off" )
2216 pcf::IndiProperty missingReq( pcf::IndiProperty::Switch );
2217 pcf::IndiProperty offReq( pcf::IndiProperty::Switch );
2219 app.m_indiP_emProtReset.setDevice(
"ocam2KCtrl" );
2220 app.m_indiP_emProtReset.setName(
"emProtReset" );
2222 missingReq.setDevice(
"ocam2KCtrl" );
2223 missingReq.setName(
"emProtReset" );
2224 offReq.setDevice(
"ocam2KCtrl" );
2225 offReq.setName(
"emProtReset" );
2226 offReq.add( pcf::IndiElement(
"request", pcf::IndiElement::Off ) );
2228 REQUIRE( app.newCallBack_m_indiP_emProtReset( missingReq ) == 0 );
2229 REQUIRE( app.newCallBack_m_indiP_emProtReset( offReq ) == 0 );
2230 REQUIRE( app.m_protectionResetConfirmed == 0 );
2231 REQUIRE( g_edtStubState.serialCommands.empty() );
2234 SECTION(
"sync frequency callback queues a reconfiguration when synchro mode changes the effective FPS" )
2236 pcf::IndiProperty
ipRecv( pcf::IndiProperty::Number );
2238 app.m_indiP_syncFreq.setDevice(
"syncDevice" );
2239 app.m_indiP_syncFreq.setName(
"C1freq" );
2241 ipRecv.setDevice(
"syncDevice" );
2242 ipRecv.setName(
"C1freq" );
2243 ipRecv.add( pcf::IndiElement(
"current" ) );
2244 ipRecv[
"current"] = 123.4;
2246 app.m_synchro =
true;
2248 app.m_modeName =
"science";
2249 app.m_nextMode =
"";
2250 app.m_reconfig =
false;
2252 REQUIRE( app.setCallBack_m_indiP_syncFreq(
ipRecv ) == 0 );
2253 REQUIRE( app.m_syncFreq == Approx( 123.4f ) );
2254 REQUIRE( app.m_fps == Approx( 123.4f ) );
2255 REQUIRE( app.m_nextMode ==
"science" );
2256 REQUIRE( app.m_reconfig ==
true );
2259 SECTION(
"sync frequency callback rejects updates that do not include the current element" )
2261 pcf::IndiProperty
ipRecv( pcf::IndiProperty::Number );
2263 app.m_indiP_syncFreq.setDevice(
"syncDevice" );
2264 app.m_indiP_syncFreq.setName(
"C1freq" );
2265 ipRecv.setDevice(
"syncDevice" );
2266 ipRecv.setName(
"C1freq" );
2268 REQUIRE( app.setCallBack_m_indiP_syncFreq(
ipRecv ) == -1 );
2276TEST_CASE(
"ocam2KCtrl static INDI callback wrappers forward requests to the instance",
"[ocam2KCtrl]" )
2278 ocam2KCtrl_test app;
2283 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
2284 XWCTEST_DOXYGEN_REF( ocam2KCtrl::st_newCallBack_m_indiP_emProtReset(
nullptr, pcf::IndiProperty() ) );
2285 XWCTEST_DOXYGEN_REF( ocam2KCtrl::st_setCallBack_m_indiP_syncFreq(
nullptr, pcf::IndiProperty() ) );
2289 setPoweredOn( app );
2291 SECTION(
"the new-property wrapper forwards EM protection reset requests" )
2293 pcf::IndiProperty
ipRecv( pcf::IndiProperty::Switch );
2295 app.m_indiP_emProtReset.setDevice(
"ocam2KCtrl" );
2296 app.m_indiP_emProtReset.setName(
"emProtReset" );
2298 ipRecv.setDevice(
"ocam2KCtrl" );
2299 ipRecv.setName(
"emProtReset" );
2300 ipRecv.add( pcf::IndiElement(
"request", pcf::IndiElement::On ) );
2302 REQUIRE( ocam2KCtrl::st_newCallBack_m_indiP_emProtReset( &app,
ipRecv ) == 0 );
2303 REQUIRE( app.m_protectionResetConfirmed == 1 );
2306 SECTION(
"the set-property wrapper forwards sync frequency updates" )
2308 pcf::IndiProperty
ipRecv( pcf::IndiProperty::Number );
2310 app.m_indiP_syncFreq.setDevice(
"syncDevice" );
2311 app.m_indiP_syncFreq.setName(
"C1freq" );
2313 ipRecv.setDevice(
"syncDevice" );
2314 ipRecv.setName(
"C1freq" );
2315 ipRecv.add( pcf::IndiElement(
"current" ) );
2316 ipRecv[
"current"] = 222.5;
2318 app.m_synchro =
true;
2320 app.m_modeName =
"science";
2322 REQUIRE( ocam2KCtrl::st_setCallBack_m_indiP_syncFreq( &app,
ipRecv ) == 0 );
2323 REQUIRE( app.m_syncFreq == Approx( 222.5f ) );
2324 REQUIRE( app.m_fps == Approx( 222.5f ) );
2325 REQUIRE( app.m_nextMode ==
"science" );
2326 REQUIRE( app.m_reconfig ==
true );
2334TEST_CASE(
"ocam2KCtrl telemetry wrappers record snapshots and stale intervals",
"[ocam2KCtrl]" )
2336 ocam2KCtrl_test app;
2341 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
2349 app.m_temps.CCD = 20.1f;
2350 app.m_temps.CPU = 41.0f;
2351 app.m_temps.POWER = 34.0f;
2352 app.m_temps.BIAS = 47.0f;
2353 app.m_temps.WATER = 24.2f;
2354 app.m_temps.LEFT = 33.0f;
2355 app.m_temps.RIGHT = 38.0f;
2356 app.m_temps.COOLING_POWER = 102.0f;
2357 app.m_modeName =
"science";
2358 app.m_currentROI.x = 119.5;
2359 app.m_currentROI.y = 119.5;
2360 app.m_currentROI.w = 240.0;
2361 app.m_currentROI.h = 240.0;
2362 app.m_currentROI.bin_x = 1;
2363 app.m_currentROI.bin_y = 1;
2365 app.m_emGain = 25.0f;
2366 app.m_ccdTemp = 20.1f;
2367 app.m_ccdTempSetpt = 20.5f;
2368 app.m_tempControlStatus =
true;
2369 app.m_tempControlOnTarget =
true;
2370 app.m_tempControlStatusStr =
"ON TARGET";
2371 app.m_shutterStatus =
"open";
2372 app.m_shutterState = 1;
2373 app.m_synchro =
false;
2381 SECTION(
"recordTelem overloads forward directly to the per-type telemetry recorders" )
2397 SECTION(
"checkRecordTimes emits all telemetry records when their intervals have elapsed" )
2403 app.m_maxInterval = 10.0;
2404 static_cast<MagAOXAppT &
>( app ).m_loopPause = 0;
2406 REQUIRE( app.checkRecordTimes() == 0 );
2417TEST_CASE(
"ocam2KCtrl appLogic handles connection and housekeeping flow",
"[ocam2KCtrl]" )
2422 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
2427 SECTION(
"NOTCONNECTED returns early during power loss" )
2429 ocam2KCtrl_test app;
2432 app.m_temps.CCD = 12.0f;
2434 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2435 app.m_powerTargetState = 0;
2437 fgThreadScope fgThread( app );
2439 REQUIRE( app.appLogic() == 0 );
2441 REQUIRE( app.m_temps.CCD == Approx( -999.0f ) );
2442 REQUIRE( g_edtStubState.serialCommands.empty() );
2445 SECTION(
"POWEROFF falls through to the final return without camera traffic" )
2447 ocam2KCtrl_test app;
2449 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2450 app.m_powerTargetState = 0;
2452 fgThreadScope fgThread( app );
2454 REQUIRE( app.appLogic() == 0 );
2456 REQUIRE( g_edtStubState.serialCommands.empty() );
2459 SECTION(
"NOTCONNECTED success falls through the READY housekeeping path in the same loop" )
2461 ocam2KCtrl_test app;
2463 setPoweredOn( app );
2466 app.m_fpsSet = 0.0f;
2467 app.m_poweredOn =
true;
2468 app.m_ccdTempSetpt = 18.5f;
2469 app.m_tempControlOnTarget =
false;
2470 app.m_indiP_emProt = pcf::IndiProperty( pcf::IndiProperty::Text );
2471 app.m_indiP_emProt.setDevice(
"ocam2KCtrl" );
2472 app.m_indiP_emProt.setName(
"emProtReset" );
2473 app.m_indiP_emProt.add( pcf::IndiElement(
"status" ) );
2474 app.m_indiP_emProt[
"status"].set(
"CONFIRMED" );
2475 app.m_protectionResetConfirmed = 1;
2476 app.m_protectionResetReqTime = mx::sys::get_curr_time() - 11.0;
2478 queueSerialResponse(
"camera online\n" );
2479 queueSerialResponse(
"fps [150.5] Hz\n" );
2480 queueSerialResponse(
"setpoint updated\n" );
2481 queueSerialResponse(
"fps max\n" );
2482 queueSerialResponse(
"synchro off\n" );
2483 queueSerialResponse(
"fps restored\n" );
2484 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
2485 "SET[185]\nCooling Power [102]mW.\n\n" );
2486 queueSerialResponse(
"fps [150.5] Hz\n" );
2487 queueSerialResponse(
"Gain set to 42 \n\n" );
2489 fgThreadScope fgThread( app );
2491 REQUIRE( app.appLogic() == 0 );
2493 REQUIRE( app.m_fps == Approx( 150.5f ) );
2494 REQUIRE( app.m_poweredOn ==
false );
2495 REQUIRE( app.m_synchroSet ==
false );
2496 REQUIRE( app.m_protectionResetConfirmed == 0 );
2497 REQUIRE( app.m_shutdown == 1 );
2498 REQUIRE( g_edtStubState.serialCommands ==
2499 std::vector<std::string>{
2500 "fps",
"fps",
"temp 18.500000",
"fps 0",
"synchro off",
"fps 0.000000",
"temp",
"fps",
"gain" } );
2503 SECTION(
"NOTCONNECTED returns after a failed connectivity probe while power remains on" )
2505 ocam2KCtrl_test app;
2507 setPoweredOn( app );
2510 queueSerialResponse(
"", -1 );
2512 fgThreadScope fgThread( app );
2514 REQUIRE( app.appLogic() == 0 );
2516 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"fps" } );
2519 SECTION(
"CONNECTED with a requested FPS transitions through OPERATING before housekeeping" )
2521 ocam2KCtrl_test app;
2523 setPoweredOn( app );
2526 app.m_fpsSet = 10.0f;
2527 app.m_poweredOn =
false;
2528 app.m_protectionResetConfirmed = 0;
2530 queueSerialResponse(
"fps [150.5] Hz\n" );
2531 queueSerialResponse(
"fps max\n" );
2532 queueSerialResponse(
"synchro off\n" );
2533 queueSerialResponse(
"fps 10.000000 restored\n" );
2534 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
2535 "SET[205]\nCooling Power [102]mW.\n\n" );
2536 queueSerialResponse(
"fps [150.5] Hz\n" );
2537 queueSerialResponse(
"Gain set to 42 \n\n" );
2539 fgThreadScope fgThread( app );
2541 REQUIRE( app.appLogic() == 0 );
2543 REQUIRE( app.m_shutdown == 1 );
2544 REQUIRE( g_edtStubState.serialCommands ==
2545 std::vector<std::string>{
"fps",
"fps 0",
"synchro off",
"fps 10.000000",
"temp",
"fps",
"gain" } );
2548 SECTION(
"CONNECTED enters ERROR when getFPS fails on powered hardware" )
2550 ocam2KCtrl_test app;
2552 setPoweredOn( app );
2555 queueSerialResponse(
"", -1 );
2557 fgThreadScope fgThread( app );
2559 REQUIRE( app.appLogic() == 0 );
2561 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"fps" } );
2564 SECTION(
"CONNECTED returns quietly if getFPS fails after power is lost" )
2566 ocam2KCtrl_test app;
2568 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2569 app.m_powerTargetState = 0;
2572 queueSerialResponse(
"", -1 );
2574 fgThreadScope fgThread( app );
2576 REQUIRE( app.appLogic() == 0 );
2578 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"fps" } );
2581 SECTION(
"CONNECTED returns quietly when setTempSetPt fails after power is lost" )
2583 ocam2KCtrl_test app;
2585 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2586 app.m_powerTargetState = 0;
2588 app.m_fpsSet = 0.0f;
2589 app.m_poweredOn =
true;
2590 app.m_ccdTempSetpt = 18.5f;
2592 queueSerialResponse(
"fps [150.5] Hz\n" );
2593 queueSerialResponse(
"", -1 );
2595 fgThreadScope fgThread( app );
2597 REQUIRE( app.appLogic() == 0 );
2599 REQUIRE( app.m_poweredOn ==
false );
2600 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"fps",
"temp 18.500000" } );
2603 SECTION(
"CONNECTED returns a software error when setTempSetPt fails on powered hardware" )
2605 ocam2KCtrl_test app;
2607 setPoweredOn( app );
2609 app.m_fpsSet = 0.0f;
2610 app.m_poweredOn =
true;
2611 app.m_ccdTempSetpt = 18.5f;
2613 queueSerialResponse(
"fps [150.5] Hz\n" );
2614 queueSerialResponse(
"", -1 );
2616 fgThreadScope fgThread( app );
2618 REQUIRE( app.appLogic() == 0 );
2620 REQUIRE( app.m_poweredOn ==
false );
2621 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"fps",
"temp 18.500000" } );
2624 SECTION(
"CONNECTED logs but continues when setSynchro fails during the initial connect" )
2626 ocam2KCtrl_test app;
2628 setPoweredOn( app );
2630 app.m_fpsSet = 0.0f;
2631 app.m_poweredOn =
false;
2633 queueSerialResponse(
"fps [150.5] Hz\n" );
2634 queueSerialResponse(
"fps max\n" );
2635 queueSerialResponse(
"", -1 );
2636 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
2637 "SET[205]\nCooling Power [102]mW.\n\n" );
2638 queueSerialResponse(
"fps [150.5] Hz\n" );
2639 queueSerialResponse(
"Gain set to 42 \n\n" );
2641 fgThreadScope fgThread( app );
2643 REQUIRE( app.appLogic() == 0 );
2645 REQUIRE( app.m_shutdown == 1 );
2646 REQUIRE( g_edtStubState.serialCommands ==
2647 std::vector<std::string>{
"fps",
"fps 0",
"synchro off",
"temp",
"fps",
"gain" } );
2650 SECTION(
"READY expires stale protection resets and completes housekeeping before telemetry shutdown" )
2652 ocam2KCtrl_test app;
2654 setPoweredOn( app );
2657 app.m_protectionResetConfirmed = 1;
2658 app.m_protectionResetReqTime = mx::sys::get_curr_time() - 11.0;
2659 app.m_indiP_emProt = pcf::IndiProperty( pcf::IndiProperty::Text );
2660 app.m_indiP_emProt.add( pcf::IndiElement(
"status" ) );
2661 app.m_indiP_emProt[
"status"].set(
"CONFIRM" );
2663 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
2664 "SET[205]\nCooling Power [102]mW.\n\n" );
2665 queueSerialResponse(
"fps [150.5] Hz\n" );
2666 queueSerialResponse(
"Gain set to 42 \n\n" );
2668 fgThreadScope fgThread( app );
2670 REQUIRE( app.appLogic() == 0 );
2671 REQUIRE( app.m_protectionResetConfirmed == 0 );
2672 REQUIRE( app.m_shutdown == 1 );
2674 REQUIRE( app.m_temps.CCD == Approx( 20.4f ) );
2675 REQUIRE( app.m_fps == Approx( 150.5f ) );
2676 REQUIRE( app.m_emGain == 42 );
2679 SECTION(
"READY returns immediately if the INDI mutex is already locked elsewhere" )
2681 ocam2KCtrl_test app;
2683 setPoweredOn( app );
2686 std::unique_lock<std::mutex> hold( app.m_indiMutex );
2687 fgThreadScope fgThread( app );
2689 REQUIRE( app.appLogic() == 0 );
2690 REQUIRE( g_edtStubState.serialCommands.empty() );
2693 SECTION(
"READY moves to ERROR when temperature polling fails on powered hardware" )
2695 ocam2KCtrl_test app;
2697 setPoweredOn( app );
2700 queueSerialResponse(
"", -1 );
2702 fgThreadScope fgThread( app );
2704 REQUIRE( app.appLogic() == 0 );
2706 REQUIRE( app.m_temps.CCD == Approx( -999.0f ) );
2707 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"temp" } );
2710 SECTION(
"READY returns quietly when temperature polling fails after power is lost" )
2712 ocam2KCtrl_test app;
2714 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2715 app.m_powerTargetState = 0;
2718 queueSerialResponse(
"", -1 );
2720 fgThreadScope fgThread( app );
2722 REQUIRE( app.appLogic() == 0 );
2724 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"temp" } );
2727 SECTION(
"READY moves to ERROR when FPS polling fails after temperatures succeed" )
2729 ocam2KCtrl_test app;
2731 setPoweredOn( app );
2734 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
2735 "SET[205]\nCooling Power [102]mW.\n\n" );
2736 queueSerialResponse(
"", -1 );
2738 fgThreadScope fgThread( app );
2740 REQUIRE( app.appLogic() == 0 );
2742 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"temp",
"fps" } );
2745 SECTION(
"READY returns quietly when FPS polling fails after power is lost" )
2747 ocam2KCtrl_test app;
2749 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2750 app.m_powerTargetState = 0;
2753 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
2754 "SET[205]\nCooling Power [102]mW.\n\n" );
2755 queueSerialResponse(
"", -1 );
2757 fgThreadScope fgThread( app );
2759 REQUIRE( app.appLogic() == 0 );
2761 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"temp",
"fps" } );
2764 SECTION(
"READY returns quietly when EM gain polling fails after power is lost" )
2766 ocam2KCtrl_test app;
2768 static_cast<MagAOXAppT &
>( app ).m_powerState = 0;
2769 app.m_powerTargetState = 0;
2772 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
2773 "SET[205]\nCooling Power [102]mW.\n\n" );
2774 queueSerialResponse(
"fps [150.5] Hz\n" );
2775 queueSerialResponse(
"", -1 );
2777 fgThreadScope fgThread( app );
2779 REQUIRE( app.appLogic() == 0 );
2781 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"temp",
"fps",
"gain" } );
2784 SECTION(
"READY moves to ERROR when EM gain polling fails on powered hardware" )
2786 ocam2KCtrl_test app;
2788 setPoweredOn( app );
2791 queueSerialResponse(
"Temperatures : CCD[20.4] CPU[41] POWER[34] BIAS[47] WATER[24.2] LEFT[33] RIGHT[38] "
2792 "SET[205]\nCooling Power [102]mW.\n\n" );
2793 queueSerialResponse(
"fps [150.5] Hz\n" );
2794 queueSerialResponse(
"", -1 );
2796 fgThreadScope fgThread( app );
2798 REQUIRE( app.appLogic() == 0 );
2800 REQUIRE( g_edtStubState.serialCommands == std::vector<std::string>{
"temp",
"fps",
"gain" } );
2808TEST_CASE(
"ocam2KCtrl reconfig reloads the next mode through edtCamera",
"[ocam2KCtrl]" )
2810 ocam2KCtrl_test app;
2816 #ifdef OCAM2KCTRL_TEST_DOXYGEN_REF
2822 app.m_cameraModes[
"science"] = config;
2823 app.m_nextMode =
"science";
2826 REQUIRE( app.reconfig() == 0 );
2828 REQUIRE( app.m_nextMode ==
"" );
2829 REQUIRE( app.m_modeName ==
"science" );
2830 REQUIRE( app.m_raw_width == 240 );
2831 REQUIRE( app.m_raw_height == 121 );
2832 REQUIRE( app.m_raw_depth == 16 );
2833 REQUIRE( app.m_cameraType ==
"stub_pdv" );
2834 REQUIRE( app.fps() == Approx( app.m_fps ) );
MagAO-X Uniblitz DSS Shutter interface.
int resetEMProtection()
Reset the EM Protection.
int setShutter(int sh)
Sets the shutter state, via call to dssShutter::setShutterState(int) [stdCamera interface].
std::string stateString()
Return the current stdCamera state string.
virtual int appLogic()
Implementation of the FSM for the OCAM 2K.
int recordTelem(const ocam_temps *)
int frameGrabberPostPublish(IMAGE *imageStream)
Publish the sync-only stream immediately after the main framegrabber publication.
int setNextROI()
Required by stdCamera, but this does not do anything for this camera [stdCamera interface].
virtual void setupConfig()
Setup the configuration system (called by MagAOXApp::setup())
int ensureSyncStream()
Ensure the sync-only ImageStreamIO stream exists with the expected 1x1 uint8 layout.
virtual int onPowerOff()
Implementation of the on-power-off FSM logic.
bool stateStringValid()
Report whether the current stdCamera state string is valid for persistence and telemetry.
int powerOnDefaults()
Set defaults for a power on state.
int setSynchro()
Set the synchro state. [stdCamera interface].
virtual int appShutdown()
Do any needed shutdown tasks.
int getEMGain()
Get the current EM Gain.
virtual void loadConfig()
load the configuration system results (called by MagAOXApp::setup())
int setExpTime()
Required by stdCamera, but this does not do anything for this camera [stdCamera interface].
float fps()
Implementation of the frameGrabber fps interface.
int loadImageIntoStream(void *dest)
Implementation of the framegrabber loadImageIntoStream interface.
int setTempControl()
Turn temperature control on or off.
int reconfig()
Implementation of the framegrabber reconfig interface.
int setTempSetPt()
Set the CCD temperature setpoint [stdCamera interface].
int startAcquisition()
Implementation of the framegrabber startAcquisition interface.
int setEMGain()
Set the EM gain.
int setFPS()
Set the frame rate. [stdCamera interface].
int getFPS()
Get the current frame rate.
int configureAcquisition()
Implementation of the framegrabber configureAcquisition interface.
int acquireAndCheckValid()
Implementation of the framegrabber acquireAndCheckValid interface.
virtual int whilePowerOff()
Implementation of the while-powered-off FSM.
int getTemps()
Get the current device temperatures.
Stub EDT dependent-configuration type used by unit tests.
Stub EDT device handle type used by unit tests.
Stub EDT configuration info structure used by unit tests.
Stub EDT camera handle type used by unit tests.
@ OPERATING
The device is operating, other than homing.
@ POWEROFF
The device power is off.
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
@ 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.
TEST_CASE("ocam2KCtrl lifecycle entrypoints handle startup failures and POWERON logic", "[ocam2KCtrl]")
Verify lifecycle entrypoints cover startup failure handling and the POWERON fast-return path.
#define XWCTEST_DOXYGEN_REF(fxn)
This inserts an unused call to a function signature to make doxygen make the link.
std::string m_serialCommand
The command to send to the camera to place it in this mode.
std::string m_configFile
The file to use for this mode, e.g. an EDT configuration file.
const pcf::IndiProperty & ipRecv
Namespace for all libXWC tests.
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.
const char * ocam2_sdkBuild()
Return sdk build.
ocam2_rc ocam2_init(ocam2_mode mode, const char *descrbFile, ocam2_id *id)
Create a camera instance with the provided mode.
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 ocam2_descramble(ocam2_id id, unsigned int *number, short *image, const short *imageRaw)
Create a camera instance with the provided mode.
void edt_close(EdtDev *edt_p)
Close a stub EDT device channel.
const char * ocam2_sdkVersion()
Return sdk version.
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.
const char * ocam2_modeStr(ocam2_mode mode)
Return a description text for camera mode.
int pdv_get_height(PdvDev *pdv_p)
Return the stub PDV frame height.
ocam2_rc ocam2_exit(ocam2_id id)
Clear a camera instance.
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.
ocam2_mode ocam2_getMode(ocam2_id id)
Return the camera mode.
int pdv_serial_read(PdvDev *pdv_p, char *buf, int size)
Read bytes from the stub PDV serial channel.
int ocam2_id
Library camera identifier.
ocam2_rc
Enum of ocam2 library return code.
enum workMode ocam2_mode
typedef of ocam2 camera mode
#define OCAM2_IMAGE_NB_OFFSET
Log entry recording the build-time git state.
static timespec lastRecord
The time of the last time this log was recorded. Used by the telemetry system.
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.