diff --git a/.github/workflows/emulator-integration.yml b/.github/workflows/emulator-integration.yml new file mode 100644 index 00000000..2f8c7dd7 --- /dev/null +++ b/.github/workflows/emulator-integration.yml @@ -0,0 +1,87 @@ +name: Emulator integration tests + +on: + push: + branches: [ "main", "mike/camera2-emulator" ] + pull_request: + branches: [ "main" ] + +jobs: + cryoscope-emulator-test: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libccfits-dev libcfitsio-dev libcurl4-openssl-dev \ + nlohmann-json3-dev libzmq3-dev \ + libopencv-dev libboost-thread-dev libboost-chrono-dev + + - name: Build and install zmqpp from source + run: | + git clone --depth 1 https://github.com/zeromq/zmqpp.git /tmp/zmqpp + cd /tmp/zmqpp + mkdir build && cd build + cmake .. + make -j$(nproc) + sudo make install + sudo ldconfig + + - name: Build camerad and emulator + run: | + mkdir -p ${{github.workspace}}/build + cd ${{github.workspace}}/build + cmake -DCONTROLLER=archon -DINSTR=hispec_tracking_camera -DDETECTOR_TYPE=Hxrg .. + make camerad emulator socksend -j$(nproc) + + - name: CryoScope synthetic test + run: | + bin/emulator Config/cryoscope/cryoscope.cfg -i generic & + sleep 2 + bin/camerad --foreground --config Config/cryoscope/cryoscope.cfg & + sleep 3 + + send() { bin/socksend -p 3031 -t 60 "$1"; } + + send "open" + send "load" + send "power on" + send "exptime 0.1" + resp=$(send "expose 1") + echo "expose -> $resp" + echo "$resp" | grep -q "DONE" || exit 1 + + pkill -f 'bin/camerad' || true + pkill -f 'bin/emulator' || true + sleep 1 + + - name: CryoScope FITS playback test + run: | + cp Config/cryoscope/cryoscope.cfg /tmp/cryoscope_fits_test.cfg + echo "EMULATOR_DATADIR=Config/cryoscope" >> /tmp/cryoscope_fits_test.cfg + + bin/emulator /tmp/cryoscope_fits_test.cfg -i generic & + sleep 2 + bin/camerad --foreground --config /tmp/cryoscope_fits_test.cfg & + sleep 3 + + send() { bin/socksend -p 3031 -t 60 "$1"; } + + send "open" + send "load" + send "power on" + send "mode VIDEORXR" + send "exptime 0.1" + resp=$(send "expose 1") + echo "expose -> $resp" + echo "$resp" | grep -q "DONE" || exit 1 + + pkill -f 'bin/camerad' || true + pkill -f 'bin/emulator' || true diff --git a/CMakeLists.txt b/CMakeLists.txt index ae4bf490..454b2f93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,5 +59,5 @@ find_package(nlohmann_json REQUIRED) add_subdirectory(${PROJECT_BASE_DIR}/utils) add_subdirectory(${PROJECT_BASE_DIR}/common) add_subdirectory(${PROJECT_BASE_DIR}/camerad) -#add_subdirectory(${PROJECT_BASE_DIR}/emulator) +add_subdirectory(${PROJECT_BASE_DIR}/emulator) add_subdirectory(${PROJECT_BASE_DIR}/tests EXCLUDE_FROM_ALL) diff --git a/Config/cryoscope/cryoscope-test_4224x2048_19.fits.gz b/Config/cryoscope/cryoscope-test_4224x2048_19.fits.gz new file mode 100644 index 00000000..36331372 Binary files /dev/null and b/Config/cryoscope/cryoscope-test_4224x2048_19.fits.gz differ diff --git a/Config/cryoscope/cryoscope.acf b/Config/cryoscope/cryoscope.acf new file mode 100644 index 00000000..e77be9f0 --- /dev/null +++ b/Config/cryoscope/cryoscope.acf @@ -0,0 +1,804 @@ +[CONFIG] +MOD10\DIO_DIR12=1 +MOD10\DIO_DIR34=1 +MOD10\DIO_POWER=1 +MOD10\DIO_SOURCE1=3 +MOD10\DIO_SOURCE2=3 +MOD10\DIO_SOURCE3=3 +MOD10\VCPU_INREG0=0 +MOD10\VCPU_INREG1=0 +MOD10\VCPU_INREG10=0 +MOD10\VCPU_INREG11=0 +MOD10\VCPU_INREG12=0 +MOD10\VCPU_INREG13=0 +MOD10\VCPU_INREG14=0 +MOD10\VCPU_INREG15=0 +MOD10\VCPU_INREG2=0 +MOD10\VCPU_INREG3=0 +MOD10\VCPU_INREG4=0 +MOD10\VCPU_INREG5=0 +MOD10\VCPU_INREG6=0 +MOD10\VCPU_INREG7=0 +MOD10\VCPU_INREG8=0 +MOD10\VCPU_INREG9=0 +MOD10\VCPU_LINE0="ALIAS r0 TriggerHw ; reads hardware trigger" +MOD10\VCPU_LINE1="ALIAS r1 TriggerSw ; reads software trigger" +MOD10\VCPU_LINE10=";" +MOD10\VCPU_LINE11="DIN_PORT=0x0000 ; digital input port register" +MOD10\VCPU_LINE12="SOFT_TRIG=0x0010 ; software trigger VCPU_INREG0" +MOD10\VCPU_LINE13="DATA_WORD=0x0011 ; VCPU_INREG1 carries the word to write serially" +MOD10\VCPU_LINE14= +MOD10\VCPU_LINE15="; bitmasks" +MOD10\VCPU_LINE16=";" +MOD10\VCPU_LINE17="DATACLK_HI=1" +MOD10\VCPU_LINE18="DATACLK_LO=0" +MOD10\VCPU_LINE19="CSB_HI=2" +MOD10\VCPU_LINE2="ALIAS r2 Once ; allows one-shot software trigger" +MOD10\VCPU_LINE20="CSB_LO=0" +MOD10\VCPU_LINE21="DATAIN_HI=4" +MOD10\VCPU_LINE22="DATAIN_LO=0" +MOD10\VCPU_LINE23="HW_TRIG=0x10 ; hardware trigger bit = DIO5" +MOD10\VCPU_LINE24= +MOD10\VCPU_LINE25="; Initialize Output Ports" +MOD10\VCPU_LINE26=";" +MOD10\VCPU_LINE27="LOAD ClockPort, 0x0101 ; ClockPort = DIO1" +MOD10\VCPU_LINE28="LOAD DataPort, 0x0104 ; DataPort = DIO3" +MOD10\VCPU_LINE29="LOAD SPI, 0x0107 ; CLK=DIO1, CSB=DIO2, DATA=DIO3" +MOD10\VCPU_LINE3="ALIAS r3 Counter ; general purpose counter" +MOD10\VCPU_LINE30= +MOD10\VCPU_LINE31="LOAD Once, 0 ; clear the Once flag" +MOD10\VCPU_LINE32="INPUT TriggerSw, SOFT_TRIG ; read the software trigger once at program start only" +MOD10\VCPU_LINE33="TEST TriggerSw, 1" +MOD10\VCPU_LINE34="IF Z GOTO Init ; if clear then leave the Once flag clear" +MOD10\VCPU_LINE35="LOAD Once, HW_TRIG ; otherwise set the Once flag same as a hardware trigger" +MOD10\VCPU_LINE36= +MOD10\VCPU_LINE37="; Initialize all ports and registers" +MOD10\VCPU_LINE38=";" +MOD10\VCPU_LINE39=Init: +MOD10\VCPU_LINE4="ALIAS r4 Data ; this register caries the data" +MOD10\VCPU_LINE40="OUTPUT SPI, 0x06 ; DATAIN=HI, CSB=HI, CLK=LO (110)" +MOD10\VCPU_LINE41= +MOD10\VCPU_LINE42="; WaitForTrigger" +MOD10\VCPU_LINE43="; Loop here until either a hardware or software trigger is received" +MOD10\VCPU_LINE44=";" +MOD10\VCPU_LINE45=WaitForTriggerLO: +MOD10\VCPU_LINE46=";LOAD Counter, 1 ; initialize Counter for LO state" +MOD10\VCPU_LINE47= +MOD10\VCPU_LINE48=TestTriggerLO: +MOD10\VCPU_LINE49="INPUT TriggerHw, DIN_PORT ; read the hardware trigger" +MOD10\VCPU_LINE5="ALIAS r5 ClockPort ; holds the address for the DATACLK port" +MOD10\VCPU_LINE50="TEST TriggerHw, HW_TRIG ; Z = NOT(TriggerHw & HW_TRIG)" +MOD10\VCPU_LINE51="IF NZ GOTO WaitForTriggerLO ; loop waiting until Trigger==0" +MOD10\VCPU_LINE52=";SUB Counter, 1 ; decrement LO counter" +MOD10\VCPU_LINE53=";IF NZ GOTO TestTriggerLO ; keep checking Trigger until LO for \"Counter\" cycles" +MOD10\VCPU_LINE54= +MOD10\VCPU_LINE55=WaitForTriggerHI: +MOD10\VCPU_LINE56=";LOAD Counter, 1 ; initialize Counter for HI state" +MOD10\VCPU_LINE57= +MOD10\VCPU_LINE58=TestTriggerHI: +MOD10\VCPU_LINE59="INPUT TriggerHw, DIN_PORT ; read the hardware trigger" +MOD10\VCPU_LINE6="ALIAS r7 DataPort ; holds the address for the DATAIN port" +MOD10\VCPU_LINE60="OR TriggerHw, Once ; OR with Once register: TriggerHw=TriggerHw|Once" +MOD10\VCPU_LINE61="TEST TriggerHw, HW_TRIG ; Z = NOT(TriggerHw & HW_TRIG)" +MOD10\VCPU_LINE62="IF Z GOTO WaitForTriggerHI ; loop waiting until Trigger==1" +MOD10\VCPU_LINE63=";SUB Counter, 1 ; increment HI counter" +MOD10\VCPU_LINE64=";IF NZ GOTO TestTriggerHI ; keep checking Trigger until HI for \"Counter\" cycles" +MOD10\VCPU_LINE65= +MOD10\VCPU_LINE66="LOAD Counter, 16 ; initialize Counter to 16 bit word length" +MOD10\VCPU_LINE67= +MOD10\VCPU_LINE68="; SendSerial" +MOD10\VCPU_LINE69="; jump here when a trigger is received to send the serial data word" +MOD10\VCPU_LINE7="ALIAS r9 SPI ; address for CLK/CS/DATA" +MOD10\VCPU_LINE70=";" +MOD10\VCPU_LINE71=SendSerial: +MOD10\VCPU_LINE72="INPUT Data, DATA_WORD ; copy the requested word from the VCPU_INREG" +MOD10\VCPU_LINE73="OUTPUT SPI, 0x04 ; enable CSB: DATAIN=HI, CSB=LO, CLK=LO (100)" +MOD10\VCPU_LINE74= +MOD10\VCPU_LINE75="SendSerialLoop: ; loop over this for each bit in the data word" +MOD10\VCPU_LINE76="OUTPUT SPI, 0x04 ; each bit starts with DATA=HI and CLK=LO" +MOD10\VCPU_LINE77="SLA Data ; shift the MSB of Data into Carry" +MOD10\VCPU_LINE78="IF NC OUTPUT DataPort, DATAIN_LO ; if C is clear then bring DATAIN LO" +MOD10\VCPU_LINE79="OUTPUT ClockPort, DATACLK_HI ; then bring the clock HI" +MOD10\VCPU_LINE8= +MOD10\VCPU_LINE80="SUB Counter, 1 ; decrement bit counter" +MOD10\VCPU_LINE81="IF NZ GOTO SendSerialLoop ; keep looping over all bits in the data word" +MOD10\VCPU_LINE82="LOAD Once, 0 ; clear the Once register to stop software triggers" +MOD10\VCPU_LINE83=GOTO Init +MOD10\VCPU_LINE84= +MOD10\VCPU_LINE9="; Define Constants" +MOD10\VCPU_LINES=85 +BIGBUF=0 +FRAMEMODE=1 +LINECOUNT=2048 +PIXELCOUNT=64 +RAWENABLE=0 +RAWENDLINE=10 +RAWSAMPLES=25600 +RAWSEL=0 +RAWSTARTLINE=0 +RAWSTARTPIXEL=0 +SAMPLEMODE=0 +SHP1=256 +SHP2=692 +SHD1=700 +SHD2=700 +TAPLINE0="AM26L,1,0" +TAPLINE1="AM45R,1,0" +TAPLINE2="AM28L,1,0" +TAPLINE3="AM49R,1,0" +TAPLINE4="AM43L,1,0" +TAPLINE5="AM47R,1,0" +TAPLINE6="AM30L,1,0" +TAPLINE7="AM33R,1,0" +TAPLINE8="AM32L,1,0" +TAPLINE9="AM48R,1,0" +TAPLINE10="AM23L,1,0" +TAPLINE11="AM40R,1,0" +TAPLINE12="AM35L,1,0" +TAPLINE13="AM27R,1,0" +TAPLINE14="AM44L,1,0" +TAPLINE15="AM22R,1,0" +TAPLINE16="AM50L,1,0" +TAPLINE17="AM31R,1,0" +TAPLINE18="AM53L,1,0" +TAPLINE19="AM25R,1,0" +TAPLINE20="AM41L,1,0" +TAPLINE21="AM34R,1,0" +TAPLINE22="AM39L,1,0" +TAPLINE23="AM21R,1,0" +TAPLINE24="AM46L,1,0" +TAPLINE25="AM54R,1,0" +TAPLINE26="AM36L,1,0" +TAPLINE27="AM51R,1,0" +TAPLINE28="AM52L,1,0" +TAPLINE29="AM29R,1,0" +TAPLINE30="AM42L,1,0" +TAPLINE31="AM24R,1,0" +TAPLINE32="AM37L,1,0" +TAPLINES=33 +TRIGOUTFORCE=0 +TRIGOUTINVERT=0 +TRIGOUTLEVEL=0 +TRIGOUTPOWER=1 +PARAMETER0="Start=0" +PARAMETER1="Expose=0" +PARAMETER2="ExposeWindow=0" +PARAMETER3="exptime=0" +PARAMETER4="longexposure=0" +PARAMETER5="Abort=0" +PARAMETER6="mode_UTR_RR=1" +PARAMETER7="mode_UTR_GR=0" +PARAMETER8="mode_EnhancedRollingReset=0" +PARAMETER9="mode_VideoRX=0" +PARAMETER10="mode_VideoRXR=0" +PARAMETER11="mode_PermanentReset=0" +PARAMETER12="mode_Guiding=0" +PARAMETER13="mode_InterleavedGuiding=0" +PARAMETER14="exp_PulseWhileGlobReset=0" +PARAMETER15="H2RG_rows=2048" +PARAMETER16="H2RG_rows_skip=0" +PARAMETER17="H2RG_columns=64" +PARAMETER18="H2RG_win_rows=10" +PARAMETER19="H2RG_win_columns=10" +PARAMETER20="H2RGMainReset=0" +PARAMETER21="EnhancedRR_Delta=100" +PARAMETER22="Hold4Programming=0" +PARAMETER23="WindowPixelNb=100" +PARAMETER24="prv_flag_reset=0" +PARAMETER25="prv_ghostframe=0" +PARAMETER26="prv_firstrow=0" +PARAMETERS=27 +LINE0=Init: +LINE1="STATE000; CALL InitClocks" +LINE2="STATE000; CALL wCLKEn" +LINE3="STATE000; CALL ResetRegistersDefault" +LINE4="STATE000; if Start GOTO WaitForExpose" +LINE5="STATE000; GOTO Init" +LINE6=ResetRegHxRG: +LINE7="STATE000; CALL ResetRegistersDefault" +LINE8="STATE000; H2RGMainReset--" +LINE9="STATE000; RETURN ResetRegHxRG" +LINE10=WaitForExpose: +LINE11="STATE000; if Expose CALL SelectMode" +LINE12="STATE000; if H2RGMainReset CALL ResetRegHxRG" +LINE13="STATE000; GOTO WaitForExpose" +LINE14=SelectMode: +LINE15="STATE000; if mode_UTR_RR CALL UTR_RR_Sequence" +LINE16="STATE000; if mode_UTR_GR CALL UTR_GR_Sequence" +LINE17="STATE000; if mode_VideoRX CALL VideoRX_Sequence" +LINE18="STATE000; if mode_VideoRXR CALL VideoRXR_Sequence" +LINE19="STATE000; if mode_PermanentReset CALL PGR_Sequence" +LINE20="STATE000; if mode_EnhancedRollingReset CALL EnhancedRR_Sequence" +LINE21="STATE000; if exp_PulseWhileGlobReset CALL Persistence_PulseGlobalReset" +LINE22="STATE000; if mode_Guiding CALL Guiding_Sequence" +LINE23="STATE000; GOTO WaitForExpose" +LINE24=UTR_RR_Sequence: +LINE25="STATE000; prv_flag_reset++" +LINE26="STATE000; CALL UTR_RR_GrabFrame(Expose)" +LINE27="STATE000; GOTO WaitForExpose" +LINE28=UTR_RR_GrabFrame: +LINE29="STATE000; Expose--" +LINE30="STATE000; Expose--" +LINE31="STATE000; if !prv_flag_reset CALL ExposureTimer" +LINE32="STATE000; CALL wFrame" +LINE33="STATE000; CALL StartFrame" +LINE34="STATE000; if prv_flag_reset CALL ResetHI" +LINE35="STATE000; if !prv_flag_reset CALL ResetDummy" +LINE36="STATE000; CALL StartRow" +LINE37="STATE000; CALL RR_SkipRow(H2RG_rows_skip)" +LINE38="STATE000; CALL RR_ReadRow(H2RG_rows)" +LINE39="STATE000; CALL PulseVCLK" +LINE40="STATE000; if prv_flag_reset CALL ResetLO" +LINE41="STATE000; if !prv_flag_reset CALL ResetDummy" +LINE42="STATE000; prv_flag_reset--" +LINE43="STATE000; RETURN UTR_RR_GrabFrame" +LINE44=RR_ReadRow: +LINE45="STATE000; CALL wLine" +LINE46="STATE000; CALL StartRow" +LINE47="STATE000; CALL wReadEN" +LINE48="STATE000; CALL ReadPixel_Blank(2)" +LINE49="STATE000; CALL ReadPixel(H2RG_columns)" +LINE50="STATE000; CALL wbHclk" +LINE51="STATE000; CALL wbReadEN" +LINE52="STATE000; RETURN RR_ReadRow" +LINE53=RR_SkipRow: +LINE54="STATE000; CALL StartRow" +LINE55="STATE000; if prv_flag_reset CALL ResetPulse" +LINE56="STATE000; RETURN RR_SkipRow" +LINE57=UTR_GR_Sequence: +LINE58="STATE000; prv_flag_reset++" +LINE59="STATE000; CALL ResetPulse" +LINE60="STATE000; CALL UTR_GR_GrabFrame(Expose)" +LINE61="STATE000; GOTO WaitForExpose" +LINE62=PGR_Sequence: +LINE63="STATE000; CALL wResetEN" +LINE64="STATE000; CALL UTR_GR_GrabFrame(Expose)" +LINE65="STATE000; CALL wbResetEN" +LINE66="STATE000; GOTO WaitForExpose" +LINE67=UTR_GR_GrabFrame: +LINE68="STATE000; Expose--" +LINE69="STATE000; CALL wFrame" +LINE70="STATE000; CALL StartFrame" +LINE71="STATE000; CALL PulseVCLK" +LINE72="STATE000; CALL GR_ReadRow(H2RG_rows)" +LINE73="STATE000; CALL PulseVCLK" +LINE74="STATE000; prv_flag_reset--" +LINE75="STATE000; RETURN UTR_GR_GrabFrame" +LINE76=GR_ReadRow: +LINE77="STATE000; CALL wLine" +LINE78="STATE000; CALL StartRow" +LINE79="STATE000; CALL wReadEN" +LINE80="STATE000; CALL ReadPixel_Blank(2)" +LINE81="STATE000; CALL ReadPixel(64)" +LINE82="STATE000; CALL wbHclk" +LINE83="STATE000; CALL wbReadEN" +LINE84="STATE000; RETURN GR_ReadRow" +LINE85=VideoRX_Sequence: +LINE86="STATE000; if Expose CALL VideoRX_GrabFrame" +LINE87="STATE000; if Abort CALL AbortSeq" +LINE88="STATE000; if !Expose GOTO WaitForExpose" +LINE89="STATE000; GOTO VideoRX_Sequence" +LINE90=VideoRX_GrabFrame: +LINE91="STATE000; if !prv_ghostframe CALL wFrame" +LINE92="STATE000; CALL StartFrame" +LINE93="STATE000; CALL StartRow" +LINE94="STATE000; CALL RR_SkipRow(H2RG_rows_skip)" +LINE95="STATE000; CALL RX_ReadRow(H2RG_rows)" +LINE96="STATE000; CALL PulseVCLK" +LINE97="STATE000; CALL ExposureTimer" +LINE98="STATE000; if !prv_ghostframe CALL DecrExposeCpt" +LINE99="STATE000; RETURN VideoRX_GrabFrame" +LINE100=RX_ReadRow: +LINE101="STATE000; CALL wLine" +LINE102="STATE000; CALL StartRow" +LINE103="STATE000; CALL wReadEN" +LINE104="STATE000; CALL ReadPixel_Blank(2)" +LINE105="STATE000; CALL ReadPixel(H2RG_columns)" +LINE106="STATE000; CALL ResetPulse" +LINE107="STATE000; CALL wbHclk" +LINE108="STATE000; CALL wbReadEN" +LINE109="STATE000; RETURN RX_ReadRow" +LINE110=VideoRXR_Sequence: +LINE111="STATE000; if Expose CALL VideoRXR_GrabFrame" +LINE112="STATE000; if Abort GOTO AbortSeq" +LINE113="STATE000; if !Expose GOTO WaitForExpose" +LINE114="STATE000; GOTO VideoRXR_Sequence" +LINE115=VideoRXR_GrabFrame: +LINE116="STATE000; if !prv_ghostframe CALL wFrame" +LINE117="STATE000; CALL StartFrame" +LINE118="STATE000; CALL StartRow" +LINE119="STATE000; prv_firstrow++" +LINE120="STATE000; if H2RG_rows_skip CALL RR_SkipRow(H2RG_rows_skip)" +LINE121="STATE000; CALL RXR_ReadRow(H2RG_rows)" +LINE122="STATE000; CALL PulseVCLK" +LINE123="STATE000; CALL ExposureTimer" +LINE124="STATE000; if !prv_ghostframe CALL DecrExposeCpt" +LINE125="STATE000; RETURN VideoRXR_GrabFrame" +LINE126=RXR_ReadRow: +LINE127="STATE000; CALL wLine" +LINE128="STATE000; CALL StartRow" +LINE129="STATE000; prv_firstrow--" +LINE130="STATE000; CALL wReadEN" +LINE131="STATE000; CALL ReadPixel_Blank(2)" +LINE132="STATE000; CALL ReadPixel(H2RG_columns)" +LINE133="STATE000; CALL ResetPulse" +LINE134="STATE000; CALL LSyncBPulse" +LINE135="STATE000; CALL ReadPixel_Blank(1)" +LINE136="STATE000; CALL ReadPixel(H2RG_columns)" +LINE137="STATE000; CALL wbHclk" +LINE138="STATE000; CALL wbReadEN" +LINE139="STATE000; RETURN RXR_ReadRow" +LINE140=Persistence_PulseGlobalReset: +LINE141="STATE000; CALL wResetEN" +LINE142="STATE000; CALL UTR_GR_GrabFrame(10)" +LINE143="STATE000; CALL wbResetEN" +LINE144="STATE000; CALL UTR_GR_GrabFrame(100)" +LINE145="STATE000; RETURN Persistence_PulseGlobalReset" +LINE146=EnhancedRR_Sequence: +LINE147="STATE000; CALL ConfigureEnhancedMode" +LINE148="STATE000; CALL EnhancedRR_GrabFrame(Expose)" +LINE149="STATE000; GOTO WaitForExpose" +LINE150=EnhancedRR_GrabFrame: +LINE151="STATE000; Expose--" +LINE152="STATE000; CALL wFrame" +LINE153="STATE000; CALL wbFSyncB" +LINE154="STATE000; CALL HDCRR_ReadRow(EnhancedRR_Delta)" +LINE155="STATE000; CALL wFSyncB" +LINE156="STATE000; CALL HDCRR_ReadRow(1948)" +LINE157="STATE000; RETURN EnhancedRR_GrabFrame" +LINE158=HDCRR_ReadRow: +LINE159="STATE000; CALL wLine" +LINE160="STATE000; CALL PulseVCLK" +LINE161="STATE000; CALL wbVReadEdge" +LINE162="STATE000; CALL LSyncBPulse" +LINE163="STATE000; CALL wReadEN" +LINE164="STATE000; CALL ResetPulse" +LINE165="STATE000; CALL ReadPixel_Blank(1)" +LINE166="STATE000; CALL ReadPixel(64)" +LINE167="STATE000; CALL wVReadEdge" +LINE168="STATE000; CALL LSyncBPulse" +LINE169="STATE000; CALL ReadPixel_Blank(1)" +LINE170="STATE000; CALL ReadPixel(64)" +LINE171="STATE000; CALL wbReadEN" +LINE172="STATE000; RETURN HDCRR_ReadRow" +LINE173=Guiding_Sequence: +LINE174="STATE000; if Expose CALL GrabWindow" +LINE175="STATE000; if Abort CALL AbortSeq" +LINE176="STATE000; GOTO Guiding_Sequence" +LINE177=GrabWindow: +LINE178="STATE000; Expose--" +LINE179="STATE000; CALL wFrame" +LINE180="STATE000; CALL StartFrame" +LINE181="STATE000; CALL WinRX_ReadRow(H2RG_win_rows)" +LINE182="STATE000; CALL PulseVCLK" +LINE183="STATE000; CALL ExposureTimer" +LINE184="STATE000; RETURN GrabWindow" +LINE185=WinRX_ReadRow: +LINE186="STATE000; CALL wLine" +LINE187="STATE000; CALL StartRow" +LINE188="STATE000; CALL wReadEN" +LINE189="STATE000; CALL ReadPixel_Blank(2)" +LINE190="STATE000; CALL ReadPixel(H2RG_win_columns)" +LINE191="STATE000; CALL ResetPulse" +LINE192="STATE000; CALL wbHclk" +LINE193="STATE000; CALL wbReadEN" +LINE194="STATE000; RETURN WinRX_ReadRow" +LINE195=ExposureTimer: +LINE196="STATE000; if longexposure CALL Sec(exptime)" +LINE197="STATE000; if !longexposure CALL MilliSec(exptime)" +LINE198="STATE000; if Abort GOTO AbortSeq" +LINE199="STATE000; RETURN ExposureTimer" +LINE200=MilliSec: +LINE201="STATE000; CALL wDelay1ms" +LINE202="STATE000; RETURN MilliSec" +LINE203=Sec: +LINE204="STATE000; CALL wDelay1ms(1000)" +LINE205="STATE000; if Abort GOTO AbortSeq" +LINE206="STATE000; RETURN Sec" +LINE207=AbortSeq: +LINE208="STATE000; CALL DecrExposeCpt(1000)" +LINE209="STATE000; CALL InitResetCpt(100)" +LINE210="STATE000; mode_VideoRX--" +LINE211="STATE000; mode_VideoRXR--" +LINE212="STATE000; mode_Guiding--" +LINE213="STATE000; Abort--" +LINE214="STATE000; GOTO WaitForExpose" +LINE215=DecrExposeCpt: +LINE216="STATE000; Expose--" +LINE217="STATE000; RETURN DecrExposeCpt" +LINE218=InitResetCpt: +LINE219="STATE000; prv_flag_reset--" +LINE220="STATE000; RETURN InitResetCpt" +LINE221=wDelay1us: +LINE222="STATE000; STATE000(98)" +LINE223="STATE000; RETURN wDelay1us" +LINE224=wDelay10us: +LINE225="STATE000; STATE000(998)" +LINE226="STATE000; RETURN wDelay10us" +LINE227=wDelay1ms: +LINE228="STATE000; STATE000(99998)" +LINE229="STATE000; RETURN wDelay1ms" +LINE230=InitClocks: +LINE231="STATE001; STATE000(99998)" +LINE232="STATE000; RETURN InitClocks" +LINE233=wCLKEn: +LINE234="STATE002; STATE000(999998)" +LINE235="STATE000; RETURN wCLKEn" +LINE236=ResetRegistersDefault: +LINE237="STATE003; STATE000(999999)" +LINE238="STATE004; STATE000(99998)" +LINE239="STATE000; RETURN ResetRegistersDefault" +LINE240=StartFrame: +LINE241="STATE005; STATE000(79)" +LINE242="STATE006; STATE000(618)" +LINE243="STATE000; RETURN StartFrame" +LINE244=StartRow: +LINE245="STATE007; STATE000(79)" +LINE246="STATE008; STATE000(618)" +LINE247="STATE000; RETURN StartRow" +LINE248=PulseVCLK: +LINE249="STATE009; STATE000(79)" +LINE250="STATE010; STATE000(798)" +LINE251="STATE000; RETURN PulseVCLK" +LINE252=wReadEN: +LINE253="STATE011; STATE000(158)" +LINE254="STATE000; RETURN wReadEN" +LINE255=wbReadEN: +LINE256="STATE012; STATE000(158)" +LINE257="STATE000; RETURN wbReadEN" +LINE258=ReadPixel: +LINE259="STATE013;" +LINE260="STATE014; STATE000(78)" +LINE261="STATE015; STATE000(618)" +LINE262="STATE000; RETURN ReadPixel" +LINE263=ReadPixel_Blank: +LINE264="STATE016; STATE000(79)" +LINE265="STATE015; STATE000(618)" +LINE266="STATE000; RETURN ReadPixel_Blank" +LINE267=wbHclk: +LINE268="STATE016; STATE000(78)" +LINE269="STATE000; RETURN wbHclk" +LINE270=ResetPulse: +LINE271="STATE017; STATE000(9999)" +LINE272="STATE018; STATE000(78)" +LINE273="STATE000; RETURN ResetPulse" +LINE274=ResetHI: +LINE275="STATE017; STATE000(9998)" +LINE276="STATE000; RETURN ResetHI" +LINE277=ResetLO: +LINE278="STATE018; STATE000(9998)" +LINE279="STATE000; RETURN ResetLO" +LINE280=ResetDummy: +LINE281="STATE018; STATE000(9998)" +LINE282="STATE000; RETURN ResetDummy" +LINE283=ResetPulse_Dummy: +LINE284="STATE018; STATE000(9999)" +LINE285="STATE018; STATE000(78)" +LINE286="STATE000; RETURN ResetPulse_Dummy" +LINE287=wResetEN: +LINE288="STATE017; STATE000(78)" +LINE289="STATE000; RETURN wResetEN" +LINE290=wbResetEN: +LINE291="STATE018; STATE000(78)" +LINE292="STATE000; RETURN wbResetEN" +LINE293=LSyncBPulse: +LINE294="STATE019; STATE000(79)" +LINE295="STATE020; STATE000(78)" +LINE296="STATE000; RETURN LSyncBPulse" +LINE297=wVReadEdge: +LINE298="STATE021; STATE000(78)" +LINE299="STATE000; RETURN wVReadEdge" +LINE300=wbVReadEdge: +LINE301="STATE022; STATE000(78)" +LINE302="STATE000; RETURN wbVReadEdge" +LINE303=wFSyncB: +LINE304="STATE006; STATE000(78)" +LINE305="STATE000; RETURN wFSyncB" +LINE306=wbFSyncB: +LINE307="STATE005; STATE000(78)" +LINE308="STATE000; RETURN wbFSyncB" +LINE309=ConfigureEnhancedMode: +LINE310="STATE023; STATE000(79)" +LINE311="STATE024; STATE000(79)" +LINE312="STATE025; STATE000(78)" +LINE313="STATE000; RETURN ConfigureEnhancedMode" +LINE314=wFrame: +LINE315="STATE026; RETURN wFrame" +LINE316=wLine: +LINE317="STATE027; RETURN wLine" +LINE318=wPixel: +LINE319="STATE028;" +LINE320="STATE014; RETURN wPixel" +LINES=321 +MOD10\LVLC_V1=2.3 +MOD10\LVLC_ORDER1=2 +MOD10\LVLC_LABEL1=Bias Gate +MOD10\LVLC_V2=3.3 +MOD10\LVLC_ORDER2=2 +MOD10\LVLC_LABEL2=Bias Power +MOD10\LVLC_V3=0.55 +MOD10\LVLC_ORDER3=2 +MOD10\LVLC_LABEL3=Diode Sub +MOD10\LVLC_V4=3.3 +MOD10\LVLC_ORDER4=1 +MOD10\LVLC_LABEL4=Digital Supply +MOD10\LVLC_V5=0.3 +MOD10\LVLC_ORDER5=2 +MOD10\LVLC_LABEL5=Diode Reset +MOD10\LVLC_V6=3.3 +MOD10\LVLC_ORDER6=1 +MOD10\LVLC_LABEL6=Analog Supply +MOD10\LVLC_V7=2.9 +MOD10\LVLC_ORDER7=1 +MOD10\LVLC_LABEL7=Preamp neg ref +MOD10\LVLC_V8=3.3 +MOD10\LVLC_ORDER8=2 +MOD10\LVLC_LABEL8=clock enable +MOD10\LVLC_V9=2.0 +MOD10\LVLC_ORDER9=2 +MOD10\LVLC_LABEL9=Preamp enable +MOD10\LVLC_V10=0.0 +MOD10\LVLC_ORDER10=1 +MOD10\LVLC_V11=0.0 +MOD10\LVLC_ORDER11=1 +MOD10\LVLC_V12=0.0 +MOD10\LVLC_ORDER12=1 +MOD10\LVLC_V13=0.0 +MOD10\LVLC_ORDER13=1 +MOD10\LVLC_V14=0.0 +MOD10\LVLC_ORDER14=1 +MOD10\LVLC_V15=0.0 +MOD10\LVLC_ORDER15=1 +MOD10\LVLC_V16=0.0 +MOD10\LVLC_ORDER16=1 +MOD10\LVLC_V17=0.0 +MOD10\LVLC_ORDER17=1 +MOD10\LVLC_V18=0.0 +MOD10\LVLC_ORDER18=1 +MOD10\LVLC_V19=0.0 +MOD10\LVLC_ORDER19=1 +MOD10\LVLC_V20=0.0 +MOD10\LVLC_ORDER20=1 +MOD10\LVLC_V21=0.0 +MOD10\LVLC_ORDER21=1 +MOD10\LVLC_V22=0.0 +MOD10\LVLC_ORDER22=1 +MOD10\LVLC_V23=0.0 +MOD10\LVLC_ORDER23=1 +MOD10\LVLC_V24=0.0 +MOD10\LVLC_ORDER24=1 +MOD10\LVHC_ENABLE1=1 +MOD10\LVHC_V1=3.3 +MOD10\LVHC_IL1=10 +MOD10\LVHC_ORDER1=3 +MOD10\LVHC_LABEL1=PullUp +MOD10\LVHC_ENABLE2=0 +MOD10\LVHC_V2=0.0 +MOD10\LVHC_IL2=50 +MOD10\LVHC_ORDER2=1 +MOD10\LVHC_LABEL2=Misc 1 +MOD10\LVHC_ENABLE3=0 +MOD10\LVHC_V3=0.0 +MOD10\LVHC_IL3=10 +MOD10\LVHC_ORDER3=1 +MOD10\LVHC_LABEL3=Misc 2 +MOD10\LVHC_ENABLE4=0 +MOD10\LVHC_V4=0.0 +MOD10\LVHC_IL4=100 +MOD10\LVHC_ORDER4=1 +MOD10\LVHC_LABEL4=Misc 3 +MOD10\LVHC_ENABLE5=1 +MOD10\LVHC_V5=0.0 +MOD10\LVHC_IL5=10 +MOD10\LVHC_ORDER5=1 +MOD10\LVHC_LABEL5=LED IR +MOD10\LVHC_ENABLE6=1 +MOD10\LVHC_V6=0.0 +MOD10\LVHC_IL6=70 +MOD10\LVHC_ORDER6=1 +MOD10\LVHC_LABEL6=Light Bulb +MOD11\DIO_SOURCE1=0 +MOD11\DIO_DIR1=0 +MOD11\DIO_SOURCE2=0 +MOD11\DIO_DIR2=0 +MOD11\DIO_SOURCE3=0 +MOD11\DIO_DIR3=0 +MOD11\DIO_SOURCE4=0 +MOD11\DIO_DIR4=0 +MOD11\DIO_POWER=0 +STATE0\NAME=STATE000 +STATE0\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE0\CONTROL="0,3F" +STATE0\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE1\NAME=STATE001 +STATE1\MOD11="1,1,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE1\CONTROL="0,3F" +STATE1\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE2\NAME=STATE002 +STATE2\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE2\CONTROL="0,3F" +STATE2\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,8,3.3" +STATE3\NAME=STATE003 +STATE3\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE3\CONTROL="0,3F" +STATE3\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE4\NAME=STATE004 +STATE4\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE4\CONTROL="0,3F" +STATE4\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE5\NAME=STATE005 +STATE5\MOD11="1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE5\CONTROL="0,3F" +STATE5\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE6\NAME=STATE006 +STATE6\MOD11="1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE6\CONTROL="0,3F" +STATE6\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE7\NAME=STATE007 +STATE7\MOD11="1,1,1,1,1,1,1,1,1,0,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE7\CONTROL="0,3F" +STATE7\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE8\NAME=STATE008 +STATE8\MOD11="1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE8\CONTROL="0,3F" +STATE8\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE9\NAME=STATE009 +STATE9\MOD11="1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE9\CONTROL="0,3F" +STATE9\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE10\NAME=STATE010 +STATE10\MOD11="1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE10\CONTROL="0,3F" +STATE10\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE11\NAME=STATE011 +STATE11\MOD11="1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE11\CONTROL="0,3F" +STATE11\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE12\NAME=STATE012 +STATE12\MOD11="1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE12\CONTROL="0,3F" +STATE12\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE13\NAME=STATE013 +STATE13\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE13\CONTROL="8,37" +STATE13\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE14\NAME=STATE014 +STATE14\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE14\CONTROL="0,31" +STATE14\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE15\NAME=STATE015 +STATE15\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE15\CONTROL="0,3F" +STATE15\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE16\NAME=STATE016 +STATE16\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE16\CONTROL="0,3F" +STATE16\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE17\NAME=STATE017 +STATE17\MOD11="1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE17\CONTROL="0,3F" +STATE17\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE18\NAME=STATE018 +STATE18\MOD11="1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE18\CONTROL="0,3F" +STATE18\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE19\NAME=STATE019 +STATE19\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE19\CONTROL="0,3F" +STATE19\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE20\NAME=STATE020 +STATE20\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE20\CONTROL="0,3F" +STATE20\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE21\NAME=STATE021 +STATE21\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE21\CONTROL="0,3F" +STATE21\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE22\NAME=STATE022 +STATE22\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE22\CONTROL="0,3F" +STATE22\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE23\NAME=STATE023 +STATE23\MOD11="1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE23\CONTROL="0,3F" +STATE23\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE24\NAME=STATE024 +STATE24\MOD11="0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE24\CONTROL="0,3F" +STATE24\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE25\NAME=STATE025 +STATE25\MOD11="1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE25\CONTROL="0,3F" +STATE25\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE26\NAME=STATE026 +STATE26\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE26\CONTROL="2,3D" +STATE26\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE27\NAME=STATE027 +STATE27\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE27\CONTROL="4,3B" +STATE27\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATE28\NAME=STATE028 +STATE28\MOD11="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +STATE28\CONTROL="8,37" +STATE28\MOD10="1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0" +STATES=29 +[SYSTEM] +BACKPLANE_ID=0000000000000000 +BACKPLANE_REV=0 +BACKPLANE_TYPE=1 +BACKPLANE_VERSION=0.0.0 +MOD10_ID=0000000000000000 +MOD10_REV=0 +MOD10_VERSION=0.0.0 +MOD10_TYPE=9 +MOD11_ID=0000000000000000 +MOD11_REV=0 +MOD11_VERSION=0.0.0 +MOD11_TYPE=10 +[MODE_DEFAULT] +FITS:REV=2025-07-10T14:27:34/git hash or revision of ACF +ACF:Expose=0 +ACF:LINECOUNT=2048 +ACF:PIXELCOUNT=64 +ACF:mode_EnhancedRollingReset=0 +ACF:mode_Guiding=0 +ACF:mode_UTR_RR=1 +ACF:mode_VideoRX=0 +ACF:mode_VideoRXR=0 +ARCH:HORI_AMPS=33 +ARCH:NUM_DETECT=1 +ARCH:VERT_AMPS=1 +FITS:READOUTMODE=UpTheRampRollingReset +[MODE_UTR_RR] +ACF:Expose=0 +ACF:LINECOUNT=2048 +ACF:PIXELCOUNT=64 +ACF:mode_EnhancedRollingReset=0 +ACF:mode_Guiding=0 +ACF:mode_UTR_RR=1 +ACF:mode_VideoRX=0 +ACF:mode_VideoRXR=0 +ARCH:HORI_AMPS=33 +ARCH:NUM_DETECT=1 +ARCH:VERT_AMPS=1 +FITS:READOUTMODE=UpTheRampRollingReset +[MODE_VIDEORX] +ACF:Expose=0 +ACF:LINECOUNT=2048 +ACF:PIXELCOUNT=64 +ACF:mode_EnhancedRollingReset=0 +ACF:mode_Guiding=0 +ACF:mode_UTR_RR=0 +ACF:mode_VideoRX=1 +ACF:mode_VideoRXR=0 +ARCH:HORI_AMPS=33 +ARCH:NUM_DETECT=1 +ARCH:VERT_AMPS=1 +FITS:READOUTMODE=VideoRX +[MODE_VIDEORXR] +ACF:Expose=0 +ACF:LINECOUNT=2048 +ACF:PIXELCOUNT=128 +ACF:mode_EnhancedRollingReset=0 +ACF:mode_Guiding=0 +ACF:mode_UTR_RR=0 +ACF:mode_VideoRX=0 +ACF:mode_VideoRXR=1 +ARCH:HORI_AMPS=33 +ARCH:NUM_DETECT=1 +ARCH:VERT_AMPS=1 +FITS:READOUTMODE=VideoRXR +[MODE_GUIDING] +ACF:Expose=0 +ACF:LINECOUNT=2048 +ACF:PIXELCOUNT=64 +ACF:mode_EnhancedRollingReset=0 +ACF:mode_Guiding=1 +ACF:mode_UTR_RR=0 +ACF:mode_VideoRX=0 +ACF:mode_VideoRXR=0 +ARCH:HORI_AMPS=1 +ARCH:NUM_DETECT=1 +ARCH:VERT_AMPS=1 +FITS:READOUTMODE=Guiding diff --git a/Config/cryoscope/cryoscope.cfg b/Config/cryoscope/cryoscope.cfg new file mode 100644 index 00000000..b6d152ba --- /dev/null +++ b/Config/cryoscope/cryoscope.cfg @@ -0,0 +1,27 @@ +# CryoScope configuration for camerad + emulator testing + +DAEMON=no +IMDIR=/tmp +LOGPATH=/tmp +BASENAME=cryoscope +DIRMODE=0077 +TM_ZONE_LOG=local +TM_ZONE=UTC +TZ_ENV=PST8PDT,M3.2.0/2,M11.1.0/2 + +NBPORT=3030 +BLKPORT=3031 +EMULATOR_PORT=3032 +EMULATOR_SYSTEM=Config/demo/demo.system + +ASYNCGROUP=239.1.1.234 +ASYNCPORT=1234 + +ARCHON_IP=localhost +ARCHON_PORT=3032 +DEFAULT_FIRMWARE=Config/cryoscope/cryoscope.acf +ABORT_PARAM=Abort +EXPOSE_PARAM=Expose +EXPTIME_MSEC_PARAM=exptime +READOUT_TIME=10000 +WRITE_TAPINFO_TO_FITS=no diff --git a/Config/demo/demo.cfg b/Config/demo/demo.cfg index b818faca..c28c8416 100644 --- a/Config/demo/demo.cfg +++ b/Config/demo/demo.cfg @@ -31,6 +31,7 @@ ARCHON_PORT=3032 DEFAULT_FIRMWARE=Config/demo/demo.acf ABORT_PARAM=abort # Archon parameter to trigger an abort EXPOSE_PARAM=Expose # Archon parameter to trigger exposure -READOUT_TIME=5000 # Timeout waiting for new frame (ms) +EXPTIME_MSEC_PARAM=exptime # Archon parameter for exposure time in msec +READOUT_TIME=5000 # Timeout waiting for new frame (ms) WRITE_TAPINFO_TO_FITS=no # Tapinfo (gain, offset) be written to FITS headers {yes|no} diff --git a/camerad/Instruments/hispec_tracking_camera b/camerad/Instruments/hispec_tracking_camera index 81257b88..c90a42ba 160000 --- a/camerad/Instruments/hispec_tracking_camera +++ b/camerad/Instruments/hispec_tracking_camera @@ -1 +1 @@ -Subproject commit 81257b8880ad0abbc27ba22f493036be0d5ed2f6 +Subproject commit c90a42ba8456f63cd29ffcffbf7ec114d7aa3ed4 diff --git a/camerad/archon_controller.cpp b/camerad/archon_controller.cpp index a4af0350..26b71097 100644 --- a/camerad/archon_controller.cpp +++ b/camerad/archon_controller.cpp @@ -761,20 +761,25 @@ namespace Camera { throw std::runtime_error("connection not open to controller"); } - // exposure time parameters must be defined - if (this->sec_param.empty() || this->msec_param.empty()) { + if (this->msec_param.empty()) { throw std::runtime_error("exposure time parameters not in configuration"); } try { - // split the requested exposure time into seconds and milliseconds - auto [sec, msec] = this->exposure_time->split(exptime); - - // Set the sec and msec parameters on the controller, - // store the exptime in the class on success. - if ( (set_parameter(sec_param, sec) == NO_ERROR) && - (set_parameter(msec_param, msec) == NO_ERROR) ) { - this->exposure_time->set(exptime); + if (!this->sec_param.empty()) { + // Split into seconds and milliseconds when both parameters are configured + auto [sec, msec] = this->exposure_time->split(exptime); + if ( (set_parameter(sec_param, sec) == NO_ERROR) && + (set_parameter(msec_param, msec) == NO_ERROR) ) { + this->exposure_time->set(exptime); + } + } + else { + // Single parameter mode: write as milliseconds + int msec = static_cast(exptime * 1000.0); + if (set_parameter(msec_param, msec) == NO_ERROR) { + this->exposure_time->set(exptime); + } } } catch (const std::exception &e) { @@ -1603,18 +1608,38 @@ namespace Camera { this->get_configmap_value("SAMPLEMODE", mode->samplemode); this->get_configmap_value("BIGBUF", mode->bigbuf); this->get_configmap_value("FRAMEMODE", mode->geometry.framemode); - this->get_configmap_value("LINECOUNT", mode->geometry.linecount); - this->get_configmap_value("PIXELCOUNT", mode->geometry.pixelcount); this->get_configmap_value("RAWENABLE", mode->rawenable); this->get_configmap_value("RAWSEL", this->rawinfo.adchan); this->get_configmap_value("RAWSAMPLES", this->rawinfo.rawsamples); this->get_configmap_value("RAWENDLINE", this->rawinfo.rawlines); + + // Read geometry from the mode's configmap (not the global one) since each + // mode section can override LINECOUNT/PIXELCOUNT + auto get_mode_value = [&](const std::string &key, int &out) { + auto it = mode->configmap.find(key); + if (it != mode->configmap.end()) { + out = std::stoi(it->second.value); + } else { + this->get_configmap_value(key, out); + } + }; + get_mode_value("LINECOUNT", mode->geometry.linecount); + get_mode_value("PIXELCOUNT", mode->geometry.pixelcount); } catch (const std::exception &e) { logwrite(function, "ERROR: "+std::string(e.what())); return ERROR; } + // Write geometry to the Archon so the controller matches the selected mode + bool changed = false; + write_config_key("LINECOUNT", std::to_string(mode->geometry.linecount).c_str(), changed); + write_config_key("PIXELCOUNT", std::to_string(mode->geometry.pixelcount).c_str(), changed); + + if (changed) { + logwrite(function, "applied mode geometry to controller"); + } + return NO_ERROR; } /***** Camera::ArchonController::load_mode_settings *************************/ @@ -1933,7 +1958,7 @@ namespace Camera { while ( this->archon.Bytes_ready() < (BLOCK_LEN+4) ) { auto now = std::chrono::steady_clock::now(); // check the time again std::chrono::duration diff = now-start; // calculate the duration - if (diff.count() > 1) { // break while loop if duration > 1 second + if (diff.count() > 5) { // break while loop if duration > 5 seconds logwrite(function, "timeout waiting for data from Archon"); error = ERROR; break; // breaks out of while loop diff --git a/camerad/archon_exposure_modes.h b/camerad/archon_exposure_modes.h index 54430cca..dc138286 100644 --- a/camerad/archon_exposure_modes.h +++ b/camerad/archon_exposure_modes.h @@ -21,7 +21,8 @@ namespace Camera { constexpr const char* RAW = "RAW"; constexpr const char* SINGLE = "SINGLE"; constexpr const char* RXRV = "RXRV"; - constexpr const char* ALLMODES[] = {RAW, SINGLE, RXRV}; + constexpr const char* UTR_RR = "UTR_RR"; + constexpr const char* ALLMODES[] = {RAW, SINGLE, RXRV, UTR_RR}; }; /** @struct ArchonImageBuffer @@ -79,6 +80,15 @@ namespace Camera { /***** Camera::ExposureModeSingle *******************************************/ + // UTR with Rolling Reset — behaves like SINGLE for now, multi-sample logic TBD + class ExposureModeUtrRR : public ExposureModeSingle { + public: + ExposureModeUtrRR(Camera::ArchonInterface* iface) + : ExposureModeSingle(iface) { + type=ArchonExposureMode::UTR_RR; + } + }; + class ExposureModeRXRV : public ArchonImageBuffer, public ExposureModeTemplate { public: ExposureModeRXRV(Camera::ArchonInterface* iface) diff --git a/camerad/archon_interface.cpp b/camerad/archon_interface.cpp index 19f766d3..9e078ad0 100644 --- a/camerad/archon_interface.cpp +++ b/camerad/archon_interface.cpp @@ -552,6 +552,10 @@ namespace Camera { if (caseCompareString(modein, ArchonExposureMode::RXRV)) { this->exposuremode = std::make_shared(this); } + else + if (caseCompareString(modein, ArchonExposureMode::UTR_RR)) { + this->exposuremode = std::make_shared(this); + } else { logwrite("Camera::ArchonInterface::set_exposure_mode", "ERROR unrecognized exposure mode \""+modein+"\""); @@ -804,6 +808,12 @@ namespace Camera { // if we made it all the way to the end then this is the selected mode this->controller->selectedmode = modeselect; + // Set the exposure mode to match the camera mode name if recognized + if (this->set_exposure_mode(modeselect, {}) != NO_ERROR) { + // Fall back to SINGLE if the camera mode name doesn't match an exposure mode + this->set_exposure_mode(std::string(ArchonExposureMode::SINGLE), {}); + } + return NO_ERROR; } /***** Camera::ArchonInterface::set_camera_mode *****************************/ @@ -844,6 +854,17 @@ namespace Camera { return ERROR; } + std::stringstream msg; + msg << "detector=" << info->detector_pixels[0] << "x" << info->detector_pixels[1] + << " image_memory=" << info->image_memory + << " image_data_bytes=" << info->image_data_bytes + << " num_detect=" << mode->geometry.num_detect + << " amps=" << mode->geometry.amps[0] << "x" << mode->geometry.amps[1] + << " pixelcount=" << mode->geometry.pixelcount + << " linecount=" << mode->geometry.linecount + << " samplemode=" << mode->samplemode; + logwrite(function, msg.str()); + return NO_ERROR; } /***** Camera::ArchonInterface::set_image_geometry **************************/ diff --git a/camerad/camera_information.h b/camerad/camera_information.h index d7874579..064ed2c8 100644 --- a/camerad/camera_information.h +++ b/camerad/camera_information.h @@ -107,7 +107,7 @@ namespace Camera { const std::string function("Camera::Information::set_axes"); std::ostringstream oss; - uint8_t bytes_per_pixel = bits_per_pixel / 2; + uint8_t bytes_per_pixel = bits_per_pixel / 8; uint32_t cols = this->region_of_interest[1] - this->region_of_interest[0] diff --git a/camerad/camera_server.cpp b/camerad/camera_server.cpp index a5aa045b..8cc34e4d 100644 --- a/camerad/camera_server.cpp +++ b/camerad/camera_server.cpp @@ -44,14 +44,15 @@ namespace Camera { */ void Server::configure_server() { const std::string function("Camera::Server::configure_server"); - logwrite(function, ""); if (interface->configfile.n_rows < 1) throw std::runtime_error("empty configuration"); - // iterate through each row in config file + std::string logpath; + std::string log_tmzone; + std::string log_stderr = "true"; + for (int row=0; row < interface->configfile.n_rows; row++) { - // BLKPORT if (interface->configfile.param[row]=="BLKPORT") { try { this->blkport = std::stoi( interface->configfile.arg[row] ); @@ -63,7 +64,23 @@ namespace Camera { throw std::runtime_error(oss.str()); } } + + if (interface->configfile.param[row]=="LOGPATH") + logpath = interface->configfile.arg[row]; + + if (interface->configfile.param[row]=="TM_ZONE_LOG") + log_tmzone = interface->configfile.arg[row]; + + if (interface->configfile.param[row]=="LONGERROR") + log_stderr = interface->configfile.arg[row]; } + + if (logpath.empty()) throw std::runtime_error("LOGPATH not specified in configuration file"); + + if (init_log("camerad", logpath, log_stderr, log_tmzone) != 0) + throw std::runtime_error("unable to initialize logging to " + logpath); + + logwrite(function, "logging initialized"); } /***** Camera::Server::configure_server *************************************/ @@ -109,7 +126,7 @@ namespace Camera { * @param[in] sock Network::TcpSocket socket object * */ - void Server::doit( Network::TcpSocket sock ) { + void Server::doit( Network::TcpSocket &sock ) { const std::string function("Camera::Server::doit"); std::stringstream message; std::string cmd, args; diff --git a/camerad/camera_server.h b/camerad/camera_server.h index ad5d9e78..887f9d88 100644 --- a/camerad/camera_server.h +++ b/camerad/camera_server.h @@ -42,7 +42,7 @@ namespace Camera { void configure_server(); void exit_cleanly(); void block_main(std::shared_ptr socket); - void doit(Network::TcpSocket sock); + void doit(Network::TcpSocket &sock); }; } diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt index 3e740e48..3949f798 100644 --- a/emulator/CMakeLists.txt +++ b/emulator/CMakeLists.txt @@ -6,12 +6,15 @@ cmake_minimum_required( VERSION 3.12 ) set( EMULATOR_DIR ${PROJECT_BASE_DIR}/emulator ) -add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -std=c++17 -ggdb ) +add_compile_options( -Wall -O1 -Wno-variadic-macros -ggdb ) -include_directories( ${PROJECT_BASE_DIR}/utils - ${PROJECT_BASE_DIR}/camerad +include_directories( ${PROJECT_BASE_DIR}/utils + ${PROJECT_BASE_DIR}/camerad ${PROJECT_BASE_DIR}/common ) +find_package(PkgConfig REQUIRED) +pkg_check_modules(CFITSIO REQUIRED cfitsio) + if( ${INTERFACE_TYPE} STREQUAL "Archon" ) add_definitions(-DSTA_ARCHON) set( EMULATOR_TARGET emulatorArchon ) @@ -21,10 +24,14 @@ if( ${INTERFACE_TYPE} STREQUAL "Archon" ) add_library(emulatorInterface STATIC ${EMULATOR_DIR}/emulator-archon.cpp ) + target_link_libraries(emulatorInterface common utilities) + target_include_directories(emulatorInterface PRIVATE ${CFITSIO_INCLUDE_DIRS}) add_library(${EMULATOR_TARGET} ${EMULATOR_SOURCE}) target_include_directories(${EMULATOR_TARGET} PUBLIC ${PROJECT_INCL_DIR}) +target_include_directories(${EMULATOR_TARGET} PRIVATE ${CFITSIO_INCLUDE_DIRS}) +target_link_libraries(${EMULATOR_TARGET} common utilities) add_executable(emulator ${EMULATOR_DIR}/emulator-server.cpp @@ -37,7 +44,10 @@ target_link_libraries(emulator common ${EMULATOR_TARGET} ${CMAKE_THREAD_LIBS_INIT} + ${CFITSIO_LIBRARIES} ) +target_include_directories(emulator PRIVATE ${CFITSIO_INCLUDE_DIRS}) +target_link_directories(emulator PRIVATE ${CFITSIO_LIBRARY_DIRS}) elseif( ${INTERFACE_TYPE} STREQUAL "AstroCam" ) message( STATUS "emulator not implemented for AstroCam" ) else() diff --git a/emulator/emulator-archon.cpp b/emulator/emulator-archon.cpp index 25a08ef5..b84dd355 100644 --- a/emulator/emulator-archon.cpp +++ b/emulator/emulator-archon.cpp @@ -54,7 +54,8 @@ namespace Archon { std::vector(Archon::NBUFS), // bufrawoffset std::vector(Archon::NBUFS), // bufrtimestamp std::vector(Archon::NBUFS), // bufretimestemp - std::vector(Archon::NBUFS) // buffetimestamp + std::vector(Archon::NBUFS), // buffetimestamp + std::vector>(Archon::NBUFS) // bufdata }, modtype( NMODS ), modversion( NMODS ) @@ -96,20 +97,22 @@ namespace Archon { */ long Interface::configure_controller() { std::string function = " (Archon::Interface::configure_controller) "; + std::string datadir; - // loop through the rows in the configuration file, stored in config class - // for ( int row=0; row < this->config.n_rows; row++ ) { try { this->image->set_config_parameter( config.param[row], config.arg[row] ); - if ( config.param.at(row).compare(0, 15, "EMULATOR_SYSTEM")==0 ) { + if ( config.param.at(row) == "EMULATOR_SYSTEM" ) { this->systemfile = config.arg.at(row); } - if ( config.param.at(row).compare(0, 12, "EXPOSE_PARAM")==0) { + if ( config.param.at(row) == "EXPOSE_PARAM" ) { this->exposeparam = config.arg[row]; } + if ( config.param.at(row) == "EMULATOR_DATADIR" ) { + datadir = config.arg[row]; + } } catch(const std::exception &e ) { std::cerr << get_timestamp() << function << "ERROR parsing row " << row << " of " << this->config.n_rows << ": " << e.what() << "\n"; @@ -121,6 +124,10 @@ namespace Archon { } } + this->frame_source = Emulator::make_frame_source(datadir, &this->active_mode); + std::cout << get_timestamp() << function << "frame source: " + << (datadir.empty() ? "synthetic" : datadir) << "\n"; + std::cout << get_timestamp() << function << "complete" << "\n"; return NO_ERROR; @@ -249,7 +256,7 @@ namespace Archon { statstr << "VALID=" << 1 << " " << "COUNT=" << 1 << " " << "LOG=" << 0 << " " - << "POWER=" << this->poweron << " " + << "POWER=" << ( this->poweron ? 4 : 2 ) << " " << "POWERGOOD=" << 1 << " " << "OVERHEAT=" << 0 << " " << "BACKPLANE_TEMP=" << 40 << " " @@ -407,69 +414,83 @@ namespace Archon { */ long Interface::fetch_data( const std::string &ref, const std::string &cmd, Network::TcpSocket &sock ) { std::string function = " (Archon::Interface::fetch_data) "; - unsigned int reqblocks; //!< number of requested blocks, from the FETCH command - unsigned int block; //!< block counter - size_t byteswritten; //!< bytes written for this block - int totalbyteswritten; //!< total bytes written for this image - size_t towrite=0; //!< remaining bytes to write for this block - char* image_data=nullptr; + unsigned int reqblocks; + uint64_t bufaddr; std::cout << get_timestamp() << function << "got command " << cmd << "\n"; - if ( cmd.length() != 21 ) { // must be "FETCHxxxxxxxxyyyyyyyy", 21 chars + if ( cmd.length() != 21 ) { std::cerr << get_timestamp() << function << "ERROR: expecting form FETCHxxxxxxxxyyyyyyyy but got \"" << cmd << "\"\n"; return ERROR; } try { - std::stringstream hexblocks; - hexblocks << std::hex << "0x" << cmd.substr(13); - hexblocks >> reqblocks; + bufaddr = std::stoull(cmd.substr(5, 8), nullptr, 16); + reqblocks = std::stoul(cmd.substr(13, 8), nullptr, 16); } - catch( std::invalid_argument & ) { - std::cerr << get_timestamp() << function << "ERROR: invalid argument parsing " << cmd << "\n"; + catch( const std::exception &e ) { + std::cerr << get_timestamp() << function << "ERROR parsing " << cmd << ": " << e.what() << "\n"; return ERROR; } - catch( std::out_of_range & ) { - std::cerr << get_timestamp() << function << "ERROR: value out of range parsing " << cmd << "\n"; - return ERROR; + + // Find which buffer matches the requested base address + int bufidx = -1; + for ( int i = 0; i < this->image->activebufs; i++ ) { + if ( this->frame.bufbase.at(i) == bufaddr ) { bufidx = i; break; } } - catch( ... ) { - std::cerr << get_timestamp() << function << "unknown error parsing " << cmd << "\n"; + + if ( bufidx < 0 || this->frame.bufdata.at(bufidx).empty() ) { + std::cerr << get_timestamp() << function << "ERROR: no frame data for address 0x" + << std::hex << bufaddr << "\n"; return ERROR; } - image_data = new char[reqblocks * BLOCKLEN]; + const auto &data = this->frame.bufdata.at(bufidx); + size_t data_size = data.size(); - std::srand( time( nullptr ) ); - for ( unsigned int i=0; i<(reqblocks*BLOCKLEN)/2; i+=10 ) { - image_data[i] = rand() % 40000 + 30000; - } + std::cout << get_timestamp() << function << "sending " << std::dec << reqblocks + << " blocks (" << data_size << " bytes available) from buffer " << bufidx+1 << "\n"; + + // Disable Nagle's algorithm for low-latency block transfer + int flag = 1; + setsockopt(sock.getfd(), IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)); std::string header = "<" + ref + ":"; - totalbyteswritten = 0; - - std::cout << get_timestamp() << function << "host requested " << std::dec << reqblocks << " (0x" << std::hex << reqblocks << ") blocks \n"; - - std::cout << "writing bytes: "; - - for ( block = 0; block < reqblocks; block++ ) { - sock.Write(header); - byteswritten = 0; - do { - int retval=0; - towrite = BLOCKLEN - byteswritten; - if ( ( retval = sock.Write(image_data, towrite) ) > 0 ) { - byteswritten += retval; - totalbyteswritten += retval; - std::cout << std::dec << std::setw(10) << totalbyteswritten << "\b\b\b\b\b\b\b\b\b\b"; + size_t header_len = header.size(); + size_t block_with_header = header_len + BLOCKLEN; + int totalbyteswritten = 0; + size_t data_offset = 0; + + // Assemble header + one block into a single buffer per write + std::vector sendbuf(block_with_header); + std::memcpy(sendbuf.data(), header.data(), header_len); + + for ( unsigned int block = 0; block < reqblocks; block++ ) { + size_t block_filled = 0; + while ( block_filled < BLOCKLEN ) { + size_t remaining = BLOCKLEN - block_filled; + if ( data_offset < data_size ) { + size_t avail = std::min(remaining, data_size - data_offset); + std::memcpy(sendbuf.data() + header_len + block_filled, data.data() + data_offset, avail); + data_offset += avail; + block_filled += avail; } - } while ( byteswritten < BLOCKLEN ); - } - std::cout << std::dec << std::setw(10) << totalbyteswritten << " complete\n"; - std::cout << get_timestamp() << function << "wrote " << std::dec << block << " blocks to host\n"; + else { + std::memset(sendbuf.data() + header_len + block_filled, 0, remaining); + block_filled = BLOCKLEN; + } + } - delete[] image_data; + // Write header + block in one syscall + size_t written = 0; + while ( written < block_with_header ) { + int retval = sock.Write(sendbuf.data() + written, block_with_header - written); + if ( retval > 0 ) written += retval; + else break; + } + totalbyteswritten += BLOCKLEN; + } + std::cout << get_timestamp() << function << "wrote " << std::dec << totalbyteswritten << " bytes\n"; return NO_ERROR; } @@ -586,6 +607,9 @@ namespace Archon { else if ( key == "TAPLINES" ) { this->image->taplines = std::stoi(value); + // Update synthetic source if active + auto* synth = dynamic_cast(this->frame_source.get()); + if (synth) synth->set_taplines(this->image->taplines); } else if ( key == "PIXELCOUNT" ) { @@ -722,6 +746,12 @@ namespace Archon { // providing polymorphic behavior based on the actual object type. // this->image->handle_key( key, ival ); + + // Detect active mode from ACF mode parameters + if ( key.compare(0, 5, "mode_") == 0 && ival > 0 ) { + this->active_mode = key.substr(5); + std::cout << get_timestamp() << function << "active mode: " << this->active_mode << "\n"; + } } } catch( std::out_of_range & ) { @@ -752,8 +782,8 @@ namespace Archon { // else { line = this->parammap[ key ].line; // line number is stored in parammap - this->configmap[ line ].value = value; // configmap is indexed by line number - this->parammap[ key ].value = value; //TODO needed?? + this->parammap[ key ].value = value; + this->configmap[ line ].value = key + "=" + value; // preserve PARAMETERn=name=value format } return NO_ERROR; @@ -841,34 +871,35 @@ namespace Archon { std::srand( time( nullptr ) ); try { - iface.frame.bufpixels.at( iface.frame.index ) = 0; - iface.frame.buflines.at( iface.frame.index ) = 0; - iface.frame.bufcomplete.at( iface.frame.index ) = 0; + int idx = iface.frame.index; + iface.frame.bufpixels.at(idx) = 0; + iface.frame.buflines.at(idx) = 0; + iface.frame.bufcomplete.at(idx) = 0; + + int width = iface.image->pixelcount * iface.image->taplines; + int height = iface.image->linecount; + size_t frame_bytes = static_cast(width) * height * sizeof(uint16_t); + + iface.frame.bufdata.at(idx).resize(frame_bytes); + if (iface.frame_source) { + iface.frame_source->fill_frame(iface.frame.bufdata.at(idx).data(), width, height); + } - // calculates instrument-specific row time - // - double rowtime = iface.image->calc_rowtime(); + iface.frame.bufwidth.at(idx) = width; + iface.frame.bufheight.at(idx) = height; - int i=0; + double rowtime = iface.image->calc_rowtime(); std::cout << function << "readout line: "; - for ( iface.frame.buflines.at(iface.frame.index) = 0; iface.frame.buflines.at(iface.frame.index) < iface.image->linecount; iface.frame.buflines.at(iface.frame.index)++ ) { - for ( iface.frame.bufpixels.at(iface.frame.index)= 0; iface.frame.bufpixels.at(iface.frame.index) < iface.image->pixelcount; iface.frame.bufpixels.at(iface.frame.index)++ ) { - for ( int tap = 0; tap < iface.image->taplines; tap++ ) { -// iface.frame.buffer.at( i ) = rand() % 40000 + 30000; // random number between {30k:40k} - i++; - } -// iface.frame.bufpixels.at( iface.frame.index )++; - } -// iface.frame.buflines.at( iface.frame.index )++; - std::cout << std::dec << std::setw(6) << iface.frame.buflines.at(iface.frame.index) << "\b\b\b\b\b\b"; -// usleep( linetime ); + for ( iface.frame.buflines.at(idx) = 0; iface.frame.buflines.at(idx) < height; iface.frame.buflines.at(idx)++ ) { + iface.frame.bufpixels.at(idx) = width; + std::cout << std::dec << std::setw(6) << iface.frame.buflines.at(idx) << "\b\b\b\b\b\b"; std::this_thread::sleep_for( std::chrono::microseconds(static_cast(rowtime)) ); } - std::cout << std::dec << std::setw(6) << iface.frame.buflines.at(iface.frame.index) << " complete\n"; - iface.frame.bufcomplete.at( iface.frame.index ) = 1; + std::cout << std::dec << std::setw(6) << iface.frame.buflines.at(idx) << " complete\n"; + iface.frame.bufcomplete.at(idx) = 1; iface.image->framen++; - iface.frame.bufframen.at( iface.frame.index ) = iface.image->framen; + iface.frame.bufframen.at(idx) = iface.image->framen; } catch( std::out_of_range & ) { std::cerr << get_timestamp() << function << "ERROR: frame.index=" << iface.frame.index << " out of range\n"; diff --git a/emulator/emulator-archon.h b/emulator/emulator-archon.h index ef49c7e8..8e718df4 100644 --- a/emulator/emulator-archon.h +++ b/emulator/emulator-archon.h @@ -17,13 +17,13 @@ #include "utilities.h" #include "common.h" -#include "camera.h" #include "config.h" #include "logentry.h" #include "network.h" #include "generic.h" #include "nirc2.h" +#include "frame_source.h" namespace Archon { @@ -40,7 +40,6 @@ namespace Archon { std::string instr; std::atomic abort{false}; std::atomic exposing{false}; - unsigned long int start_timer, finish_timer; //!< Archon internal timer, start and end of exposure // Declare a map to contain image types for each recognized instrument. // @@ -51,6 +50,7 @@ namespace Archon { public: std::unique_ptr image; ///!< smart pointer to the base class + std::unique_ptr frame_source; Interface( const std::string &instr ); @@ -63,7 +63,9 @@ namespace Archon { unsigned long long init_time; bool poweron; //!< is the power on? bool bigbuf; //!< is BIGBUF==1 in ACF file? - std::string exposeparam; //!< param name to trigger exposure when set =1 + std::string exposeparam; //!< param name to trigger exposure when set =1 + + std::string active_mode; //!< currently active mode detected from ACF parameters struct image_t { uint32_t framen; @@ -114,6 +116,7 @@ namespace Archon { std::vector buftimestamp; // buffer hex 64 bit timestamp std::vector bufretimestamp; // buf trigger rising edge time stamp std::vector buffetimestamp; // buf trigger falling edge time stamp + std::vector> bufdata; // pixel data per buffer } frame; // Functions diff --git a/emulator/emulator-server.cpp b/emulator/emulator-server.cpp index 0d1b04f8..a74b9cdc 100644 --- a/emulator/emulator-server.cpp +++ b/emulator/emulator-server.cpp @@ -85,7 +85,7 @@ int main(int argc, char **argv) { // get the configuration file from the command line // - long ret; + long ret=NO_ERROR; if (argc>1) { server->config.filename = std::string( argv[1] ); ret = server->config.read_config(); // read configuration file specified on command line diff --git a/emulator/frame_source.h b/emulator/frame_source.h new file mode 100644 index 00000000..3716a8b6 --- /dev/null +++ b/emulator/frame_source.h @@ -0,0 +1,181 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utilities.h" + +namespace Emulator { + + // Abstract interface for filling frame buffers with pixel data + class FrameSource { + public: + virtual ~FrameSource() = default; + + // Fill buffer with one frame of pixel data (uint16 pixels) + /// @param buffer destination buffer, must be at least width*height*2 bytes + /// @param width frame width in pixels + /// @param height frame height in pixels + /// @return true on success + virtual bool fill_frame(char* buffer, int width, int height) = 0; + }; + + + // Generate synthetic frames with bias + Gaussian noise + // Mode-aware: RXR generates signal/reset halves with correlated noise + class SyntheticSource : public FrameSource { + private: + std::mt19937 rng{std::random_device{}()}; + double noise_stddev = 50.0; + std::string* active_mode; // non-owning pointer to emulator's active mode + int taplines = 0; + + public: + // @param mode pointer to the active mode string (owned by Interface) + // @param taps number of taplines for RXR half-width calculation + SyntheticSource(std::string* mode = nullptr, int taps = 0) + : active_mode(mode), taplines(taps) {} + + void set_taplines(int taps) { taplines = taps; } + + bool fill_frame(char* buffer, int width, int height) override { + auto* pixels = reinterpret_cast(buffer); + std::normal_distribution noise(0.0, noise_stddev); + size_t npixels = static_cast(width) * height; + + // Check if active mode contains "RXR" (covers VideoRXR, RXRV, etc) + bool rxr_mode = active_mode && + active_mode->find("RXR") != std::string::npos; + + if (rxr_mode && taplines > 0) { + // RXR: each tap has signal pixels (first half) then reset pixels (second half) + // Generate correlated noise per pixel pair so CDS subtraction cancels it, + // leaving bias difference (10000 - 5000 = 5000) + small readout noise + int pixels_per_tap = width / taplines; + int half = pixels_per_tap / 2; + + for (int row = 0; row < height; row++) { + for (int tap = 0; tap < taplines; tap++) { + int tap_offset = row * width + tap * pixels_per_tap; + // Generate noise once per pixel pair, apply to both signal and reset + for (int col = 0; col < half; col++) { + double common_noise = noise(rng); + double sig_val = 10000.0 + common_noise + noise(rng) * 0.3; + double res_val = 5000.0 + common_noise + noise(rng) * 0.3; + pixels[tap_offset + col] = static_cast(std::clamp(sig_val, 0.0, 65535.0)); + pixels[tap_offset + col + half] = static_cast(std::clamp(res_val, 0.0, 65535.0)); + } + } + } + } + else { + // Default: flat bias + noise + horizontal gradient + for (size_t i = 0; i < npixels; i++) { + int col = i % width; + double val = 5000.0 + noise(rng) + static_cast(col) * 100.0 / width; + pixels[i] = static_cast(std::clamp(val, 0.0, 65535.0)); + } + } + return true; + } + }; + + + // Read FITS files from folder, serve them sequentially, cycling + // Not mode-aware: FITS data is already in the correct format for its mode + class FitsFileSource : public FrameSource { + private: + std::vector files; + size_t current_index = 0; + + public: + explicit FitsFileSource(const std::string &datadir) { + std::string function = "(Emulator::FitsFileSource) "; + for (const auto &entry : std::filesystem::directory_iterator(datadir)) { + auto path = entry.path().string(); + if (path.ends_with(".fits") || path.ends_with(".fits.gz") || + path.ends_with(".fit") || path.ends_with(".fit.gz")) { + files.push_back(path); + } + } + std::sort(files.begin(), files.end()); + std::cout << get_timestamp() << function << files.size() + << " FITS files found in " << datadir << "\n"; + } + + bool fill_frame(char* buffer, int width, int height) override { + std::string function = "(Emulator::FitsFileSource::fill_frame) "; + if (files.empty()) { + std::cerr << get_timestamp() << function << "ERROR: no FITS files\n"; + return false; + } + + const auto &path = files[current_index % files.size()]; + current_index++; + + fitsfile* fptr = nullptr; + int status = 0; + + fits_open_file(&fptr, path.c_str(), READONLY, &status); + if (status) { + std::cerr << get_timestamp() << function << "ERROR opening " << path << "\n"; + return false; + } + + int naxis = 0; + long naxes[2] = {0, 0}; + fits_get_img_dim(fptr, &naxis, &status); + fits_get_img_size(fptr, 2, naxes, &status); + + if (status || naxis != 2) { + std::cerr << get_timestamp() << function << "ERROR: " << path + << " is not a 2D image\n"; + fits_close_file(fptr, &status); + return false; + } + + if (naxes[0] != width || naxes[1] != height) { + std::cerr << get_timestamp() << function << "ERROR: " << path + << " dimensions " << naxes[0] << "x" << naxes[1] + << " don't match expected " << width << "x" << height << "\n"; + fits_close_file(fptr, &status); + return false; + } + + long fpixel[2] = {1, 1}; + long nelements = static_cast(width) * height; + fits_read_pix(fptr, TUSHORT, fpixel, nelements, nullptr, buffer, nullptr, &status); + fits_close_file(fptr, &status); + + if (status) { + std::cerr << get_timestamp() << function << "ERROR reading pixels from " << path << "\n"; + return false; + } + + std::cout << get_timestamp() << function << "loaded " << path << "\n"; + return true; + } + }; + + + // Create the appropriate FrameSource based on config + inline std::unique_ptr make_frame_source( + const std::string &datadir, + std::string* active_mode = nullptr, + int taplines = 0) { + if (!datadir.empty() && std::filesystem::is_directory(datadir)) { + return std::make_unique(datadir); + } + return std::make_unique(active_mode, taplines); + } + +} diff --git a/emulator/generic.h b/emulator/generic.h index 51073294..a09fc034 100644 --- a/emulator/generic.h +++ b/emulator/generic.h @@ -29,7 +29,7 @@ namespace Archon { } /***** GenericImage::calc_rowtime ***************************************/ - void set_config_parameter( const std::string &key, const std::string &val ) { + void set_config_parameter( const std::string &key, const std::string &val ) override { ImageInfoBase::set_config_parameter( key, val ); // Call base class method for common parameters if ( key == "READOUT_TIME" ) { this->readtime = std::stoi( val ); } return; diff --git a/emulator/nirc2.h b/emulator/nirc2.h index ed11bd51..802ff593 100644 --- a/emulator/nirc2.h +++ b/emulator/nirc2.h @@ -103,7 +103,7 @@ namespace Archon { * @param[in] value value * */ - void set_config_parameter( const std::string &key, const std::string &val ) { + void set_config_parameter( const std::string &key, const std::string &val ) override { ImageInfoBase::set_config_parameter( key, val ); // Call base class method for common parameters if ( key == "PIXEL_TIME" ) { this->pixel_time = std::stod( val ); } else @@ -145,7 +145,7 @@ namespace Archon { } /***** Nirc2Image::get_readouttime **************************************/ - inline int get_frames_per_exposure() { return numsamples * ( iscds ? 2 : 1 ); } + int get_frames_per_exposure() const override { return numsamples * ( iscds ? 2 : 1 ); } }; /***** Archon::Nirc2Image ***************************************************/ } diff --git a/utils/network.cpp b/utils/network.cpp index ad8ce7a1..4d5de668 100644 --- a/utils/network.cpp +++ b/utils/network.cpp @@ -595,9 +595,9 @@ namespace Network { message.str(""); message << ( revents & POLLHUP ? "POLLHUP " : "" ) << ( revents & POLLERR ? "POLLERR " : "" ) << ( revents & POLLNVAL ? "POLLNVAL " : "" ) - << "recevied: closing socket " << this->host << "/" << this->port << " on fd " << this->fd; + << "on socket " << this->host << "/" << this->port << " fd " << this->fd; logwrite( function, message.str() ); - this->Close(); + return -1; } return( ret ); diff --git a/utils/sendcmd.cpp b/utils/sendcmd.cpp index 9f5187e9..af0af624 100644 --- a/utils/sendcmd.cpp +++ b/utils/sendcmd.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -85,6 +86,24 @@ int main(int argc, char *argv[]) { return -errno; } + // Wait for non-blocking connect to complete + struct pollfd pfd = { sock, POLLOUT, 0 }; + int pret = poll(&pfd, 1, timeout * 1000); + if ( pret <= 0 ) { + std::cerr << "ERROR connect timeout: " << std::strerror(errno) << "\n"; + close(sock); + return -ETIME; + } + // Verify the connection actually succeeded + int sockerr = 0; + socklen_t errlen = sizeof(sockerr); + getsockopt(sock, SOL_SOCKET, SO_ERROR, &sockerr, &errlen); + if ( sockerr != 0 ) { + std::cerr << "ERROR connecting: " << std::strerror(sockerr) << "\n"; + close(sock); + return -sockerr; + } + message += "\n"; while ( ( nread = write( sock, message.c_str(), message.size() ) ) < 0 ) {